summary refs log tree commit diff
path: root/src/core/settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/settings')
-rw-r--r--src/core/settings/nm-agent-manager.c1618
-rw-r--r--src/core/settings/nm-agent-manager.h78
-rw-r--r--src/core/settings/nm-secret-agent.c808
-rw-r--r--src/core/settings/nm-secret-agent.h95
-rw-r--r--src/core/settings/nm-settings-connection.c2810
-rw-r--r--src/core/settings/nm-settings-connection.h394
-rw-r--r--src/core/settings/nm-settings-plugin.c290
-rw-r--r--src/core/settings/nm-settings-plugin.h200
-rw-r--r--src/core/settings/nm-settings-storage.c157
-rw-r--r--src/core/settings/nm-settings-storage.h112
-rw-r--r--src/core/settings/nm-settings-utils.c161
-rw-r--r--src/core/settings/nm-settings-utils.h91
-rw-r--r--src/core/settings/nm-settings.c4144
-rw-r--r--src/core/settings/nm-settings.h130
-rw-r--r--src/core/settings/plugins/ifcfg-rh/meson.build62
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf11
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml26
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifdown3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifup3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h63
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c1253
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h27
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c6642
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c175
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h82
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c1119
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h164
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c3654
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h26
-rw-r--r--src/core/settings/plugins/ifcfg-rh/shvar.c1585
-rw-r--r--src/core/settings/plugins/ifcfg-rh/shvar.h123
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/meson.build16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected8
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected34
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected11
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected21
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected23
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem012
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:992
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem112
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem212
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem311
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected41
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-114
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type7
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib7
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main8
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb43
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities7
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-10012
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft10
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband8
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected11
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions8
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-25
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-24
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized7
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-114
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-210
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface11
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-26
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces11
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden10
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always7
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap20
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls25
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls28
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-219
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted219
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches17
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv215
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip26
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only11
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore8
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-114
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-213
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-313
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-413
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only14
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-116
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default11
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-1614
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-2414
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-814
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-18
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected12
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-23
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-33
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-436
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected31
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes19
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem118
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem27
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12bin0 -> 2848 bytes
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c11858
-rw-r--r--src/core/settings/plugins/ifupdown/meson.build29
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c388
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h43
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c686
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h16
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c399
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h28
-rw-r--r--src/core/settings/plugins/ifupdown/tests/meson.build15
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test-ifupdown.c648
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test16
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test115
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test125
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test133
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test145
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test153
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test162
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip45
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip66
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen3
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test24
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test20-source-stanza1
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth02
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth12
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza1
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth02
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas8
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test35
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test43
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test53
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test63
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test73
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test85
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test910
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.c1348
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.h67
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-reader.c237
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-reader.h31
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.c272
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.h255
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-utils.c389
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-utils.h71
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.c489
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.h42
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT24
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain24
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component15
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main18
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property8
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection41
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection13
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_211
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format13
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format10
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID9
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags15
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name13
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names16
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection72
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP620
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case32
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection33
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_12
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_21
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_14
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_27
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_34
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_44
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem27
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem118
-rw-r--r--src/core/settings/plugins/keyfile/tests/meson.build15
-rw-r--r--src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c2923
-rw-r--r--src/core/settings/plugins/meson.build13
330 files changed, 49835 insertions, 0 deletions
diff --git a/src/core/settings/nm-agent-manager.c b/src/core/settings/nm-agent-manager.c
new file mode 100644
index 00000000..7e617909
--- /dev/null
+++ b/src/core/settings/nm-agent-manager.c
@@ -0,0 +1,1618 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2013 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-agent-manager.h"
+
+#include <pwd.h>
+
+#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "nm-dbus-interface.h"
+#include "nm-secret-agent.h"
+#include "nm-auth-utils.h"
+#include "nm-setting-vpn.h"
+#include "nm-auth-manager.h"
+#include "nm-dbus-manager.h"
+#include "nm-session-monitor.h"
+#include "nm-simple-connection.h"
+#include "NetworkManagerUtils.h"
+#include "nm-core-internal.h"
+#include "c-list/src/c-list.h"
+
+/*****************************************************************************/
+
+enum {
+    AGENT_REGISTERED,
+    LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct {
+    NMAuthManager *   auth_mgr;
+    NMSessionMonitor *session_monitor;
+
+    CList agent_lst_head;
+
+    CList request_lst_head;
+
+    guint64 agent_version_id;
+} NMAgentManagerPrivate;
+
+struct _NMAgentManager {
+    NMDBusObject          parent;
+    NMAgentManagerPrivate _priv;
+};
+
+struct _NMAgentManagerClass {
+    NMDBusObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMAgentManager, nm_agent_manager, NM_TYPE_DBUS_OBJECT)
+
+#define NM_AGENT_MANAGER_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMAgentManager, NM_IS_AGENT_MANAGER)
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER(NMAgentManager, nm_agent_manager_get, NM_TYPE_AGENT_MANAGER);
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "agent-manager"
+#define _NMLOG_DOMAIN      LOGD_AGENTS
+#define _NMLOG(level, agent, ...)                                            \
+    G_STMT_START                                                             \
+    {                                                                        \
+        if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) {                  \
+            char           __prefix1[32];                                    \
+            char           __prefix2[128];                                   \
+            NMSecretAgent *__agent = (agent);                                \
+                                                                             \
+            if (!(self))                                                     \
+                g_snprintf(__prefix1,                                        \
+                           sizeof(__prefix1),                                \
+                           "%s%s",                                           \
+                           ""_NMLOG_PREFIX_NAME                              \
+                           "",                                               \
+                           "[]");                                            \
+            else if ((self) != singleton_instance)                           \
+                g_snprintf(__prefix1,                                        \
+                           sizeof(__prefix1),                                \
+                           "%s[" NM_HASH_OBFUSCATE_PTR_FMT "]",              \
+                           ""_NMLOG_PREFIX_NAME                              \
+                           "",                                               \
+                           NM_HASH_OBFUSCATE_PTR(self));                     \
+            else                                                             \
+                g_strlcpy(__prefix1, _NMLOG_PREFIX_NAME, sizeof(__prefix1)); \
+            if (__agent) {                                                   \
+                g_snprintf(__prefix2,                                        \
+                           sizeof(__prefix2),                                \
+                           ": agent[" NM_HASH_OBFUSCATE_PTR_FMT ",%s]",      \
+                           NM_HASH_OBFUSCATE_PTR(__agent),                   \
+                           nm_secret_agent_get_description(__agent));        \
+            } else                                                           \
+                __prefix2[0] = '\0';                                         \
+            _nm_log((level),                                                 \
+                    (_NMLOG_DOMAIN),                                         \
+                    0,                                                       \
+                    NULL,                                                    \
+                    NULL,                                                    \
+                    "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),             \
+                    __prefix1,                                               \
+                    __prefix2 _NM_UTILS_MACRO_REST(__VA_ARGS__));            \
+        }                                                                    \
+    }                                                                        \
+    G_STMT_END
+
+#define LOG_REQ_FMT "[" NM_HASH_OBFUSCATE_PTR_FMT "/%s%s%s%s%s%s]"
+#define LOG_REQ_ARG(req)                                                  \
+    NM_HASH_OBFUSCATE_PTR(req), NM_PRINT_FMT_QUOTE_STRING((req)->detail), \
+        NM_PRINT_FMT_QUOTED(((req)->request_type == REQUEST_TYPE_CON_GET) \
+                                && (req)->con.get.setting_name,           \
+                            "/\"",                                        \
+                            (req)->con.get.setting_name,                  \
+                            "\"",                                         \
+                            ((req)->request_type == REQUEST_TYPE_CON_GET  \
+                                 ? "/(none)"                              \
+                                 : _request_type_to_string((req)->request_type, FALSE)))
+
+/*****************************************************************************/
+
+typedef struct _NMAgentManagerCallId Request;
+
+static void request_add_agent(Request *req, NMSecretAgent *agent);
+
+static void request_remove_agent(Request *req, NMSecretAgent *agent);
+
+static void request_next_agent(Request *req);
+
+static void _con_get_request_start(Request *req);
+static void _con_save_request_start(Request *req);
+static void _con_del_request_start(Request *req);
+
+static gboolean _con_get_try_complete_early(Request *req);
+
+static void agent_disconnected_cb(NMSecretAgent *agent, gpointer user_data);
+
+/*****************************************************************************/
+
+guint64
+nm_agent_manager_get_agent_version_id(NMAgentManager *self)
+{
+    g_return_val_if_fail(NM_IS_AGENT_MANAGER(self), 0);
+
+    return NM_AGENT_MANAGER_GET_PRIVATE(self)->agent_version_id;
+}
+
+/*****************************************************************************/
+
+typedef enum {
+    REQUEST_TYPE_INVALID,
+    REQUEST_TYPE_CON_GET,
+    REQUEST_TYPE_CON_SAVE,
+    REQUEST_TYPE_CON_DEL,
+} RequestType;
+
+static const char *
+_request_type_to_string(RequestType request_type, gboolean verbose)
+{
+    switch (request_type) {
+    case REQUEST_TYPE_CON_GET:
+        return verbose ? "getting" : "get";
+    case REQUEST_TYPE_CON_SAVE:
+        return verbose ? "saving" : "sav";
+    case REQUEST_TYPE_CON_DEL:
+        return verbose ? "deleting" : "del";
+    default:
+        return "??";
+    }
+}
+
+/*****************************************************************************/
+
+struct _NMAgentManagerCallId {
+    CList request_lst;
+
+    NMAgentManager *self;
+
+    RequestType request_type;
+
+    char *detail;
+
+    NMAuthSubject *subject;
+
+    /* Current agent being asked for secrets */
+    NMSecretAgent *      current;
+    NMSecretAgentCallId *current_call_id;
+
+    /* Stores the sorted list of NMSecretAgents which will be asked for secrets */
+    GSList *pending;
+
+    guint idle_id;
+
+    union {
+        struct {
+            char *        path;
+            NMConnection *connection;
+
+            NMAuthChain *chain;
+
+            /* Whether the agent currently being asked for secrets
+             * has the system.modify privilege.
+             */
+            gboolean current_has_modify;
+
+            union {
+                struct {
+                    NMSecretAgentGetSecretsFlags flags;
+                    char *                       setting_name;
+                    char **                      hints;
+
+                    GVariant *existing_secrets;
+
+                    NMAgentSecretsResultFunc callback;
+                    gpointer                 callback_data;
+                } get;
+            };
+        } con;
+    };
+};
+
+/*****************************************************************************/
+
+static NMSecretAgent *
+_agent_find_by_owner(NMAgentManagerPrivate *priv, const char *owner)
+{
+    NMSecretAgent *agent;
+
+    c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+        if (nm_streq(nm_secret_agent_get_dbus_owner(agent), owner))
+            return agent;
+    }
+    return NULL;
+}
+
+static NMSecretAgent *
+_agent_find_by_identifier_and_uid(NMAgentManagerPrivate *priv,
+                                  const char *           identifier,
+                                  gulong                 sender_uid)
+{
+    NMSecretAgent *agent;
+
+    c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+        if (nm_streq0(nm_secret_agent_get_identifier(agent), identifier)
+            && sender_uid == nm_secret_agent_get_owner_uid(agent))
+            return agent;
+    }
+    return NULL;
+}
+
+/*****************************************************************************/
+
+static void
+_agent_remove(NMAgentManager *self, NMSecretAgent *agent)
+{
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+    CList *                iter, *safe;
+
+    nm_assert(NM_IS_SECRET_AGENT(agent));
+    nm_assert(c_list_contains(&priv->agent_lst_head, &agent->agent_lst));
+
+    _LOGD(agent, "agent unregistered or disappeared");
+
+    nm_clear_pointer(&agent->auth_chain, nm_auth_chain_destroy);
+
+    c_list_unlink(&agent->agent_lst);
+
+    g_signal_handlers_disconnect_by_func(agent, G_CALLBACK(agent_disconnected_cb), self);
+
+    /* Remove this agent from any in-progress secrets requests */
+    c_list_for_each_safe (iter, safe, &priv->request_lst_head)
+        request_remove_agent(c_list_entry(iter, Request, request_lst), agent);
+
+    g_object_unref(agent);
+}
+
+/* Call this *after* calling request_next_agent() */
+static void
+maybe_remove_agent_on_error(NMAgentManager *self, NMSecretAgent *agent, GError *error)
+{
+    if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CLOSED)
+        && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED)
+        && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
+        return;
+
+    if (!c_list_is_empty(&agent->agent_lst))
+        _agent_remove(self, agent);
+}
+
+/*****************************************************************************/
+
+static gboolean
+validate_identifier(const char *identifier, GError **error)
+{
+    const char *p = identifier;
+    size_t      id_len;
+
+    if (!identifier) {
+        g_set_error_literal(error,
+                            NM_AGENT_MANAGER_ERROR,
+                            NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
+                            "No identifier was given");
+        return FALSE;
+    }
+
+    /* Length between 3 and 255 characters inclusive */
+    id_len = strlen(identifier);
+    if (id_len < 3 || id_len > 255) {
+        g_set_error_literal(error,
+                            NM_AGENT_MANAGER_ERROR,
+                            NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
+                            "Identifier length not between 3 and 255 characters (inclusive)");
+        return FALSE;
+    }
+
+    if ((identifier[0] == '.') || (identifier[id_len - 1] == '.')) {
+        g_set_error_literal(error,
+                            NM_AGENT_MANAGER_ERROR,
+                            NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
+                            "Identifier must not start or end with '.'");
+        return FALSE;
+    }
+
+    /* FIXME: do complete validation here */
+    while (p && *p) {
+        if (!g_ascii_isalnum(*p) && (*p != '_') && (*p != '-') && (*p != '.')) {
+            g_set_error(error,
+                        NM_AGENT_MANAGER_ERROR,
+                        NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
+                        "Identifier contains invalid character '%c'",
+                        *p);
+            return FALSE;
+        }
+
+        if ((*p == '.') && (*(p + 1) == '.')) {
+            g_set_error_literal(error,
+                                NM_AGENT_MANAGER_ERROR,
+                                NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
+                                "Identifier contains two '.' characters in sequence");
+            return FALSE;
+        }
+        p++;
+    }
+
+    return TRUE;
+}
+
+static void
+_agent_permissions_check_done(NMAuthChain *          chain,
+                              GDBusMethodInvocation *context,
+                              gpointer               user_data)
+{
+    NMAgentManager *       self = NM_AGENT_MANAGER(user_data);
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+    NMSecretAgent *        agent;
+    Request *              request;
+
+    nm_assert(!context || G_IS_DBUS_METHOD_INVOCATION(context));
+
+    agent = nm_auth_chain_steal_data(chain, "agent");
+
+    nm_assert(NM_IS_SECRET_AGENT(agent));
+    nm_assert(agent->auth_chain == chain);
+    nm_assert(agent->fully_registered == (!context));
+    nm_assert(c_list_contains(&priv->agent_lst_head, &agent->agent_lst));
+
+    agent->auth_chain = NULL;
+
+    nm_secret_agent_add_permission(
+        agent,
+        NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED,
+        (nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED)
+         == NM_AUTH_CALL_RESULT_YES));
+    nm_secret_agent_add_permission(
+        agent,
+        NM_AUTH_PERMISSION_WIFI_SHARE_OPEN,
+        (nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN)
+         == NM_AUTH_CALL_RESULT_YES));
+
+    if (agent->fully_registered) {
+        _LOGD(agent, "updated agent permissions");
+        return;
+    }
+
+    _LOGI(agent, "agent registered");
+
+    agent->fully_registered = TRUE;
+
+    priv->agent_version_id += 1;
+
+    g_dbus_method_invocation_return_value(context, NULL);
+
+    c_list_for_each_entry (request, &priv->request_lst_head, request_lst)
+        request_add_agent(request, agent);
+
+    g_signal_emit(self, signals[AGENT_REGISTERED], 0, agent);
+}
+
+static NMAuthChain *
+_agent_create_auth_chain(NMAgentManager *self, NMSecretAgent *agent, GDBusMethodInvocation *context)
+{
+    NMAuthChain *chain;
+
+    _LOGD(agent, "requesting permissions");
+
+    nm_assert(!agent->auth_chain
+              || (agent->fully_registered == (!nm_auth_chain_get_context(agent->auth_chain))));
+
+    if (agent->auth_chain && !context && !agent->fully_registered) {
+        /* we restart the authorization check (without a @context), but the currently
+         * pending auth-chain carries a context. We need to pass it on as we replace
+         * the auth-chain. */
+        context = nm_auth_chain_get_context(agent->auth_chain);
+        nm_assert(context);
+    }
+
+    chain = nm_auth_chain_new_subject(nm_secret_agent_get_subject(agent),
+                                      context,
+                                      _agent_permissions_check_done,
+                                      self);
+
+    nm_auth_chain_set_data(chain, "agent", agent, NULL);
+    nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
+    nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
+
+    nm_clear_pointer(&agent->auth_chain, nm_auth_chain_destroy);
+    agent->auth_chain = chain;
+    return chain;
+}
+
+static void
+agent_disconnected_cb(NMSecretAgent *agent, gpointer user_data)
+{
+    _agent_remove(NM_AGENT_MANAGER(user_data), agent);
+}
+
+static void
+agent_manager_register_with_capabilities(NMAgentManager *       self,
+                                         GDBusMethodInvocation *context,
+                                         const char *           identifier,
+                                         guint32                capabilities)
+{
+    NMAgentManagerPrivate *priv               = NM_AGENT_MANAGER_GET_PRIVATE(self);
+    gs_unref_object NMAuthSubject *subject    = NULL;
+    gulong                         sender_uid = G_MAXULONG;
+    GError *                       error      = NULL;
+    NMSecretAgent *                agent;
+
+    subject = nm_dbus_manager_new_auth_subject_from_context(context);
+    if (!subject) {
+        error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
+                                    NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
+                                    NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
+        g_dbus_method_invocation_take_error(context, error);
+        return;
+    }
+    sender_uid = nm_auth_subject_get_unix_process_uid(subject);
+
+    /* Validate the identifier */
+    if (!validate_identifier(identifier, &error)) {
+        g_dbus_method_invocation_take_error(context, error);
+        return;
+    }
+
+    /* Only one agent for each identifier is allowed per user */
+    if (_agent_find_by_identifier_and_uid(priv, identifier, sender_uid)) {
+        error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
+                                    NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
+                                    "An agent with this ID is already registered for this user.");
+        g_dbus_method_invocation_take_error(context, error);
+        return;
+    }
+
+    agent = nm_secret_agent_new(context, subject, identifier, capabilities);
+
+    g_signal_connect(agent, NM_SECRET_AGENT_DISCONNECTED, G_CALLBACK(agent_disconnected_cb), self);
+
+    c_list_link_tail(&priv->agent_lst_head, &agent->agent_lst);
+
+    _agent_create_auth_chain(self, agent, context);
+}
+
+static void
+impl_agent_manager_register(NMDBusObject *                     obj,
+                            const NMDBusInterfaceInfoExtended *interface_info,
+                            const NMDBusMethodInfoExtended *   method_info,
+                            GDBusConnection *                  connection,
+                            const char *                       sender,
+                            GDBusMethodInvocation *            invocation,
+                            GVariant *                         parameters)
+{
+    const char *identifier;
+
+    g_variant_get(parameters, "(&s)", &identifier);
+    agent_manager_register_with_capabilities(NM_AGENT_MANAGER(obj), invocation, identifier, 0);
+}
+
+static void
+impl_agent_manager_register_with_capabilities(NMDBusObject *                     obj,
+                                              const NMDBusInterfaceInfoExtended *interface_info,
+                                              const NMDBusMethodInfoExtended *   method_info,
+                                              GDBusConnection *                  connection,
+                                              const char *                       sender,
+                                              GDBusMethodInvocation *            invocation,
+                                              GVariant *                         parameters)
+{
+    const char *identifier;
+    guint32     capabilities;
+
+    g_variant_get(parameters, "(&su)", &identifier, &capabilities);
+    agent_manager_register_with_capabilities(NM_AGENT_MANAGER(obj),
+                                             invocation,
+                                             identifier,
+                                             capabilities);
+}
+
+static void
+impl_agent_manager_unregister(NMDBusObject *                     obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended *   method_info,
+                              GDBusConnection *                  connection,
+                              const char *                       sender,
+                              GDBusMethodInvocation *            invocation,
+                              GVariant *                         parameters)
+{
+    NMAgentManager *       self = NM_AGENT_MANAGER(obj);
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+    NMSecretAgent *        agent;
+
+    agent = _agent_find_by_owner(priv, sender);
+    if (!agent) {
+        g_dbus_method_invocation_return_error_literal(invocation,
+                                                      NM_AGENT_MANAGER_ERROR,
+                                                      NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
+                                                      "Caller is not registered as an Agent");
+        return;
+    }
+
+    _agent_remove(self, agent);
+
+    g_dbus_method_invocation_return_value(invocation, NULL);
+}
+
+/*****************************************************************************/
+
+static Request *
+request_new(NMAgentManager *self,
+            RequestType     request_type,
+            const char *    detail,
+            NMAuthSubject * subject)
+{
+    Request *req;
+
+    req               = g_slice_new0(Request);
+    req->self         = g_object_ref(self);
+    req->request_type = request_type;
+    req->detail       = g_strdup(detail);
+    req->subject      = g_object_ref(subject);
+    c_list_link_tail(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head, &req->request_lst);
+    return req;
+}
+
+static void
+request_free(Request *req)
+{
+    switch (req->request_type) {
+    case REQUEST_TYPE_CON_GET:
+    case REQUEST_TYPE_CON_SAVE:
+    case REQUEST_TYPE_CON_DEL:
+        g_object_unref(req->con.connection);
+        g_free(req->con.path);
+        nm_clear_pointer(&req->con.chain, nm_auth_chain_destroy);
+        if (req->request_type == REQUEST_TYPE_CON_GET) {
+            g_free(req->con.get.setting_name);
+            g_strfreev(req->con.get.hints);
+            if (req->con.get.existing_secrets)
+                g_variant_unref(req->con.get.existing_secrets);
+        }
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
+    if (req->idle_id)
+        g_source_remove(req->idle_id);
+
+    /* cancel-secrets invokes the done-callback synchronously -- in which case
+     * the handler just return.
+     * Hence, we can proceed to free @req... */
+    nm_secret_agent_cancel_call(req->current, req->current_call_id);
+
+    g_object_unref(req->subject);
+
+    g_free(req->detail);
+    g_slist_free_full(req->pending, g_object_unref);
+
+    g_object_unref(req->self);
+
+    if (req->current)
+        g_object_unref(req->current);
+
+    memset(req, 0, sizeof(Request));
+    g_slice_free(Request, req);
+}
+
+static void
+req_complete_release(Request *   req,
+                     GVariant *  secrets,
+                     const char *agent_dbus_owner,
+                     const char *agent_username,
+                     GError *    error)
+{
+    NMAgentManager *self = req->self;
+
+    switch (req->request_type) {
+    case REQUEST_TYPE_CON_GET:
+        req->con.get.callback(self,
+                              req,
+                              agent_dbus_owner,
+                              agent_username,
+                              req->con.current_has_modify,
+                              req->con.get.setting_name,
+                              req->con.get.flags,
+                              error ? NULL : secrets,
+                              error,
+                              req->con.get.callback_data);
+
+        break;
+    case REQUEST_TYPE_CON_SAVE:
+    case REQUEST_TYPE_CON_DEL:
+        break;
+    default:
+        g_return_if_reached();
+    }
+
+    request_free(req);
+}
+
+static void
+req_complete_cancel(Request *req, gboolean is_disposing)
+{
+    gs_free_error GError *error = NULL;
+
+    nm_assert(req && req->self);
+    nm_assert(!c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(req->self)->request_lst_head,
+                               &req->request_lst));
+
+    nm_utils_error_set_cancelled(&error, is_disposing, "NMAgentManager");
+    req_complete_release(req, NULL, NULL, NULL, error);
+}
+
+static void
+req_complete(Request *   req,
+             GVariant *  secrets,
+             const char *agent_dbus_owner,
+             const char *agent_username,
+             GError *    error)
+{
+    NMAgentManager *self = req->self;
+
+    nm_assert(
+        c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head, &req->request_lst));
+
+    c_list_unlink(&req->request_lst);
+
+    req_complete_release(req, secrets, agent_dbus_owner, agent_username, error);
+}
+
+static void
+req_complete_error(Request *req, GError *error)
+{
+    req_complete(req, NULL, NULL, NULL, error);
+}
+
+static int
+agent_compare_func(gconstpointer aa, gconstpointer bb, gpointer user_data)
+{
+    NMSecretAgent *   a   = (NMSecretAgent *) aa;
+    NMSecretAgent *   b   = (NMSecretAgent *) bb;
+    Request *         req = user_data;
+    NMSessionMonitor *sm;
+    gboolean          a_active, b_active;
+    gulong            a_pid, b_pid, requester;
+    guint64           a_start, b_start;
+
+    a_pid = nm_secret_agent_get_pid(a);
+    b_pid = nm_secret_agent_get_pid(b);
+
+    /* Prefer agents in the process the request came from */
+    if (nm_auth_subject_get_subject_type(req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
+        requester = nm_auth_subject_get_unix_process_pid(req->subject);
+
+        if (a_pid != b_pid) {
+            if (a_pid == requester)
+                return -1;
+            else if (b_pid == requester)
+                return 1;
+        }
+    }
+
+    /* Prefer agents in active sessions */
+    sm       = NM_AGENT_MANAGER_GET_PRIVATE(req->self)->session_monitor;
+    a_active = nm_session_monitor_session_exists(sm, nm_secret_agent_get_owner_uid(a), TRUE);
+    b_active = nm_session_monitor_session_exists(sm, nm_secret_agent_get_owner_uid(b), TRUE);
+    if (a_active && !b_active)
+        return -1;
+    else if (!a_active && b_active)
+        return 1;
+
+    /* Prefer agents launched later (this is essentially to ease agent debugging) */
+    a_start = nm_utils_get_start_time_for_pid(a_pid, NULL, NULL);
+    b_start = nm_utils_get_start_time_for_pid(b_pid, NULL, NULL);
+    if (a_start > b_start)
+        return -1;
+    else if (a_start < b_start)
+        return 1;
+
+    return 0;
+}
+
+static void
+request_add_agent(Request *req, NMSecretAgent *agent)
+{
+    NMAgentManager *self;
+
+    g_return_if_fail(req != NULL);
+    g_return_if_fail(agent != NULL);
+
+    self = req->self;
+
+    if (req->request_type == REQUEST_TYPE_CON_GET) {
+        NMAuthSubject *subject = nm_secret_agent_get_subject(agent);
+
+        /* Ensure the caller's username exists in the connection's permissions,
+         * or that the permissions is empty (ie, visible by everyone).
+         */
+        if (!nm_auth_is_subject_in_acl(req->con.connection, subject, NULL)) {
+            _LOGD(agent,
+                  "agent ignored for secrets request " LOG_REQ_FMT " (not in ACL)",
+                  LOG_REQ_ARG(req));
+            /* Connection not visible to this agent's user */
+            return;
+        }
+    }
+
+    /* If the request should filter agents by UID, do that now */
+    if (nm_auth_subject_get_subject_type(req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
+        uid_t agent_uid, subject_uid;
+
+        agent_uid   = nm_secret_agent_get_owner_uid(agent);
+        subject_uid = nm_auth_subject_get_unix_process_uid(req->subject);
+        if (agent_uid != subject_uid) {
+            _LOGD(agent,
+                  "agent ignored for secrets request " LOG_REQ_FMT " "
+                  "(uid %ld not required %ld)",
+                  LOG_REQ_ARG(req),
+                  (long) agent_uid,
+                  (long) subject_uid);
+            return;
+        }
+    }
+
+    _LOGD(agent, "agent allowed for secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req));
+
+    /* Add this agent to the list, sorted appropriately */
+    req->pending =
+        g_slist_insert_sorted_with_data(req->pending, g_object_ref(agent), agent_compare_func, req);
+}
+
+static void
+request_add_agents(NMAgentManager *self, Request *req)
+{
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+    NMSecretAgent *        agent;
+
+    c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+        if (agent->fully_registered)
+            request_add_agent(req, agent);
+    }
+}
+
+static void
+request_next_agent(Request *req)
+{
+    NMAgentManager *self;
+    GError *        error = NULL;
+
+    self = req->self;
+
+    nm_secret_agent_cancel_call(req->current, req->current_call_id);
+    nm_assert(!req->current_call_id);
+    g_clear_object(&req->current);
+
+    if (req->pending) {
+        /* Send the request to the next agent */
+        req->current = req->pending->data;
+        req->pending = g_slist_remove(req->pending, req->current);
+
+        _LOGD(req->current,
+              "agent %s secrets for request " LOG_REQ_FMT,
+              _request_type_to_string(req->request_type, TRUE),
+              LOG_REQ_ARG(req));
+
+        switch (req->request_type) {
+        case REQUEST_TYPE_CON_GET:
+            _con_get_request_start(req);
+            break;
+        case REQUEST_TYPE_CON_SAVE:
+            _con_save_request_start(req);
+            break;
+        case REQUEST_TYPE_CON_DEL:
+            _con_del_request_start(req);
+            break;
+        default:
+            g_assert_not_reached();
+        }
+    } else {
+        /* No more secret agents are available to fulfill this secrets request */
+        error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
+                                    NM_AGENT_MANAGER_ERROR_NO_SECRETS,
+                                    "No agents were available for this request.");
+        req_complete_error(req, error);
+        g_error_free(error);
+    }
+}
+
+static void
+request_remove_agent(Request *req, NMSecretAgent *agent)
+{
+    NMAgentManager *self;
+
+    g_return_if_fail(req != NULL);
+    g_return_if_fail(agent != NULL);
+
+    self = req->self;
+
+    if (agent == req->current) {
+        nm_assert(!g_slist_find(req->pending, agent));
+
+        _LOGD(agent, "current agent removed from secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req));
+
+        switch (req->request_type) {
+        case REQUEST_TYPE_CON_GET:
+        case REQUEST_TYPE_CON_SAVE:
+        case REQUEST_TYPE_CON_DEL:
+            /* This cancels the pending authorization requests. */
+            nm_clear_pointer(&req->con.chain, nm_auth_chain_destroy);
+            break;
+        default:
+            g_assert_not_reached();
+        }
+
+        request_next_agent(req);
+    } else if (g_slist_find(req->pending, agent)) {
+        req->pending = g_slist_remove(req->pending, agent);
+
+        _LOGD(agent, "agent removed from secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req));
+
+        g_object_unref(agent);
+    }
+}
+
+static gboolean
+request_start(gpointer user_data)
+{
+    Request *req = user_data;
+
+    req->idle_id = 0;
+
+    switch (req->request_type) {
+    case REQUEST_TYPE_CON_GET:
+        if (_con_get_try_complete_early(req))
+            goto out;
+        break;
+    default:
+        break;
+    }
+    request_next_agent(req);
+
+out:
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+static void
+_con_get_request_done(NMSecretAgent *      agent,
+                      NMSecretAgentCallId *call_id,
+                      GVariant *           secrets,
+                      GError *             error,
+                      gpointer             user_data)
+{
+    NMAgentManager *self;
+    Request *       req = user_data;
+    GVariant *      setting_secrets;
+    const char *    agent_dbus_owner;
+    gs_free char *  agent_name = NULL;
+
+    g_return_if_fail(call_id == req->current_call_id);
+    g_return_if_fail(agent == req->current);
+    g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET);
+
+    self = req->self;
+
+    req->current_call_id = NULL;
+
+    if (error) {
+        if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+            _LOGD(agent, "get secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req));
+            return;
+        }
+
+        _LOGD(agent,
+              "agent failed secrets request " LOG_REQ_FMT ": %s",
+              LOG_REQ_ARG(req),
+              error->message);
+
+        if (g_error_matches(error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_USER_CANCELED)) {
+            error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
+                                        NM_AGENT_MANAGER_ERROR_USER_CANCELED,
+                                        "User canceled the secrets request.");
+            req_complete_error(req, error);
+            g_error_free(error);
+        } else {
+            /* Tell the failed agent we're no longer interested. */
+            nm_secret_agent_cancel_call(req->current, req->current_call_id);
+
+            /* Try the next agent */
+            request_next_agent(req);
+            maybe_remove_agent_on_error(self, agent, error);
+        }
+        return;
+    }
+
+    /* Ensure the setting we wanted secrets for got returned and has something in it */
+    setting_secrets =
+        g_variant_lookup_value(secrets, req->con.get.setting_name, NM_VARIANT_TYPE_SETTING);
+    if (!setting_secrets || !g_variant_n_children(setting_secrets)) {
+        _LOGD(agent, "agent returned no secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
+        /* Try the next agent */
+        request_next_agent(req);
+        return;
+    }
+
+    _LOGD(agent, "agent returned secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
+
+    agent_name = nm_utils_uid_to_name(nm_secret_agent_get_owner_uid(agent));
+    if (agent_name && !g_utf8_validate(agent_name, -1, NULL)) {
+        /* Needs to be UTF-8 valid since it may be pushed through D-Bus */
+        nm_clear_g_free(&agent_name);
+    }
+
+    agent_dbus_owner = nm_secret_agent_get_dbus_owner(agent);
+
+    req_complete(req, secrets, agent_dbus_owner, agent_name, NULL);
+}
+
+static void
+set_secrets_not_required(NMConnection *connection, GVariant *dict)
+{
+    GVariantIter iter, setting_iter;
+    const char * setting_name = NULL;
+    GVariant *   setting_dict = NULL;
+
+    /* Iterate through the settings dicts */
+    g_variant_iter_init(&iter, dict);
+    while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) {
+        const char *key_name = NULL;
+        NMSetting * setting;
+        GVariant *  val;
+
+        setting = nm_connection_get_setting_by_name(connection, setting_name);
+        if (setting) {
+            /* Now through each secret in the setting and mark it as not required */
+            g_variant_iter_init(&setting_iter, setting_dict);
+            while (g_variant_iter_next(&setting_iter, "{&sv}", &key_name, &val)) {
+                /* For each secret, set the flag that it's not required; VPN
+                 * secrets need slightly different treatment here since the
+                 * "secrets" property is actually a dictionary of secrets.
+                 */
+                if (strcmp(setting_name, NM_SETTING_VPN_SETTING_NAME) == 0
+                    && strcmp(key_name, NM_SETTING_VPN_SECRETS) == 0
+                    && g_variant_is_of_type(val, G_VARIANT_TYPE("a{ss}"))) {
+                    GVariantIter vpn_secret_iter;
+                    const char * secret_name, *secret;
+
+                    g_variant_iter_init(&vpn_secret_iter, val);
+                    while (g_variant_iter_next(&vpn_secret_iter, "{&s&s}", &secret_name, &secret))
+                        nm_setting_set_secret_flags(setting,
+                                                    secret_name,
+                                                    NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+                                                    NULL);
+                } else
+                    nm_setting_set_secret_flags(setting,
+                                                key_name,
+                                                NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+                                                NULL);
+                g_variant_unref(val);
+            }
+        }
+        g_variant_unref(setting_dict);
+    }
+}
+
+static void
+_con_get_request_start_proceed(Request *req, gboolean include_system_secrets)
+{
+    NMConnection *tmp;
+
+    g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET);
+
+    tmp = nm_simple_connection_new_clone(req->con.connection);
+    nm_connection_clear_secrets(tmp);
+    if (include_system_secrets) {
+        if (req->con.get.existing_secrets)
+            (void) nm_connection_update_secrets(tmp,
+                                                req->con.get.setting_name,
+                                                req->con.get.existing_secrets,
+                                                NULL);
+    } else {
+        /* Update secret flags in the temporary connection to indicate that
+         * the system secrets we're not sending to the agent aren't required,
+         * so the agent can properly validate UI controls and such.
+         */
+        if (req->con.get.existing_secrets)
+            set_secrets_not_required(tmp, req->con.get.existing_secrets);
+    }
+
+    req->current_call_id = nm_secret_agent_get_secrets(req->current,
+                                                       req->con.path,
+                                                       tmp,
+                                                       req->con.get.setting_name,
+                                                       (const char **) req->con.get.hints,
+                                                       req->con.get.flags,
+                                                       _con_get_request_done,
+                                                       req);
+    if (!req->current_call_id) {
+        g_warn_if_reached();
+        request_next_agent(req);
+    }
+
+    g_object_unref(tmp);
+}
+
+static void
+_con_get_request_start_validated(NMAuthChain *          chain,
+                                 GDBusMethodInvocation *context,
+                                 gpointer               user_data)
+{
+    NMAgentManager *self;
+    Request *       req = user_data;
+    const char *    perm;
+
+    g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET);
+
+    self = req->self;
+
+    req->con.chain = NULL;
+
+    /* If the agent obtained the 'modify' permission, we send all system secrets
+     * to it.  If it didn't, we still ask it for secrets, but we don't send
+     * any system secrets.
+     */
+    perm = nm_auth_chain_get_data(chain, "perm");
+    g_assert(perm);
+    if (nm_auth_chain_get_result(chain, perm) == NM_AUTH_CALL_RESULT_YES)
+        req->con.current_has_modify = TRUE;
+
+    _LOGD(req->current,
+          "agent " LOG_REQ_FMT " MODIFY check result %s",
+          LOG_REQ_ARG(req),
+          req->con.current_has_modify ? "YES" : "NO");
+
+    _con_get_request_start_proceed(req, req->con.current_has_modify);
+}
+
+static void
+_con_get_request_start(Request *req)
+{
+    NMAgentManager *     self;
+    NMSettingConnection *s_con;
+    const char *         agent_dbus_owner, *perm;
+
+    self = req->self;
+
+    req->con.current_has_modify = FALSE;
+
+    agent_dbus_owner = nm_secret_agent_get_dbus_owner(req->current);
+
+    /* If the request flags allow user interaction, and there are existing
+     * system secrets (or blank secrets that are supposed to be system-owned),
+     * check whether the agent has the 'modify' permission before sending those
+     * secrets to the agent.  We shouldn't leak system-owned secrets to
+     * unprivileged users.
+     */
+    if ((req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE)
+        && (req->con.get.existing_secrets
+            || _nm_connection_aggregate(req->con.connection,
+                                        NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS,
+                                        NULL))) {
+        _LOGD(NULL,
+              "(" LOG_REQ_FMT ") request has system secrets; checking agent %s for MODIFY",
+              LOG_REQ_ARG(req),
+              agent_dbus_owner);
+
+        req->con.chain = nm_auth_chain_new_subject(nm_secret_agent_get_subject(req->current),
+                                                   NULL,
+                                                   _con_get_request_start_validated,
+                                                   req);
+        nm_assert(req->con.chain);
+
+        /* If the caller is the only user in the connection's permissions, then
+         * we use the 'modify.own' permission instead of 'modify.system'.  If the
+         * request affects more than just the caller, require 'modify.system'.
+         */
+        s_con = nm_connection_get_setting_connection(req->con.connection);
+        g_assert(s_con);
+        if (nm_setting_connection_get_num_permissions(s_con) == 1)
+            perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
+        else
+            perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
+        nm_auth_chain_set_data(req->con.chain, "perm", (gpointer) perm, NULL);
+
+        nm_auth_chain_add_call_unsafe(req->con.chain, perm, TRUE);
+    } else {
+        _LOGD(NULL,
+              "(" LOG_REQ_FMT ") requesting user-owned secrets from agent %s",
+              LOG_REQ_ARG(req),
+              agent_dbus_owner);
+
+        _con_get_request_start_proceed(req, FALSE);
+    }
+}
+
+static gboolean
+_con_get_try_complete_early(Request *req)
+{
+    NMAgentManager * self;
+    gs_unref_variant GVariant *setting_secrets = NULL;
+    gs_unref_object NMConnection *tmp          = NULL;
+    GError *                      error        = NULL;
+
+    self = req->self;
+
+    /* Check if there are any existing secrets */
+    if (req->con.get.existing_secrets)
+        setting_secrets = g_variant_lookup_value(req->con.get.existing_secrets,
+                                                 req->con.get.setting_name,
+                                                 NM_VARIANT_TYPE_SETTING);
+
+    if (!setting_secrets || !g_variant_n_children(setting_secrets))
+        return FALSE;
+
+    /* The connection already had secrets; check if any more are required.
+     * If no more are required, we're done.  If secrets are still needed,
+     * ask a secret agent for more.  This allows admins to provide generic
+     * secrets but allow additional user-specific ones as well.
+     */
+    tmp = nm_simple_connection_new_clone(req->con.connection);
+    g_assert(tmp);
+
+    if (!nm_connection_update_secrets(tmp,
+                                      req->con.get.setting_name,
+                                      req->con.get.existing_secrets,
+                                      &error)) {
+        req_complete_error(req, error);
+        g_clear_error(&error);
+        return TRUE;
+    }
+    /* Do we have everything we need? */
+    if (NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM)
+        || ((nm_connection_need_secrets(tmp, NULL) == NULL)
+            && !NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW))) {
+        _LOGD(NULL, "(" LOG_REQ_FMT ") system settings secrets sufficient", LOG_REQ_ARG(req));
+
+        /* Got everything, we're done */
+        req_complete(req, req->con.get.existing_secrets, NULL, NULL, NULL);
+        return TRUE;
+    }
+
+    _LOGD(NULL,
+          "(" LOG_REQ_FMT ") system settings secrets insufficient, asking agents",
+          LOG_REQ_ARG(req));
+
+    /* We don't, so ask some agents for additional secrets */
+    if (req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS && !req->pending) {
+        /* The request initiated from GetSecrets() via DBus,
+         * don't error out if any secrets are missing. */
+        req_complete(req, req->con.get.existing_secrets, NULL, NULL, NULL);
+        return TRUE;
+    }
+
+    /* Couldn't get secrets from system settings, so now we ask the
+     * agents for secrets.  Let the Agent Manager handle which agents
+     * we'll ask and in which order.
+     */
+    return FALSE;
+}
+
+/**
+ * nm_agent_manager_get_secrets:
+ * @self:
+ * @path:
+ * @connection:
+ * @subject:
+ * @existing_secrets:
+ * @flags:
+ * @hints:
+ * @callback:
+ * @callback_data:
+ *
+ * Requests secrets for a connection.
+ *
+ * This function cannot fail. The callback will be invoked
+ * asynchronously, but it will always be invoked exactly once.
+ * Even for cancellation and disposing of @self. In those latter
+ * cases, the callback is invoked synchronously during the cancellation/
+ * disposal.
+ *
+ * Returns: a call-id to cancel the call.
+ */
+NMAgentManagerCallId
+nm_agent_manager_get_secrets(NMAgentManager *             self,
+                             const char *                 path,
+                             NMConnection *               connection,
+                             NMAuthSubject *              subject,
+                             GVariant *                   existing_secrets,
+                             const char *                 setting_name,
+                             NMSecretAgentGetSecretsFlags flags,
+                             const char *const *          hints,
+                             NMAgentSecretsResultFunc     callback,
+                             gpointer                     callback_data)
+{
+    Request *req;
+
+    g_return_val_if_fail(self != NULL, NULL);
+    g_return_val_if_fail(path && *path, NULL);
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
+    g_return_val_if_fail(callback != NULL, NULL);
+
+    nm_log_dbg(LOGD_SETTINGS,
+               "Secrets requested for connection %s (%s/%s)",
+               path,
+               nm_connection_get_id(connection),
+               setting_name);
+
+    /* NOTE: a few things in the Request handling depend on existing_secrets
+     * being NULL if there aren't any system-owned secrets for this connection.
+     * This in turn depends on nm_connection_to_dbus() and nm_setting_to_hash()
+     * both returning NULL if they didn't hash anything.
+     */
+    req = request_new(self, REQUEST_TYPE_CON_GET, nm_connection_get_id(connection), subject);
+
+    req->con.path       = g_strdup(path);
+    req->con.connection = g_object_ref(connection);
+    if (existing_secrets)
+        req->con.get.existing_secrets = g_variant_ref(existing_secrets);
+    req->con.get.setting_name  = g_strdup(setting_name);
+    req->con.get.hints         = g_strdupv((char **) hints);
+    req->con.get.flags         = flags;
+    req->con.get.callback      = callback;
+    req->con.get.callback_data = callback_data;
+
+    if (!(req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM))
+        request_add_agents(self, req);
+    req->idle_id = g_idle_add(request_start, req);
+    return req;
+}
+
+void
+nm_agent_manager_cancel_secrets(NMAgentManager *self, NMAgentManagerCallId request_id)
+{
+    g_return_if_fail(self != NULL);
+    g_return_if_fail(request_id);
+    g_return_if_fail(request_id->request_type == REQUEST_TYPE_CON_GET);
+
+    nm_assert(c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head,
+                              &request_id->request_lst));
+
+    c_list_unlink(&request_id->request_lst);
+
+    req_complete_cancel(request_id, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+_con_save_request_done(NMSecretAgent *      agent,
+                       NMSecretAgentCallId *call_id,
+                       GVariant *           secrets,
+                       GError *             error,
+                       gpointer             user_data)
+{
+    NMAgentManager *self;
+    Request *       req = user_data;
+    const char *    agent_dbus_owner;
+
+    g_return_if_fail(call_id == req->current_call_id);
+    g_return_if_fail(agent == req->current);
+    g_return_if_fail(req->request_type == REQUEST_TYPE_CON_SAVE);
+
+    self = req->self;
+
+    req->current_call_id = NULL;
+
+    if (error) {
+        if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+            _LOGD(agent, "save secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req));
+            return;
+        }
+
+        _LOGD(agent,
+              "agent failed save secrets request " LOG_REQ_FMT ": %s",
+              LOG_REQ_ARG(req),
+              error->message);
+        /* Try the next agent */
+        request_next_agent(req);
+        maybe_remove_agent_on_error(self, agent, error);
+        return;
+    }
+
+    _LOGD(agent, "agent saved secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
+
+    agent_dbus_owner = nm_secret_agent_get_dbus_owner(agent);
+    req_complete(req, NULL, NULL, agent_dbus_owner, NULL);
+}
+
+static void
+_con_save_request_start(Request *req)
+{
+    req->current_call_id = nm_secret_agent_save_secrets(req->current,
+                                                        req->con.path,
+                                                        req->con.connection,
+                                                        _con_save_request_done,
+                                                        req);
+    if (!req->current_call_id) {
+        g_warn_if_reached();
+        request_next_agent(req);
+    }
+}
+
+void
+nm_agent_manager_save_secrets(NMAgentManager *self,
+                              const char *    path,
+                              NMConnection *  connection,
+                              NMAuthSubject * subject)
+{
+    Request *req;
+
+    g_return_if_fail(self);
+    g_return_if_fail(path && *path);
+    g_return_if_fail(NM_IS_CONNECTION(connection));
+
+    nm_log_dbg(LOGD_SETTINGS,
+               "Saving secrets for connection %s (%s)",
+               path,
+               nm_connection_get_id(connection));
+
+    req = request_new(self, REQUEST_TYPE_CON_SAVE, nm_connection_get_id(connection), subject);
+    req->con.path       = g_strdup(path);
+    req->con.connection = g_object_ref(connection);
+
+    request_add_agents(self, req);
+    req->idle_id = g_idle_add(request_start, req);
+}
+
+/*****************************************************************************/
+
+static void
+_con_del_request_done(NMSecretAgent *      agent,
+                      NMSecretAgentCallId *call_id,
+                      GVariant *           secrets,
+                      GError *             error,
+                      gpointer             user_data)
+{
+    NMAgentManager *self;
+    Request *       req = user_data;
+
+    g_return_if_fail(call_id == req->current_call_id);
+    g_return_if_fail(agent == req->current);
+    g_return_if_fail(req->request_type == REQUEST_TYPE_CON_DEL);
+
+    self = req->self;
+
+    req->current_call_id = NULL;
+
+    if (error) {
+        if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+            _LOGD(agent, "delete secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req));
+            return;
+        }
+
+        _LOGD(agent,
+              "agent failed delete secrets request " LOG_REQ_FMT ": %s",
+              LOG_REQ_ARG(req),
+              error->message);
+    } else {
+        _LOGD(agent, "agent deleted secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
+    }
+
+    /* Tell the next agent to delete secrets */
+    request_next_agent(req);
+    if (error)
+        maybe_remove_agent_on_error(self, agent, error);
+}
+
+static void
+_con_del_request_start(Request *req)
+{
+    req->current_call_id = nm_secret_agent_delete_secrets(req->current,
+                                                          req->con.path,
+                                                          req->con.connection,
+                                                          _con_del_request_done,
+                                                          req);
+    if (!req->current_call_id) {
+        g_warn_if_reached();
+        request_next_agent(req);
+    }
+}
+
+void
+nm_agent_manager_delete_secrets(NMAgentManager *self, const char *path, NMConnection *connection)
+{
+    NMAuthSubject *subject;
+    Request *      req;
+
+    g_return_if_fail(self != NULL);
+    g_return_if_fail(path && *path);
+    g_return_if_fail(NM_IS_CONNECTION(connection));
+
+    nm_log_dbg(LOGD_SETTINGS,
+               "Deleting secrets for connection %s (%s)",
+               path,
+               nm_connection_get_id(connection));
+
+    subject = nm_auth_subject_new_internal();
+    req     = request_new(self, REQUEST_TYPE_CON_DEL, nm_connection_get_id(connection), subject);
+    req->con.path       = g_strdup(path);
+    req->con.connection = g_object_ref(connection);
+    g_object_unref(subject);
+
+    request_add_agents(self, req);
+    req->idle_id = g_idle_add(request_start, req);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_agent_manager_has_agent_with_permission(NMAgentManager *self,
+                                           const char *    username,
+                                           const char *    permission)
+{
+    NMAgentManagerPrivate *priv;
+    NMSecretAgent *        agent;
+
+    g_return_val_if_fail(NM_IS_AGENT_MANAGER(self), FALSE);
+    g_return_val_if_fail(username, FALSE);
+    g_return_val_if_fail(permission, FALSE);
+
+    priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+
+    c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+        if (!agent->fully_registered)
+            continue;
+        if (!nm_streq0(nm_secret_agent_get_owner_username(agent), username))
+            continue;
+        if (nm_secret_agent_has_permission(agent, permission))
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_agent_manager_all_agents_have_capability(NMAgentManager *          manager,
+                                            NMAuthSubject *           subject,
+                                            NMSecretAgentCapabilities capability)
+{
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(manager);
+    NMSecretAgent *        agent;
+    gboolean               subject_is_unix_process =
+        (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
+    gulong subject_uid =
+        subject_is_unix_process ? nm_auth_subject_get_unix_process_uid(subject) : 0u;
+
+    c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+        if (!agent->fully_registered)
+            continue;
+        if (subject_is_unix_process && nm_secret_agent_get_owner_uid(agent) != subject_uid)
+            continue;
+        if (!(nm_secret_agent_get_capabilities(agent) & capability))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+authority_changed_cb(NMAuthManager *auth_manager, NMAgentManager *self)
+{
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+    NMSecretAgent *        agent;
+
+    c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst)
+        _agent_create_auth_chain(self, agent, NULL);
+}
+
+/*****************************************************************************/
+
+static void
+nm_agent_manager_init(NMAgentManager *self)
+{
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+
+    priv->agent_version_id = 1;
+    c_list_init(&priv->agent_lst_head);
+    c_list_init(&priv->request_lst_head);
+}
+
+static void
+constructed(GObject *object)
+{
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(object);
+
+    G_OBJECT_CLASS(nm_agent_manager_parent_class)->constructed(object);
+
+    priv->auth_mgr        = g_object_ref(nm_auth_manager_get());
+    priv->session_monitor = g_object_ref(nm_session_monitor_get());
+
+    nm_dbus_object_export(NM_DBUS_OBJECT(object));
+
+    g_signal_connect(priv->auth_mgr,
+                     NM_AUTH_MANAGER_SIGNAL_CHANGED,
+                     G_CALLBACK(authority_changed_cb),
+                     object);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMAgentManager *       self = NM_AGENT_MANAGER(object);
+    NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
+    Request *              request;
+    NMSecretAgent *        agent;
+
+    while ((request = c_list_first_entry(&priv->request_lst_head, Request, request_lst))) {
+        c_list_unlink(&request->request_lst);
+        req_complete_cancel(request, TRUE);
+    }
+
+    while ((agent = c_list_first_entry(&priv->agent_lst_head, NMSecretAgent, agent_lst)))
+        _agent_remove(self, agent);
+
+    if (priv->auth_mgr) {
+        g_signal_handlers_disconnect_by_func(priv->auth_mgr,
+                                             G_CALLBACK(authority_changed_cb),
+                                             object);
+        g_clear_object(&priv->auth_mgr);
+    }
+
+    nm_dbus_object_unexport(NM_DBUS_OBJECT(object));
+
+    g_clear_object(&priv->session_monitor);
+
+    G_OBJECT_CLASS(nm_agent_manager_parent_class)->dispose(object);
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_agent_manager = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_AGENT_MANAGER,
+        .methods = NM_DEFINE_GDBUS_METHOD_INFOS(
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "Register",
+                    .in_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("identifier", "s"), ), ),
+                .handle = impl_agent_manager_register, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "RegisterWithCapabilities",
+                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("identifier", "s"),
+                        NM_DEFINE_GDBUS_ARG_INFO("capabilities", "u"), ), ),
+                .handle = impl_agent_manager_register_with_capabilities, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Unregister", ),
+                                                .handle = impl_agent_manager_unregister, ), ), ),
+};
+
+static void
+nm_agent_manager_class_init(NMAgentManagerClass *agent_manager_class)
+{
+    GObjectClass *     object_class      = G_OBJECT_CLASS(agent_manager_class);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(agent_manager_class);
+
+    dbus_object_class->export_path     = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH_AGENT_MANAGER);
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_agent_manager);
+
+    object_class->constructed = constructed;
+    object_class->dispose     = dispose;
+
+    signals[AGENT_REGISTERED] = g_signal_new(NM_AGENT_MANAGER_AGENT_REGISTERED,
+                                             G_OBJECT_CLASS_TYPE(object_class),
+                                             G_SIGNAL_RUN_FIRST,
+                                             0,
+                                             NULL,
+                                             NULL,
+                                             g_cclosure_marshal_VOID__OBJECT,
+                                             G_TYPE_NONE,
+                                             1,
+                                             G_TYPE_OBJECT);
+}
diff --git a/src/core/settings/nm-agent-manager.h b/src/core/settings/nm-agent-manager.h
new file mode 100644
index 00000000..b26af4ef
--- /dev/null
+++ b/src/core/settings/nm-agent-manager.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2011 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_AGENT_MANAGER_H__
+#define __NETWORKMANAGER_AGENT_MANAGER_H__
+
+#include "nm-connection.h"
+
+#include "nm-dbus-object.h"
+#include "nm-secret-agent.h"
+
+#define NM_TYPE_AGENT_MANAGER (nm_agent_manager_get_type())
+#define NM_AGENT_MANAGER(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_AGENT_MANAGER, NMAgentManager))
+#define NM_AGENT_MANAGER_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_AGENT_MANAGER, NMAgentManagerClass))
+#define NM_IS_AGENT_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_AGENT_MANAGER))
+#define NM_IS_AGENT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_AGENT_MANAGER))
+#define NM_AGENT_MANAGER_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_AGENT_MANAGER, NMAgentManagerClass))
+
+#define NM_AGENT_MANAGER_AGENT_REGISTERED "agent-registered"
+
+typedef struct _NMAgentManagerCallId *NMAgentManagerCallId;
+
+typedef struct _NMAgentManagerClass NMAgentManagerClass;
+
+GType nm_agent_manager_get_type(void);
+
+NMAgentManager *nm_agent_manager_get(void);
+
+guint64 nm_agent_manager_get_agent_version_id(NMAgentManager *self);
+
+/* If no agent fulfilled the secrets request, agent_dbus_owner will be NULL */
+typedef void (*NMAgentSecretsResultFunc)(NMAgentManager *             manager,
+                                         NMAgentManagerCallId         call_id,
+                                         const char *                 agent_dbus_owner,
+                                         const char *                 agent_uname,
+                                         gboolean                     agent_has_modify,
+                                         const char *                 setting_name,
+                                         NMSecretAgentGetSecretsFlags flags,
+                                         GVariant *                   secrets,
+                                         GError *                     error,
+                                         gpointer                     user_data);
+
+NMAgentManagerCallId nm_agent_manager_get_secrets(NMAgentManager *             manager,
+                                                  const char *                 path,
+                                                  NMConnection *               connection,
+                                                  NMAuthSubject *              subject,
+                                                  GVariant *                   existing_secrets,
+                                                  const char *                 setting_name,
+                                                  NMSecretAgentGetSecretsFlags flags,
+                                                  const char *const *          hints,
+                                                  NMAgentSecretsResultFunc     callback,
+                                                  gpointer                     callback_data);
+
+void nm_agent_manager_cancel_secrets(NMAgentManager *manager, NMAgentManagerCallId request_id);
+
+void nm_agent_manager_save_secrets(NMAgentManager *manager,
+                                   const char *    path,
+                                   NMConnection *  connection,
+                                   NMAuthSubject * subject);
+
+void nm_agent_manager_delete_secrets(NMAgentManager *manager,
+                                     const char *    path,
+                                     NMConnection *  connection);
+
+gboolean nm_agent_manager_has_agent_with_permission(NMAgentManager *self,
+                                                    const char *    username,
+                                                    const char *    permission);
+
+gboolean nm_agent_manager_all_agents_have_capability(NMAgentManager *          manager,
+                                                     NMAuthSubject *           subject,
+                                                     NMSecretAgentCapabilities capability);
+
+#endif /* __NETWORKMANAGER_AGENT_MANAGER_H__ */
diff --git a/src/core/settings/nm-secret-agent.c b/src/core/settings/nm-secret-agent.c
new file mode 100644
index 00000000..7e7c5d7d
--- /dev/null
+++ b/src/core/settings/nm-secret-agent.c
@@ -0,0 +1,808 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2011 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-secret-agent.h"
+
+#include <sys/types.h>
+#include <pwd.h>
+
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-dbus-aux.h"
+#include "nm-dbus-interface.h"
+#include "nm-core-internal.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
+#include "nm-simple-connection.h"
+#include "NetworkManagerUtils.h"
+#include "c-list/src/c-list.h"
+
+/*****************************************************************************/
+
+#define METHOD_GET_SECRETS        "GetSecrets"
+#define METHOD_CANCEL_GET_SECRETS "CancelGetSecrets"
+#define METHOD_SAVE_SECRETS       "SaveSecrets"
+#define METHOD_DELETE_SECRETS     "DeleteSecrets"
+
+enum {
+    DISCONNECTED,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct _NMSecretAgentPrivate {
+    CList                     permissions;
+    CList                     requests;
+    GDBusConnection *         dbus_connection;
+    char *                    description;
+    NMAuthSubject *           subject;
+    char *                    identifier;
+    char *                    owner_username;
+    char *                    dbus_owner;
+    GCancellable *            name_owner_cancellable;
+    guint                     name_owner_changed_id;
+    NMSecretAgentCapabilities capabilities;
+    bool                      shutdown_wait_obj_registered : 1;
+} NMSecretAgentPrivate;
+
+struct _NMSecretAgentClass {
+    GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT)
+
+#define NM_SECRET_AGENT_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE_PTR(self, NMSecretAgent, NM_IS_SECRET_AGENT)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "secret-agent"
+#define _NMLOG_DOMAIN      LOGD_AGENTS
+#define _NMLOG(level, ...)                                                       \
+    G_STMT_START                                                                 \
+    {                                                                            \
+        if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) {                      \
+            char _prefix[64];                                                    \
+                                                                                 \
+            if ((self)) {                                                        \
+                g_snprintf(_prefix,                                              \
+                           sizeof(_prefix),                                      \
+                           _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT "]", \
+                           NM_HASH_OBFUSCATE_PTR(self));                         \
+            } else                                                               \
+                g_strlcpy(_prefix, _NMLOG_PREFIX_NAME, sizeof(_prefix));         \
+                                                                                 \
+            _nm_log((level),                                                     \
+                    (_NMLOG_DOMAIN),                                             \
+                    0,                                                           \
+                    NULL,                                                        \
+                    NULL,                                                        \
+                    "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                   \
+                    _prefix _NM_UTILS_MACRO_REST(__VA_ARGS__));                  \
+        }                                                                        \
+    }                                                                            \
+    G_STMT_END
+
+#define _NMLOG2(level, call_id, ...)                                                             \
+    G_STMT_START                                                                                 \
+    {                                                                                            \
+        NMSecretAgentCallId *const _call_id = (call_id);                                         \
+                                                                                                 \
+        nm_assert(_call_id);                                                                     \
+                                                                                                 \
+        nm_log((level),                                                                          \
+               (_NMLOG_DOMAIN),                                                                  \
+               NULL,                                                                             \
+               NULL,                                                                             \
+               "%s[" NM_HASH_OBFUSCATE_PTR_FMT "] request [" NM_HASH_OBFUSCATE_PTR_FMT           \
+               ",%s,%s%s%s%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                             \
+               _NMLOG_PREFIX_NAME,                                                               \
+               NM_HASH_OBFUSCATE_PTR(_call_id->self),                                            \
+               NM_HASH_OBFUSCATE_PTR(_call_id),                                                  \
+               _call_id->method_name,                                                            \
+               NM_PRINT_FMT_QUOTE_STRING(_call_id->path),                                        \
+               (_call_id->cancellable ? "" : " (cancelled)") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                                            \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static NM_UTILS_FLAGS2STR_DEFINE(_capabilities_to_string,
+                                 NMSecretAgentCapabilities,
+                                 NM_UTILS_FLAGS2STR(NM_SECRET_AGENT_CAPABILITY_NONE, "none"),
+                                 NM_UTILS_FLAGS2STR(NM_SECRET_AGENT_CAPABILITY_VPN_HINTS,
+                                                    "vpn-hints"), );
+
+/*****************************************************************************/
+
+struct _NMSecretAgentCallId {
+    CList                 lst;
+    NMSecretAgent *       self;
+    GCancellable *        cancellable;
+    char *                path;
+    const char *          method_name;
+    char *                setting_name;
+    NMSecretAgentCallback callback;
+    gpointer              callback_data;
+};
+
+static NMSecretAgentCallId *
+_call_id_new(NMSecretAgent *       self,
+             const char *          method_name, /* this must be a static string. */
+             const char *          path,
+             const char *          setting_name,
+             NMSecretAgentCallback callback,
+             gpointer              callback_data)
+{
+    NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+    NMSecretAgentCallId * call_id;
+
+    call_id  = g_slice_new(NMSecretAgentCallId);
+    *call_id = (NMSecretAgentCallId){
+        .self          = g_object_ref(self),
+        .path          = g_strdup(path),
+        .setting_name  = g_strdup(setting_name),
+        .method_name   = method_name,
+        .callback      = callback,
+        .callback_data = callback_data,
+        .cancellable   = g_cancellable_new(),
+    };
+    c_list_link_tail(&priv->requests, &call_id->lst);
+
+    _LOG2T(call_id, "new request...");
+
+    if (!priv->shutdown_wait_obj_registered) {
+        /* self has async requests (that keep self alive). As long as
+         * we have pending requests, shutdown is blocked. */
+        priv->shutdown_wait_obj_registered = TRUE;
+        nm_shutdown_wait_obj_register_object(G_OBJECT(self), "secret-agent");
+    }
+
+    return call_id;
+}
+
+#define _call_id_new(self, method_name, path, setting_name, callback, callback_data) \
+    _call_id_new(self, "" method_name "", path, setting_name, callback, callback_data)
+
+static void
+_call_id_free(NMSecretAgentCallId *call_id)
+{
+    c_list_unlink_stale(&call_id->lst);
+    g_free(call_id->path);
+    g_free(call_id->setting_name);
+    nm_g_object_unref(call_id->cancellable);
+    g_object_unref(call_id->self);
+    nm_g_slice_free(call_id);
+}
+
+static void
+_call_id_invoke_callback(NMSecretAgentCallId *call_id,
+                         GVariant *           secrets,
+                         GError *             error,
+                         gboolean             cancelled,
+                         gboolean             free_call_id)
+{
+    gs_free_error GError *error_cancelled = NULL;
+
+    nm_assert(call_id);
+    nm_assert(!c_list_is_empty(&call_id->lst));
+
+    c_list_unlink(&call_id->lst);
+
+    if (cancelled) {
+        nm_assert(!secrets);
+        nm_assert(!error);
+        if (call_id->callback) {
+            nm_utils_error_set_cancelled(&error_cancelled, FALSE, "NMSecretAgent");
+            error = error_cancelled;
+        }
+        _LOG2T(call_id, "cancelled");
+    } else if (error) {
+        nm_assert(!secrets);
+        _LOG2T(call_id, "completed with failure: %s", error->message);
+    } else {
+        nm_assert(!secrets || g_variant_is_of_type(secrets, G_VARIANT_TYPE("a{sa{sv}}")));
+        nm_assert((!!secrets) == nm_streq0(call_id->method_name, METHOD_GET_SECRETS));
+        _LOG2T(call_id, "completed successfully");
+    }
+
+    if (call_id->callback)
+        call_id->callback(call_id->self, call_id, secrets, error, call_id->callback_data);
+
+    if (free_call_id)
+        _call_id_free(call_id);
+}
+
+/*****************************************************************************/
+
+static char *
+_create_description(const char *dbus_owner, const char *identifier, gulong uid)
+{
+    return g_strdup_printf("%s/%s/%lu", dbus_owner, identifier, uid);
+}
+
+const char *
+nm_secret_agent_get_description(NMSecretAgent *agent)
+{
+    NMSecretAgentPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL);
+
+    priv = NM_SECRET_AGENT_GET_PRIVATE(agent);
+    if (!priv->description) {
+        priv->description =
+            _create_description(priv->dbus_owner,
+                                priv->identifier,
+                                nm_auth_subject_get_unix_process_uid(priv->subject));
+    }
+
+    return priv->description;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_secret_agent_get_dbus_owner(NMSecretAgent *agent)
+{
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL);
+
+    return NM_SECRET_AGENT_GET_PRIVATE(agent)->dbus_owner;
+}
+
+const char *
+nm_secret_agent_get_identifier(NMSecretAgent *agent)
+{
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL);
+
+    return NM_SECRET_AGENT_GET_PRIVATE(agent)->identifier;
+}
+
+gulong
+nm_secret_agent_get_owner_uid(NMSecretAgent *agent)
+{
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), G_MAXULONG);
+
+    return nm_auth_subject_get_unix_process_uid(NM_SECRET_AGENT_GET_PRIVATE(agent)->subject);
+}
+
+const char *
+nm_secret_agent_get_owner_username(NMSecretAgent *agent)
+{
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL);
+
+    return NM_SECRET_AGENT_GET_PRIVATE(agent)->owner_username;
+}
+
+gulong
+nm_secret_agent_get_pid(NMSecretAgent *agent)
+{
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), G_MAXULONG);
+
+    return nm_auth_subject_get_unix_process_pid(NM_SECRET_AGENT_GET_PRIVATE(agent)->subject);
+}
+
+NMSecretAgentCapabilities
+nm_secret_agent_get_capabilities(NMSecretAgent *agent)
+{
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NM_SECRET_AGENT_CAPABILITY_NONE);
+
+    return NM_SECRET_AGENT_GET_PRIVATE(agent)->capabilities;
+}
+
+NMAuthSubject *
+nm_secret_agent_get_subject(NMSecretAgent *agent)
+{
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL);
+
+    return NM_SECRET_AGENT_GET_PRIVATE(agent)->subject;
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_secret_agent_add_permission:
+ * @agent: A #NMSecretAgent.
+ * @permission: The name of the permission
+ *
+ * Records whether or not the agent has a given permission.
+ */
+void
+nm_secret_agent_add_permission(NMSecretAgent *agent, const char *permission, gboolean allowed)
+{
+    NMSecretAgentPrivate *priv;
+    NMCListElem *         elem;
+
+    g_return_if_fail(agent != NULL);
+    g_return_if_fail(permission != NULL);
+
+    priv = NM_SECRET_AGENT_GET_PRIVATE(agent);
+
+    elem = nm_c_list_elem_find_first(&priv->permissions, p, nm_streq(p, permission));
+
+    if (elem) {
+        if (!allowed)
+            nm_c_list_elem_free_full(elem, g_free);
+        return;
+    }
+
+    if (allowed) {
+        c_list_link_tail(&priv->permissions, &nm_c_list_elem_new_stale(g_strdup(permission))->lst);
+    }
+}
+
+/**
+ * nm_secret_agent_has_permission:
+ * @agent: A #NMSecretAgent.
+ * @permission: The name of the permission to check for
+ *
+ * Returns whether or not the agent has the given permission.
+ *
+ * Returns: %TRUE if the agent has the given permission, %FALSE if it does not
+ * or if the permission was not previous recorded with
+ * nm_secret_agent_add_permission().
+ */
+gboolean
+nm_secret_agent_has_permission(NMSecretAgent *agent, const char *permission)
+{
+    g_return_val_if_fail(agent != NULL, FALSE);
+    g_return_val_if_fail(permission != NULL, FALSE);
+
+    return !!nm_c_list_elem_find_first(&NM_SECRET_AGENT_GET_PRIVATE(agent)->permissions,
+                                       p,
+                                       nm_streq(p, permission));
+}
+
+/*****************************************************************************/
+
+static void
+_dbus_call_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    NMSecretAgentCallId *call_id;
+    gs_unref_variant GVariant *ret     = NULL;
+    gs_unref_variant GVariant *secrets = NULL;
+    gs_free_error GError *error        = NULL;
+
+    ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
+
+    if (!ret && nm_utils_error_is_cancelled(error))
+        return;
+
+    call_id = user_data;
+
+    if (!ret)
+        g_dbus_error_strip_remote_error(error);
+    else {
+        if (nm_streq(call_id->method_name, METHOD_GET_SECRETS)) {
+            g_variant_get(ret, "(@a{sa{sv}})", &secrets);
+        }
+    }
+
+    _call_id_invoke_callback(call_id, secrets, error, FALSE, TRUE);
+}
+
+/*****************************************************************************/
+
+NMSecretAgentCallId *
+nm_secret_agent_get_secrets(NMSecretAgent *              self,
+                            const char *                 path,
+                            NMConnection *               connection,
+                            const char *                 setting_name,
+                            const char **                hints,
+                            NMSecretAgentGetSecretsFlags flags,
+                            NMSecretAgentCallback        callback,
+                            gpointer                     callback_data)
+{
+    NMSecretAgentPrivate *priv;
+    GVariant *            dict;
+    NMSecretAgentCallId * call_id;
+
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL);
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
+    g_return_val_if_fail(path && *path, NULL);
+    g_return_val_if_fail(setting_name, NULL);
+    g_return_val_if_fail(callback, NULL);
+
+    priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL);
+
+    /* Mask off the private flags if present */
+    flags &= ~(NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM
+               | NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS);
+
+    call_id = _call_id_new(self, METHOD_GET_SECRETS, path, setting_name, callback, callback_data);
+
+    g_dbus_connection_call(priv->dbus_connection,
+                           priv->dbus_owner,
+                           NM_DBUS_PATH_SECRET_AGENT,
+                           NM_DBUS_INTERFACE_SECRET_AGENT,
+                           call_id->method_name,
+                           g_variant_new("(@a{sa{sv}}os^asu)",
+                                         dict,
+                                         path,
+                                         setting_name,
+                                         hints ?: NM_PTRARRAY_EMPTY(const char *),
+                                         (guint32) flags),
+                           G_VARIANT_TYPE("(a{sa{sv}})"),
+                           G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                           120000,
+                           call_id->cancellable,
+                           _dbus_call_cb,
+                           call_id);
+
+    return call_id;
+}
+
+/*****************************************************************************/
+
+static void
+_call_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    NMSecretAgentCallId *call_id   = user_data;
+    gs_free_error GError *error    = NULL;
+    gs_unref_variant GVariant *ret = NULL;
+
+    ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
+
+    if (ret)
+        _LOG2T(call_id, "success cancelling GetSecrets");
+    else if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
+        _LOG2T(call_id, "cancelling GetSecrets no longer works as service disconnected");
+    else {
+        _LOG2T(call_id, "failed to cancel GetSecrets: %s", error->message);
+    }
+
+    _call_id_free(call_id);
+}
+
+/**
+ * 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,
+ *   in which case it does nothing.
+ *
+ * It is an error to pass an invalid @call_id or a @call_id for an operation
+ * that already completed. It is also an error to cancel the call from inside
+ * the callback, at that point the call is already completed.
+ * In case of nm_secret_agent_cancel_call() this will synchronously invoke the
+ * callback before nm_secret_agent_cancel_call() returns.
+ */
+void
+nm_secret_agent_cancel_call(NMSecretAgent *self, NMSecretAgentCallId *call_id)
+{
+    NMSecretAgentPrivate *priv;
+    gboolean              free_call_id = TRUE;
+
+    if (!call_id) {
+        /* for convenience, %NULL is accepted fine. */
+        nm_assert(!self || NM_IS_SECRET_AGENT(self));
+        return;
+    }
+
+    g_return_if_fail(NM_IS_SECRET_AGENT(call_id->self));
+    g_return_if_fail(!c_list_is_empty(&call_id->lst));
+
+    /* Theoretically, call-id already has a self pointer. But nm_secret_agent_cancel_call() has only
+     * one user: NMAgentManager. And that one has the self-pointer at hand, so the only purpose of
+     * the @self argument is to assert that we are cancelling the expected call.
+     *
+     * We could drop the @self argument, but that just remove an additional assert-check from
+     * our code, without making a simplification for the only caller of this function. */
+    g_return_if_fail(self == call_id->self);
+
+    priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    nm_assert(c_list_contains(&priv->requests, &call_id->lst));
+
+    nm_clear_g_cancellable(&call_id->cancellable);
+
+    if (nm_streq(call_id->method_name, METHOD_GET_SECRETS)) {
+        g_dbus_connection_call(
+            priv->dbus_connection,
+            priv->dbus_owner,
+            NM_DBUS_PATH_SECRET_AGENT,
+            NM_DBUS_INTERFACE_SECRET_AGENT,
+            METHOD_CANCEL_GET_SECRETS,
+            g_variant_new("(os)", call_id->path, call_id->setting_name),
+            G_VARIANT_TYPE("()"),
+            G_DBUS_CALL_FLAGS_NO_AUTO_START,
+            NM_SHUTDOWN_TIMEOUT_MS,
+            NULL, /* this operation is not cancellable. We rely on the timeout. */
+            _call_cancel_cb,
+            call_id);
+        /* we keep call-id alive, but it will be unlinked from priv->requests.
+         * _call_cancel_cb() will finally free it later. */
+        free_call_id = FALSE;
+    }
+
+    _call_id_invoke_callback(call_id, NULL, NULL, TRUE, free_call_id);
+}
+
+/*****************************************************************************/
+
+NMSecretAgentCallId *
+nm_secret_agent_save_secrets(NMSecretAgent *       self,
+                             const char *          path,
+                             NMConnection *        connection,
+                             NMSecretAgentCallback callback,
+                             gpointer              callback_data)
+{
+    NMSecretAgentPrivate *priv;
+    GVariant *            dict;
+    NMSecretAgentCallId * call_id;
+
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL);
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
+    g_return_val_if_fail(path && *path, NULL);
+
+    priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    /* Caller should have ensured that only agent-owned secrets exist in 'connection' */
+    dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL);
+
+    call_id = _call_id_new(self, METHOD_SAVE_SECRETS, path, NULL, callback, callback_data);
+
+    g_dbus_connection_call(priv->dbus_connection,
+                           priv->dbus_owner,
+                           NM_DBUS_PATH_SECRET_AGENT,
+                           NM_DBUS_INTERFACE_SECRET_AGENT,
+                           call_id->method_name,
+                           g_variant_new("(@a{sa{sv}}o)", dict, path),
+                           G_VARIANT_TYPE("()"),
+                           G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                           60000,
+                           call_id->cancellable,
+                           _dbus_call_cb,
+                           call_id);
+
+    return call_id;
+}
+
+/*****************************************************************************/
+
+NMSecretAgentCallId *
+nm_secret_agent_delete_secrets(NMSecretAgent *       self,
+                               const char *          path,
+                               NMConnection *        connection,
+                               NMSecretAgentCallback callback,
+                               gpointer              callback_data)
+{
+    NMSecretAgentPrivate *priv;
+    GVariant *            dict;
+    NMSecretAgentCallId * call_id;
+
+    g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL);
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
+    g_return_val_if_fail(path && *path, NULL);
+
+    priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    /* No secrets sent; agents must be smart enough to track secrets using the UUID or something */
+    dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
+
+    call_id = _call_id_new(self, METHOD_DELETE_SECRETS, path, NULL, callback, callback_data);
+
+    g_dbus_connection_call(priv->dbus_connection,
+                           priv->dbus_owner,
+                           NM_DBUS_PATH_SECRET_AGENT,
+                           NM_DBUS_INTERFACE_SECRET_AGENT,
+                           call_id->method_name,
+                           g_variant_new("(@a{sa{sv}}o)", dict, path),
+                           G_VARIANT_TYPE("()"),
+                           G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                           60000,
+                           call_id->cancellable,
+                           _dbus_call_cb,
+                           call_id);
+    return call_id;
+}
+
+/*****************************************************************************/
+
+static void
+name_owner_changed(NMSecretAgent *self, const char *owner)
+{
+    NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    nm_assert(!priv->name_owner_cancellable);
+
+    owner = nm_str_not_empty(owner);
+
+    _LOGT("name-owner-changed: %s%s%s",
+          NM_PRINT_FMT_QUOTED(owner, "has ", owner, "", "disconnected"));
+
+    if (owner)
+        return;
+
+    nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
+
+    g_signal_emit(self, signals[DISCONNECTED], 0);
+}
+
+static void
+name_owner_changed_cb(GDBusConnection *dbus_connection,
+                      const char *     sender_name,
+                      const char *     object_path,
+                      const char *     interface_name,
+                      const char *     signal_name,
+                      GVariant *       parameters,
+                      gpointer         user_data)
+{
+    NMSecretAgent *self      = NM_SECRET_AGENT(user_data);
+    const char *   new_owner = NULL;
+
+    if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) {
+        g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner);
+    }
+
+    nm_clear_g_cancellable(&NM_SECRET_AGENT_GET_PRIVATE(self)->name_owner_cancellable);
+
+    name_owner_changed(self, new_owner);
+}
+
+static void
+get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data)
+{
+    NMSecretAgent *self;
+
+    if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+    self = user_data;
+
+    g_clear_object(&NM_SECRET_AGENT_GET_PRIVATE(self)->name_owner_cancellable);
+
+    name_owner_changed(self, name_owner);
+}
+
+/*****************************************************************************/
+
+NMSecretAgent *
+nm_secret_agent_new(GDBusMethodInvocation *   context,
+                    NMAuthSubject *           subject,
+                    const char *              identifier,
+                    NMSecretAgentCapabilities capabilities)
+{
+    NMSecretAgent *       self;
+    NMSecretAgentPrivate *priv;
+    const char *          dbus_owner;
+    gs_free char *        owner_username = NULL;
+    char *                description    = NULL;
+    char                  buf_subject[64];
+    char                  buf_caps[150];
+    gulong                uid;
+    GDBusConnection *     dbus_connection;
+
+    g_return_val_if_fail(context != NULL, NULL);
+    g_return_val_if_fail(NM_IS_AUTH_SUBJECT(subject), NULL);
+    g_return_val_if_fail(nm_auth_subject_get_subject_type(subject)
+                             == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
+                         NULL);
+    g_return_val_if_fail(identifier != NULL, NULL);
+
+    dbus_connection = g_dbus_method_invocation_get_connection(context);
+
+    g_return_val_if_fail(G_IS_DBUS_CONNECTION(dbus_connection), NULL);
+
+    uid = nm_auth_subject_get_unix_process_uid(subject);
+
+    owner_username = nm_utils_uid_to_name(uid);
+
+    dbus_owner = nm_auth_subject_get_unix_process_dbus_sender(subject);
+
+    self = g_object_new(NM_TYPE_SECRET_AGENT, NULL);
+
+    priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    priv->dbus_connection = g_object_ref(dbus_connection);
+
+    _LOGT("constructed: %s, owner=%s%s%s (%s), unique-name=%s%s%s, capabilities=%s",
+          (description = _create_description(dbus_owner, identifier, uid)),
+          NM_PRINT_FMT_QUOTE_STRING(owner_username),
+          nm_auth_subject_to_string(subject, buf_subject, sizeof(buf_subject)),
+          NM_PRINT_FMT_QUOTE_STRING(g_dbus_connection_get_unique_name(priv->dbus_connection)),
+          _capabilities_to_string(capabilities, buf_caps, sizeof(buf_caps)));
+
+    priv->identifier     = g_strdup(identifier);
+    priv->owner_username = g_steal_pointer(&owner_username);
+    priv->dbus_owner     = g_strdup(dbus_owner);
+    priv->description    = description;
+    priv->capabilities   = capabilities;
+    priv->subject        = g_object_ref(subject);
+
+    priv->name_owner_changed_id =
+        nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection,
+                                                               priv->dbus_owner,
+                                                               name_owner_changed_cb,
+                                                               self,
+                                                               NULL);
+
+    priv->name_owner_cancellable = g_cancellable_new();
+    nm_dbus_connection_call_get_name_owner(priv->dbus_connection,
+                                           priv->dbus_owner,
+                                           -1,
+                                           priv->name_owner_cancellable,
+                                           get_name_owner_cb,
+                                           self);
+
+    return self;
+}
+
+static void
+nm_secret_agent_init(NMSecretAgent *self)
+{
+    NMSecretAgentPrivate *priv;
+
+    priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_SECRET_AGENT, NMSecretAgentPrivate);
+
+    self->_priv = priv;
+
+    c_list_init(&self->agent_lst);
+    c_list_init(&priv->permissions);
+    c_list_init(&priv->requests);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSecretAgent *       self = NM_SECRET_AGENT(object);
+    NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    nm_assert(c_list_is_empty(&self->agent_lst));
+    nm_assert(!self->auth_chain);
+    nm_assert(c_list_is_empty(&priv->requests));
+
+    nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
+
+    nm_clear_g_cancellable(&priv->name_owner_cancellable);
+
+    G_OBJECT_CLASS(nm_secret_agent_parent_class)->dispose(object);
+}
+
+static void
+finalize(GObject *object)
+{
+    NMSecretAgent *       self = NM_SECRET_AGENT(object);
+    NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self);
+
+    g_free(priv->description);
+    g_free(priv->identifier);
+    g_free(priv->owner_username);
+    g_free(priv->dbus_owner);
+
+    nm_c_list_elem_free_all(&priv->permissions, g_free);
+
+    g_clear_object(&priv->subject);
+
+    g_clear_object(&priv->dbus_connection);
+
+    G_OBJECT_CLASS(nm_secret_agent_parent_class)->finalize(object);
+
+    _LOGT("finalized");
+}
+
+static void
+nm_secret_agent_class_init(NMSecretAgentClass *config_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(config_class);
+
+    g_type_class_add_private(object_class, sizeof(NMSecretAgentPrivate));
+
+    object_class->dispose  = dispose;
+    object_class->finalize = finalize;
+
+    signals[DISCONNECTED] = g_signal_new(NM_SECRET_AGENT_DISCONNECTED,
+                                         G_OBJECT_CLASS_TYPE(object_class),
+                                         G_SIGNAL_RUN_FIRST,
+                                         0,
+                                         NULL,
+                                         NULL,
+                                         g_cclosure_marshal_VOID__VOID,
+                                         G_TYPE_NONE,
+                                         0);
+}
diff --git a/src/core/settings/nm-secret-agent.h b/src/core/settings/nm-secret-agent.h
new file mode 100644
index 00000000..ce591425
--- /dev/null
+++ b/src/core/settings/nm-secret-agent.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2011 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SECRET_AGENT_H__
+#define __NETWORKMANAGER_SECRET_AGENT_H__
+
+#include "nm-connection.h"
+
+#include "c-list/src/c-list.h"
+
+#define NM_TYPE_SECRET_AGENT (nm_secret_agent_get_type())
+#define NM_SECRET_AGENT(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SECRET_AGENT, NMSecretAgent))
+#define NM_SECRET_AGENT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SECRET_AGENT, NMSecretAgentClass))
+#define NM_IS_SECRET_AGENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SECRET_AGENT))
+#define NM_IS_SECRET_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SECRET_AGENT))
+#define NM_SECRET_AGENT_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SECRET_AGENT, NMSecretAgentClass))
+
+#define NM_SECRET_AGENT_DISCONNECTED "disconnected"
+
+typedef struct _NMSecretAgentClass  NMSecretAgentClass;
+typedef struct _NMSecretAgentCallId NMSecretAgentCallId;
+
+struct _NMAuthChain;
+struct _NMSecretAgentPrivate;
+
+struct _NMSecretAgent {
+    GObject                       parent;
+    CList                         agent_lst;
+    struct _NMAuthChain *         auth_chain;
+    struct _NMSecretAgentPrivate *_priv;
+    bool                          fully_registered : 1;
+};
+
+GType nm_secret_agent_get_type(void);
+
+NMSecretAgent *nm_secret_agent_new(GDBusMethodInvocation *   context,
+                                   NMAuthSubject *           subject,
+                                   const char *              identifier,
+                                   NMSecretAgentCapabilities capabilities);
+
+const char *nm_secret_agent_get_description(NMSecretAgent *agent);
+
+const char *nm_secret_agent_get_dbus_owner(NMSecretAgent *agent);
+
+const char *nm_secret_agent_get_identifier(NMSecretAgent *agent);
+
+gulong nm_secret_agent_get_owner_uid(NMSecretAgent *agent);
+
+const char *nm_secret_agent_get_owner_username(NMSecretAgent *agent);
+
+gulong nm_secret_agent_get_pid(NMSecretAgent *agent);
+
+NMSecretAgentCapabilities nm_secret_agent_get_capabilities(NMSecretAgent *agent);
+
+NMAuthSubject *nm_secret_agent_get_subject(NMSecretAgent *agent);
+
+void nm_secret_agent_add_permission(NMSecretAgent *agent, const char *permission, gboolean allowed);
+
+gboolean nm_secret_agent_has_permission(NMSecretAgent *agent, const char *permission);
+
+typedef void (*NMSecretAgentCallback)(NMSecretAgent *      agent,
+                                      NMSecretAgentCallId *call_id,
+                                      GVariant *           new_secrets, /* NULL for save & delete */
+                                      GError *             error,
+                                      gpointer             user_data);
+
+NMSecretAgentCallId *nm_secret_agent_get_secrets(NMSecretAgent *              agent,
+                                                 const char *                 path,
+                                                 NMConnection *               connection,
+                                                 const char *                 setting_name,
+                                                 const char **                hints,
+                                                 NMSecretAgentGetSecretsFlags flags,
+                                                 NMSecretAgentCallback        callback,
+                                                 gpointer                     callback_data);
+
+NMSecretAgentCallId *nm_secret_agent_save_secrets(NMSecretAgent *       agent,
+                                                  const char *          path,
+                                                  NMConnection *        connection,
+                                                  NMSecretAgentCallback callback,
+                                                  gpointer              callback_data);
+
+NMSecretAgentCallId *nm_secret_agent_delete_secrets(NMSecretAgent *       agent,
+                                                    const char *          path,
+                                                    NMConnection *        connection,
+                                                    NMSecretAgentCallback callback,
+                                                    gpointer              callback_data);
+
+void nm_secret_agent_cancel_call(NMSecretAgent *self, NMSecretAgentCallId *call_id);
+
+#endif /* __NETWORKMANAGER_SECRET_AGENT_H__ */
diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c
new file mode 100644
index 00000000..0ff07189
--- /dev/null
+++ b/src/core/settings/nm-settings-connection.c
@@ -0,0 +1,2810 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2014 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-settings-connection.h"
+
+#include "c-list/src/c-list.h"
+
+#include "nm-glib-aux/nm-keyfile-aux.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "nm-config.h"
+#include "nm-config-data.h"
+#include "nm-dbus-interface.h"
+#include "nm-session-monitor.h"
+#include "nm-auth-manager.h"
+#include "nm-auth-utils.h"
+#include "nm-agent-manager.h"
+#include "NetworkManagerUtils.h"
+#include "nm-core-internal.h"
+#include "nm-audit-manager.h"
+#include "nm-settings.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 _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES                           \
+    ((NMSettingsUpdate2Flags)(                                                \
+        NM_SETTINGS_UPDATE2_FLAG_TO_DISK | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY \
+        | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))
+
+/*****************************************************************************/
+
+NMConnection **
+nm_settings_connections_array_to_connections(NMSettingsConnection *const *connections,
+                                             gssize                       n_connections)
+{
+    NMConnection **arr;
+    gssize         i;
+
+    if (n_connections < 0)
+        n_connections = NM_PTRARRAY_LEN(connections);
+    if (n_connections == 0)
+        return NULL;
+
+    arr = g_new(NMConnection *, n_connections + 1);
+    for (i = 0; i < n_connections; i++)
+        arr[i] = nm_settings_connection_get_connection(connections[i]);
+    arr[i] = NULL;
+    return arr;
+}
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection, PROP_UNSAVED, PROP_FLAGS, PROP_FILENAME, );
+
+enum { UPDATED_INTERNAL, FLAGS_CHANGED, LAST_SIGNAL };
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct _NMSettingsConnectionPrivate {
+    NMSettings *settings;
+
+    NMKeyFileDB *kf_db_timestamps;
+    NMKeyFileDB *kf_db_seen_bssids;
+
+    NMAgentManager *agent_mgr;
+
+    /* List of pending authentication requests */
+    CList auth_lst_head;
+
+    CList call_ids_lst_head; /* in-progress secrets requests */
+
+    NMConnection *connection;
+
+    NMSettingsStorage *storage;
+
+    char *filename;
+
+    NMDevice *default_wired_device;
+
+    /* Caches secrets from on-disk connections; were they not cached any
+     * call to nm_connection_clear_secrets() wipes them out and we'd have
+     * to re-read them from disk which defeats the purpose of having the
+     * connection in-memory at all.
+     */
+    GVariant *system_secrets;
+
+    /* Caches secrets from agents during the activation process; if new system
+     * secrets are returned from an agent, they get written out to disk,
+     * triggering a re-read of the connection, which reads only system
+     * secrets, and would wipe out any agent-owned or not-saved secrets the
+     * agent also returned.
+     */
+    GVariant *agent_secrets;
+
+    GHashTable *seen_bssids; /* Up-to-date BSSIDs that's been seen for the connection */
+
+    guint64 timestamp; /* Up-to-date timestamp of connection use */
+
+    guint64 last_secret_agent_version_id;
+
+    int autoconnect_retries;
+
+    gint32 autoconnect_retries_blocked_until;
+
+    bool timestamp_set : 1;
+
+    NMSettingsAutoconnectBlockedReason autoconnect_blocked_reason : 4;
+
+    NMSettingsConnectionIntFlags flags : 5;
+
+} NMSettingsConnectionPrivate;
+
+struct _NMSettingsConnectionClass {
+    NMDBusObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMSettingsConnection, nm_settings_connection, NM_TYPE_DBUS_OBJECT)
+
+#define NM_SETTINGS_CONNECTION_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE_PTR(self, NMSettingsConnection, NM_IS_SETTINGS_CONNECTION)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG_PREFIX_NAME "settings-connection"
+#define _NMLOG(level, ...)                                                                  \
+    G_STMT_START                                                                            \
+    {                                                                                       \
+        const NMLogLevel __level = (level);                                                 \
+                                                                                            \
+        if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) {                                   \
+            char        __prefix[128];                                                      \
+            const char *__p_prefix = _NMLOG_PREFIX_NAME;                                    \
+            const char *__uuid     = (self) ? nm_settings_connection_get_uuid(self) : NULL; \
+                                                                                            \
+            if (self) {                                                                     \
+                g_snprintf(__prefix,                                                        \
+                           sizeof(__prefix),                                                \
+                           "%s[" NM_HASH_OBFUSCATE_PTR_FMT "%s%s]",                         \
+                           _NMLOG_PREFIX_NAME,                                              \
+                           NM_HASH_OBFUSCATE_PTR(self),                                     \
+                           __uuid ? "," : "",                                               \
+                           __uuid ?: "");                                                   \
+                __p_prefix = __prefix;                                                      \
+            }                                                                               \
+            _nm_log(__level,                                                                \
+                    _NMLOG_DOMAIN,                                                          \
+                    0,                                                                      \
+                    NULL,                                                                   \
+                    __uuid,                                                                 \
+                    "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                              \
+                    __p_prefix _NM_UTILS_MACRO_REST(__VA_ARGS__));                          \
+        }                                                                                   \
+    }                                                                                       \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static const GDBusSignalInfo             signal_info_updated;
+static const GDBusSignalInfo             signal_info_removed;
+static const NMDBusInterfaceInfoExtended interface_info_settings_connection;
+
+static void update_system_secrets_cache(NMSettingsConnection *self, NMConnection *new);
+static void update_agent_secrets_cache(NMSettingsConnection *self, NMConnection *new);
+
+/*****************************************************************************/
+
+NMDevice *
+nm_settings_connection_default_wired_get_device(NMSettingsConnection *self)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    nm_assert(!priv->default_wired_device || NM_IS_DEVICE(priv->default_wired_device));
+
+    return priv->default_wired_device;
+}
+
+void
+nm_settings_connection_default_wired_set_device(NMSettingsConnection *self, NMDevice *device)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    nm_assert(!priv->default_wired_device || NM_IS_DEVICE(priv->default_wired_device));
+    nm_assert(!device || NM_IS_DEVICE(device));
+
+    nm_assert((!!priv->default_wired_device) != (!!device));
+
+    priv->default_wired_device = device;
+}
+
+/*****************************************************************************/
+
+NMSettingsStorage *
+nm_settings_connection_get_storage(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->storage;
+}
+
+void
+_nm_settings_connection_set_storage(NMSettingsConnection *self, NMSettingsStorage *storage)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    const char *                 filename;
+
+    nm_assert(NM_IS_SETTINGS_STORAGE(storage));
+    nm_assert(!priv->storage
+              || nm_streq(nm_settings_storage_get_uuid(storage),
+                          nm_settings_storage_get_uuid(priv->storage)));
+
+    nm_g_object_ref_set(&priv->storage, storage);
+
+    filename = nm_settings_storage_get_filename(priv->storage);
+
+    if (!nm_streq0(priv->filename, filename)) {
+        g_free(priv->filename);
+        priv->filename = g_strdup(filename);
+        _notify(self, PROP_FILENAME);
+    }
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_connection_still_valid(NMSettingsConnection *self)
+{
+    gboolean valid;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE);
+
+    valid = !c_list_is_empty(&self->_connections_lst);
+
+    nm_assert(
+        valid
+        == nm_settings_has_connection(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings, self));
+
+    return valid;
+}
+
+/*****************************************************************************/
+
+static GHashTable *
+_seen_bssids_hash_new(void)
+{
+    return g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+}
+
+/*****************************************************************************/
+
+NMConnection *
+nm_settings_connection_get_connection(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection;
+}
+
+void
+_nm_settings_connection_set_connection(NMSettingsConnection *           self,
+                                       NMConnection *                   new_connection,
+                                       NMConnection **                  out_connection_old,
+                                       NMSettingsConnectionUpdateReason update_reason)
+{
+    NMSettingsConnectionPrivate *priv            = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    gs_unref_object NMConnection *connection_old = NULL;
+
+    nm_assert(NM_IS_CONNECTION(new_connection));
+    nm_assert(NM_IS_SETTINGS_STORAGE(priv->storage));
+    nm_assert(nm_streq0(nm_settings_storage_get_uuid(priv->storage),
+                        nm_connection_get_uuid(new_connection)));
+    nm_assert(!out_connection_old || !*out_connection_old);
+
+    if (!priv->connection
+        || !nm_connection_compare(priv->connection,
+                                  new_connection,
+                                  NM_SETTING_COMPARE_FLAG_EXACT)) {
+        connection_old   = priv->connection;
+        priv->connection = g_object_ref(new_connection);
+        nmtst_connection_assert_unchanging(priv->connection);
+
+        /* note that we only return @connection_old if the new connection actually differs from
+         * before.
+         *
+         * So, there are three cases:
+         *
+         *  - return %NULL when setting the connection the first time.
+         *  - return %NULL if setting a profile with the same content that we already have.
+         *  - return the previous pointer if the connection changed. */
+        NM_SET_OUT(out_connection_old, g_steal_pointer(&connection_old));
+    }
+
+    if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS))
+        update_system_secrets_cache(self, NULL);
+    else if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS))
+        update_system_secrets_cache(self, priv->connection);
+
+    if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS))
+        update_agent_secrets_cache(self, NULL);
+    else if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS))
+        update_agent_secrets_cache(self, priv->connection);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_connection_has_unmodified_applied_connection(NMSettingsConnection *self,
+                                                         NMConnection *        applied_connection,
+                                                         NMSettingCompareFlags compare_flags)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE);
+    g_return_val_if_fail(NM_IS_CONNECTION(applied_connection), FALSE);
+
+    /* for convenience, we *always* ignore certain settings. */
+    compare_flags |=
+        NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS | NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP;
+
+    return nm_connection_compare(nm_settings_connection_get_connection(self),
+                                 applied_connection,
+                                 compare_flags);
+}
+
+/*****************************************************************************/
+
+guint64
+nm_settings_connection_get_last_secret_agent_version_id(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), 0);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->last_secret_agent_version_id;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_connection_check_visibility(NMSettingsConnection *self,
+                                        NMSessionMonitor *    session_monitor)
+{
+    NMSettingConnection *s_con;
+    guint32              num, i;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE);
+
+    nm_assert(NM_IS_SESSION_MONITOR(session_monitor));
+
+    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self));
+
+    /* Check every user in the ACL for a session */
+    num = nm_setting_connection_get_num_permissions(s_con);
+    if (num == 0)
+        return TRUE;
+
+    for (i = 0; i < num; i++) {
+        const char *ptype;
+        const char *user;
+        uid_t       uid;
+
+        if (!nm_setting_connection_get_permission(s_con, i, &ptype, &user, NULL))
+            continue;
+        if (!nm_streq(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER))
+            continue;
+        if (!nm_utils_name_to_uid(user, &uid))
+            continue;
+        if (!nm_session_monitor_session_exists(session_monitor, uid, FALSE))
+            continue;
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+/* Return TRUE if any active user in the connection's ACL has the given
+ * permission without having to authorize for it via PolicyKit.  Connections
+ * visible to everyone automatically pass the check.
+ */
+gboolean
+nm_settings_connection_check_permission(NMSettingsConnection *self, const char *permission)
+{
+    NMSettingsConnectionPrivate *priv;
+    NMSettingConnection *        s_con;
+    guint32                      num, i;
+    const char *                 puser;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE);
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    if (!NM_FLAGS_HAS(nm_settings_connection_get_flags(self),
+                      NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE))
+        return FALSE;
+
+    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self));
+
+    /* Check every user in the ACL for a session */
+    num = nm_setting_connection_get_num_permissions(s_con);
+    if (num == 0) {
+        /* Visible to all so it's OK to auto-activate */
+        return TRUE;
+    }
+
+    for (i = 0; i < num; i++) {
+        const char *ptype;
+
+        /* For each user get their secret agent and check if that agent has the
+         * required permission.
+         *
+         * FIXME: what if the user isn't running an agent?  PolKit needs a bus
+         * name or a PID but if the user isn't running an agent they won't have
+         * either.
+         */
+        if (!nm_setting_connection_get_permission(s_con, i, &ptype, &puser, NULL))
+            continue;
+        if (!nm_streq(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER))
+            continue;
+
+        if (nm_agent_manager_has_agent_with_permission(priv->agent_mgr, puser, permission))
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+static void
+update_system_secrets_cache(NMSettingsConnection *self, NMConnection *new)
+{
+    NMSettingsConnectionPrivate *priv               = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    gs_unref_object NMConnection *connection_cloned = NULL;
+    gs_unref_variant GVariant *old_secrets          = NULL;
+
+    old_secrets = g_steal_pointer(&priv->system_secrets);
+
+    if (!new)
+        goto out;
+
+    /* FIXME: improve NMConnection API so we can avoid the overhead of cloning the connection,
+     *   in particular if there are no secrets to begin with. */
+
+    connection_cloned = nm_simple_connection_new_clone(new);
+
+    /* Clear out non-system-owned and not-saved secrets */
+    _nm_connection_clear_secrets_by_secret_flags(connection_cloned, NM_SETTING_SECRET_FLAG_NONE);
+
+    priv->system_secrets = nm_g_variant_ref_sink(
+        nm_connection_to_dbus(connection_cloned, NM_CONNECTION_SERIALIZE_ONLY_SECRETS));
+
+out:
+    if (_LOGT_ENABLED()) {
+        if ((!!old_secrets) != (!!priv->system_secrets)) {
+            _LOGT("update system secrets: secrets %s", old_secrets ? "cleared" : "set");
+        } else if (priv->system_secrets && !g_variant_equal(old_secrets, priv->system_secrets))
+            _LOGT("update system secrets: secrets updated");
+    }
+}
+
+static void
+update_agent_secrets_cache(NMSettingsConnection *self, NMConnection *new)
+{
+    NMSettingsConnectionPrivate *priv               = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    gs_unref_object NMConnection *connection_cloned = NULL;
+    gs_unref_variant GVariant *old_secrets          = NULL;
+
+    old_secrets = g_steal_pointer(&priv->agent_secrets);
+
+    if (!new)
+        goto out;
+
+    /* FIXME: improve NMConnection API so we can avoid the overhead of cloning the connection,
+     *   in particular if there are no secrets to begin with. */
+
+    connection_cloned = nm_simple_connection_new_clone(new);
+
+    /* Clear out non-system-owned secrets */
+    _nm_connection_clear_secrets_by_secret_flags(connection_cloned,
+                                                 NM_SETTING_SECRET_FLAG_NOT_SAVED
+                                                     | NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+
+    priv->agent_secrets = nm_g_variant_ref_sink(
+        nm_connection_to_dbus(connection_cloned, NM_CONNECTION_SERIALIZE_ONLY_SECRETS));
+
+out:
+    if (_LOGT_ENABLED()) {
+        if ((!!old_secrets) != (!!priv->agent_secrets)) {
+            _LOGT("update agent secrets: secrets %s", old_secrets ? "cleared" : "set");
+        } else if (priv->agent_secrets && !g_variant_equal(old_secrets, priv->agent_secrets))
+            _LOGT("update agent secrets: secrets updated");
+    }
+}
+
+void
+nm_settings_connection_clear_secrets(NMSettingsConnection *self,
+                                     gboolean              clear_cached_system_secrets,
+                                     gboolean              persist)
+{
+    gs_unref_object NMConnection *connection_cloned = NULL;
+
+    if (!nm_settings_connection_still_valid(self))
+        return;
+
+    /* FIXME: add API to NMConnection so that we can clone a profile without secrets. */
+
+    connection_cloned = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self));
+
+    nm_connection_clear_secrets(connection_cloned);
+
+    if (!nm_settings_connection_update(
+            self,
+            connection_cloned,
+            persist ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP
+                    : NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE
+                | (clear_cached_system_secrets
+                       ? NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS
+                       : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE)
+                | NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS,
+            "clear-secrets",
+            NULL))
+        nm_assert_not_reached();
+}
+
+static gboolean
+_secrets_update(NMConnection * connection,
+                const char *   setting_name,
+                GVariant *     secrets,
+                NMConnection **out_new_connection,
+                GError **      error)
+{
+    gs_unref_variant GVariant *secrets_setting = NULL;
+
+    nm_assert(NM_IS_CONNECTION(connection));
+
+    if (setting_name && !nm_connection_get_setting_by_name(connection, setting_name)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
+                            setting_name);
+        return FALSE;
+    }
+
+    if (!secrets)
+        return TRUE;
+
+    nm_assert(g_variant_is_of_type(secrets, NM_VARIANT_TYPE_SETTING)
+              || g_variant_is_of_type(secrets, NM_VARIANT_TYPE_CONNECTION));
+
+    if (g_variant_n_children(secrets) == 0)
+        return TRUE;
+
+    if (setting_name && g_variant_is_of_type(secrets, NM_VARIANT_TYPE_CONNECTION)) {
+        secrets_setting = g_variant_lookup_value(secrets, setting_name, NM_VARIANT_TYPE_SETTING);
+        if (!secrets_setting) {
+            /* The connection dictionary didn't contain any secrets for
+             * @setting_name; just return success.
+             */
+            return TRUE;
+        }
+        secrets = secrets_setting;
+    }
+
+    /* if @out_new_connection is provided, we don't modify @connection but clone
+     * and return it. Otherwise, we update @connection inplace. */
+    if (out_new_connection) {
+        nm_assert(!*out_new_connection);
+        connection          = nm_simple_connection_new_clone(connection);
+        *out_new_connection = connection;
+    }
+
+    if (!nm_connection_update_secrets(connection, setting_name, secrets, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+gboolean
+nm_settings_connection_update(NMSettingsConnection *           self,
+                              NMConnection *                   new_connection,
+                              NMSettingsConnectionPersistMode  persist_mode,
+                              NMSettingsConnectionIntFlags     sett_flags,
+                              NMSettingsConnectionIntFlags     sett_mask,
+                              NMSettingsConnectionUpdateReason update_reason,
+                              const char *                     log_context_name,
+                              GError **                        error)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE);
+
+    return nm_settings_update_connection(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings,
+                                         self,
+                                         new_connection,
+                                         persist_mode,
+                                         sett_flags,
+                                         sett_mask,
+                                         update_reason,
+                                         log_context_name,
+                                         error);
+}
+
+void
+nm_settings_connection_delete(NMSettingsConnection *self, gboolean allow_add_to_no_auto_default)
+{
+    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
+
+    nm_settings_delete_connection(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings,
+                                  self,
+                                  allow_add_to_no_auto_default);
+}
+
+/*****************************************************************************/
+
+typedef enum {
+    CALL_ID_TYPE_REQ,
+    CALL_ID_TYPE_IDLE,
+} CallIdType;
+
+struct _NMSettingsConnectionCallId {
+    NMSettingsConnection *          self;
+    CList                           call_ids_lst;
+    gboolean                        had_applied_connection;
+    NMConnection *                  applied_connection;
+    NMSettingsConnectionSecretsFunc callback;
+    gpointer                        callback_data;
+
+    CallIdType type;
+    union {
+        struct {
+            NMAgentManagerCallId id;
+        } req;
+        struct {
+            guint32 id;
+            GError *error;
+        } idle;
+    } t;
+};
+
+static void
+_get_secrets_info_callback(NMSettingsConnectionCallId *call_id,
+                           const char *                agent_username,
+                           const char *                setting_name,
+                           GError *                    error)
+{
+    if (call_id->callback) {
+        call_id->callback(call_id->self,
+                          call_id,
+                          agent_username,
+                          setting_name,
+                          error,
+                          call_id->callback_data);
+    }
+}
+
+static void
+_get_secrets_info_free(NMSettingsConnectionCallId *call_id)
+{
+    g_return_if_fail(call_id && call_id->self);
+    nm_assert(!c_list_is_linked(&call_id->call_ids_lst));
+
+    if (call_id->applied_connection)
+        g_object_remove_weak_pointer(G_OBJECT(call_id->applied_connection),
+                                     (gpointer *) &call_id->applied_connection);
+
+    if (call_id->type == CALL_ID_TYPE_IDLE)
+        g_clear_error(&call_id->t.idle.error);
+
+    memset(call_id, 0, sizeof(*call_id));
+    g_slice_free(NMSettingsConnectionCallId, call_id);
+}
+
+typedef struct {
+    NMSettingSecretFlags required;
+    NMSettingSecretFlags forbidden;
+} ForEachSecretFlags;
+
+static gboolean
+validate_secret_flags_cb(NMSettingSecretFlags flags, gpointer user_data)
+{
+    ForEachSecretFlags *cmp_flags = user_data;
+
+    if (!NM_FLAGS_ALL(flags, cmp_flags->required))
+        return FALSE;
+    if (NM_FLAGS_ANY(flags, cmp_flags->forbidden))
+        return FALSE;
+    return TRUE;
+}
+
+static GVariant *
+validate_secret_flags(NMConnection *connection, GVariant *secrets, ForEachSecretFlags *cmp_flags)
+{
+    return g_variant_ref_sink(_nm_connection_for_each_secret(connection,
+                                                             secrets,
+                                                             TRUE,
+                                                             validate_secret_flags_cb,
+                                                             cmp_flags));
+}
+
+static gboolean
+secret_is_system_owned(NMSettingSecretFlags flags, gpointer user_data)
+{
+    return !NM_FLAGS_HAS(flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+}
+
+static void
+get_cmp_flags(NMSettingsConnection *       self,    /* only needed for logging */
+              NMSettingsConnectionCallId * call_id, /* only needed for logging */
+              NMConnection *               connection,
+              const char *                 agent_dbus_owner,
+              gboolean                     agent_has_modify,
+              const char *                 setting_name, /* only needed for logging */
+              NMSecretAgentGetSecretsFlags flags,
+              GVariant *                   secrets,
+              gboolean *                   agent_had_system,
+              ForEachSecretFlags *         cmp_flags)
+{
+    gboolean is_self = (nm_settings_connection_get_connection(self) == connection);
+
+    g_return_if_fail(secrets);
+
+    cmp_flags->required  = NM_SETTING_SECRET_FLAG_NONE;
+    cmp_flags->forbidden = NM_SETTING_SECRET_FLAG_NONE;
+
+    *agent_had_system = FALSE;
+
+    if (agent_dbus_owner) {
+        if (is_self) {
+            _LOGD("(%s:%p) secrets returned from agent %s",
+                  setting_name,
+                  call_id,
+                  agent_dbus_owner);
+        }
+
+        /* If the agent returned any system-owned secrets (initial connect and no
+         * secrets given when the connection was created, or something like that)
+         * make sure the agent's UID has the 'modify' permission before we use or
+         * save those system-owned secrets.  If not, discard them and use the
+         * existing secrets, or fail the connection.
+         */
+        *agent_had_system =
+            _nm_connection_find_secret(connection, secrets, secret_is_system_owned, NULL);
+        if (*agent_had_system) {
+            if (flags == NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) {
+                /* No user interaction was allowed when requesting secrets; the
+                 * agent is being bad.  Remove system-owned secrets.
+                 */
+                if (is_self) {
+                    _LOGD("(%s:%p) interaction forbidden but agent %s returned system secrets",
+                          setting_name,
+                          call_id,
+                          agent_dbus_owner);
+                }
+
+                cmp_flags->required |= NM_SETTING_SECRET_FLAG_AGENT_OWNED;
+            } else if (agent_has_modify == FALSE) {
+                /* Agent didn't successfully authenticate; clear system-owned secrets
+                 * from the secrets the agent returned.
+                 */
+                if (is_self) {
+                    _LOGD("(%s:%p) agent failed to authenticate but provided system secrets",
+                          setting_name,
+                          call_id);
+                }
+
+                cmp_flags->required |= NM_SETTING_SECRET_FLAG_AGENT_OWNED;
+            }
+        }
+    } else {
+        if (is_self) {
+            _LOGD("(%s:%p) existing secrets returned", setting_name, call_id);
+        }
+    }
+
+    /* If no user interaction was allowed, make sure that no "unsaved" secrets
+     * came back.  Unsaved secrets by definition require user interaction.
+     */
+    if (flags == NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) {
+        cmp_flags->forbidden |=
+            (NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED);
+    }
+}
+
+gboolean
+nm_settings_connection_new_secrets(NMSettingsConnection *self,
+                                   NMConnection *        applied_connection,
+                                   const char *          setting_name,
+                                   GVariant *            secrets,
+                                   GError **             error)
+{
+    gs_unref_object NMConnection *new_connection = NULL;
+    NMConnection *                connection;
+
+    if (!nm_settings_connection_has_unmodified_applied_connection(self,
+                                                                  applied_connection,
+                                                                  NM_SETTING_COMPARE_FLAG_NONE)) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_FAILED,
+                            "The connection was modified since activation");
+        return FALSE;
+    }
+
+    connection = nm_settings_connection_get_connection(self);
+
+    if (!_secrets_update(connection, setting_name, secrets, &new_connection, error))
+        return FALSE;
+
+    if (!nm_settings_connection_update(
+            self,
+            new_connection ?: connection,
+            NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE
+                | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+                | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS,
+            "new-secrets",
+            NULL))
+        nm_assert_not_reached();
+    return TRUE;
+}
+
+static void
+get_secrets_done_cb(NMAgentManager *             manager,
+                    NMAgentManagerCallId         call_id_a,
+                    const char *                 agent_dbus_owner,
+                    const char *                 agent_username,
+                    gboolean                     agent_has_modify,
+                    const char *                 setting_name,
+                    NMSecretAgentGetSecretsFlags flags,
+                    GVariant *                   secrets,
+                    GError *                     error,
+                    gpointer                     user_data)
+{
+    NMSettingsConnectionCallId * call_id = user_data;
+    NMSettingsConnection *       self;
+    NMSettingsConnectionPrivate *priv;
+    NMConnection *               applied_connection;
+    gs_free_error GError *local                    = NULL;
+    gs_unref_variant GVariant *system_secrets      = NULL;
+    gs_unref_object NMConnection *new_connection   = NULL;
+    gboolean                      agent_had_system = FALSE;
+    ForEachSecretFlags cmp_flags = {NM_SETTING_SECRET_FLAG_NONE, NM_SETTING_SECRET_FLAG_NONE};
+    gs_unref_variant GVariant *filtered_secrets = NULL;
+
+    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+    self = call_id->self;
+    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst));
+
+    c_list_unlink(&call_id->call_ids_lst);
+
+    if (error) {
+        _LOGD("(%s:%p) secrets request error: %s", setting_name, call_id, error->message);
+
+        _get_secrets_info_callback(call_id, NULL, setting_name, error);
+        goto out;
+    }
+
+    if (call_id->had_applied_connection && !call_id->applied_connection) {
+        g_set_error_literal(&local,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
+                            "Applied connection deleted since requesting secrets");
+        _get_secrets_info_callback(call_id, NULL, setting_name, local);
+        goto out;
+    }
+
+    if (call_id->had_applied_connection
+        && !nm_settings_connection_has_unmodified_applied_connection(
+            self,
+            call_id->applied_connection,
+            NM_SETTING_COMPARE_FLAG_NONE)) {
+        g_set_error_literal(&local,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_FAILED,
+                            "The connection was modified since activation");
+        _get_secrets_info_callback(call_id, NULL, setting_name, local);
+        goto out;
+    }
+
+    if (!nm_connection_get_setting_by_name(nm_settings_connection_get_connection(self),
+                                           setting_name)) {
+        g_set_error(&local,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
+                    "Connection didn't have requested setting '%s'.",
+                    setting_name);
+        _get_secrets_info_callback(call_id, NULL, setting_name, local);
+        goto out;
+    }
+
+    get_cmp_flags(self,
+                  call_id,
+                  nm_settings_connection_get_connection(self),
+                  agent_dbus_owner,
+                  agent_has_modify,
+                  setting_name,
+                  flags,
+                  secrets,
+                  &agent_had_system,
+                  &cmp_flags);
+
+    _LOGD("(%s:%p) secrets request completed", setting_name, call_id);
+
+    system_secrets = nm_g_variant_ref(priv->system_secrets);
+
+    new_connection = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self));
+
+    nm_connection_clear_secrets(new_connection);
+
+    if (!_secrets_update(new_connection, setting_name, system_secrets, NULL, &local)) {
+        _LOGD("(%s:%p) failed to update with existing secrets: %s",
+              setting_name,
+              call_id,
+              local->message);
+    }
+
+    /* Update the connection with the agent's secrets; by this point if any
+     * system-owned secrets exist in 'secrets' the agent that provided them
+     * will have been authenticated, so those secrets can replace the existing
+     * system secrets.
+     */
+    filtered_secrets = validate_secret_flags(new_connection, secrets, &cmp_flags);
+
+    if (!_secrets_update(new_connection, setting_name, filtered_secrets, NULL, &local)) {
+        _LOGD("(%s:%p) failed to update with agent secrets: %s",
+              setting_name,
+              call_id,
+              local->message);
+    }
+
+    /* Only save secrets to backing storage if the agent returned any
+     * new system secrets.  If it didn't, then the secrets are agent-
+     * owned and there's no point to writing out the connection when
+     * nothing has changed, since agent-owned secrets don't get saved here.
+     */
+    if (agent_had_system) {
+        _LOGD("(%s:%p) saving new secrets to backing storage", setting_name, call_id);
+    } else {
+        _LOGD("(%s:%p) new agent secrets processed", setting_name, call_id);
+    }
+    if (!nm_settings_connection_update(
+            self,
+            new_connection,
+            agent_had_system ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP
+                             : NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE
+                | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+                | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS,
+            "get-new-secrets",
+            NULL))
+        nm_assert_not_reached();
+
+    applied_connection = call_id->applied_connection;
+    if (applied_connection) {
+        get_cmp_flags(self,
+                      call_id,
+                      applied_connection,
+                      agent_dbus_owner,
+                      agent_has_modify,
+                      setting_name,
+                      flags,
+                      secrets,
+                      &agent_had_system,
+                      &cmp_flags);
+
+        nm_connection_clear_secrets(applied_connection);
+
+        if (!system_secrets
+            || nm_connection_update_secrets(applied_connection,
+                                            setting_name,
+                                            system_secrets,
+                                            NULL)) {
+            gs_unref_variant GVariant *filtered_secrets2 = NULL;
+
+            filtered_secrets2 = validate_secret_flags(applied_connection, secrets, &cmp_flags);
+            nm_connection_update_secrets(applied_connection, setting_name, filtered_secrets2, NULL);
+        }
+    }
+
+    _get_secrets_info_callback(call_id, agent_username, setting_name, local);
+    g_clear_error(&local);
+
+out:
+    _get_secrets_info_free(call_id);
+}
+
+static gboolean
+get_secrets_idle_cb(NMSettingsConnectionCallId *call_id)
+{
+    NMSettingsConnectionPrivate *priv;
+
+    g_return_val_if_fail(call_id && NM_IS_SETTINGS_CONNECTION(call_id->self), G_SOURCE_REMOVE);
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(call_id->self);
+
+    nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst));
+
+    c_list_unlink(&call_id->call_ids_lst);
+
+    _get_secrets_info_callback(call_id, NULL, NULL, call_id->t.idle.error);
+
+    _get_secrets_info_free(call_id);
+    return G_SOURCE_REMOVE;
+}
+
+/**
+ * nm_settings_connection_get_secrets:
+ * @self: the #NMSettingsConnection
+ * @applied_connection: (allow-none): 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
+ * @setting_name: the setting to return secrets for
+ * @flags: flags to modify the secrets request
+ * @hints: key names in @setting_name for which secrets may be required, or some
+ *   other information about the request
+ * @callback: the function to call with returned secrets
+ * @callback_data: user data to pass to @callback
+ *
+ * Retrieves secrets from persistent storage and queries any secret agents for
+ * additional secrets.
+ *
+ * With the returned call-id, the call can be cancelled. It is an error
+ * to cancel a call more then once or a call that already completed.
+ * The callback will always be invoked exactly once, also for cancellation
+ * and disposing of @self. In those latter cases, the callback will be invoked
+ * synchronously during cancellation/disposing.
+ *
+ * Returns: a call ID which may be used to cancel the ongoing secrets request.
+ **/
+NMSettingsConnectionCallId *
+nm_settings_connection_get_secrets(NMSettingsConnection *          self,
+                                   NMConnection *                  applied_connection,
+                                   NMAuthSubject *                 subject,
+                                   const char *                    setting_name,
+                                   NMSecretAgentGetSecretsFlags    flags,
+                                   const char *const *             hints,
+                                   NMSettingsConnectionSecretsFunc callback,
+                                   gpointer                        callback_data)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    NMAgentManagerCallId         call_id_a;
+    gs_free char *               joined_hints = NULL;
+    NMSettingsConnectionCallId * call_id;
+    GError *                     local = NULL;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+    g_return_val_if_fail(
+        !applied_connection
+            || (NM_IS_CONNECTION(applied_connection)
+                && (nm_settings_connection_get_connection(self) != applied_connection)),
+        NULL);
+
+    call_id       = g_slice_new0(NMSettingsConnectionCallId);
+    call_id->self = self;
+    if (applied_connection) {
+        call_id->had_applied_connection = TRUE;
+        call_id->applied_connection     = applied_connection;
+        g_object_add_weak_pointer(G_OBJECT(applied_connection),
+                                  (gpointer *) &call_id->applied_connection);
+    }
+    call_id->callback      = callback;
+    call_id->callback_data = callback_data;
+    c_list_link_tail(&priv->call_ids_lst_head, &call_id->call_ids_lst);
+
+    /* Make sure the request actually requests something we can return */
+    if (!nm_connection_get_setting_by_name(nm_settings_connection_get_connection(self),
+                                           setting_name)) {
+        g_set_error(&local,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
+                    "Connection didn't have requested setting '%s'.",
+                    setting_name);
+        goto schedule_dummy;
+    }
+
+    if (applied_connection
+        && !nm_settings_connection_has_unmodified_applied_connection(
+            self,
+            applied_connection,
+            NM_SETTING_COMPARE_FLAG_NONE)) {
+        g_set_error_literal(&local,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_FAILED,
+                            "The connection was modified since activation");
+        goto schedule_dummy;
+    }
+
+    /* we remember the current version-id of the secret-agents. The version-id is strictly increasing,
+     * as new agents register the number. We know hence, that this request was made against a certain
+     * set of secret-agents.
+     * If after making this request a new secret-agent registers, the version-id increases.
+     * Then we know that the this request probably did not yet include the latest secret-agent. */
+    priv->last_secret_agent_version_id = nm_agent_manager_get_agent_version_id(priv->agent_mgr);
+
+    /* Use priv->system_secrets to work around the fact that nm_connection_clear_secrets()
+     * will clear secrets on this object's settings.
+     */
+    call_id_a = nm_agent_manager_get_secrets(priv->agent_mgr,
+                                             nm_dbus_object_get_path(NM_DBUS_OBJECT(self)),
+                                             nm_settings_connection_get_connection(self),
+                                             subject,
+                                             priv->system_secrets,
+                                             setting_name,
+                                             flags,
+                                             hints,
+                                             get_secrets_done_cb,
+                                             call_id);
+    nm_assert(call_id_a);
+
+    _LOGD("(%s:%p) secrets requested flags 0x%X hints '%s'",
+          setting_name,
+          call_id_a,
+          flags,
+          (hints && hints[0]) ? (joined_hints = g_strjoinv(",", (char **) hints)) : "(none)");
+
+    if (call_id_a) {
+        call_id->type     = CALL_ID_TYPE_REQ;
+        call_id->t.req.id = call_id_a;
+    } else {
+schedule_dummy:
+        call_id->type = CALL_ID_TYPE_IDLE;
+        g_propagate_error(&call_id->t.idle.error, local);
+        call_id->t.idle.id = g_idle_add((GSourceFunc) get_secrets_idle_cb, call_id);
+    }
+    return call_id;
+}
+
+static void
+_get_secrets_cancel(NMSettingsConnection *      self,
+                    NMSettingsConnectionCallId *call_id,
+                    gboolean                    is_disposing)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    gs_free_error GError *error       = NULL;
+
+    nm_assert(c_list_contains(&priv->call_ids_lst_head, &call_id->call_ids_lst));
+
+    c_list_unlink(&call_id->call_ids_lst);
+
+    if (call_id->type == CALL_ID_TYPE_REQ)
+        nm_agent_manager_cancel_secrets(priv->agent_mgr, call_id->t.req.id);
+    else
+        g_source_remove(call_id->t.idle.id);
+
+    nm_utils_error_set_cancelled(&error, is_disposing, "NMSettingsConnection");
+
+    _get_secrets_info_callback(call_id, NULL, NULL, error);
+
+    _get_secrets_info_free(call_id);
+}
+
+void
+nm_settings_connection_cancel_secrets(NMSettingsConnection *      self,
+                                      NMSettingsConnectionCallId *call_id)
+{
+    _LOGD("(%p) secrets canceled", call_id);
+
+    _get_secrets_cancel(self, call_id, FALSE);
+}
+
+/*****************************************************************************/
+
+typedef void (*AuthCallback)(NMSettingsConnection * self,
+                             GDBusMethodInvocation *context,
+                             NMAuthSubject *        subject,
+                             GError *               error,
+                             gpointer               data);
+
+typedef struct {
+    CList                  auth_lst;
+    NMAuthManagerCallId *  call_id;
+    NMSettingsConnection * self;
+    AuthCallback           callback;
+    gpointer               callback_data;
+    GDBusMethodInvocation *invocation;
+    NMAuthSubject *        subject;
+} AuthData;
+
+static void
+pk_auth_cb(NMAuthManager *      auth_manager,
+           NMAuthManagerCallId *auth_call_id,
+           gboolean             is_authorized,
+           gboolean             is_challenge,
+           GError *             auth_error,
+           gpointer             user_data)
+{
+    AuthData *            auth_data = user_data;
+    NMSettingsConnection *self;
+    gs_free_error GError *error = NULL;
+
+    nm_assert(auth_data);
+    nm_assert(NM_IS_SETTINGS_CONNECTION(auth_data->self));
+
+    self = auth_data->self;
+
+    auth_data->call_id = NULL;
+
+    c_list_unlink(&auth_data->auth_lst);
+
+    if (g_error_matches(auth_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+        error = g_error_new(NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_FAILED,
+                            "Error checking authorization: connection was deleted");
+    } else if (auth_error) {
+        error = g_error_new(NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_FAILED,
+                            "Error checking authorization: %s",
+                            auth_error->message);
+    } else if (nm_auth_call_result_eval(is_authorized, is_challenge, auth_error)
+               != NM_AUTH_CALL_RESULT_YES) {
+        error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                    NM_UTILS_ERROR_MSG_INSUFF_PRIV);
+    }
+
+    auth_data->callback(self,
+                        auth_data->invocation,
+                        auth_data->subject,
+                        error,
+                        auth_data->callback_data);
+
+    g_object_unref(auth_data->invocation);
+    g_object_unref(auth_data->subject);
+    g_slice_free(AuthData, auth_data);
+}
+
+/**
+ * _new_auth_subject:
+ * @context: the D-Bus method invocation context
+ * @error: on failure, a #GError
+ *
+ * Creates an NMAuthSubject for the caller.
+ *
+ * Returns: the #NMAuthSubject on success, or %NULL on failure and sets @error
+ */
+static NMAuthSubject *
+_new_auth_subject(GDBusMethodInvocation *context, GError **error)
+{
+    NMAuthSubject *subject;
+
+    subject = nm_dbus_manager_new_auth_subject_from_context(context);
+    if (!subject) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                            NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
+    }
+
+    return subject;
+}
+
+/* may either invoke callback synchronously or asynchronously. */
+static void
+auth_start(NMSettingsConnection * self,
+           GDBusMethodInvocation *invocation,
+           NMAuthSubject *        subject,
+           const char *           check_permission,
+           AuthCallback           callback,
+           gpointer               callback_data)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    AuthData *                   auth_data;
+    GError *                     error = NULL;
+
+    nm_assert(nm_dbus_object_is_exported(NM_DBUS_OBJECT(self)));
+    nm_assert(G_IS_DBUS_METHOD_INVOCATION(invocation));
+    nm_assert(NM_IS_AUTH_SUBJECT(subject));
+
+    if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(self),
+                                             subject,
+                                             NM_SETTINGS_ERROR,
+                                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                             &error)) {
+        callback(self, invocation, subject, error, callback_data);
+        g_clear_error(&error);
+        return;
+    }
+
+    if (!check_permission) {
+        /* Don't need polkit auth, automatic success */
+        callback(self, invocation, subject, NULL, callback_data);
+        return;
+    }
+
+    auth_data                = g_slice_new(AuthData);
+    auth_data->self          = self;
+    auth_data->callback      = callback;
+    auth_data->callback_data = callback_data;
+    auth_data->invocation    = g_object_ref(invocation);
+    auth_data->subject       = g_object_ref(subject);
+    c_list_link_tail(&priv->auth_lst_head, &auth_data->auth_lst);
+    auth_data->call_id = nm_auth_manager_check_authorization(nm_auth_manager_get(),
+                                                             subject,
+                                                             check_permission,
+                                                             TRUE,
+                                                             pk_auth_cb,
+                                                             auth_data);
+}
+
+/**** DBus method handlers ************************************/
+
+static void
+get_settings_auth_cb(NMSettingsConnection * self,
+                     GDBusMethodInvocation *context,
+                     NMAuthSubject *        subject,
+                     GError *               error,
+                     gpointer               data)
+{
+    gs_free const char **            seen_bssids = NULL;
+    NMConnectionSerializationOptions options     = {};
+    GVariant *                       settings;
+
+    if (error) {
+        g_dbus_method_invocation_return_gerror(context, error);
+        return;
+    }
+
+    /* Timestamp is not updated in connection's 'timestamp' property,
+     * because it would force updating the connection and in turn
+     * writing to /etc periodically, which we want to avoid. Rather real
+     * timestamps are kept track of in a private variable. So, substitute
+     * timestamp property with the real one here before returning the settings.
+     */
+    options.timestamp.has = TRUE;
+    nm_settings_connection_get_timestamp(self, &options.timestamp.val);
+
+    /* Seen BSSIDs are not updated in 802-11-wireless 'seen-bssids' property
+     * from the same reason as timestamp. Thus we put it here to GetSettings()
+     * return settings too.
+     */
+    seen_bssids         = nm_settings_connection_get_seen_bssids(self);
+    options.seen_bssids = seen_bssids;
+
+    /* Secrets should *never* be returned by the GetSettings method, they
+     * get returned by the GetSecrets method which can be better
+     * protected against leakage of secrets to unprivileged callers.
+     */
+    settings = nm_connection_to_dbus_full(nm_settings_connection_get_connection(self),
+                                          NM_CONNECTION_SERIALIZE_NO_SECRETS,
+                                          &options);
+    g_dbus_method_invocation_return_value(context, g_variant_new("(@a{sa{sv}})", settings));
+}
+
+static void
+impl_settings_connection_get_settings(NMDBusObject *                     obj,
+                                      const NMDBusInterfaceInfoExtended *interface_info,
+                                      const NMDBusMethodInfoExtended *   method_info,
+                                      GDBusConnection *                  connection,
+                                      const char *                       sender,
+                                      GDBusMethodInvocation *            invocation,
+                                      GVariant *                         parameters)
+{
+    NMSettingsConnection *self             = NM_SETTINGS_CONNECTION(obj);
+    gs_unref_object NMAuthSubject *subject = NULL;
+    GError *                       error   = NULL;
+
+    subject = _new_auth_subject(invocation, &error);
+    if (!subject) {
+        g_dbus_method_invocation_take_error(invocation, error);
+        return;
+    }
+
+    auth_start(self, invocation, subject, NULL, get_settings_auth_cb, NULL);
+}
+
+typedef struct {
+    GDBusMethodInvocation *context;
+    NMAgentManager *       agent_mgr;
+    NMAuthSubject *        subject;
+    NMConnection *         new_settings;
+    NMSettingsUpdate2Flags flags;
+    char *                 audit_args;
+    bool                   is_update2 : 1;
+} UpdateInfo;
+
+static void
+update_complete(NMSettingsConnection *self, UpdateInfo *info, GError *error)
+{
+    if (error)
+        g_dbus_method_invocation_return_gerror(info->context, error);
+    else if (info->is_update2) {
+        GVariantBuilder result;
+
+        g_variant_builder_init(&result, G_VARIANT_TYPE("a{sv}"));
+        g_dbus_method_invocation_return_value(info->context, g_variant_new("(a{sv})", &result));
+    } else
+        g_dbus_method_invocation_return_value(info->context, NULL);
+
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONN_UPDATE,
+                               self,
+                               !error,
+                               info->audit_args,
+                               info->subject,
+                               error ? error->message : NULL);
+
+    g_clear_object(&info->subject);
+    g_clear_object(&info->agent_mgr);
+    g_clear_object(&info->new_settings);
+    g_free(info->audit_args);
+    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;
+    }
+}
+
+static void
+update_auth_cb(NMSettingsConnection * self,
+               GDBusMethodInvocation *context,
+               NMAuthSubject *        subject,
+               GError *               error,
+               gpointer               data)
+{
+    NMSettingsConnectionPrivate *priv;
+    UpdateInfo *                 info     = data;
+    gs_free_error GError *          local = NULL;
+    NMSettingsConnectionPersistMode persist_mode;
+
+    if (error) {
+        update_complete(self, info, error);
+        return;
+    }
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    if (info->new_settings) {
+        if (!_nm_connection_aggregate(info->new_settings,
+                                      NM_CONNECTION_AGGREGATE_ANY_SECRETS,
+                                      NULL)) {
+            /* If the new connection has no secrets, we do not want to remove all
+             * secrets, rather we keep all the existing ones. Do that by merging
+             * them in to the new connection.
+             */
+            if (priv->agent_secrets)
+                nm_connection_update_secrets(info->new_settings, NULL, priv->agent_secrets, NULL);
+            if (priv->system_secrets)
+                nm_connection_update_secrets(info->new_settings, NULL, priv->system_secrets, NULL);
+        } else {
+            /* Cache the new secrets from the agent, as stuff like inotify-triggered
+             * changes to connection's backing config files will blow them away if
+             * they're in the main connection.
+             */
+            update_agent_secrets_cache(self, info->new_settings);
+
+            /* New secrets, allow autoconnection again */
+            if (nm_settings_connection_autoconnect_blocked_reason_set(
+                    self,
+                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
+                    FALSE)
+                && !nm_settings_connection_autoconnect_blocked_reason_get(self))
+                nm_settings_connection_autoconnect_retries_reset(self);
+        }
+    }
+
+    if (info->new_settings) {
+        if (nm_audit_manager_audit_enabled(nm_audit_manager_get())) {
+            gs_unref_hashtable GHashTable *diff = NULL;
+            gboolean                       same;
+
+            same = nm_connection_diff(nm_settings_connection_get_connection(self),
+                                      info->new_settings,
+                                      NM_SETTING_COMPARE_FLAG_EXACT
+                                          | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT,
+                                      &diff);
+            if (!same && diff)
+                info->audit_args = nm_utils_format_con_diff_for_audit(diff);
+        }
+    }
+
+    nm_assert(
+        !NM_FLAGS_ANY(info->flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)
+        || nm_utils_is_power_of_two(info->flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES));
+
+    if (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_TO_DISK))
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+    else if (NM_FLAGS_ANY(info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY))
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY;
+    else if (NM_FLAGS_ANY(info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED))
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED;
+    else if (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY)) {
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+    } else
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP;
+
+    nm_settings_connection_update(
+        self,
+        info->new_settings,
+        persist_mode,
+        (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE)
+             ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+             : 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_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+            | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
+            | (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT)
+                   ? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT
+                   : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE),
+        "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 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);
+
+    update_complete(self, info, local);
+}
+
+static const char *
+get_update_modify_permission(NMConnection *old, NMConnection *new)
+{
+    NMSettingConnection *s_con;
+    guint32              orig_num = 0, new_num = 0;
+
+    s_con    = nm_connection_get_setting_connection(old);
+    orig_num = nm_setting_connection_get_num_permissions(s_con);
+
+    s_con   = nm_connection_get_setting_connection(new);
+    new_num = nm_setting_connection_get_num_permissions(s_con);
+
+    /* If the caller is the only user in either connection's permissions, then
+     * we use the 'modify.own' permission instead of 'modify.system'.
+     */
+    if (orig_num == 1 && new_num == 1)
+        return NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
+
+    /* If the update request affects more than just the caller (ie if the old
+     * settings were system-wide, or the new ones are), require 'modify.system'.
+     */
+    return NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
+}
+
+static void
+settings_connection_update(NMSettingsConnection * self,
+                           gboolean               is_update2,
+                           GDBusMethodInvocation *context,
+                           GVariant *             new_settings,
+                           NMSettingsUpdate2Flags flags)
+{
+    NMSettingsConnectionPrivate *priv    = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    NMAuthSubject *              subject = NULL;
+    NMConnection *               tmp     = NULL;
+    GError *                     error   = NULL;
+    UpdateInfo *                 info;
+    const char *                 permission;
+
+    /* Check if the settings are valid first */
+    if (new_settings) {
+        if (!g_variant_is_of_type(new_settings, NM_VARIANT_TYPE_CONNECTION)) {
+            g_set_error_literal(&error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                                "settings is of invalid type");
+            goto error;
+        }
+
+        if (g_variant_n_children(new_settings) > 0) {
+            tmp = _nm_simple_connection_new_from_dbus(new_settings,
+                                                      NM_SETTING_PARSE_FLAGS_STRICT
+                                                          | NM_SETTING_PARSE_FLAGS_NORMALIZE,
+                                                      &error);
+            if (!tmp)
+                goto error;
+
+            if (!nm_connection_verify_secrets(tmp, &error))
+                goto error;
+        }
+    }
+
+    subject = _new_auth_subject(context, &error);
+    if (!subject)
+        goto error;
+
+    /* And that the new connection settings will be visible to the user
+     * that's sending the update request.  You can't make a connection
+     * invisible to yourself.
+     */
+    if (!nm_auth_is_subject_in_acl_set_error(tmp ?: nm_settings_connection_get_connection(self),
+                                             subject,
+                                             NM_SETTINGS_ERROR,
+                                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                             &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;
+
+    permission = get_update_modify_permission(nm_settings_connection_get_connection(self),
+                                              tmp ?: nm_settings_connection_get_connection(self));
+    auth_start(self, context, subject, permission, update_auth_cb, info);
+    return;
+
+error:
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONN_UPDATE, self, FALSE, NULL, subject, error->message);
+
+    g_clear_object(&tmp);
+    g_clear_object(&subject);
+
+    g_dbus_method_invocation_take_error(context, error);
+}
+
+static void
+impl_settings_connection_update(NMDBusObject *                     obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *   method_info,
+                                GDBusConnection *                  connection,
+                                const char *                       sender,
+                                GDBusMethodInvocation *            invocation,
+                                GVariant *                         parameters)
+{
+    NMSettingsConnection *self          = NM_SETTINGS_CONNECTION(obj);
+    gs_unref_variant GVariant *settings = NULL;
+
+    g_variant_get(parameters, "(@a{sa{sv}})", &settings);
+    settings_connection_update(self, FALSE, invocation, settings, NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
+}
+
+static void
+impl_settings_connection_update_unsaved(NMDBusObject *                     obj,
+                                        const NMDBusInterfaceInfoExtended *interface_info,
+                                        const NMDBusMethodInfoExtended *   method_info,
+                                        GDBusConnection *                  connection,
+                                        const char *                       sender,
+                                        GDBusMethodInvocation *            invocation,
+                                        GVariant *                         parameters)
+{
+    NMSettingsConnection *self          = NM_SETTINGS_CONNECTION(obj);
+    gs_unref_variant GVariant *settings = NULL;
+
+    g_variant_get(parameters, "(@a{sa{sv}})", &settings);
+    settings_connection_update(self,
+                               FALSE,
+                               invocation,
+                               settings,
+                               NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY);
+}
+
+static void
+impl_settings_connection_save(NMDBusObject *                     obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended *   method_info,
+                              GDBusConnection *                  connection,
+                              const char *                       sender,
+                              GDBusMethodInvocation *            invocation,
+                              GVariant *                         parameters)
+{
+    NMSettingsConnection *self = NM_SETTINGS_CONNECTION(obj);
+
+    settings_connection_update(self, FALSE, invocation, NULL, NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
+}
+
+static void
+impl_settings_connection_update2(NMDBusObject *                     obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *   method_info,
+                                 GDBusConnection *                  connection,
+                                 const char *                       sender,
+                                 GDBusMethodInvocation *            invocation,
+                                 GVariant *                         parameters)
+{
+    NMSettingsConnection *self          = NM_SETTINGS_CONNECTION(obj);
+    gs_unref_variant GVariant *settings = NULL;
+    gs_unref_variant GVariant *args     = NULL;
+    guint32                    flags_u;
+    GError *                   error = NULL;
+    GVariantIter               iter;
+    const char *               args_name;
+    NMSettingsUpdate2Flags     flags;
+
+    g_variant_get(parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args);
+
+    if (NM_FLAGS_ANY(flags_u,
+                     ~((guint32)(_NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES
+                                 | NM_SETTINGS_UPDATE2_FLAG_VOLATILE
+                                 | NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT
+                                 | NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)))) {
+        error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                                    "Unknown flags");
+        g_dbus_method_invocation_take_error(invocation, error);
+        return;
+    }
+
+    flags = (NMSettingsUpdate2Flags) flags_u;
+
+    if ((NM_FLAGS_ANY(flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)
+         && !nm_utils_is_power_of_two(flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES))
+        || (NM_FLAGS_HAS(flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE)
+            && !NM_FLAGS_ANY(flags,
+                             NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY
+                                 | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED
+                                 | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))) {
+        error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                                    "Conflicting flags");
+        g_dbus_method_invocation_take_error(invocation, error);
+        return;
+    }
+
+    nm_assert(g_variant_is_of_type(args, G_VARIANT_TYPE("a{sv}")));
+
+    g_variant_iter_init(&iter, args);
+    while (g_variant_iter_next(&iter, "{&sv}", &args_name, NULL)) {
+        error = g_error_new(NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                            "Unsupported argument '%s'",
+                            args_name);
+        g_dbus_method_invocation_take_error(invocation, error);
+        return;
+    }
+
+    settings_connection_update(self, TRUE, invocation, settings, flags);
+}
+
+static void
+delete_auth_cb(NMSettingsConnection * self,
+               GDBusMethodInvocation *context,
+               NMAuthSubject *        subject,
+               GError *               error,
+               gpointer               data)
+{
+    gs_unref_object NMSettingsConnection *self_keep_alive = NULL;
+
+    self_keep_alive = g_object_ref(self);
+
+    if (error) {
+        nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DELETE,
+                                   self,
+                                   FALSE,
+                                   NULL,
+                                   subject,
+                                   error->message);
+        g_dbus_method_invocation_return_gerror(context, error);
+        return;
+    }
+
+    nm_settings_connection_delete(self, TRUE);
+
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DELETE, self, TRUE, NULL, subject, NULL);
+    g_dbus_method_invocation_return_value(context, NULL);
+}
+
+static const char *
+get_modify_permission_basic(NMSettingsConnection *self)
+{
+    NMSettingConnection *s_con;
+
+    /* If the caller is the only user in the connection's permissions, then
+     * we use the 'modify.own' permission instead of 'modify.system'.  If the
+     * request affects more than just the caller, require 'modify.system'.
+     */
+    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self));
+    if (nm_setting_connection_get_num_permissions(s_con) == 1)
+        return NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
+
+    return NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
+}
+
+static void
+impl_settings_connection_delete(NMDBusObject *                     obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *   method_info,
+                                GDBusConnection *                  connection,
+                                const char *                       sender,
+                                GDBusMethodInvocation *            invocation,
+                                GVariant *                         parameters)
+{
+    NMSettingsConnection *self             = NM_SETTINGS_CONNECTION(obj);
+    gs_unref_object NMAuthSubject *subject = NULL;
+    GError *                       error   = NULL;
+
+    nm_assert(nm_settings_connection_still_valid(self));
+
+    subject = _new_auth_subject(invocation, &error);
+    if (!subject)
+        goto err;
+
+    auth_start(self, invocation, subject, get_modify_permission_basic(self), delete_auth_cb, NULL);
+    return;
+err:
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DELETE, self, FALSE, NULL, subject, error->message);
+    g_dbus_method_invocation_take_error(invocation, error);
+}
+
+/*****************************************************************************/
+
+static void
+dbus_get_agent_secrets_cb(NMSettingsConnection *      self,
+                          NMSettingsConnectionCallId *call_id,
+                          const char *                agent_username,
+                          const char *                setting_name,
+                          GError *                    error,
+                          gpointer                    user_data)
+{
+    GDBusMethodInvocation *context = user_data;
+    GVariant *             dict;
+
+    if (error)
+        g_dbus_method_invocation_return_gerror(context, error);
+    else {
+        /* Return secrets from agent and backing storage to the D-Bus caller;
+         * nm_settings_connection_get_secrets() will have updated itself with
+         * secrets from backing storage and those returned from the agent
+         * by the time we get here.
+         */
+        dict = nm_connection_to_dbus(nm_settings_connection_get_connection(self),
+                                     NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
+        if (!dict)
+            dict = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0);
+        g_dbus_method_invocation_return_value(context, g_variant_new("(@a{sa{sv}})", dict));
+    }
+}
+
+static void
+dbus_get_secrets_auth_cb(NMSettingsConnection * self,
+                         GDBusMethodInvocation *context,
+                         NMAuthSubject *        subject,
+                         GError *               error,
+                         gpointer               user_data)
+{
+    char *setting_name = user_data;
+
+    if (!error) {
+        nm_settings_connection_get_secrets(self,
+                                           NULL,
+                                           subject,
+                                           setting_name,
+                                           NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED
+                                               | NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS,
+                                           NULL,
+                                           dbus_get_agent_secrets_cb,
+                                           context);
+    }
+
+    if (error)
+        g_dbus_method_invocation_return_gerror(context, error);
+
+    g_free(setting_name);
+}
+
+static void
+impl_settings_connection_get_secrets(NMDBusObject *                     obj,
+                                     const NMDBusInterfaceInfoExtended *interface_info,
+                                     const NMDBusMethodInfoExtended *   method_info,
+                                     GDBusConnection *                  connection,
+                                     const char *                       sender,
+                                     GDBusMethodInvocation *            invocation,
+                                     GVariant *                         parameters)
+{
+    NMSettingsConnection *self             = NM_SETTINGS_CONNECTION(obj);
+    gs_unref_object NMAuthSubject *subject = NULL;
+    GError *                       error   = NULL;
+    const char *                   setting_name;
+
+    subject = _new_auth_subject(invocation, &error);
+    if (!subject) {
+        g_dbus_method_invocation_take_error(invocation, error);
+        return;
+    }
+
+    g_variant_get(parameters, "(&s)", &setting_name);
+
+    auth_start(self,
+               invocation,
+               subject,
+               get_modify_permission_basic(self),
+               dbus_get_secrets_auth_cb,
+               g_strdup(setting_name));
+}
+
+static void
+dbus_clear_secrets_auth_cb(NMSettingsConnection * self,
+                           GDBusMethodInvocation *context,
+                           NMAuthSubject *        subject,
+                           GError *               error,
+                           gpointer               user_data)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    gs_free_error GError *local       = NULL;
+
+    if (error) {
+        g_dbus_method_invocation_return_gerror(context, error);
+        nm_audit_log_connection_op(NM_AUDIT_OP_CONN_CLEAR_SECRETS,
+                                   self,
+                                   FALSE,
+                                   NULL,
+                                   subject,
+                                   error->message);
+        return;
+    }
+
+    nm_settings_connection_clear_secrets(self, TRUE, TRUE);
+
+    /* Tell agents to remove secrets for this connection */
+    nm_agent_manager_delete_secrets(priv->agent_mgr,
+                                    nm_dbus_object_get_path(NM_DBUS_OBJECT(self)),
+                                    nm_settings_connection_get_connection(self));
+
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONN_CLEAR_SECRETS,
+                               self,
+                               !local,
+                               NULL,
+                               subject,
+                               local ? local->message : NULL);
+
+    if (local)
+        g_dbus_method_invocation_return_gerror(context, local);
+    else
+        g_dbus_method_invocation_return_value(context, NULL);
+}
+
+static void
+impl_settings_connection_clear_secrets(NMDBusObject *                     obj,
+                                       const NMDBusInterfaceInfoExtended *interface_info,
+                                       const NMDBusMethodInfoExtended *   method_info,
+                                       GDBusConnection *                  connection,
+                                       const char *                       sender,
+                                       GDBusMethodInvocation *            invocation,
+                                       GVariant *                         parameters)
+{
+    NMSettingsConnection *self             = NM_SETTINGS_CONNECTION(obj);
+    gs_unref_object NMAuthSubject *subject = NULL;
+    GError *                       error   = NULL;
+
+    subject = _new_auth_subject(invocation, &error);
+    if (!subject) {
+        nm_audit_log_connection_op(NM_AUDIT_OP_CONN_CLEAR_SECRETS,
+                                   self,
+                                   FALSE,
+                                   NULL,
+                                   NULL,
+                                   error->message);
+        g_dbus_method_invocation_take_error(invocation, error);
+        return;
+    }
+    auth_start(self,
+               invocation,
+               subject,
+               get_modify_permission_basic(self),
+               dbus_clear_secrets_auth_cb,
+               NULL);
+}
+
+/*****************************************************************************/
+
+void
+_nm_settings_connection_emit_dbus_signal_updated(NMSettingsConnection *self)
+{
+    nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
+                               &interface_info_settings_connection,
+                               &signal_info_updated,
+                               "()");
+}
+
+void
+_nm_settings_connection_emit_dbus_signal_removed(NMSettingsConnection *self)
+{
+    nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
+                               &interface_info_settings_connection,
+                               &signal_info_removed,
+                               "()");
+}
+
+void
+_nm_settings_connection_emit_signal_updated_internal(NMSettingsConnection *           self,
+                                                     NMSettingsConnectionUpdateReason update_reason)
+{
+    g_signal_emit(self, signals[UPDATED_INTERNAL], 0, (guint) update_reason);
+}
+
+/*****************************************************************************/
+
+static NM_UTILS_FLAGS2STR_DEFINE(
+    _settings_connection_flags_to_string,
+    NMSettingsConnectionIntFlags,
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, "none"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED, "unsaved"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, "nm-generated"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE, "volatile"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE, "visible"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, "external"), );
+
+NMSettingsConnectionIntFlags
+nm_settings_connection_get_flags(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->flags;
+}
+
+NMSettingsConnectionIntFlags
+nm_settings_connection_set_flags_full(NMSettingsConnection *       self,
+                                      NMSettingsConnectionIntFlags mask,
+                                      NMSettingsConnectionIntFlags value)
+{
+    NMSettingsConnectionPrivate *priv;
+    NMSettingsConnectionIntFlags old_flags;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
+
+    nm_assert(!NM_FLAGS_ANY(mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL));
+    nm_assert(!NM_FLAGS_ANY(value, ~mask));
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    value = (priv->flags & ~mask) | value;
+
+    old_flags = priv->flags;
+    if (old_flags != value) {
+        gboolean notify_unsaved = FALSE;
+        char     buf1[255], buf2[255];
+
+        _LOGT("update settings-connection flags to %s (was %s)",
+              _settings_connection_flags_to_string(value, buf1, sizeof(buf1)),
+              _settings_connection_flags_to_string(priv->flags, buf2, sizeof(buf2)));
+        priv->flags = value;
+        nm_assert(priv->flags == value);
+
+        if (NM_FLAGS_HAS(old_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED)
+            != NM_FLAGS_HAS(value, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED)) {
+            g_object_freeze_notify(G_OBJECT(self));
+            _notify(self, PROP_UNSAVED);
+            notify_unsaved = TRUE;
+        }
+        _notify(self, PROP_FLAGS);
+        if (notify_unsaved)
+            g_object_thaw_notify(G_OBJECT(self));
+
+        g_signal_emit(self, signals[FLAGS_CHANGED], 0);
+    }
+    return old_flags;
+}
+
+/*****************************************************************************/
+
+static int
+_cmp_timestamp(NMSettingsConnection *a, NMSettingsConnection *b)
+{
+    gboolean a_has_ts, b_has_ts;
+    guint64  ats = 0, bts = 0;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(a));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(b));
+
+    a_has_ts = !!nm_settings_connection_get_timestamp(a, &ats);
+    b_has_ts = !!nm_settings_connection_get_timestamp(b, &bts);
+    if (a_has_ts != b_has_ts)
+        return a_has_ts ? -1 : 1;
+    if (a_has_ts && ats != bts)
+        return (ats > bts) ? -1 : 1;
+    return 0;
+}
+
+static int
+_cmp_last_resort(NMSettingsConnection *a, NMSettingsConnection *b)
+{
+    NM_CMP_DIRECT_STRCMP0(nm_settings_connection_get_uuid(a), nm_settings_connection_get_uuid(b));
+
+    /* hm, same UUID. Use their pointer value to give them a stable
+     * order. */
+    return (a > b) ? -1 : 1;
+}
+
+/* sorting for "best" connections.
+ * The function sorts connections in descending timestamp order.
+ * That means an older connection (lower timestamp) goes after
+ * a newer one.
+ */
+int
+nm_settings_connection_cmp_timestamp(NMSettingsConnection *a, NMSettingsConnection *b)
+{
+    NM_CMP_SELF(a, b);
+
+    NM_CMP_RETURN(_cmp_timestamp(a, b));
+    NM_CMP_RETURN(
+        nm_utils_cmp_connection_by_autoconnect_priority(nm_settings_connection_get_connection(a),
+                                                        nm_settings_connection_get_connection(b)));
+    return _cmp_last_resort(a, b);
+}
+
+int
+nm_settings_connection_cmp_timestamp_p_with_data(gconstpointer pa,
+                                                 gconstpointer pb,
+                                                 gpointer      user_data)
+{
+    return nm_settings_connection_cmp_timestamp(*((NMSettingsConnection **) pa),
+                                                *((NMSettingsConnection **) pb));
+}
+
+int
+nm_settings_connection_cmp_autoconnect_priority(NMSettingsConnection *a, NMSettingsConnection *b)
+{
+    if (a == b)
+        return 0;
+    NM_CMP_RETURN(
+        nm_utils_cmp_connection_by_autoconnect_priority(nm_settings_connection_get_connection(a),
+                                                        nm_settings_connection_get_connection(b)));
+    NM_CMP_RETURN(_cmp_timestamp(a, b));
+    return _cmp_last_resort(a, b);
+}
+
+int
+nm_settings_connection_cmp_autoconnect_priority_p_with_data(gconstpointer pa,
+                                                            gconstpointer pb,
+                                                            gpointer      user_data)
+{
+    return nm_settings_connection_cmp_autoconnect_priority(*((NMSettingsConnection **) pa),
+                                                           *((NMSettingsConnection **) pb));
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_settings_connection_get_timestamp:
+ * @self: the #NMSettingsConnection
+ * @out_timestamp: the connection's timestamp
+ *
+ * Returns the time (in seconds since the Unix epoch) when the connection
+ * was last successfully activated.
+ *
+ * Returns: %TRUE if the timestamp has ever been set, otherwise %FALSE.
+ **/
+gboolean
+nm_settings_connection_get_timestamp(NMSettingsConnection *self, guint64 *out_timestamp)
+{
+    NMSettingsConnectionPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE);
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    NM_SET_OUT(out_timestamp, priv->timestamp);
+    return priv->timestamp_set;
+}
+
+/**
+ * nm_settings_connection_update_timestamp:
+ * @self: the #NMSettingsConnection
+ * @timestamp: timestamp to set into the connection and to store into
+ * the timestamps database
+ *
+ * Updates the connection and timestamps database with the provided timestamp.
+ **/
+void
+nm_settings_connection_update_timestamp(NMSettingsConnection *self, guint64 timestamp)
+{
+    NMSettingsConnectionPrivate *priv;
+    const char *                 connection_uuid;
+    char                         sbuf[60];
+
+    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    if (priv->timestamp == timestamp && priv->timestamp_set)
+        return;
+
+    priv->timestamp     = timestamp;
+    priv->timestamp_set = TRUE;
+
+    _LOGT("timestamp: set timestamp %" G_GUINT64_FORMAT, timestamp);
+
+    if (!priv->kf_db_timestamps)
+        return;
+
+    connection_uuid = nm_settings_connection_get_uuid(self);
+    if (connection_uuid) {
+        nm_key_file_db_set_value(priv->kf_db_timestamps,
+                                 connection_uuid,
+                                 nm_sprintf_buf(sbuf, "%" G_GUINT64_FORMAT, timestamp));
+    }
+}
+
+void
+_nm_settings_connection_register_kf_dbs(NMSettingsConnection *self,
+                                        NMKeyFileDB *         kf_db_timestamps,
+                                        NMKeyFileDB *         kf_db_seen_bssids)
+{
+    NMSettingsConnectionPrivate *priv;
+    const char *                 connection_uuid;
+
+    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
+    g_return_if_fail(kf_db_timestamps);
+    g_return_if_fail(kf_db_seen_bssids);
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    connection_uuid = nm_settings_connection_get_uuid(self);
+
+    if (priv->kf_db_timestamps != kf_db_timestamps) {
+        gs_free char *tmp_str = NULL;
+        guint64       timestamp;
+
+        nm_key_file_db_unref(priv->kf_db_timestamps);
+        priv->kf_db_timestamps = nm_key_file_db_ref(kf_db_timestamps);
+
+        tmp_str = nm_key_file_db_get_value(priv->kf_db_timestamps, connection_uuid);
+
+        timestamp = _nm_utils_ascii_str_to_uint64(tmp_str, 10, 0, G_MAXUINT64, G_MAXUINT64);
+        if (timestamp != G_MAXUINT64) {
+            priv->timestamp     = timestamp;
+            priv->timestamp_set = TRUE;
+            _LOGT("timestamp: read timestamp %" G_GUINT64_FORMAT " from keyfile database \"%s\"",
+                  timestamp,
+                  nm_key_file_db_get_filename(priv->kf_db_timestamps));
+        } else
+            _LOGT("timestamp: no timestamp from keyfile database \"%s\"",
+                  nm_key_file_db_get_filename(priv->kf_db_timestamps));
+    }
+
+    if (priv->kf_db_seen_bssids != kf_db_seen_bssids) {
+        gs_strfreev char **tmp_strv = NULL;
+        gsize              i, len;
+
+        nm_key_file_db_unref(priv->kf_db_seen_bssids);
+        priv->kf_db_seen_bssids = nm_key_file_db_ref(kf_db_seen_bssids);
+
+        tmp_strv = nm_key_file_db_get_string_list(priv->kf_db_seen_bssids, connection_uuid, &len);
+
+        nm_clear_pointer(&priv->seen_bssids, g_hash_table_unref);
+
+        if (len > 0) {
+            _LOGT("read %zu seen-bssids from keyfile database \"%s\"",
+                  len,
+                  nm_key_file_db_get_filename(priv->kf_db_seen_bssids));
+            priv->seen_bssids = _seen_bssids_hash_new();
+            for (i = len; i > 0;)
+                g_hash_table_add(priv->seen_bssids, g_steal_pointer(&tmp_strv[--i]));
+            nm_clear_g_free(&tmp_strv);
+        } else {
+            NMSettingWireless *s_wifi;
+
+            _LOGT("no seen-bssids from keyfile database \"%s\"",
+                  nm_key_file_db_get_filename(priv->kf_db_seen_bssids));
+
+            /* If this connection didn't have an entry in the seen-bssids database,
+             * maybe this is the first time we've read it in, so populate the
+             * seen-bssids list from the deprecated seen-bssids property of the
+             * wifi setting.
+             */
+            s_wifi =
+                nm_connection_get_setting_wireless(nm_settings_connection_get_connection(self));
+            if (s_wifi) {
+                len = nm_setting_wireless_get_num_seen_bssids(s_wifi);
+                if (len > 0) {
+                    priv->seen_bssids = _seen_bssids_hash_new();
+                    for (i = 0; i < len; i++) {
+                        const char *bssid = nm_setting_wireless_get_seen_bssid(s_wifi, i);
+
+                        g_hash_table_add(priv->seen_bssids, g_strdup(bssid));
+                    }
+                }
+            }
+        }
+    }
+}
+
+/**
+ * nm_settings_connection_get_seen_bssids:
+ * @self: the #NMSettingsConnection
+ *
+ * Returns current list of seen BSSIDs for the connection.
+ *
+ * Returns: (transfer container) list of seen BSSIDs (in the standard hex-digits-and-colons notation).
+ * The caller is responsible for freeing the list, but not the content.
+ **/
+const char **
+nm_settings_connection_get_seen_bssids(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+
+    return nm_utils_strdict_get_keys(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->seen_bssids,
+                                     TRUE,
+                                     NULL);
+}
+
+/**
+ * nm_settings_connection_has_seen_bssid:
+ * @self: the #NMSettingsConnection
+ * @bssid: the BSSID to check the seen BSSID list for
+ *
+ * Returns: %TRUE if the given @bssid is in the seen BSSIDs list
+ **/
+gboolean
+nm_settings_connection_has_seen_bssid(NMSettingsConnection *self, const char *bssid)
+{
+    NMSettingsConnectionPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), FALSE);
+    g_return_val_if_fail(bssid, FALSE);
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    return priv->seen_bssids
+           && g_hash_table_contains(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->seen_bssids, bssid);
+}
+
+/**
+ * nm_settings_connection_add_seen_bssid:
+ * @self: the #NMSettingsConnection
+ * @seen_bssid: BSSID to set into the connection and to store into
+ * the seen-bssids database
+ *
+ * Updates the connection and seen-bssids database with the provided BSSID.
+ **/
+void
+nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *seen_bssid)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    gs_free const char **        strv = NULL;
+    const char *                 connection_uuid;
+
+    g_return_if_fail(seen_bssid != NULL);
+
+    if (!priv->seen_bssids)
+        priv->seen_bssids = _seen_bssids_hash_new();
+
+    g_hash_table_add(priv->seen_bssids, g_strdup(seen_bssid));
+
+    if (!priv->kf_db_seen_bssids)
+        return;
+
+    connection_uuid = nm_settings_connection_get_uuid(self);
+    if (!connection_uuid)
+        return;
+
+    strv = nm_utils_strdict_get_keys(priv->seen_bssids, TRUE, NULL);
+
+    nm_key_file_db_set_string_list(priv->kf_db_seen_bssids,
+                                   connection_uuid,
+                                   strv ?: NM_PTRARRAY_EMPTY(const char *),
+                                   -1);
+}
+
+/*****************************************************************************/
+
+/**
+ * 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)
+{
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->autoconnect_blocked_reason;
+}
+
+gboolean
+nm_settings_connection_autoconnect_blocked_reason_set_full(NMSettingsConnection *             self,
+                                                           NMSettingsAutoconnectBlockedReason mask,
+                                                           NMSettingsAutoconnectBlockedReason value)
+{
+    NMSettingsAutoconnectBlockedReason v;
+    NMSettingsConnectionPrivate *      priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    char                               buf[100];
+
+    nm_assert(mask);
+    nm_assert(!NM_FLAGS_ANY(value, ~mask));
+
+    v = priv->autoconnect_blocked_reason;
+    v = (v & ~mask) | (value & mask);
+
+    if (priv->autoconnect_blocked_reason == v)
+        return FALSE;
+
+    _LOGT("autoconnect: blocked reason: %s",
+          _autoconnect_blocked_reason_to_string(v, buf, sizeof(buf)));
+    priv->autoconnect_blocked_reason = v;
+    return TRUE;
+}
+
+gboolean
+nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self)
+{
+    NMSettingsConnectionPrivate *priv;
+    NMSettingsConnectionIntFlags flags;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), TRUE);
+
+    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)
+        return TRUE;
+
+    flags = priv->flags;
+    if (NM_FLAGS_ANY(flags,
+                     NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                         | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
+        return TRUE;
+    if (!NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE))
+        return TRUE;
+
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_settings_connection_get_filename:
+ * @self: an #NMSettingsConnection
+ *
+ * Gets the filename that @self was read from/written to.  This may be
+ * %NULL if @self is unsaved, or if it is associated with a backend that
+ * does not store each connection in a separate file.
+ *
+ * Returns: @self's filename.
+ */
+const char *
+nm_settings_connection_get_filename(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->filename;
+}
+
+const char *
+nm_settings_connection_get_id(NMSettingsConnection *self)
+{
+    return nm_connection_get_id(nm_settings_connection_get_connection(self));
+}
+
+const char *
+nm_settings_connection_get_uuid(NMSettingsConnection *self)
+{
+    NMSettingsConnectionPrivate *priv;
+    const char *                 uuid;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+
+    priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+
+    uuid = nm_settings_storage_get_uuid(priv->storage);
+
+    nm_assert(
+        uuid
+        && nm_streq0(uuid, nm_connection_get_uuid(nm_settings_connection_get_connection(self))));
+
+    return uuid;
+}
+
+const char *
+nm_settings_connection_get_connection_type(NMSettingsConnection *self)
+{
+    return nm_connection_get_connection_type(nm_settings_connection_get_connection(self));
+}
+
+/*****************************************************************************/
+
+void
+_nm_settings_connection_cleanup_after_remove(NMSettingsConnection *self)
+{
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    AuthData *                   auth_data;
+
+    while ((auth_data = c_list_first_entry(&priv->auth_lst_head, AuthData, auth_lst)))
+        nm_auth_manager_check_authorization_cancel(auth_data->call_id);
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMSettingsConnection *self = NM_SETTINGS_CONNECTION(object);
+
+    switch (prop_id) {
+    case PROP_UNSAVED:
+        g_value_set_boolean(value, nm_settings_connection_get_unsaved(self));
+        break;
+    case PROP_FLAGS:
+        g_value_set_uint(value,
+                         nm_settings_connection_get_flags(self)
+                             & _NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK);
+        break;
+    case PROP_FILENAME:
+        g_value_set_string(value, nm_settings_connection_get_filename(self));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_settings_connection_init(NMSettingsConnection *self)
+{
+    NMSettingsConnectionPrivate *priv;
+
+    priv =
+        G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionPrivate);
+    self->_priv = priv;
+
+    c_list_init(&self->_connections_lst);
+
+    c_list_init(&priv->call_ids_lst_head);
+    c_list_init(&priv->auth_lst_head);
+
+    priv->agent_mgr = g_object_ref(nm_agent_manager_get());
+    priv->settings  = g_object_ref(nm_settings_get());
+
+    priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET;
+}
+
+NMSettingsConnection *
+nm_settings_connection_new(void)
+{
+    return g_object_new(NM_TYPE_SETTINGS_CONNECTION, NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSettingsConnection *       self = NM_SETTINGS_CONNECTION(object);
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
+    NMSettingsConnectionCallId * call_id, *call_id_safe;
+
+    _LOGD("disposing");
+
+    nm_assert(!priv->default_wired_device);
+
+    nm_assert(c_list_is_empty(&self->_connections_lst));
+    nm_assert(c_list_is_empty(&priv->auth_lst_head));
+
+    /* Cancel in-progress secrets requests */
+    if (priv->agent_mgr) {
+        c_list_for_each_entry_safe (call_id, call_id_safe, &priv->call_ids_lst_head, call_ids_lst)
+            _get_secrets_cancel(self, call_id, TRUE);
+    }
+
+    nm_clear_pointer(&priv->system_secrets, g_variant_unref);
+    nm_clear_pointer(&priv->agent_secrets, g_variant_unref);
+
+    nm_clear_pointer(&priv->seen_bssids, g_hash_table_destroy);
+
+    g_clear_object(&priv->agent_mgr);
+
+    g_clear_object(&priv->connection);
+
+    nm_clear_pointer(&priv->kf_db_timestamps, nm_key_file_db_unref);
+    nm_clear_pointer(&priv->kf_db_seen_bssids, nm_key_file_db_unref);
+
+    G_OBJECT_CLASS(nm_settings_connection_parent_class)->dispose(object);
+
+    g_clear_object(&priv->storage);
+
+    nm_clear_g_free(&priv->filename);
+
+    g_clear_object(&priv->settings);
+}
+
+/*****************************************************************************/
+
+static const GDBusSignalInfo signal_info_updated = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT("Updated", );
+
+static const GDBusSignalInfo signal_info_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT("Removed", );
+
+static const NMDBusInterfaceInfoExtended interface_info_settings_connection = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
+        .methods = NM_DEFINE_GDBUS_METHOD_INFOS(
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "Update",
+                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("properties", "a{sa{sv}}"), ), ),
+                .handle = impl_settings_connection_update, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "UpdateUnsaved",
+                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("properties", "a{sa{sv}}"), ), ),
+                .handle = impl_settings_connection_update_unsaved, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Delete", ),
+                                                .handle = impl_settings_connection_delete, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "GetSettings",
+                    .out_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("settings", "a{sa{sv}}"), ), ),
+                .handle = impl_settings_connection_get_settings, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "GetSecrets",
+                    .in_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("setting_name", "s"), ),
+                    .out_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("secrets", "a{sa{sv}}"), ), ),
+                .handle = impl_settings_connection_get_secrets, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("ClearSecrets", ),
+                                                .handle = impl_settings_connection_clear_secrets, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Save", ),
+                                                .handle = impl_settings_connection_save, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "Update2",
+                    .in_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("settings", "a{sa{sv}}"),
+                                                  NM_DEFINE_GDBUS_ARG_INFO("flags", "u"),
+                                                  NM_DEFINE_GDBUS_ARG_INFO("args", "a{sv}"), ),
+                    .out_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("result", "a{sv}"), ), ),
+                .handle = impl_settings_connection_update2, ), ),
+        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy,
+                                                &signal_info_updated,
+                                                &signal_info_removed, ),
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Unsaved",
+                                                             "b",
+                                                             NM_SETTINGS_CONNECTION_UNSAVED),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Flags",
+                                                           "u",
+                                                           NM_SETTINGS_CONNECTION_FLAGS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Filename",
+                                                           "s",
+                                                           NM_SETTINGS_CONNECTION_FILENAME), ), ),
+    .legacy_property_changed = TRUE,
+};
+
+static void
+nm_settings_connection_class_init(NMSettingsConnectionClass *klass)
+{
+    GObjectClass *     object_class      = G_OBJECT_CLASS(klass);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+
+    g_type_class_add_private(klass, sizeof(NMSettingsConnectionPrivate));
+
+    dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH_SETTINGS);
+    dbus_object_class->interface_infos =
+        NM_DBUS_INTERFACE_INFOS(&interface_info_settings_connection);
+
+    object_class->dispose      = dispose;
+    object_class->get_property = get_property;
+
+    obj_properties[PROP_UNSAVED] = g_param_spec_boolean(NM_SETTINGS_CONNECTION_UNSAVED,
+                                                        "",
+                                                        "",
+                                                        FALSE,
+                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_SETTINGS_CONNECTION_FLAGS,
+                                                   "",
+                                                   "",
+                                                   0,
+                                                   G_MAXUINT32,
+                                                   0,
+                                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_FILENAME] = g_param_spec_string(NM_SETTINGS_CONNECTION_FILENAME,
+                                                        "",
+                                                        "",
+                                                        NULL,
+                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    /* internal signal, with an argument (NMSettingsConnectionUpdateReason update_reason) as
+     * guint. */
+    signals[UPDATED_INTERNAL] = g_signal_new(NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
+                                             G_TYPE_FROM_CLASS(klass),
+                                             G_SIGNAL_RUN_FIRST,
+                                             0,
+                                             NULL,
+                                             NULL,
+                                             g_cclosure_marshal_VOID__UINT,
+                                             G_TYPE_NONE,
+                                             1,
+                                             G_TYPE_UINT);
+
+    signals[FLAGS_CHANGED] = g_signal_new(NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
+                                          G_TYPE_FROM_CLASS(klass),
+                                          G_SIGNAL_RUN_FIRST,
+                                          0,
+                                          NULL,
+                                          NULL,
+                                          g_cclosure_marshal_VOID__VOID,
+                                          G_TYPE_NONE,
+                                          0);
+}
diff --git a/src/core/settings/nm-settings-connection.h b/src/core/settings/nm-settings-connection.h
new file mode 100644
index 00000000..7c61dbeb
--- /dev/null
+++ b/src/core/settings/nm-settings-connection.h
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2013 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SETTINGS_CONNECTION_H__
+#define __NETWORKMANAGER_SETTINGS_CONNECTION_H__
+
+#include "nm-dbus-object.h"
+#include "nm-connection.h"
+
+#include "nm-settings-storage.h"
+
+/*****************************************************************************/
+
+typedef enum {
+
+    NM_SETTINGS_CONNECTION_ADD_REASON_NONE = 0,
+
+    NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT = (1u << 0),
+
+} NMSettingsConnectionAddReason;
+
+typedef enum {
+
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE = 0,
+
+    /* with persist-mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, and
+     * update tries to update the profile on disk (which can always fail).
+     * In some cases we want to ignore such failure and proceed. For example,
+     * when we receive secrets from a secret-agent, we want to update the connection
+     * at all cost and ignore failures to write them to disk. */
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE = (1u << 0),
+
+    /* When updating the profile, force renaming the file on disk. That matters
+     * only for keyfile plugin. Keyfile prefers a filename based on connection.id.
+     * When the connection.id changes we might want to rename the file on disk
+     * (that is, don't overwrite the existing file, but delete it and write it
+     * with the new name).
+     * This flag forces such rename. */
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME = (1u << 1),
+
+    /* Usually, changing a profile that is currently active does not immediately
+     * reapply the changes. The exception are connection.zone and connection.metered
+     * properties. When this flag is set, then these two properties are reapplied
+     * right away.
+     *
+     * See also %NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY flag, to prevent partial reapply
+     * during Update2(). */
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL = (1u << 2),
+
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS = (1u << 3),
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS = (1u << 4),
+
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS = (1u << 5),
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS = (1u << 6),
+
+    /* if a profile was generated as default-wired connection for a device, then
+     * when the user modifies it via D-Bus, the profile should become persisted
+     * to disk and the initial purpose the profile was created should be forgotten. */
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_DEFAULT_WIRED = (1u << 7),
+
+    NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT = (1u << 8),
+
+} NMSettingsConnectionUpdateReason;
+
+typedef enum {
+
+    /* if the profile is in-memory, update it in-memory and keep it.
+     * if the profile is on-disk, update it on-disk, and keep it. */
+    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+
+    /* persist to disk. If the profile is currently in-memory, remove
+     * it from /run. Depending on the shadowed-storage, the pre-existing
+     * file is reused when moving the storage.
+     *
+     * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_TO_DISK. */
+    NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+
+    /* Update in-memory (i.e. persist to /run). If the profile is currently on disk,
+     * then a reference to the profile is remembered as "shadowed-storage".
+     * Later, when storing again to persistent storage, the shadowed-storage is
+     * updated. When deleting the profile, the shadowed-storage is also deleted
+     * from disk.
+     *
+     * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY. */
+    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+
+    /* Update in-memory (i.e. persist to /run). This is almost like
+     * %NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, except the in-memory profile
+     * remembers not to own the shadowed-storage ("shadowed-owned").
+     * The difference is that when deleting the in-memory profile, the original
+     * profile is not deleted but instead the nmmeta tombstone remembers the
+     * shadowed-storage and re-used it when re-adding the profile.
+     *
+     * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED. */
+    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+
+    /* Update in-memory (i.e. persist to /run). If the profile is currently on disk,
+     * delete it from disk.
+     *
+     * If the profile is in-memory and has a shadowed-storage, the original profile
+     * will be deleted from disk.
+     *
+     * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. */
+    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+
+    /* This only updates the connection in-memory. Note that "in-memory" above
+     * means to write to keyfile in /run. This mode really means to not notify the
+     * settings plugin about the change. This should be only used for updating
+     * secrets.
+     */
+    NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+
+} NMSettingsConnectionPersistMode;
+
+/*****************************************************************************/
+
+#define NM_TYPE_SETTINGS_CONNECTION (nm_settings_connection_get_type())
+#define NM_SETTINGS_CONNECTION(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnection))
+#define NM_SETTINGS_CONNECTION_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionClass))
+#define NM_IS_SETTINGS_CONNECTION(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS_CONNECTION))
+#define NM_IS_SETTINGS_CONNECTION_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS_CONNECTION))
+#define NM_SETTINGS_CONNECTION_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionClass))
+
+#define NM_SETTINGS_CONNECTION_GET_SECRETS      "get-secrets"
+#define NM_SETTINGS_CONNECTION_CANCEL_SECRETS   "cancel-secrets"
+#define NM_SETTINGS_CONNECTION_UPDATED_INTERNAL "updated-internal"
+#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"
+
+/**
+ * NMSettingsConnectionIntFlags:
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_NONE: no flag set
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED: the connection is not saved to disk.
+ *  See also #NM_SETTINGS_CONNECTION_FLAG_UNSAVED.
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED: A connection is "nm-generated" if
+ *  it was generated by NetworkManger. If the connection gets modified or saved
+ *  by the user, the flag gets cleared. A nm-generated is implicitly unsaved.
+ *  See also #NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED.
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE: The connection will be deleted
+ *  when it disconnects. That is for in-memory connections (unsaved), which are
+ *  currently active but cleanup on disconnect.
+ *  See also #NM_SETTINGS_CONNECTION_FLAG_VOLATILE.
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL: the profile was generated to
+ *  represent the external activation of a device. See also #NM_SETTINGS_CONNECTION_FLAG_EXTERNAL.
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE: The connection is visible
+ * @_NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK: the entire enum is
+ *   internal, however, parts of it is public API as #NMSettingsConnectionFlags.
+ *   This mask, are the public flags.
+ * @_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL: special mask, for all known flags
+ *
+ * #NMSettingsConnection flags.
+ **/
+typedef enum _NMSettingsConnectionIntFlags {
+    NM_SETTINGS_CONNECTION_INT_FLAGS_NONE = 0,
+
+    NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED      = NM_SETTINGS_CONNECTION_FLAG_UNSAVED,
+    NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED = NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED,
+    NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE     = NM_SETTINGS_CONNECTION_FLAG_VOLATILE,
+    NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL     = NM_SETTINGS_CONNECTION_FLAG_EXTERNAL,
+
+    NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE = 0x10,
+
+    _NM_SETTINGS_CONNECTION_INT_FLAGS_LAST,
+
+    _NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK =
+        0 | NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+        | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL | 0,
+
+    _NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK =
+        0 | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+        | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL | 0,
+
+    _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;
+
+struct _NMSettingsConnectionPrivate;
+
+struct _NMSettingsConnection {
+    NMDBusObject                         parent;
+    CList                                _connections_lst;
+    struct _NMSettingsConnectionPrivate *_priv;
+};
+
+GType nm_settings_connection_get_type(void);
+
+NMSettingsConnection *nm_settings_connection_new(void);
+
+NMConnection *nm_settings_connection_get_connection(NMSettingsConnection *self);
+
+void _nm_settings_connection_set_connection(NMSettingsConnection *           self,
+                                            NMConnection *                   new_connection,
+                                            NMConnection **                  out_old_connection,
+                                            NMSettingsConnectionUpdateReason update_reason);
+
+NMSettingsStorage *nm_settings_connection_get_storage(NMSettingsConnection *self);
+
+void _nm_settings_connection_set_storage(NMSettingsConnection *self, NMSettingsStorage *storage);
+
+gboolean nm_settings_connection_still_valid(NMSettingsConnection *self);
+
+const char *nm_settings_connection_get_filename(NMSettingsConnection *self);
+
+guint64 nm_settings_connection_get_last_secret_agent_version_id(NMSettingsConnection *self);
+
+gboolean
+nm_settings_connection_has_unmodified_applied_connection(NMSettingsConnection *self,
+                                                         NMConnection *        applied_connection,
+                                                         NMSettingCompareFlags compare_flage);
+
+gboolean nm_settings_connection_update(NMSettingsConnection *           self,
+                                       NMConnection *                   new_connection,
+                                       NMSettingsConnectionPersistMode  persist_mode,
+                                       NMSettingsConnectionIntFlags     sett_flags,
+                                       NMSettingsConnectionIntFlags     sett_mask,
+                                       NMSettingsConnectionUpdateReason update_reason,
+                                       const char *                     log_context_name,
+                                       GError **                        error);
+
+void nm_settings_connection_delete(NMSettingsConnection *self,
+                                   gboolean              allow_add_to_no_auto_default);
+
+typedef void (*NMSettingsConnectionSecretsFunc)(NMSettingsConnection *      self,
+                                                NMSettingsConnectionCallId *call_id,
+                                                const char *                agent_username,
+                                                const char *                setting_name,
+                                                GError *                    error,
+                                                gpointer                    user_data);
+
+gboolean nm_settings_connection_new_secrets(NMSettingsConnection *self,
+                                            NMConnection *        applied_connection,
+                                            const char *          setting_name,
+                                            GVariant *            secrets,
+                                            GError **             error);
+
+NMSettingsConnectionCallId *
+nm_settings_connection_get_secrets(NMSettingsConnection *          self,
+                                   NMConnection *                  applied_connection,
+                                   NMAuthSubject *                 subject,
+                                   const char *                    setting_name,
+                                   NMSecretAgentGetSecretsFlags    flags,
+                                   const char *const *             hints,
+                                   NMSettingsConnectionSecretsFunc callback,
+                                   gpointer                        callback_data);
+
+void nm_settings_connection_cancel_secrets(NMSettingsConnection *      self,
+                                           NMSettingsConnectionCallId *call_id);
+
+void nm_settings_connection_clear_secrets(NMSettingsConnection *self,
+                                          gboolean              clear_cached_system_secrets,
+                                          gboolean              persist);
+
+gboolean nm_settings_connection_check_visibility(NMSettingsConnection *self,
+                                                 NMSessionMonitor *    session_monitor);
+
+gboolean nm_settings_connection_check_permission(NMSettingsConnection *self,
+                                                 const char *          permission);
+
+/*****************************************************************************/
+
+NMDevice *nm_settings_connection_default_wired_get_device(NMSettingsConnection *self);
+void nm_settings_connection_default_wired_set_device(NMSettingsConnection *self, NMDevice *device);
+
+/*****************************************************************************/
+
+NMSettingsConnectionIntFlags nm_settings_connection_get_flags(NMSettingsConnection *self);
+
+static inline gboolean
+nm_settings_connection_get_unsaved(NMSettingsConnection *self)
+{
+    return NM_FLAGS_HAS(nm_settings_connection_get_flags(self),
+                        NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED);
+}
+
+NMSettingsConnectionIntFlags
+nm_settings_connection_set_flags_full(NMSettingsConnection *       self,
+                                      NMSettingsConnectionIntFlags mask,
+                                      NMSettingsConnectionIntFlags value);
+
+static inline NMSettingsConnectionIntFlags
+nm_settings_connection_set_flags(NMSettingsConnection *       self,
+                                 NMSettingsConnectionIntFlags flags,
+                                 gboolean                     set)
+{
+    return nm_settings_connection_set_flags_full(self,
+                                                 flags,
+                                                 set ? flags
+                                                     : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
+}
+
+/*****************************************************************************/
+
+int nm_settings_connection_cmp_timestamp(NMSettingsConnection *ac, NMSettingsConnection *ab);
+int nm_settings_connection_cmp_timestamp_p_with_data(gconstpointer pa,
+                                                     gconstpointer pb,
+                                                     gpointer      user_data);
+int nm_settings_connection_cmp_autoconnect_priority(NMSettingsConnection *a,
+                                                    NMSettingsConnection *b);
+int nm_settings_connection_cmp_autoconnect_priority_p_with_data(gconstpointer pa,
+                                                                gconstpointer pb,
+                                                                gpointer      user_data);
+
+struct _NMKeyFileDB;
+
+void _nm_settings_connection_register_kf_dbs(NMSettingsConnection *self,
+                                             struct _NMKeyFileDB * kf_db_timestamps,
+                                             struct _NMKeyFileDB * kf_db_seen_bssids);
+
+gboolean nm_settings_connection_get_timestamp(NMSettingsConnection *self, guint64 *out_timestamp);
+
+void nm_settings_connection_update_timestamp(NMSettingsConnection *self, guint64 timestamp);
+
+const char **nm_settings_connection_get_seen_bssids(NMSettingsConnection *self);
+
+gboolean nm_settings_connection_has_seen_bssid(NMSettingsConnection *self, const char *bssid);
+
+void nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *seen_bssid);
+
+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);
+
+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);
+
+static inline 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);
+
+const char *nm_settings_connection_get_id(NMSettingsConnection *connection);
+const char *nm_settings_connection_get_uuid(NMSettingsConnection *connection);
+const char *nm_settings_connection_get_connection_type(NMSettingsConnection *connection);
+
+/*****************************************************************************/
+
+NMConnection **
+nm_settings_connections_array_to_connections(NMSettingsConnection *const *connections,
+                                             gssize                       n_connections);
+
+/*****************************************************************************/
+
+void _nm_settings_connection_emit_dbus_signal_updated(NMSettingsConnection *self);
+void _nm_settings_connection_emit_dbus_signal_removed(NMSettingsConnection *self);
+
+void _nm_settings_connection_emit_signal_updated_internal(
+    NMSettingsConnection *           self,
+    NMSettingsConnectionUpdateReason update_reason);
+
+void _nm_settings_connection_cleanup_after_remove(NMSettingsConnection *self);
+
+#endif /* __NETWORKMANAGER_SETTINGS_CONNECTION_H__ */
diff --git a/src/core/settings/nm-settings-plugin.c b/src/core/settings/nm-settings-plugin.c
new file mode 100644
index 00000000..9e81caab
--- /dev/null
+++ b/src/core/settings/nm-settings-plugin.c
@@ -0,0 +1,290 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2007 - 2018 Red Hat, Inc.
+ * Copyright (C) 2008 Novell, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-settings-plugin.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+
+#include "nm-settings-connection.h"
+
+/*****************************************************************************/
+
+enum {
+    UNMANAGED_SPECS_CHANGED,
+    UNRECOGNIZED_SPECS_CHANGED,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE(NMSettingsPlugin, nm_settings_plugin, G_TYPE_OBJECT)
+
+/*****************************************************************************/
+
+int
+nm_settings_plugin_cmp_by_priority(const NMSettingsPlugin *a,
+                                   const NMSettingsPlugin *b,
+                                   const GSList *          plugin_list)
+{
+    nm_assert(NM_IS_SETTINGS_PLUGIN(a));
+    nm_assert(NM_IS_SETTINGS_PLUGIN(b));
+
+    if (a != b) {
+        int idx_a = g_slist_index((GSList *) plugin_list, a);
+        int idx_b = g_slist_index((GSList *) plugin_list, b);
+
+        /* the plugins must be found in the list. */
+        nm_assert(idx_a >= 0);
+        nm_assert(idx_b >= 0);
+
+        /* plugins that appear first in @plugin_list have higher priority.
+         * That means: smaller index -> higher priority. Reverse sort. */
+        NM_CMP_DIRECT(idx_b, idx_a);
+    }
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+GSList *
+nm_settings_plugin_get_unmanaged_specs(NMSettingsPlugin *self)
+{
+    NMSettingsPluginClass *klass;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), NULL);
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+    if (!klass->get_unmanaged_specs)
+        return NULL;
+    return klass->get_unmanaged_specs(self);
+}
+
+GSList *
+nm_settings_plugin_get_unrecognized_specs(NMSettingsPlugin *self)
+{
+    NMSettingsPluginClass *klass;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), NULL);
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+    if (!klass->get_unrecognized_specs)
+        return NULL;
+    return klass->get_unrecognized_specs(self);
+}
+
+void
+nm_settings_plugin_reload_connections(NMSettingsPlugin *                     self,
+                                      NMSettingsPluginConnectionLoadCallback callback,
+                                      gpointer                               user_data)
+{
+    NMSettingsPluginClass *klass;
+
+    g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self));
+    g_return_if_fail(callback);
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+    if (klass->reload_connections)
+        klass->reload_connections(self, callback, user_data);
+}
+
+NMSettingsPluginConnectionLoadEntry *
+nm_settings_plugin_create_connection_load_entries(const char *const *filenames, gsize *out_len)
+{
+    NMSettingsPluginConnectionLoadEntry *entries;
+    gsize                                len;
+    gsize                                i;
+
+    len = NM_PTRARRAY_LEN(filenames);
+    if (len == 0) {
+        *out_len = 0;
+        return NULL;
+    }
+
+    entries = g_new(NMSettingsPluginConnectionLoadEntry, len);
+    for (i = 0; i < len; i++) {
+        entries[i] = (NMSettingsPluginConnectionLoadEntry){
+            .filename = filenames[i],
+            .error    = NULL,
+            .handled  = FALSE,
+        };
+    }
+
+    *out_len = len;
+    return entries;
+}
+
+void
+nm_settings_plugin_load_connections(NMSettingsPlugin *                     self,
+                                    NMSettingsPluginConnectionLoadEntry *  entries,
+                                    gsize                                  n_entries,
+                                    NMSettingsPluginConnectionLoadCallback callback,
+                                    gpointer                               user_data)
+{
+    NMSettingsPluginClass *klass;
+
+    g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self));
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+    if (klass->load_connections)
+        klass->load_connections(self, entries, n_entries, callback, user_data);
+}
+
+void
+nm_settings_plugin_load_connections_done(NMSettingsPlugin *self)
+{
+    NMSettingsPluginClass *klass;
+
+    g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self));
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+    if (klass->load_connections_done)
+        klass->load_connections_done(self);
+}
+
+gboolean
+nm_settings_plugin_add_connection(NMSettingsPlugin *  self,
+                                  NMConnection *      connection,
+                                  NMSettingsStorage **out_storage,
+                                  NMConnection **     out_connection,
+                                  GError **           error)
+{
+    NMSettingsPluginClass *klass;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE);
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
+
+#if NM_MORE_ASSERTS > 5
+    nm_assert(nm_connection_verify(connection, NULL));
+#endif
+
+    NM_SET_OUT(out_storage, NULL);
+    NM_SET_OUT(out_connection, NULL);
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+    if (!klass->add_connection) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_NOT_SUPPORTED,
+                            "settings plugin does not support adding connections");
+        return FALSE;
+    }
+    return klass->add_connection(self, connection, out_storage, out_connection, error);
+}
+
+gboolean
+nm_settings_plugin_update_connection(NMSettingsPlugin *  self,
+                                     NMSettingsStorage * storage,
+                                     NMConnection *      connection,
+                                     NMSettingsStorage **out_storage,
+                                     NMConnection **     out_connection,
+                                     GError **           error)
+{
+    NMSettingsPluginClass *klass = NULL;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE);
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), FALSE);
+    g_return_val_if_fail(nm_settings_storage_get_plugin(storage) == self, FALSE);
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
+
+#if NM_MORE_ASSERTS > 5
+    nm_assert(nm_connection_verify(connection, NULL));
+    nm_assert(nm_streq(nm_connection_get_uuid(connection), nm_settings_storage_get_uuid(storage)));
+#endif
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+
+    NM_SET_OUT(out_storage, NULL);
+    NM_SET_OUT(out_connection, NULL);
+
+    if (!klass->update_connection) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_NOT_SUPPORTED,
+                    "settings plugin does not support modifying connections");
+        return FALSE;
+    }
+    return klass->update_connection(self, storage, connection, out_storage, out_connection, error);
+}
+
+gboolean
+nm_settings_plugin_delete_connection(NMSettingsPlugin * self,
+                                     NMSettingsStorage *storage,
+                                     GError **          error)
+{
+    NMSettingsPluginClass *klass = NULL;
+
+    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE);
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), FALSE);
+    g_return_val_if_fail(nm_settings_storage_get_plugin(storage) == self, FALSE);
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+
+    if (!klass->delete_connection) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_NOT_SUPPORTED,
+                    "settings plugin does not support deleting connections");
+        return FALSE;
+    }
+
+    return klass->delete_connection(self, storage, error);
+}
+
+/*****************************************************************************/
+
+void
+_nm_settings_plugin_emit_signal_unmanaged_specs_changed(NMSettingsPlugin *self)
+{
+    nm_assert(NM_IS_SETTINGS_PLUGIN(self));
+
+    g_signal_emit(self, signals[UNMANAGED_SPECS_CHANGED], 0);
+}
+
+void
+_nm_settings_plugin_emit_signal_unrecognized_specs_changed(NMSettingsPlugin *self)
+{
+    nm_assert(NM_IS_SETTINGS_PLUGIN(self));
+
+    g_signal_emit(self, signals[UNRECOGNIZED_SPECS_CHANGED], 0);
+}
+
+/*****************************************************************************/
+
+static void
+nm_settings_plugin_init(NMSettingsPlugin *self)
+{}
+
+static void
+nm_settings_plugin_class_init(NMSettingsPluginClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    signals[UNMANAGED_SPECS_CHANGED] = g_signal_new(NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
+                                                    G_OBJECT_CLASS_TYPE(object_class),
+                                                    G_SIGNAL_RUN_FIRST,
+                                                    0,
+                                                    NULL,
+                                                    NULL,
+                                                    g_cclosure_marshal_VOID__VOID,
+                                                    G_TYPE_NONE,
+                                                    0);
+
+    signals[UNRECOGNIZED_SPECS_CHANGED] =
+        g_signal_new(NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
+                     G_OBJECT_CLASS_TYPE(object_class),
+                     G_SIGNAL_RUN_FIRST,
+                     0,
+                     NULL,
+                     NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+}
diff --git a/src/core/settings/nm-settings-plugin.h b/src/core/settings/nm-settings-plugin.h
new file mode 100644
index 00000000..a9b9b2ae
--- /dev/null
+++ b/src/core/settings/nm-settings-plugin.h
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2007 - 2018 Red Hat, Inc.
+ * Copyright (C) 2008 Novell, Inc.
+ */
+
+#ifndef __NM_SETTINGS_PLUGIN_H__
+#define __NM_SETTINGS_PLUGIN_H__
+
+#include "nm-connection.h"
+
+#include "nm-settings-storage.h"
+
+typedef struct _NMSettingsPlugin NMSettingsPlugin;
+
+typedef void (*NMSettingsPluginConnectionLoadCallback)(NMSettingsPlugin * self,
+                                                       NMSettingsStorage *storage,
+                                                       NMConnection *     connection,
+                                                       gpointer           user_data);
+
+typedef struct {
+    const char *filename;
+    GError *    error;
+    bool        handled : 1;
+} NMSettingsPluginConnectionLoadEntry;
+
+#define NM_TYPE_SETTINGS_PLUGIN (nm_settings_plugin_get_type())
+#define NM_SETTINGS_PLUGIN(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPlugin))
+#define NM_SETTINGS_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginClass))
+#define NM_IS_SETTINGS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS_PLUGIN))
+#define NM_IS_SETTINGS_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS_PLUGIN))
+#define NM_SETTINGS_PLUGIN_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginClass))
+
+#define NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED    "unmanaged-specs-changed"
+#define NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED "unrecognized-specs-changed"
+
+struct _NMSettingsPlugin {
+    GObject parent;
+};
+
+typedef struct {
+    GObjectClass parent;
+
+    /*
+     * Return a string list of specifications of devices which NetworkManager
+     * should not manage.  Returned list will be freed by the system settings
+     * service, and each element must be allocated using g_malloc() or its
+     * variants (g_strdup, g_strdup_printf, etc).
+     *
+     * Each string in the list must be in one of the formats recognized by
+     * nm_device_spec_match_list().
+     */
+    GSList *(*get_unmanaged_specs)(NMSettingsPlugin *self);
+
+    /*
+     * Return a string list of specifications of devices for which at least
+     * one non-NetworkManager-based configuration is defined. Returned list
+     * will be freed by the system settings service, and each element must be
+     * allocated using g_malloc() or its variants (g_strdup, g_strdup_printf,
+     * etc).
+     *
+     * Each string in the list must be in one of the formats recognized by
+     * nm_device_spec_match_list().
+     */
+    GSList *(*get_unrecognized_specs)(NMSettingsPlugin *self);
+
+    /* Requests that the plugin load/reload a set of filenames.
+     */
+    void (*load_connections)(NMSettingsPlugin *                     self,
+                             NMSettingsPluginConnectionLoadEntry *  entries,
+                             gsize                                  n_entries,
+                             NMSettingsPluginConnectionLoadCallback callback,
+                             gpointer                               user_data);
+
+    /* Requests that the plugin reload all connection files from disk,
+     * and emit signals reflecting new, changed, and removed connections.
+     */
+    void (*reload_connections)(NMSettingsPlugin *                     self,
+                               NMSettingsPluginConnectionLoadCallback callback,
+                               gpointer                               user_data);
+
+    void (*load_connections_done)(NMSettingsPlugin *self);
+
+    gboolean (*add_connection)(NMSettingsPlugin *  self,
+                               NMConnection *      connection,
+                               NMSettingsStorage **out_storage,
+                               NMConnection **     out_connection,
+                               GError **           error);
+
+    gboolean (*update_connection)(NMSettingsPlugin *  self,
+                                  NMSettingsStorage * storage,
+                                  NMConnection *      connection,
+                                  NMSettingsStorage **out_storage,
+                                  NMConnection **     out_connection,
+                                  GError **           error);
+
+    gboolean (*delete_connection)(NMSettingsPlugin * self,
+                                  NMSettingsStorage *storage,
+                                  GError **          error);
+
+    const char *plugin_name;
+
+} NMSettingsPluginClass;
+
+/*****************************************************************************/
+
+GType nm_settings_plugin_get_type(void);
+
+/*****************************************************************************/
+
+#define NM_SETTINGS_STORAGE_PRINT_FMT NM_HASH_OBFUSCATE_PTR_FMT "/%s"
+
+#define NM_SETTINGS_STORAGE_PRINT_ARG(storage) \
+    NM_HASH_OBFUSCATE_PTR(storage),            \
+        nm_settings_plugin_get_plugin_name(nm_settings_storage_get_plugin(storage))
+
+static inline const char *
+nm_settings_plugin_get_plugin_name(NMSettingsPlugin *self)
+{
+    NMSettingsPluginClass *klass;
+
+    nm_assert(NM_SETTINGS_PLUGIN(self));
+
+    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
+
+    nm_assert(klass && klass->plugin_name && strlen(klass->plugin_name) > 0);
+
+    return klass->plugin_name;
+}
+
+/*****************************************************************************/
+
+GSList *nm_settings_plugin_get_unmanaged_specs(NMSettingsPlugin *self);
+GSList *nm_settings_plugin_get_unrecognized_specs(NMSettingsPlugin *self);
+
+void nm_settings_plugin_reload_connections(NMSettingsPlugin *                     self,
+                                           NMSettingsPluginConnectionLoadCallback callback,
+                                           gpointer                               user_data);
+
+NMSettingsPluginConnectionLoadEntry *
+nm_settings_plugin_create_connection_load_entries(const char *const *filenames, gsize *out_len);
+
+void nm_settings_plugin_load_connections(NMSettingsPlugin *                     self,
+                                         NMSettingsPluginConnectionLoadEntry *  entries,
+                                         gsize                                  n_entries,
+                                         NMSettingsPluginConnectionLoadCallback callback,
+                                         gpointer                               user_data);
+
+void nm_settings_plugin_load_connections_done(NMSettingsPlugin *self);
+
+gboolean nm_settings_plugin_add_connection(NMSettingsPlugin *  self,
+                                           NMConnection *      connection,
+                                           NMSettingsStorage **out_storage,
+                                           NMConnection **     out_connection,
+                                           GError **           error);
+
+gboolean nm_settings_plugin_update_connection(NMSettingsPlugin *  self,
+                                              NMSettingsStorage * storage,
+                                              NMConnection *      connection,
+                                              NMSettingsStorage **out_storage,
+                                              NMConnection **     out_connection,
+                                              GError **           error);
+
+gboolean nm_settings_plugin_delete_connection(NMSettingsPlugin * self,
+                                              NMSettingsStorage *storage,
+                                              GError **          error);
+
+/*****************************************************************************/
+
+typedef NMSettingsPlugin *(*NMSettingsPluginFactoryFunc)(void);
+
+NMSettingsPlugin *nm_settings_plugin_factory(void);
+
+/*****************************************************************************
+ * Internal API
+ *****************************************************************************/
+
+void _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NMSettingsPlugin *self);
+
+void _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NMSettingsPlugin *self);
+
+/*****************************************************************************/
+
+int nm_settings_plugin_cmp_by_priority(const NMSettingsPlugin *a,
+                                       const NMSettingsPlugin *b,
+                                       const GSList *          plugin_list);
+
+/*****************************************************************************/
+
+/* forward declare this function from NMSettings. It's used by the ifcfg-rh plugin,
+ * but that shouldn't include all "nm-settings.h" header. */
+NMSettings *nm_settings_get(void);
+
+const char *nm_settings_get_dbus_path_for_uuid(NMSettings *self, const char *uuid);
+
+#endif /* __NM_SETTINGS_PLUGIN_H__ */
diff --git a/src/core/settings/nm-settings-storage.c b/src/core/settings/nm-settings-storage.c
new file mode 100644
index 00000000..c56ba075
--- /dev/null
+++ b/src/core/settings/nm-settings-storage.c
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-settings-storage.h"
+
+#include "nm-utils.h"
+#include "nm-settings-plugin.h"
+
+#include "settings/plugins/keyfile/nms-keyfile-storage.h"
+
+/*****************************************************************************/
+
+int
+nm_settings_storage_cmp(NMSettingsStorage *a, NMSettingsStorage *b, const GSList *plugin_list)
+{
+    NMSettingsStorageClass *klass;
+
+    /* Sort by priority.
+     *
+     * If a > b (by priority), we return a positive number (as one
+     * would expect by a cmp() function). */
+
+    nm_assert(NM_IS_SETTINGS_STORAGE(a));
+    nm_assert(NM_IS_SETTINGS_STORAGE(b));
+    nm_assert(a != b);
+    nm_assert(nm_streq(nm_settings_storage_get_uuid(a), nm_settings_storage_get_uuid(b)));
+
+    /* in-memory has always higher priority */
+    NM_CMP_DIRECT(nm_settings_storage_is_keyfile_run(a), nm_settings_storage_is_keyfile_run(b));
+
+    NM_CMP_RETURN(nm_settings_plugin_cmp_by_priority(nm_settings_storage_get_plugin(a),
+                                                     nm_settings_storage_get_plugin(b),
+                                                     plugin_list));
+
+    klass = NM_SETTINGS_STORAGE_GET_CLASS(a);
+    if (klass != NM_SETTINGS_STORAGE_GET_CLASS(b)) {
+        /* one plugin must return storages of the same type. Otherwise, it's
+         * unclear how cmp_fcn() should compare them. */
+        nm_assert_not_reached();
+        return 0;
+    }
+
+    if (klass->cmp_fcn)
+        NM_CMP_RETURN(klass->cmp_fcn(a, b));
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLUGIN, PROP_UUID, PROP_FILENAME, );
+
+G_DEFINE_TYPE(NMSettingsStorage, nm_settings_storage, G_TYPE_OBJECT)
+
+/*****************************************************************************/
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NMSettingsStorage *self = NM_SETTINGS_STORAGE(object);
+
+    switch (prop_id) {
+    case PROP_PLUGIN:
+        /* construct-only */
+        self->_plugin = g_object_ref(g_value_get_object(value));
+        nm_assert(NM_IS_SETTINGS_PLUGIN(self->_plugin));
+        break;
+    case PROP_UUID:
+        /* construct-only */
+        self->_uuid = g_value_dup_string(value);
+        nm_assert(!self->_uuid || nm_utils_is_uuid(self->_uuid));
+        break;
+    case PROP_FILENAME:
+        /* construct-only */
+        self->_filename = g_value_dup_string(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_settings_storage_init(NMSettingsStorage *self)
+{
+    c_list_init(&self->_storage_lst);
+    c_list_init(&self->_storage_by_uuid_lst);
+}
+
+NMSettingsStorage *
+nm_settings_storage_new(NMSettingsPlugin *plugin, const char *uuid, const char *filename)
+{
+    nm_assert(NM_IS_SETTINGS_PLUGIN(plugin));
+    nm_assert(nm_utils_is_uuid(uuid));
+
+    return g_object_new(NM_TYPE_SETTINGS_STORAGE,
+                        NM_SETTINGS_STORAGE_PLUGIN,
+                        plugin,
+                        NM_SETTINGS_STORAGE_UUID,
+                        uuid,
+                        NM_SETTINGS_STORAGE_FILENAME,
+                        filename,
+                        NULL);
+}
+
+static void
+finalize(GObject *object)
+{
+    NMSettingsStorage *self = NM_SETTINGS_STORAGE(object);
+
+    c_list_unlink_stale(&self->_storage_lst);
+    c_list_unlink_stale(&self->_storage_by_uuid_lst);
+
+    g_object_unref(self->_plugin);
+    g_free(self->_uuid);
+    g_free(self->_filename);
+
+    G_OBJECT_CLASS(nm_settings_storage_parent_class)->finalize(object);
+}
+
+static void
+nm_settings_storage_class_init(NMSettingsStorageClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->set_property = set_property;
+    object_class->finalize     = finalize;
+
+    obj_properties[PROP_PLUGIN] =
+        g_param_spec_object(NM_SETTINGS_STORAGE_PLUGIN,
+                            "",
+                            "",
+                            NM_TYPE_SETTINGS_PLUGIN,
+                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_UUID] =
+        g_param_spec_string(NM_SETTINGS_STORAGE_UUID,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_FILENAME] =
+        g_param_spec_string(NM_SETTINGS_STORAGE_FILENAME,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/core/settings/nm-settings-storage.h b/src/core/settings/nm-settings-storage.h
new file mode 100644
index 00000000..970cfd77
--- /dev/null
+++ b/src/core/settings/nm-settings-storage.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTINGS_STORAGE_H__
+#define __NM_SETTINGS_STORAGE_H__
+
+/*****************************************************************************/
+
+#include "c-list/src/c-list.h"
+
+#define NM_TYPE_SETTINGS_STORAGE (nm_settings_storage_get_type())
+#define NM_SETTINGS_STORAGE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorage))
+#define NM_SETTINGS_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorageClass))
+#define NM_IS_SETTINGS_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS_STORAGE))
+#define NM_IS_SETTINGS_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS_STORAGE))
+#define NM_SETTINGS_STORAGE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorageClass))
+
+#define NM_SETTINGS_STORAGE_PLUGIN   "plugin"
+#define NM_SETTINGS_STORAGE_UUID     "uuid"
+#define NM_SETTINGS_STORAGE_FILENAME "filename"
+
+struct _NMSettingsPlugin;
+
+typedef struct NMSettingsStorage {
+    GObject                   parent;
+    struct _NMSettingsPlugin *_plugin;
+    char *                    _uuid;
+    char *                    _filename;
+    CList                     _storage_lst;
+    CList                     _storage_by_uuid_lst;
+} NMSettingsStorage;
+
+typedef struct {
+    GObjectClass parent;
+
+    int (*cmp_fcn)(NMSettingsStorage *a, NMSettingsStorage *b);
+
+} NMSettingsStorageClass;
+
+GType nm_settings_storage_get_type(void);
+
+NMSettingsStorage *
+nm_settings_storage_new(struct _NMSettingsPlugin *plugin, const char *uuid, const char *filename);
+
+static inline struct _NMSettingsPlugin *
+nm_settings_storage_get_plugin(const NMSettingsStorage *self)
+{
+    GType nm_settings_plugin_get_type(void);
+
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL);
+
+    nm_assert(G_TYPE_CHECK_INSTANCE_TYPE(self->_plugin, nm_settings_plugin_get_type()));
+    return self->_plugin;
+}
+
+static inline const char *
+nm_settings_storage_get_uuid(const NMSettingsStorage *self)
+{
+    gboolean nm_utils_is_uuid(const char *str);
+
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL);
+
+    nm_assert(nm_utils_is_uuid(self->_uuid));
+    return self->_uuid;
+}
+
+static inline const char *
+nm_settings_storage_get_uuid_opt(const NMSettingsStorage *self)
+{
+    gboolean nm_utils_is_uuid(const char *str);
+
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL);
+
+    nm_assert(!self->_uuid || nm_utils_is_uuid(self->_uuid));
+    return self->_uuid;
+}
+
+static inline const char *
+nm_settings_storage_get_filename(const NMSettingsStorage *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(self), NULL);
+
+    return self->_filename;
+}
+
+/*****************************************************************************/
+
+#define nm_assert_valid_settings_storage(plugin, storage)                           \
+    G_STMT_START                                                                    \
+    {                                                                               \
+        NMSettingsPlugin *const  _plugin  = (plugin);                               \
+        NMSettingsStorage *const _storage = (storage);                              \
+                                                                                    \
+        nm_assert(!_plugin || NM_IS_SETTINGS_PLUGIN(_plugin));                      \
+        nm_assert(NM_IS_SETTINGS_STORAGE(_storage));                                \
+        nm_assert(!_plugin || nm_settings_storage_get_plugin(_storage) == _plugin); \
+    }                                                                               \
+    G_STMT_END
+
+/*****************************************************************************/
+
+int nm_settings_storage_cmp(NMSettingsStorage *sd_a,
+                            NMSettingsStorage *sd_b,
+                            const GSList *     plugin_list);
+
+#endif /* __NM_SETTINGS_STORAGE_H__ */
diff --git a/src/core/settings/nm-settings-utils.c b/src/core/settings/nm-settings-utils.c
new file mode 100644
index 00000000..775d0396
--- /dev/null
+++ b/src/core/settings/nm-settings-utils.c
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-settings-utils.h"
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "nm-settings-plugin.h"
+
+/*****************************************************************************/
+
+const struct timespec *
+nm_sett_util_stat_mtime(const char *filename, gboolean do_lstat, struct timespec *out_val)
+{
+    struct stat    st;
+    struct timeval now_tv;
+
+    if (filename) {
+        if (do_lstat) {
+            if (lstat(filename, &st) == 0) {
+                *out_val = st.st_mtim;
+                return out_val;
+            }
+        } else {
+            if (stat(filename, &st) == 0) {
+                *out_val = st.st_mtim;
+                return out_val;
+            }
+        }
+    }
+
+    if (gettimeofday(&now_tv, NULL) == 0) {
+        *out_val = (struct timespec){
+            .tv_sec  = now_tv.tv_sec,
+            .tv_nsec = now_tv.tv_usec * 1000u,
+        };
+        return out_val;
+    }
+
+    *out_val = (struct timespec){};
+    return out_val;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_sett_util_allow_filename_cb(const char *filename, gpointer user_data)
+{
+    const NMSettUtilAllowFilenameData *allow_filename_data = user_data;
+
+    if (allow_filename_data->allowed_filename
+        && nm_streq(allow_filename_data->allowed_filename, filename))
+        return TRUE;
+
+    return !g_hash_table_contains(allow_filename_data->idx_by_filename, filename);
+}
+
+/*****************************************************************************/
+
+void
+nm_sett_util_storage_by_uuid_head_destroy(NMSettUtilStorageByUuidHead *sbuh)
+{
+    CList *iter;
+
+    while ((iter = c_list_first(&sbuh->_storage_by_uuid_lst_head)))
+        c_list_unlink(iter);
+    g_free(sbuh);
+}
+
+/*****************************************************************************/
+
+void
+nm_sett_util_storages_clear(NMSettUtilStorages *storages)
+{
+    nm_clear_pointer(&storages->idx_by_uuid, g_hash_table_destroy);
+    nm_clear_pointer(&storages->idx_by_filename, g_hash_table_destroy);
+    nm_assert(c_list_is_empty(&storages->_storage_lst_head));
+}
+
+void
+nm_sett_util_storages_add_take(NMSettUtilStorages *storages,
+                               gpointer storage_take_p /* NMSettingsStorage *, take reference */)
+{
+    NMSettingsStorage *          storage_take = storage_take_p;
+    NMSettUtilStorageByUuidHead *sbuh;
+    const char *                 uuid;
+
+    nm_assert(storage_take);
+    nm_assert(c_list_is_empty(&storage_take->_storage_lst));
+    nm_assert(c_list_is_empty(&storage_take->_storage_by_uuid_lst));
+    nm_assert(nm_settings_storage_get_filename(storage_take));
+
+    if (!g_hash_table_replace(storages->idx_by_filename,
+                              (char *) nm_settings_storage_get_filename(storage_take),
+                              storage_take /* takes ownership of reference. */))
+        nm_assert_not_reached();
+
+    uuid = nm_settings_storage_get_uuid_opt(storage_take);
+
+    if (uuid) {
+        sbuh = nm_sett_util_storages_lookup_by_uuid(storages, uuid);
+        if (!sbuh) {
+            gsize l = strlen(uuid) + 1;
+
+            sbuh       = g_malloc(sizeof(NMSettUtilStorageByUuidHead) + l);
+            sbuh->uuid = sbuh->uuid_data;
+            c_list_init(&sbuh->_storage_by_uuid_lst_head);
+            memcpy(sbuh->uuid_data, uuid, l);
+            g_hash_table_add(storages->idx_by_uuid, sbuh);
+        }
+        c_list_link_tail(&sbuh->_storage_by_uuid_lst_head, &storage_take->_storage_by_uuid_lst);
+    }
+
+    c_list_link_tail(&storages->_storage_lst_head, &storage_take->_storage_lst);
+}
+
+gpointer /* NMSettingsStorage * */
+nm_sett_util_storages_steal(NMSettUtilStorages *storages,
+                            gpointer            storage_p /* NMSettingsStorage **/)
+{
+    NMSettingsStorage *          storage = storage_p;
+    NMSettUtilStorageByUuidHead *sbuh;
+    const char *                 uuid;
+
+    nm_assert(storage);
+    nm_assert(nm_sett_util_storages_lookup_by_filename(storages,
+                                                       nm_settings_storage_get_filename(storage))
+              == storage);
+    nm_assert(c_list_contains(&storages->_storage_lst_head, &storage->_storage_lst));
+
+    uuid = nm_settings_storage_get_uuid_opt(storage);
+
+    if (!uuid) {
+        nm_assert(c_list_is_empty(&storage->_storage_by_uuid_lst));
+    } else {
+        nm_assert(!c_list_is_empty(&storage->_storage_by_uuid_lst));
+
+        sbuh = nm_sett_util_storages_lookup_by_uuid(storages, uuid);
+
+        nm_assert(sbuh);
+        nm_assert(
+            c_list_contains(&sbuh->_storage_by_uuid_lst_head, &storage->_storage_by_uuid_lst));
+        c_list_unlink(&storage->_storage_by_uuid_lst);
+
+        if (c_list_is_empty(&sbuh->_storage_by_uuid_lst_head))
+            g_hash_table_remove(storages->idx_by_uuid, sbuh);
+    }
+
+    c_list_unlink(&storage->_storage_lst);
+
+    g_hash_table_steal(storages->idx_by_filename, nm_settings_storage_get_filename(storage));
+
+    return storage;
+}
diff --git a/src/core/settings/nm-settings-utils.h b/src/core/settings/nm-settings-utils.h
new file mode 100644
index 00000000..d3f50ddf
--- /dev/null
+++ b/src/core/settings/nm-settings-utils.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTINGS_UTILS_H__
+#define __NM_SETTINGS_UTILS_H__
+
+#include "nm-settings-storage.h"
+
+/*****************************************************************************/
+
+struct timespec;
+
+const struct timespec *
+nm_sett_util_stat_mtime(const char *filename, gboolean do_lstat, struct timespec *out_val);
+
+/*****************************************************************************/
+
+typedef struct {
+    const char *uuid;
+
+    CList _storage_by_uuid_lst_head;
+
+    char uuid_data[];
+} NMSettUtilStorageByUuidHead;
+
+typedef struct {
+    CList       _storage_lst_head;
+    GHashTable *idx_by_filename;
+    GHashTable *idx_by_uuid;
+} NMSettUtilStorages;
+
+void nm_sett_util_storage_by_uuid_head_destroy(NMSettUtilStorageByUuidHead *sbuh);
+
+#define NM_SETT_UTIL_STORAGES_INIT(storages, storage_destroy_fcn)                              \
+    {                                                                                          \
+        ._storage_lst_head = C_LIST_INIT(((storages)._storage_lst_head)),                      \
+        .idx_by_filename   = g_hash_table_new_full(nm_str_hash,                                \
+                                                 g_str_equal,                                \
+                                                 NULL,                                       \
+                                                 (GDestroyNotify) storage_destroy_fcn),      \
+        .idx_by_uuid =                                                                         \
+            g_hash_table_new_full(nm_pstr_hash,                                                \
+                                  nm_pstr_equal,                                               \
+                                  NULL,                                                        \
+                                  (GDestroyNotify) nm_sett_util_storage_by_uuid_head_destroy), \
+    }
+
+void nm_sett_util_storages_clear(NMSettUtilStorages *storages);
+
+#define nm_auto_clear_sett_util_storages nm_auto(nm_sett_util_storages_clear)
+
+void nm_sett_util_storages_add_take(NMSettUtilStorages *storages, gpointer storage_take_p);
+
+gpointer nm_sett_util_storages_steal(NMSettUtilStorages *storages, gpointer storage_p);
+
+/*****************************************************************************/
+
+static inline gpointer /* NMSettingsStorage * */
+nm_sett_util_storages_lookup_by_filename(NMSettUtilStorages *storages, const char *filename)
+{
+    nm_assert(filename);
+
+    return g_hash_table_lookup(storages->idx_by_filename, filename);
+}
+
+static inline NMSettUtilStorageByUuidHead *
+nm_sett_util_storages_lookup_by_uuid(NMSettUtilStorages *storages, const char *uuid)
+{
+    nm_assert(uuid);
+
+    return g_hash_table_lookup(storages->idx_by_uuid, &uuid);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    GHashTable *idx_by_filename;
+    const char *allowed_filename;
+} NMSettUtilAllowFilenameData;
+
+#define NM_SETT_UTIL_ALLOW_FILENAME_DATA(_storages, _allowed_filename) \
+    (&((NMSettUtilAllowFilenameData){                                  \
+        .idx_by_filename  = (_storages)->idx_by_filename,              \
+        .allowed_filename = (_allowed_filename),                       \
+    }))
+
+gboolean nm_sett_util_allow_filename_cb(const char *filename, gpointer user_data);
+
+#endif /* __NM_SETTINGS_UTILS_H__ */
diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c
new file mode 100644
index 00000000..3d31fb8b
--- /dev/null
+++ b/src/core/settings/nm-settings.c
@@ -0,0 +1,4144 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Søren Sandmann <sandmann@daimi.au.dk>
+ * Dan Williams <dcbw@redhat.com>
+ * Tambet Ingo <tambet@gmail.com>
+ * Copyright (C) 2007 - 2011 Red Hat, Inc.
+ * Copyright (C) 2008 Novell, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-settings.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <gmodule.h>
+#include <pwd.h>
+
+#if HAVE_SELINUX
+    #include <selinux/selinux.h>
+#endif
+
+#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "nm-glib-aux/nm-keyfile-aux.h"
+#include "nm-keyfile-internal.h"
+#include "nm-dbus-interface.h"
+#include "nm-connection.h"
+#include "nm-setting-8021x.h"
+#include "nm-setting-bluetooth.h"
+#include "nm-setting-cdma.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-gsm.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-setting-olpc-mesh.h"
+#include "nm-setting-ppp.h"
+#include "nm-setting-pppoe.h"
+#include "nm-setting-serial.h"
+#include "nm-setting-vpn.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-adsl.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+#include "nm-setting-proxy.h"
+#include "nm-setting-bond.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-dbus-object.h"
+#include "devices/nm-device-ethernet.h"
+#include "nm-settings-connection.h"
+#include "nm-settings-plugin.h"
+#include "nm-dbus-manager.h"
+#include "nm-auth-utils.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
+#include "nm-session-monitor.h"
+#include "plugins/keyfile/nms-keyfile-plugin.h"
+#include "plugins/keyfile/nms-keyfile-storage.h"
+#include "nm-agent-manager.h"
+#include "nm-config.h"
+#include "nm-manager.h"
+#include "nm-audit-manager.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dispatcher.h"
+#include "nm-hostname-manager.h"
+
+/*****************************************************************************/
+
+static NM_CACHED_QUARK_FCN("default-wired-connection", _default_wired_connection_quark);
+
+static NM_CACHED_QUARK_FCN("default-wired-connection-blocked",
+                           _default_wired_connection_blocked_quark);
+
+/*****************************************************************************/
+
+typedef struct _StorageData {
+    CList              sd_lst;
+    NMSettingsStorage *storage;
+    NMConnection *     connection;
+    bool               prioritize : 1;
+} StorageData;
+
+static StorageData *
+_storage_data_new_stale(NMSettingsStorage *storage, NMConnection *connection)
+{
+    StorageData *sd;
+
+    sd             = g_slice_new(StorageData);
+    sd->storage    = g_object_ref(storage);
+    sd->connection = nm_g_object_ref(connection);
+    sd->prioritize = FALSE;
+    return sd;
+}
+
+static void
+_storage_data_destroy(StorageData *sd)
+{
+    c_list_unlink_stale(&sd->sd_lst);
+    g_object_unref(sd->storage);
+    nm_g_object_unref(sd->connection);
+    g_slice_free(StorageData, sd);
+}
+
+static StorageData *
+_storage_data_find_in_lst(CList *head, NMSettingsStorage *storage)
+{
+    StorageData *sd;
+
+    nm_assert(head);
+    nm_assert(NM_IS_SETTINGS_STORAGE(storage));
+
+    c_list_for_each_entry (sd, head, sd_lst) {
+        if (sd->storage == storage)
+            return sd;
+    }
+    return NULL;
+}
+
+static void
+nm_assert_storage_data_lst(CList *head)
+{
+#if NM_MORE_ASSERTS > 5
+    const char * uuid = NULL;
+    StorageData *sd;
+    CList *      iter;
+
+    nm_assert(head);
+
+    if (c_list_is_empty(head))
+        return;
+
+    c_list_for_each_entry (sd, head, sd_lst) {
+        const char *u;
+
+        nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage));
+        nm_assert(!sd->connection || NM_IS_CONNECTION(sd->connection));
+        u = nm_settings_storage_get_uuid(sd->storage);
+        if (!uuid) {
+            uuid = u;
+            nm_assert(nm_utils_is_uuid(uuid));
+        } else
+            nm_assert(nm_streq0(uuid, u));
+    }
+
+    /* assert that all storages are unique. */
+    c_list_for_each_entry (sd, head, sd_lst) {
+        for (iter = sd->sd_lst.next; iter != head; iter = iter->next)
+            nm_assert(c_list_entry(iter, StorageData, sd_lst)->storage != sd->storage);
+    }
+#endif
+}
+
+static gboolean
+_storage_data_is_alive(StorageData *sd)
+{
+    /* If the storage tracks a connection, it is considered alive.
+     *
+     * Meta-data storages are special: they never track a connection.
+     * We need to check them specially to know when to drop them. */
+    return sd->connection || nm_settings_storage_is_meta_data_alive(sd->storage);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    const char *          uuid;
+    NMSettingsConnection *sett_conn;
+    NMSettingsStorage *   storage;
+    CList                 sd_lst_head;
+    CList                 dirty_sd_lst_head;
+
+    CList sce_dirty_lst;
+
+    char _uuid_data[];
+} SettConnEntry;
+
+static SettConnEntry *
+_sett_conn_entry_new(const char *uuid)
+{
+    SettConnEntry *sett_conn_entry;
+    gsize          l_p_1;
+
+    nm_assert(nm_utils_is_uuid(uuid));
+
+    l_p_1 = strlen(uuid) + 1;
+
+    sett_conn_entry            = g_malloc(sizeof(SettConnEntry) + l_p_1);
+    sett_conn_entry->uuid      = sett_conn_entry->_uuid_data;
+    sett_conn_entry->sett_conn = NULL;
+    sett_conn_entry->storage   = NULL;
+    c_list_init(&sett_conn_entry->sd_lst_head);
+    c_list_init(&sett_conn_entry->dirty_sd_lst_head);
+    c_list_init(&sett_conn_entry->sce_dirty_lst);
+    memcpy(sett_conn_entry->_uuid_data, uuid, l_p_1);
+    return sett_conn_entry;
+}
+
+static void
+_sett_conn_entry_free(SettConnEntry *sett_conn_entry)
+{
+    c_list_unlink_stale(&sett_conn_entry->sce_dirty_lst);
+    nm_c_list_free_all(&sett_conn_entry->sd_lst_head, StorageData, sd_lst, _storage_data_destroy);
+    nm_c_list_free_all(&sett_conn_entry->dirty_sd_lst_head,
+                       StorageData,
+                       sd_lst,
+                       _storage_data_destroy);
+    nm_g_object_unref(sett_conn_entry->sett_conn);
+    nm_g_object_unref(sett_conn_entry->storage);
+    g_free(sett_conn_entry);
+}
+
+static NMSettingsConnection *
+_sett_conn_entry_get_conn(SettConnEntry *sett_conn_entry)
+{
+    return sett_conn_entry ? sett_conn_entry->sett_conn : NULL;
+}
+
+/**
+ * _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.
+ * @plugins: the list of plugins sorted in descending priority. This determines
+ *   the priority and whether a storage conflicts.
+ *
+ * If we were to add the a storage to @target_plugin, then this function checks
+ * whether there are already other storages that would hide the storage after we
+ * add it. Those conflicting/hiding storages are a problem, because they have higher
+ * priority, so we cannot add the storage.
+ *
+ * @storage_check_including is optional, and if given then it checks whether updating
+ * the profile in this storage would result in confict. This is the check before
+ * update-connection. If this parameter is omitted, then it's about what happens
+ * when adding a new profile (add-connection).
+ *
+ * Returns: the conflicting storage or %NULL if there is none.
+ */
+static NMSettingsStorage *
+_sett_conn_entry_storage_find_conflicting_storage(SettConnEntry *    sett_conn_entry,
+                                                  NMSettingsPlugin * target_plugin,
+                                                  NMSettingsStorage *storage_check_including,
+                                                  const GSList *     plugins)
+{
+    StorageData *sd;
+
+    if (!sett_conn_entry)
+        return NULL;
+
+    if (storage_check_including && nm_settings_storage_is_keyfile_run(storage_check_including)) {
+        /* the storage we check against is in-memory. It always has highest
+         * priority, so there can be no other conflicting storages. */
+        return NULL;
+    }
+
+    /* Finds the first (highest priority) storage that has a connection.
+     * Note that due to tombstones (that have a high priority), the connection
+     * may not actually be exposed. This is to find hidden/shadowed storages
+     * that provide a connection. */
+    c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+        nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage));
+
+        if (!sd->connection) {
+            /* We only consider storages with connection. In particular,
+             * tombstones are not relevant, because we can delete them to
+             * resolve the conflict. */
+            continue;
+        }
+
+        if (sd->storage == storage_check_including) {
+            /* ok, the storage is the one we are about to check. All other
+             * storages are lower priority, so there is no storage that hides
+             * our storage_check_including. */
+            return NULL;
+        }
+
+        if (nm_settings_plugin_cmp_by_priority(nm_settings_storage_get_plugin(sd->storage),
+                                               target_plugin,
+                                               plugins)
+            <= 0) {
+            /* the plugin of the existing storage is less important than @target_plugin.
+             * We have no conflicting/hiding storage. */
+            return NULL;
+        }
+
+        /* Found. If we would add the profile to @target_plugin, then it would be hidden
+         * by existing_storage. */
+        return sd->storage;
+    }
+
+    return NULL;
+}
+
+static NMSettingsStorage *
+_sett_conn_entry_find_shadowed_storage(SettConnEntry *    sett_conn_entry,
+                                       const char *       shadowed_storage_filename,
+                                       NMSettingsStorage *blacklisted_storage)
+{
+    StorageData *sd;
+
+    if (!shadowed_storage_filename)
+        return NULL;
+
+    c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+        nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage));
+
+        if (!sd->connection)
+            continue;
+
+        if (blacklisted_storage == sd->storage)
+            continue;
+
+        if (!nm_streq0(nm_settings_storage_get_filename_for_shadowed_storage(sd->storage),
+                       shadowed_storage_filename))
+            continue;
+
+        return sd->storage;
+    }
+
+    return NULL;
+}
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettings,
+                             PROP_MANAGER,
+                             PROP_UNMANAGED_SPECS,
+                             PROP_HOSTNAME,
+                             PROP_CAN_MODIFY,
+                             PROP_CONNECTIONS,
+                             PROP_STARTUP_COMPLETE, );
+
+enum {
+    CONNECTION_ADDED,
+    CONNECTION_UPDATED,
+    CONNECTION_REMOVED,
+    CONNECTION_FLAGS_CHANGED,
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct {
+    NMAgentManager *agent_mgr;
+
+    NMConfig *config;
+
+    NMPlatform *platform;
+
+    NMManager *manager;
+
+    NMHostnameManager *hostname_manager;
+
+    NMSessionMonitor *session_monitor;
+
+    CList auth_lst_head;
+
+    NMSKeyfilePlugin *keyfile_plugin;
+
+    GSList *plugins;
+
+    NMKeyFileDB *kf_db_timestamps;
+    NMKeyFileDB *kf_db_seen_bssids;
+
+    GHashTable *sce_idx;
+
+    CList sce_dirty_lst_head;
+
+    CList connections_lst_head;
+
+    NMSettingsConnection **connections_cached_list;
+
+    GSList *unmanaged_specs;
+    GSList *unrecognized_specs;
+
+    gint64      startup_complete_start_timestamp_msec;
+    GHashTable *startup_complete_idx;
+    CList       startup_complete_scd_lst_head;
+    guint       startup_complete_timeout_id;
+
+    guint connections_len;
+
+    guint connections_generation;
+
+    guint kf_db_flush_idle_id_timestamps;
+    guint kf_db_flush_idle_id_seen_bssids;
+
+    bool started : 1;
+
+} NMSettingsPrivate;
+
+struct _NMSettings {
+    NMDBusObject      parent;
+    NMSettingsPrivate _priv;
+};
+
+struct _NMSettingsClass {
+    NMDBusObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMSettings, nm_settings, NM_TYPE_DBUS_OBJECT);
+
+#define NM_SETTINGS_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMSettings, NM_IS_SETTINGS)
+
+/*****************************************************************************/
+
+/* FIXME: a lot of logging lines are directly connected to a profile. Set the @con_uuid
+ *   argument for structured logging. */
+
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "settings", __VA_ARGS__)
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended interface_info_settings;
+static const GDBusSignalInfo             signal_info_new_connection;
+static const GDBusSignalInfo             signal_info_connection_removed;
+
+static void default_wired_clear_tag(NMSettings *          self,
+                                    NMDevice *            device,
+                                    NMSettingsConnection *sett_conn,
+                                    gboolean              add_to_no_auto_default);
+
+static void _clear_connections_cached_list(NMSettingsPrivate *priv);
+
+static void _startup_complete_check(NMSettings *self, gint64 now_msec);
+
+/*****************************************************************************/
+
+static void
+_emit_connection_added(NMSettings *self, NMSettingsConnection *sett_conn)
+{
+    g_signal_emit(self, signals[CONNECTION_ADDED], 0, sett_conn);
+}
+
+static void
+_emit_connection_updated(NMSettings *                     self,
+                         NMSettingsConnection *           sett_conn,
+                         NMSettingsConnectionUpdateReason update_reason)
+{
+    _nm_settings_connection_emit_signal_updated_internal(sett_conn, update_reason);
+    g_signal_emit(self, signals[CONNECTION_UPDATED], 0, sett_conn, (guint) update_reason);
+}
+
+static void
+_emit_connection_removed(NMSettings *self, NMSettingsConnection *sett_conn)
+{
+    g_signal_emit(self, signals[CONNECTION_REMOVED], 0, sett_conn);
+}
+
+static void
+_emit_connection_flags_changed(NMSettings *self, NMSettingsConnection *sett_conn)
+{
+    g_signal_emit(self, signals[CONNECTION_FLAGS_CHANGED], 0, sett_conn);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    NMSettingsConnection *sett_conn;
+    CList                 scd_lst;
+    gint64                timeout_msec;
+} StartupCompleteData;
+
+static void
+_startup_complete_data_destroy(StartupCompleteData *scd)
+{
+    c_list_unlink_stale(&scd->scd_lst);
+    g_object_unref(scd->sett_conn);
+    nm_g_slice_free(scd);
+}
+
+static gboolean
+_startup_complete_check_is_ready(NMSettings *          self,
+                                 NMSettingsConnection *sett_conn,
+                                 gboolean              ignore_pending_actions)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    NMConnection *     conn;
+    const CList *      tmp_lst;
+    NMDevice *         device;
+
+    if (!priv->manager)
+        return TRUE;
+
+    conn = nm_settings_connection_get_connection(sett_conn);
+
+    nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+        if (!nm_device_is_real(device))
+            continue;
+
+        if (nm_device_get_state(device) < NM_DEVICE_STATE_UNAVAILABLE
+            || (!ignore_pending_actions && nm_device_has_pending_action(device))) {
+            /* while a device is not yet available and still has a pending
+             * action itself, it's not a suitable candidate. */
+            continue;
+        }
+
+        if (!nm_device_check_connection_compatible(device, conn, NULL))
+            continue;
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static gboolean
+_startup_complete_timeout_cb(gpointer user_data)
+{
+    NMSettings *       self = user_data;
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    priv->startup_complete_timeout_id = 0;
+    _startup_complete_check(self, 0);
+    return G_SOURCE_REMOVE;
+}
+
+static void
+_startup_complete_check(NMSettings *self, gint64 now_msec)
+{
+    NMSettingsPrivate *  priv = NM_SETTINGS_GET_PRIVATE(self);
+    StartupCompleteData *scd_not_ready;
+    StartupCompleteData *scd_safe;
+    StartupCompleteData *scd;
+    gint64               elapsed_msec;
+    CList                ready_lst;
+
+    if (priv->startup_complete_start_timestamp_msec == 0) {
+        /* we are already done for good or didn't start yet. */
+        return;
+    }
+
+    if (!priv->started) {
+        /* before we are started there is no need to evaluate our list because
+         * we are anyway blocking startup-complete. */
+        return;
+    }
+
+    if (c_list_is_empty(&priv->startup_complete_scd_lst_head))
+        goto ready;
+
+    nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+
+    elapsed_msec = now_msec - priv->startup_complete_start_timestamp_msec;
+
+    /* We search the entire list whether they all timed-out or found a compatible device.
+     * We do that by appending elements that are ready to the end of the list, so that
+     * we hopefully keep testing the elements that are ready already (and can shortcut
+     * the test in common cases).
+     *
+     * Note that all profiles that we wait for need to have their dependencies satisfied
+     * at the same time. For example, consider connection A is waiting for device A' which is ready.
+     * Connection B waits for device B', which isn't ready. Once B'/B becomes ready, A/A' must
+     * still be ready. Otherwise, we would wait for A/A' to become ready again. */
+    scd_not_ready = NULL;
+    c_list_init(&ready_lst);
+    c_list_for_each_entry_safe (scd, scd_safe, &priv->startup_complete_scd_lst_head, scd_lst) {
+        if (scd->timeout_msec <= elapsed_msec)
+            goto next_with_ready;
+
+        if (_startup_complete_check_is_ready(self, scd->sett_conn, FALSE))
+            goto next_with_ready;
+
+        scd_not_ready = scd;
+        break;
+
+next_with_ready:
+        /* this element is ready. We move it to a temporary list, so that we
+         * can reorder the list (to next time evaluate the non-ready element first). */
+        nm_c_list_move_tail(&ready_lst, &scd->scd_lst);
+    }
+    c_list_splice(&priv->startup_complete_scd_lst_head, &ready_lst);
+
+    nm_clear_g_source(&priv->startup_complete_timeout_id);
+
+    if (scd_not_ready) {
+        gint64 timeout_msec;
+
+        timeout_msec = priv->startup_complete_start_timestamp_msec + scd_not_ready->timeout_msec
+                       - nm_utils_get_monotonic_timestamp_msec();
+        priv->startup_complete_timeout_id =
+            g_timeout_add(NM_CLAMP(0, timeout_msec, 60000), _startup_complete_timeout_cb, self);
+        _LOGT("startup-complete: wait for suitable device for connection \"%s\" (%s) which has "
+              "\"connection.wait-device-timeout\" set",
+              nm_settings_connection_get_id(scd_not_ready->sett_conn),
+              nm_settings_connection_get_uuid(scd_not_ready->sett_conn));
+        return;
+    }
+
+    if (_LOGW_ENABLED()) {
+        c_list_for_each_entry (scd, &priv->startup_complete_scd_lst_head, scd_lst) {
+            if (!_startup_complete_check_is_ready(self, scd->sett_conn, TRUE)) {
+                _LOGW("startup-complete: profile \"%s\" (%s) was waiting for non-existing device "
+                      "(with timeout \"connection.wait-device-timeout=%" G_GINT64_FORMAT "\")",
+                      nm_settings_connection_get_id(scd->sett_conn),
+                      nm_settings_connection_get_uuid(scd->sett_conn),
+                      scd->timeout_msec);
+            }
+        }
+    }
+
+ready:
+    nm_clear_pointer(&priv->startup_complete_idx, g_hash_table_destroy);
+    nm_assert(c_list_is_empty(&priv->startup_complete_scd_lst_head));
+    nm_assert(priv->started);
+    _LOGT("startup-complete: ready, no more profiles to wait for");
+    priv->startup_complete_start_timestamp_msec = 0;
+    nm_assert(!priv->startup_complete_idx);
+    nm_assert(priv->startup_complete_timeout_id == 0);
+    _notify(self, PROP_STARTUP_COMPLETE);
+}
+
+static void
+_startup_complete_notify_connection(NMSettings *          self,
+                                    NMSettingsConnection *sett_conn,
+                                    gboolean              forget)
+{
+    NMSettingsPrivate *  priv = NM_SETTINGS_GET_PRIVATE(self);
+    StartupCompleteData *scd;
+    gint64               timeout_msec;
+    gint64               now_msec = 0;
+    NMSettingConnection *s_con;
+    gint32               v;
+
+    nm_assert(priv->startup_complete_start_timestamp_msec != 0);
+
+    if (forget) {
+        if (!priv->startup_complete_idx)
+            return;
+        if (!g_hash_table_remove(priv->startup_complete_idx, &sett_conn))
+            return;
+        goto check;
+    }
+
+    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn));
+    v     = nm_setting_connection_get_wait_device_timeout(s_con);
+    if (v > 0)
+        timeout_msec = v;
+    else
+        timeout_msec = 0;
+
+    if (!priv->startup_complete_idx) {
+        nm_assert(!priv->started);
+
+        if (timeout_msec == 0)
+            return;
+
+        priv->startup_complete_idx =
+            g_hash_table_new_full(nm_pdirect_hash,
+                                  nm_pdirect_equal,
+                                  NULL,
+                                  (GDestroyNotify) _startup_complete_data_destroy);
+        scd = NULL;
+    } else
+        scd = g_hash_table_lookup(priv->startup_complete_idx, &sett_conn);
+
+    if (!scd) {
+        if (timeout_msec == 0)
+            return;
+        scd  = g_slice_new(StartupCompleteData);
+        *scd = (StartupCompleteData){
+            .sett_conn    = g_object_ref(sett_conn),
+            .timeout_msec = timeout_msec,
+        };
+        g_hash_table_add(priv->startup_complete_idx, scd);
+        c_list_link_tail(&priv->startup_complete_scd_lst_head, &scd->scd_lst);
+    } else {
+        scd->timeout_msec = timeout_msec;
+        nm_c_list_move_front(&priv->startup_complete_scd_lst_head, &scd->scd_lst);
+    }
+
+check:
+    _startup_complete_check(self, now_msec);
+}
+
+const char *
+nm_settings_get_startup_complete_blocked_reason(NMSettings *self, gboolean force_reload)
+{
+    NMSettingsPrivate *  priv = NM_SETTINGS_GET_PRIVATE(self);
+    StartupCompleteData *scd;
+    const char *         uuid;
+
+    if (priv->startup_complete_start_timestamp_msec == 0)
+        goto out_done;
+
+    if (force_reload)
+        _startup_complete_check(self, 0);
+
+    if (c_list_is_empty(&priv->startup_complete_scd_lst_head))
+        goto out_done;
+
+    scd = c_list_first_entry(&priv->startup_complete_scd_lst_head, StartupCompleteData, scd_lst);
+
+    nm_assert(scd);
+    nm_assert(NM_IS_SETTINGS_CONNECTION(scd->sett_conn));
+    nm_assert(scd == nm_g_hash_table_lookup(priv->startup_complete_idx, &scd->sett_conn));
+
+    uuid = nm_settings_connection_get_uuid(scd->sett_conn);
+    if (uuid)
+        return uuid;
+
+    g_return_val_if_reached("settings-starting");
+
+out_done:
+    if (!priv->started)
+        return "settings-starting";
+    return NULL;
+}
+
+/*****************************************************************************/
+
+const GSList *
+nm_settings_get_unmanaged_specs(NMSettings *self)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    return priv->unmanaged_specs;
+}
+
+static gboolean
+update_specs(NMSettings *self, GSList **specs_ptr, GSList *(*get_specs_func)(NMSettingsPlugin *) )
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    GSList *new             = NULL;
+    GSList *iter;
+
+    for (iter = priv->plugins; iter; iter = g_slist_next(iter)) {
+        GSList *specs;
+
+        specs = get_specs_func(iter->data);
+        while (specs) {
+            GSList *s = specs;
+
+            specs = g_slist_remove_link(specs, s);
+            if (nm_utils_g_slist_find_str(new, s->data)) {
+                g_free(s->data);
+                g_slist_free_1(s);
+                continue;
+            }
+            s->next = new;
+            new     = s;
+        }
+    }
+
+    if (nm_utils_g_slist_strlist_cmp(new, *specs_ptr) == 0) {
+        g_slist_free_full(new, g_free);
+        return FALSE;
+    }
+
+    g_slist_free_full(*specs_ptr, g_free);
+    *specs_ptr = new;
+    return TRUE;
+}
+
+static void
+_plugin_unmanaged_specs_changed(NMSettingsPlugin *config, gpointer user_data)
+{
+    NMSettings *       self = NM_SETTINGS(user_data);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    if (update_specs(self, &priv->unmanaged_specs, nm_settings_plugin_get_unmanaged_specs))
+        _notify(self, PROP_UNMANAGED_SPECS);
+}
+
+static void
+_plugin_unrecognized_specs_changed(NMSettingsPlugin *config, gpointer user_data)
+{
+    NMSettings *       self = NM_SETTINGS(user_data);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    update_specs(self, &priv->unrecognized_specs, nm_settings_plugin_get_unrecognized_specs);
+}
+
+/*****************************************************************************/
+
+static void
+connection_flags_changed(NMSettingsConnection *sett_conn, gpointer user_data)
+{
+    _emit_connection_flags_changed(NM_SETTINGS(user_data), sett_conn);
+}
+
+/*****************************************************************************/
+
+static SettConnEntry *
+_sett_conn_entries_get(NMSettings *self, const char *uuid)
+{
+    nm_assert(uuid);
+    return g_hash_table_lookup(NM_SETTINGS_GET_PRIVATE(self)->sce_idx, &uuid);
+}
+
+static SettConnEntry *
+_sett_conn_entries_create_and_add(NMSettings *self, const char *uuid)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    SettConnEntry *    sett_conn_entry;
+
+    sett_conn_entry = _sett_conn_entry_new(uuid);
+
+    if (!g_hash_table_add(priv->sce_idx, sett_conn_entry))
+        nm_assert_not_reached();
+    else if (g_hash_table_size(priv->sce_idx) == 1)
+        g_object_ref(self);
+
+    return sett_conn_entry;
+}
+
+static void
+_sett_conn_entries_remove_and_destroy(NMSettings *self, SettConnEntry *sett_conn_entry)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    if (!g_hash_table_remove(priv->sce_idx, sett_conn_entry))
+        nm_assert_not_reached();
+    else if (g_hash_table_size(priv->sce_idx) == 0)
+        g_object_unref(self);
+}
+
+/*****************************************************************************/
+
+static int
+_sett_conn_entry_sds_update_cmp_ascending(const StorageData *sd_a,
+                                          const StorageData *sd_b,
+                                          const GSList *     plugins)
+{
+    const NMSettingsMetaData *meta_data_a;
+    const NMSettingsMetaData *meta_data_b;
+    bool                      is_keyfile_run_a;
+    bool                      is_keyfile_run_b;
+
+    /* Sort storages by priority. More important storages are sorted
+     * higher (ascending sort). For example, if "sd_a" is more important than
+     * "sd_b" (sd_a>sd_b), a positive integer is returned. */
+
+    meta_data_a = nm_settings_storage_is_meta_data(sd_a->storage);
+    meta_data_b = nm_settings_storage_is_meta_data(sd_b->storage);
+
+    /* runtime storages (both connections and meta-data) are always more
+     * important. */
+    is_keyfile_run_a = nm_settings_storage_is_keyfile_run(sd_a->storage);
+    is_keyfile_run_b = nm_settings_storage_is_keyfile_run(sd_b->storage);
+    if (is_keyfile_run_a != is_keyfile_run_b) {
+        if (!meta_data_a && !meta_data_b) {
+            /* Ok, both are non-meta-data providing actual profiles. But one is in /run and one is in
+             * another storage. In this case we first honor whether one of the storages is explicitly
+             * prioritized. The prioritize flag is an in-memory hack to overwrite relative priorities
+             * contrary to what exists on-disk.
+             *
+             * This is done because when we use explicit D-Bus API (like update-connection)
+             * to update a profile, then we really want to prioritize the candidate
+             * despite having multiple other profiles.
+             *
+             * The example is if you have the same UUID twice in /run (one of them shadowed).
+             * If you move it to disk, then one of the profiles gets deleted and re-created
+             * on disk, but that on-disk profile must win against the remainging profile in
+             * /run. At least until the next reload/restart. */
+            NM_CMP_FIELD_UNSAFE(sd_a, sd_b, prioritize);
+        }
+
+        /* in-memory has higher priority. That is regardless of whether any of
+         * them is meta-data/tombstone or a profile.
+         *
+         * That works, because if any of them are tombstones/metadata, then we are in full
+         * control. There can by only one meta-data file, which is fully owned (and accordingly
+         * created/deleted) by NetworkManager.
+         *
+         * The only case where this might not be right is if we have profiles
+         * in /run that are shadowed. When we move such a profile to disk, then
+         * a conflict might arise. That is handled by "prioritize" above! */
+        NM_CMP_DIRECT(is_keyfile_run_a, is_keyfile_run_b);
+    }
+
+    /* After we determined that both profiles are either in /run or not,
+     * tombstones are always more important than non-tombstones. */
+    NM_CMP_DIRECT(meta_data_a && meta_data_a->is_tombstone,
+                  meta_data_b && meta_data_b->is_tombstone);
+
+    /* Again, prioritized entries are sorted first (higher priority). */
+    NM_CMP_FIELD_UNSAFE(sd_a, sd_b, prioritize);
+
+    /* finally, compare the storages. This basically honors the timestamp
+     * of the profile and the relative order of the source plugin (via the
+     * @plugins list). */
+    return nm_settings_storage_cmp(sd_a->storage, sd_b->storage, plugins);
+}
+
+static int
+_sett_conn_entry_sds_update_cmp(const CList *ls_a, const CList *ls_b, gconstpointer user_data)
+{
+    /* we sort highest priority storages first (descending). Hence, the order is swapped. */
+    return _sett_conn_entry_sds_update_cmp_ascending(c_list_entry(ls_b, StorageData, sd_lst),
+                                                     c_list_entry(ls_a, StorageData, sd_lst),
+                                                     user_data);
+}
+
+static void
+_sett_conn_entry_sds_update(NMSettings *self, SettConnEntry *sett_conn_entry)
+{
+    StorageData *sd;
+    StorageData *sd_safe;
+    StorageData *sd_dirty;
+    gboolean     reprioritize;
+
+    nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head);
+    nm_assert_storage_data_lst(&sett_conn_entry->dirty_sd_lst_head);
+
+    /* we merge the dirty list with the previous list.
+     *
+     * The idea is:
+     *
+     *  - _connection_changed_track() appends events for the same UUID. Meaning:
+     *    if the storage is new, it get appended (having lower priority).
+     *    If it already exist and is an update for an event that we already
+     *    track it, it keeps the list position in @dirty_sd_lst_head unchanged.
+     *
+     *  - during merge, we want to preserve the previous order (with higher
+     *    priority first in the list).
+     */
+
+    /* first go through all storages that we track and check whether they
+     * got an update...*/
+
+    reprioritize = FALSE;
+    c_list_for_each_entry (sd, &sett_conn_entry->dirty_sd_lst_head, sd_lst) {
+        if (sd->prioritize) {
+            reprioritize = TRUE;
+            break;
+        }
+    }
+
+    nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head);
+
+    c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
+        sd_dirty = _storage_data_find_in_lst(&sett_conn_entry->dirty_sd_lst_head, sd->storage);
+        if (!sd_dirty) {
+            /* there is no update for this storage (except maybe reprioritize). */
+            if (reprioritize)
+                sd->prioritize = FALSE;
+            continue;
+        }
+
+        nm_g_object_ref_set(&sd->connection, sd_dirty->connection);
+        sd->prioritize = sd_dirty->prioritize;
+
+        _storage_data_destroy(sd_dirty);
+    }
+
+    nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head);
+
+    /* all remaining (so far unseen) dirty entries are appended to the merged list.
+     * (append means lower priority). */
+
+    c_list_splice(&sett_conn_entry->sd_lst_head, &sett_conn_entry->dirty_sd_lst_head);
+
+    nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head);
+
+    /* we drop the entries that are no longer "alive" (meaning, they no longer
+     * indicate a connection and are not a tombstone). */
+    c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
+        if (!_storage_data_is_alive(sd))
+            _storage_data_destroy(sd);
+    }
+
+    nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head);
+    nm_assert(c_list_is_empty(&sett_conn_entry->dirty_sd_lst_head));
+
+    /* as last, we sort the entries. Note that this is a stable-sort... */
+    c_list_sort(&sett_conn_entry->sd_lst_head,
+                _sett_conn_entry_sds_update_cmp,
+                NM_SETTINGS_GET_PRIVATE(self)->plugins);
+
+    nm_assert_storage_data_lst(&sett_conn_entry->sd_lst_head);
+    nm_assert(c_list_is_empty(&sett_conn_entry->dirty_sd_lst_head));
+}
+
+/*****************************************************************************/
+
+static NMConnection *
+_connection_changed_normalize_connection(NMSettingsStorage *storage,
+                                         NMConnection *     connection,
+                                         GVariant *         secrets_to_merge,
+                                         NMConnection **    out_connection_cloned)
+{
+    gs_unref_object NMConnection *connection_cloned = NULL;
+    gs_free_error GError *error                     = NULL;
+    const char *          uuid;
+
+    nm_assert(NM_IS_SETTINGS_STORAGE(storage));
+    nm_assert(out_connection_cloned && !*out_connection_cloned);
+
+    if (!connection)
+        return NULL;
+
+    nm_assert(NM_IS_CONNECTION(connection));
+
+    uuid = nm_settings_storage_get_uuid(storage);
+
+    if (secrets_to_merge) {
+        connection_cloned = nm_simple_connection_new_clone(connection);
+        connection        = connection_cloned;
+        nm_connection_update_secrets(connection, NULL, secrets_to_merge, NULL);
+    }
+
+    if (!_nm_connection_ensure_normalized(connection,
+                                          !!connection_cloned,
+                                          uuid,
+                                          FALSE,
+                                          connection_cloned ? NULL : &connection_cloned,
+                                          &error)) {
+        /* this is most likely a bug in the plugin. It provided a connection that no longer verifies.
+         * Well, I guess it could also happen when we merge @secrets_to_merge above. In any case
+         * somewhere is a bug. */
+        _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT
+              "]: plugin provided an invalid connection: %s",
+              uuid,
+              NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+              error->message);
+        return NULL;
+    }
+    if (connection_cloned)
+        connection = connection_cloned;
+
+    *out_connection_cloned = g_steal_pointer(&connection_cloned);
+    return connection;
+}
+
+/*****************************************************************************/
+
+static void
+_connection_changed_update(NMSettings *                     self,
+                           SettConnEntry *                  sett_conn_entry,
+                           NMConnection *                   connection,
+                           NMSettingsConnectionIntFlags     sett_flags,
+                           NMSettingsConnectionIntFlags     sett_mask,
+                           NMSettingsConnectionUpdateReason update_reason)
+{
+    NMSettingsPrivate *priv                         = NM_SETTINGS_GET_PRIVATE(self);
+    gs_unref_object NMConnection *connection_old    = NULL;
+    NMSettingsStorage *           storage           = sett_conn_entry->storage;
+    gs_unref_object NMSettingsConnection *sett_conn = g_object_ref(sett_conn_entry->sett_conn);
+    const char *                          path;
+    gboolean                              is_new;
+
+    nm_assert(!NM_FLAGS_ANY(sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+    nm_assert(!NM_FLAGS_ANY(sett_flags, ~sett_mask));
+
+    is_new = c_list_is_empty(&sett_conn->_connections_lst);
+
+    _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT ")",
+          nm_settings_storage_get_uuid(storage),
+          is_new ? "adding" : "updating",
+          nm_connection_get_id(connection),
+          NM_SETTINGS_STORAGE_PRINT_ARG(storage));
+
+    _nm_settings_connection_set_storage(sett_conn, storage);
+
+    _nm_settings_connection_set_connection(sett_conn, connection, &connection_old, update_reason);
+
+    if (is_new) {
+        _nm_settings_connection_register_kf_dbs(sett_conn,
+                                                priv->kf_db_timestamps,
+                                                priv->kf_db_seen_bssids);
+
+        _clear_connections_cached_list(priv);
+        c_list_link_tail(&priv->connections_lst_head, &sett_conn->_connections_lst);
+        priv->connections_len++;
+        priv->connections_generation++;
+
+        g_signal_connect(sett_conn,
+                         NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
+                         G_CALLBACK(connection_flags_changed),
+                         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,
+            TRUE);
+    }
+
+    sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
+    if (nm_settings_connection_check_visibility(sett_conn, priv->session_monitor))
+        sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
+    else
+        nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE));
+
+    sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
+    if (nm_settings_storage_is_keyfile_run(storage))
+        sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
+    else {
+        nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED));
+
+        /* Profiles that don't reside in /run, are never nm-generated,
+         * volatile, and external. */
+        sett_mask |= (NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                      | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                      | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
+        sett_flags &= ~(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                        | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                        | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
+    }
+
+    nm_settings_connection_set_flags_full(sett_conn, sett_mask, sett_flags);
+
+    if (is_new) {
+        /* FIXME(shutdown): The NMSettings instance can't be disposed
+         * while there is any exported connection. Ideally we should
+         * unexport all connections on NMSettings' disposal, but for now
+         * leak @self on termination when there are connections alive. */
+        path = nm_dbus_object_export(NM_DBUS_OBJECT(sett_conn));
+    } else
+        path = nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn));
+
+    if (is_new || connection_old) {
+        nm_utils_log_connection_diff(nm_settings_connection_get_connection(sett_conn),
+                                     connection_old,
+                                     LOGL_DEBUG,
+                                     LOGD_CORE,
+                                     is_new ? "new connection" : "update connection",
+                                     "++ ",
+                                     path);
+    }
+
+    if (is_new) {
+        nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
+                                   &interface_info_settings,
+                                   &signal_info_new_connection,
+                                   "(o)",
+                                   path);
+        _notify(self, PROP_CONNECTIONS);
+        _emit_connection_added(self, sett_conn);
+    } else {
+        _nm_settings_connection_emit_dbus_signal_updated(sett_conn);
+        _emit_connection_updated(self, sett_conn, update_reason);
+    }
+
+    if (priv->startup_complete_start_timestamp_msec != 0) {
+        if (nm_settings_has_connection(self, sett_conn))
+            _startup_complete_notify_connection(self, sett_conn, FALSE);
+    }
+}
+
+static void
+_connection_changed_delete(NMSettings *          self,
+                           NMSettingsStorage *   storage,
+                           NMSettingsConnection *sett_conn,
+                           gboolean              allow_add_to_no_auto_default)
+{
+    NMSettingsPrivate *priv                             = NM_SETTINGS_GET_PRIVATE(self);
+    gs_unref_object NMConnection *connection_for_agents = NULL;
+    NMDevice *                    device;
+    const char *                  uuid;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+    nm_assert(c_list_contains(&priv->connections_lst_head, &sett_conn->_connections_lst));
+    nm_assert(nm_dbus_object_is_exported(NM_DBUS_OBJECT(sett_conn)));
+
+    uuid = nm_settings_storage_get_uuid(storage);
+
+    _LOGT("update[%s]: delete connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT ")",
+          uuid,
+          nm_settings_connection_get_id(sett_conn),
+          NM_SETTINGS_STORAGE_PRINT_ARG(storage));
+
+    /* When the default wired sett_conn is removed (either deleted or saved to
+     * a new persistent sett_conn by a plugin), write the MAC address of the
+     * wired device to the config file and don't create a new default wired
+     * sett_conn for that device again.
+     */
+    device = nm_settings_connection_default_wired_get_device(sett_conn);
+    if (device)
+        default_wired_clear_tag(self, device, sett_conn, allow_add_to_no_auto_default);
+
+    g_signal_handlers_disconnect_by_func(sett_conn, G_CALLBACK(connection_flags_changed), self);
+
+    _clear_connections_cached_list(priv);
+    c_list_unlink(&sett_conn->_connections_lst);
+    priv->connections_len--;
+    priv->connections_generation++;
+
+    /* Tell agents to remove secrets for this connection */
+    connection_for_agents =
+        nm_simple_connection_new_clone(nm_settings_connection_get_connection(sett_conn));
+    nm_connection_clear_secrets(connection_for_agents);
+    nm_agent_manager_delete_secrets(priv->agent_mgr,
+                                    nm_dbus_object_get_path(NM_DBUS_OBJECT(self)),
+                                    connection_for_agents);
+
+    _notify(self, PROP_CONNECTIONS);
+    _nm_settings_connection_emit_dbus_signal_removed(sett_conn);
+    nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
+                               &interface_info_settings,
+                               &signal_info_connection_removed,
+                               "(o)",
+                               nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)));
+
+    nm_dbus_object_unexport(NM_DBUS_OBJECT(sett_conn));
+
+    nm_settings_connection_set_flags(sett_conn,
+                                     NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE
+                                         | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                                         | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
+                                     FALSE);
+
+    _emit_connection_removed(self, sett_conn);
+
+    _nm_settings_connection_cleanup_after_remove(sett_conn);
+
+    nm_key_file_db_remove_key(priv->kf_db_timestamps, uuid);
+    nm_key_file_db_remove_key(priv->kf_db_seen_bssids, uuid);
+
+    if (priv->startup_complete_start_timestamp_msec != 0)
+        _startup_complete_notify_connection(self, sett_conn, TRUE);
+}
+
+static void
+_connection_changed_process_one(NMSettings *                     self,
+                                SettConnEntry *                  sett_conn_entry,
+                                gboolean                         allow_add_to_no_auto_default,
+                                NMSettingsConnectionIntFlags     sett_flags,
+                                NMSettingsConnectionIntFlags     sett_mask,
+                                gboolean                         override_sett_flags,
+                                NMSettingsConnectionUpdateReason update_reason)
+{
+    StorageData *sd_best;
+
+    c_list_unlink(&sett_conn_entry->sce_dirty_lst);
+
+    _sett_conn_entry_sds_update(self, sett_conn_entry);
+
+    sd_best = c_list_first_entry(&sett_conn_entry->sd_lst_head, StorageData, sd_lst);
+
+    if (!sd_best || !sd_best->connection) {
+        gs_unref_object NMSettingsConnection *sett_conn = NULL;
+        gs_unref_object NMSettingsStorage *storage      = NULL;
+
+        if (!sett_conn_entry->sett_conn) {
+            if (!sd_best) {
+                _sett_conn_entries_remove_and_destroy(self, sett_conn_entry);
+                return;
+            }
+
+            if (sett_conn_entry->storage != sd_best->storage) {
+                _LOGT("update[%s]: shadow UUID (" NM_SETTINGS_STORAGE_PRINT_FMT ")",
+                      sett_conn_entry->uuid,
+                      NM_SETTINGS_STORAGE_PRINT_ARG(sd_best->storage));
+            }
+
+            nm_g_object_ref_set(&sett_conn_entry->storage, sd_best->storage);
+            return;
+        }
+
+        sett_conn = g_steal_pointer(&sett_conn_entry->sett_conn);
+        if (sd_best) {
+            storage = g_object_ref(sd_best->storage);
+            nm_g_object_ref_set(&sett_conn_entry->storage, storage);
+            nm_assert_valid_settings_storage(NULL, storage);
+        } else {
+            storage = g_object_ref(sett_conn_entry->storage);
+            _sett_conn_entries_remove_and_destroy(self, sett_conn_entry);
+        }
+
+        _connection_changed_delete(self, storage, sett_conn, allow_add_to_no_auto_default);
+        return;
+    }
+
+    if (override_sett_flags) {
+        NMSettingsConnectionIntFlags s_f, s_m;
+
+        nm_settings_storage_load_sett_flags(sd_best->storage, &s_f, &s_m);
+
+        nm_assert(!NM_FLAGS_ANY(s_f, ~s_m));
+
+        sett_mask |= s_m;
+        sett_flags = (sett_flags & ~s_m) | (s_f & s_m);
+    }
+
+    nm_g_object_ref_set(&sett_conn_entry->storage, sd_best->storage);
+
+    if (!sett_conn_entry->sett_conn)
+        sett_conn_entry->sett_conn = nm_settings_connection_new();
+
+    _connection_changed_update(self,
+                               sett_conn_entry,
+                               sd_best->connection,
+                               sett_flags,
+                               sett_mask,
+                               update_reason);
+}
+
+static void
+_connection_changed_process_all_dirty(NMSettings *                     self,
+                                      gboolean                         allow_add_to_no_auto_default,
+                                      NMSettingsConnectionIntFlags     sett_flags,
+                                      NMSettingsConnectionIntFlags     sett_mask,
+                                      gboolean                         override_sett_flags,
+                                      NMSettingsConnectionUpdateReason update_reason)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    SettConnEntry *    sett_conn_entry;
+
+    while ((sett_conn_entry =
+                c_list_first_entry(&priv->sce_dirty_lst_head, SettConnEntry, sce_dirty_lst))) {
+        _connection_changed_process_one(self,
+                                        sett_conn_entry,
+                                        allow_add_to_no_auto_default,
+                                        sett_flags,
+                                        sett_mask,
+                                        override_sett_flags,
+                                        update_reason);
+    }
+}
+
+static SettConnEntry *
+_connection_changed_track(NMSettings *       self,
+                          NMSettingsStorage *storage,
+                          NMConnection *     connection,
+                          gboolean           prioritize)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    SettConnEntry *    sett_conn_entry;
+    StorageData *      sd;
+    const char *       uuid;
+
+    nm_assert_valid_settings_storage(NULL, storage);
+
+    uuid = nm_settings_storage_get_uuid(storage);
+
+    nm_assert(!connection || NM_IS_CONNECTION(connection));
+    nm_assert(!connection
+              || (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+    nm_assert(!connection || nm_streq0(uuid, nm_connection_get_uuid(connection)));
+
+    nmtst_connection_assert_unchanging(connection);
+
+    sett_conn_entry =
+        _sett_conn_entries_get(self, uuid) ?: _sett_conn_entries_create_and_add(self, uuid);
+
+    if (_LOGT_ENABLED()) {
+        const char *              filename;
+        const NMSettingsMetaData *meta_data;
+        const char *              shadowed_storage;
+        gboolean                  shadowed_owned;
+
+        filename = nm_settings_storage_get_filename(storage);
+        if (connection) {
+            shadowed_storage = nm_settings_storage_get_shadowed_storage(storage, &shadowed_owned);
+            _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT
+                  "]: change event with connection \"%s\"%s%s%s%s%s%s",
+                  sett_conn_entry->uuid,
+                  NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+                  nm_connection_get_id(connection),
+                  NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""),
+                  NM_PRINT_FMT_QUOTED(shadowed_storage,
+                                      shadowed_owned ? " (owns \"" : " (shadows \"",
+                                      shadowed_storage,
+                                      "\")",
+                                      ""));
+        } else if ((meta_data = nm_settings_storage_is_meta_data(storage))) {
+            nm_assert(meta_data->is_tombstone);
+            shadowed_storage = nm_settings_storage_get_shadowed_storage(storage, &shadowed_owned);
+            _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT
+                  "]: change event for %shiding profile%s%s%s%s%s%s",
+                  sett_conn_entry->uuid,
+                  NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+                  nm_settings_storage_is_meta_data_alive(storage) ? "" : "dropping ",
+                  NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""),
+                  NM_PRINT_FMT_QUOTED(shadowed_storage,
+                                      shadowed_owned ? " (owns \"" : " (shadows \"",
+                                      shadowed_storage,
+                                      "\")",
+                                      ""));
+        } else {
+            _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT
+                  "]: change event for dropping profile%s%s%s",
+                  sett_conn_entry->uuid,
+                  NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+                  NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""));
+        }
+    }
+
+    /* see _sett_conn_entry_sds_update() for why we append the new events
+     * and leave existing ones at their position. */
+    sd = _storage_data_find_in_lst(&sett_conn_entry->dirty_sd_lst_head, storage);
+    if (sd)
+        nm_g_object_ref_set(&sd->connection, connection);
+    else {
+        sd = _storage_data_new_stale(storage, connection);
+        c_list_link_tail(&sett_conn_entry->dirty_sd_lst_head, &sd->sd_lst);
+    }
+
+    if (prioritize) {
+        StorageData *sd2;
+
+        /* only one entry can be prioritized. */
+        c_list_for_each_entry (sd2, &sett_conn_entry->dirty_sd_lst_head, sd_lst)
+            sd2->prioritize = FALSE;
+        sd->prioritize = TRUE;
+    }
+
+    nm_c_list_move_tail(&priv->sce_dirty_lst_head, &sett_conn_entry->sce_dirty_lst);
+
+    return sett_conn_entry;
+}
+
+/*****************************************************************************/
+
+static void
+_plugin_connections_reload_cb(NMSettingsPlugin * plugin,
+                              NMSettingsStorage *storage,
+                              NMConnection *     connection,
+                              gpointer           user_data)
+{
+    _connection_changed_track(user_data, storage, connection, FALSE);
+}
+
+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);
+    }
+
+    _connection_changed_process_all_dirty(
+        self,
+        FALSE,
+        NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+        NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+        TRUE,
+        NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+            | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
+
+    for (iter = priv->plugins; iter; iter = iter->next)
+        nm_settings_plugin_load_connections_done(iter->data);
+}
+
+/*****************************************************************************/
+
+static gboolean
+_add_connection_to_first_plugin(NMSettings *                 self,
+                                SettConnEntry *              sett_conn_entry,
+                                NMConnection *               new_connection,
+                                gboolean                     in_memory,
+                                NMSettingsConnectionIntFlags sett_flags,
+                                const char *                 shadowed_storage,
+                                gboolean                     shadowed_owned,
+                                NMSettingsStorage **         out_new_storage,
+                                NMConnection **              out_new_connection,
+                                GError **                    error)
+{
+    NMSettingsPrivate *priv           = NM_SETTINGS_GET_PRIVATE(self);
+    gs_free_error GError *first_error = NULL;
+    GSList *              iter;
+    const char *          uuid;
+
+    uuid = nm_connection_get_uuid(new_connection);
+
+    nm_assert(nm_utils_is_uuid(uuid));
+
+    for (iter = priv->plugins; iter; iter = iter->next) {
+        NMSettingsPlugin *plugin                               = NM_SETTINGS_PLUGIN(iter->data);
+        gs_unref_object NMSettingsStorage *storage             = NULL;
+        gs_unref_object NMConnection *connection_to_add        = NULL;
+        gs_unref_object NMConnection *connection_to_add_cloned = NULL;
+        NMConnection *                connection_to_add_real   = NULL;
+        gs_unref_variant GVariant *agent_owned_secrets         = NULL;
+        gs_free_error GError *add_error                        = NULL;
+        gboolean              success;
+        const char *          filename;
+
+        if (!in_memory) {
+            NMSettingsStorage *conflicting_storage;
+
+            conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage(sett_conn_entry,
+                                                                                    plugin,
+                                                                                    NULL,
+                                                                                    priv->plugins);
+            if (conflicting_storage) {
+                /* we have a connection provided by a plugin with higher priority than the one
+                 * we would want to add the connection. We cannot do that, because doing so
+                 * would result in adding a connection that gets hidden by the existing profile.
+                 * Also, since we test the plugins in order of priority, all following plugins
+                 * are unsuitable.
+                 *
+                 * Multiple connection plugins are so cumbersome, especially if they are unable
+                 * to add the connection. I suggest to disable all plugins except keyfile. */
+                _LOGT("add-connection: failed to add %s/'%s': there is an existing "
+                      "storage " NM_SETTINGS_STORAGE_PRINT_FMT " with higher priority",
+                      nm_connection_get_uuid(new_connection),
+                      nm_connection_get_id(new_connection),
+                      NM_SETTINGS_STORAGE_PRINT_ARG(conflicting_storage));
+                nm_assert(first_error);
+                break;
+            }
+        }
+
+        if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
+            success = nms_keyfile_plugin_add_connection(
+                priv->keyfile_plugin,
+                new_connection,
+                in_memory,
+                NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
+                NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+                NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL),
+                shadowed_storage,
+                shadowed_owned,
+                &storage,
+                &connection_to_add,
+                &add_error);
+        } else {
+            if (in_memory)
+                continue;
+            nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED));
+            nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+            nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL));
+            success = nm_settings_plugin_add_connection(plugin,
+                                                        new_connection,
+                                                        &storage,
+                                                        &connection_to_add,
+                                                        &add_error);
+        }
+
+        if (!success) {
+            _LOGT("add-connection: failed to add %s/'%s': %s",
+                  nm_connection_get_uuid(new_connection),
+                  nm_connection_get_id(new_connection),
+                  add_error->message);
+            if (!first_error)
+                first_error = g_steal_pointer(&add_error);
+            continue;
+        }
+
+        if (!nm_streq0(nm_settings_storage_get_uuid(storage), uuid)) {
+            nm_assert_not_reached();
+            continue;
+        }
+
+        agent_owned_secrets =
+            nm_connection_to_dbus(new_connection,
+                                  NM_CONNECTION_SERIALIZE_ONLY_SECRETS
+                                      | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
+        connection_to_add_real =
+            _connection_changed_normalize_connection(storage,
+                                                     connection_to_add,
+                                                     agent_owned_secrets,
+                                                     &connection_to_add_cloned);
+        if (!connection_to_add_real) {
+            nm_assert_not_reached();
+            continue;
+        }
+
+        filename = nm_settings_storage_get_filename(storage);
+        _LOGT(
+            "add-connection: successfully added connection %s,'%s' (" NM_SETTINGS_STORAGE_PRINT_FMT
+            "%s%s%s",
+            nm_settings_storage_get_uuid(storage),
+            nm_connection_get_id(new_connection),
+            NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+            NM_PRINT_FMT_QUOTED(filename, ", \"", filename, "\")", ")"));
+
+        *out_new_storage = g_steal_pointer(&storage);
+        *out_new_connection =
+            g_steal_pointer(&connection_to_add_cloned) ?: g_steal_pointer(&connection_to_add);
+        nm_assert(NM_IS_CONNECTION(*out_new_connection));
+        return TRUE;
+    }
+
+    nm_assert(first_error);
+    g_propagate_error(error, g_steal_pointer(&first_error));
+    return FALSE;
+}
+
+static gboolean
+_update_connection_to_plugin(NMSettings *                 self,
+                             NMSettingsStorage *          storage,
+                             NMConnection *               connection,
+                             NMSettingsConnectionIntFlags sett_flags,
+                             gboolean                     force_rename,
+                             const char *                 shadowed_storage,
+                             gboolean                     shadowed_owned,
+                             NMSettingsStorage **         out_new_storage,
+                             NMConnection **              out_new_connection,
+                             GError **                    error)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    NMSettingsPlugin * plugin;
+    gboolean           success;
+
+    plugin = nm_settings_storage_get_plugin(storage);
+
+    if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
+        success = nms_keyfile_plugin_update_connection(
+            priv->keyfile_plugin,
+            storage,
+            connection,
+            NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
+            NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+            NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL),
+            shadowed_storage,
+            shadowed_owned,
+            force_rename,
+            out_new_storage,
+            out_new_connection,
+            error);
+    } else {
+        nm_assert(!shadowed_storage);
+        nm_assert(!shadowed_owned);
+        success = nm_settings_plugin_update_connection(plugin,
+                                                       storage,
+                                                       connection,
+                                                       out_new_storage,
+                                                       out_new_connection,
+                                                       error);
+    }
+
+    return success;
+}
+
+static void
+_set_nmmeta_tombstone(NMSettings *self,
+                      const char *uuid,
+                      gboolean    tombstone_on_disk,
+                      gboolean    tombstone_in_memory,
+                      const char *shadowed_storage)
+{
+    NMSettingsPrivate *priv                                = NM_SETTINGS_GET_PRIVATE(self);
+    gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL;
+    gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL;
+
+    if (tombstone_on_disk) {
+        if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin,
+                                                     FALSE,
+                                                     uuid,
+                                                     FALSE,
+                                                     TRUE,
+                                                     NULL,
+                                                     &tombstone_1_storage,
+                                                     NULL))
+            tombstone_in_memory = TRUE;
+        if (tombstone_1_storage)
+            _connection_changed_track(self, tombstone_1_storage, NULL, FALSE);
+    }
+
+    if (tombstone_in_memory) {
+        if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin,
+                                                     FALSE,
+                                                     uuid,
+                                                     TRUE,
+                                                     TRUE,
+                                                     shadowed_storage,
+                                                     &tombstone_2_storage,
+                                                     NULL)) {
+            nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin,
+                                                    TRUE,
+                                                    uuid,
+                                                    TRUE,
+                                                    TRUE,
+                                                    shadowed_storage,
+                                                    &tombstone_2_storage,
+                                                    NULL);
+        }
+        _connection_changed_track(self, tombstone_2_storage, NULL, FALSE);
+    }
+}
+
+/**
+ * nm_settings_add_connection:
+ * @self: the #NMSettings object
+ * @connection: the source connection to create a new #NMSettingsConnection from
+ * @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.
+ * @error: on return, a location to store any errors that may occur
+ *
+ * Creates a new #NMSettingsConnection for the given source @connection.
+ * The returned object is owned by @self and the caller must reference
+ * the object to continue using it.
+ *
+ * Returns: TRUE on success.
+ */
+gboolean
+nm_settings_add_connection(NMSettings *                    self,
+                           NMConnection *                  connection,
+                           NMSettingsConnectionPersistMode persist_mode,
+                           NMSettingsConnectionAddReason   add_reason,
+                           NMSettingsConnectionIntFlags    sett_flags,
+                           NMSettingsConnection **         out_sett_conn,
+                           GError **                       error)
+{
+    NMSettingsPrivate *priv;
+    gs_unref_object NMConnection *connection_cloned_1   = NULL;
+    gs_unref_object NMConnection *new_connection        = NULL;
+    gs_unref_object NMSettingsStorage *new_storage      = NULL;
+    gs_unref_object NMSettingsStorage *shadowed_storage = NULL;
+    NMSettingsStorage *                update_storage   = NULL;
+    gs_free_error GError *local                         = NULL;
+    SettConnEntry *       sett_conn_entry;
+    const char *          uuid;
+    StorageData *         sd;
+    gboolean              new_in_memory;
+    gboolean              success;
+    const char *          shadowed_storage_filename = NULL;
+
+    priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    nm_assert(NM_IN_SET(persist_mode,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
+
+    new_in_memory = (persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK);
+
+    nm_assert(!NM_FLAGS_ANY(sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+
+    if (NM_FLAGS_ANY(sett_flags,
+                     NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                         | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                         | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) {
+        nm_assert(new_in_memory);
+        new_in_memory = TRUE;
+    }
+
+    nm_assert(!NM_FLAGS_ANY(add_reason, ~NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT));
+
+    NM_SET_OUT(out_sett_conn, NULL);
+
+    uuid = nm_connection_get_uuid(connection);
+
+    sett_conn_entry = _sett_conn_entries_get(self, uuid);
+    if (_sett_conn_entry_get_conn(sett_conn_entry)) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_UUID_EXISTS,
+                            "a connection with this UUID already exists");
+        return FALSE;
+    }
+
+    if (!_nm_connection_ensure_normalized(connection,
+                                          FALSE,
+                                          NULL,
+                                          FALSE,
+                                          &connection_cloned_1,
+                                          &local)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "connection is invalid: %s",
+                    local->message);
+        return FALSE;
+    }
+    if (connection_cloned_1)
+        connection = connection_cloned_1;
+
+    if (sett_conn_entry) {
+        c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+            if (!nm_settings_storage_is_meta_data(sd->storage))
+                continue;
+            shadowed_storage = nm_g_object_ref(_sett_conn_entry_find_shadowed_storage(
+                sett_conn_entry,
+                nm_settings_storage_get_shadowed_storage(sd->storage, NULL),
+                NULL));
+            if (shadowed_storage) {
+                /* We have a nmmeta tombstone that indicates that a storage is shadowed.
+                 *
+                 * This happens when deleting a in-memory profile that was decoupled from
+                 * the persistent storage with NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED.
+                 * We need to take over this storage again... */
+                break;
+            }
+        }
+    }
+
+    if (shadowed_storage && !new_in_memory) {
+        NMSettingsStorage *conflicting_storage;
+
+        conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage(
+            sett_conn_entry,
+            nm_settings_storage_get_plugin(shadowed_storage),
+            shadowed_storage,
+            priv->plugins);
+        if (conflicting_storage) {
+            /* We cannot add the profile as @shadowed_storage, because there is another, existing storage
+             * that would hide it. Just add it as new storage. In general, this leads to duplication of profiles,
+             * but the circumstances where this happens are very exotic (you need at least one additional settings
+             * plugin, then going through the paths of making shadowed_storage in-memory-detached and delete it,
+             * and finally adding the conflicting storage outside of NM and restart/reload). */
+            _LOGT("ignore shadowed storage " NM_SETTINGS_STORAGE_PRINT_FMT
+                  " due to conflicting storage " NM_SETTINGS_STORAGE_PRINT_FMT,
+                  NM_SETTINGS_STORAGE_PRINT_ARG(shadowed_storage),
+                  NM_SETTINGS_STORAGE_PRINT_ARG(conflicting_storage));
+        } else
+            update_storage = shadowed_storage;
+    }
+
+    shadowed_storage_filename =
+        (shadowed_storage && !update_storage)
+            ? nm_settings_storage_get_filename_for_shadowed_storage(shadowed_storage)
+            : NULL;
+
+again_add_connection:
+
+    if (!update_storage) {
+        success = _add_connection_to_first_plugin(self,
+                                                  sett_conn_entry,
+                                                  connection,
+                                                  new_in_memory,
+                                                  sett_flags,
+                                                  shadowed_storage_filename,
+                                                  FALSE,
+                                                  &new_storage,
+                                                  &new_connection,
+                                                  &local);
+    } else {
+        success = _update_connection_to_plugin(self,
+                                               update_storage,
+                                               connection,
+                                               sett_flags,
+                                               FALSE,
+                                               shadowed_storage_filename,
+                                               FALSE,
+                                               &new_storage,
+                                               &new_connection,
+                                               &local);
+        if (!success) {
+            if (!NMS_IS_KEYFILE_STORAGE(update_storage)) {
+                /* hm, the intended storage is not keyfile (it's ifcfg-rh). This settings
+                 * plugin may not support the new connection. So step back and retry adding
+                 * the profile anew. */
+                _LOGT("failure to add profile as existing storage \"%s\": %s",
+                      nm_settings_storage_get_filename(update_storage),
+                      local->message);
+                update_storage = NULL;
+                g_clear_object(&shadowed_storage);
+                shadowed_storage_filename = NULL;
+                g_clear_error(&local);
+                goto again_add_connection;
+            }
+        }
+    }
+
+    if (!success) {
+        if (!update_storage) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "failure adding connection: %s",
+                        local->message);
+        } else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "failure writing connection to existing storage \"%s\": %s",
+                        nm_settings_storage_get_filename(update_storage),
+                        local->message);
+        }
+        return FALSE;
+    }
+
+    sett_conn_entry = _connection_changed_track(self, new_storage, new_connection, TRUE);
+
+    c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+        const NMSettingsMetaData *meta_data;
+        gs_unref_object NMSettingsStorage *new_tombstone_storage = NULL;
+        gboolean                           in_memory;
+        gboolean                           simulate;
+
+        meta_data = nm_settings_storage_is_meta_data_alive(sd->storage);
+        if (!meta_data || !meta_data->is_tombstone)
+            continue;
+
+        if (nm_settings_storage_is_keyfile_run(sd->storage))
+            in_memory = TRUE;
+        else {
+            if (nm_settings_storage_is_keyfile_run(new_storage)) {
+                /* Don't remove the file from /etc if we just wrote an in-memory connection */
+                continue;
+            }
+            in_memory = FALSE;
+        }
+
+        simulate = FALSE;
+again_delete_tombstone:
+        if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin,
+                                                     simulate,
+                                                     uuid,
+                                                     in_memory,
+                                                     FALSE,
+                                                     NULL,
+                                                     &new_tombstone_storage,
+                                                     NULL)) {
+            /* Ups, something went wrong. We really need to get rid of the tombstone. At least
+             * forget about it in-memory. Upong next restart/reload, this might be reverted
+             * however :( .*/
+            if (!simulate) {
+                simulate = TRUE;
+                goto again_delete_tombstone;
+            }
+        }
+        if (new_tombstone_storage)
+            _connection_changed_track(self, new_tombstone_storage, NULL, FALSE);
+    }
+
+    _connection_changed_process_all_dirty(
+        self,
+        FALSE,
+        sett_flags,
+        _NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK,
+        FALSE,
+        NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+            | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
+            | (NM_FLAGS_HAS(add_reason, NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT)
+                   ? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT
+                   : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE));
+
+    nm_assert(sett_conn_entry == _sett_conn_entries_get(self, sett_conn_entry->uuid));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn_entry->sett_conn));
+
+    NM_SET_OUT(out_sett_conn, _sett_conn_entry_get_conn(sett_conn_entry));
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_update_connection(NMSettings *                     self,
+                              NMSettingsConnection *           sett_conn,
+                              NMConnection *                   connection,
+                              NMSettingsConnectionPersistMode  persist_mode,
+                              NMSettingsConnectionIntFlags     sett_flags,
+                              NMSettingsConnectionIntFlags     sett_mask,
+                              NMSettingsConnectionUpdateReason update_reason,
+                              const char *                     log_context_name,
+                              GError **                        error)
+{
+    gs_unref_object NMConnection *connection_cloned_1   = NULL;
+    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;
+    SettConnEntry *                    sett_conn_entry;
+    gboolean                           cur_in_memory;
+    gboolean                           new_in_memory;
+    const char *                       uuid;
+    gboolean                           tombstone_in_memory = FALSE;
+    gboolean                           tombstone_on_disk   = FALSE;
+
+    g_return_val_if_fail(NM_IS_SETTINGS(self), FALSE);
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn), FALSE);
+    g_return_val_if_fail(!connection || NM_IS_CONNECTION(connection), FALSE);
+
+    nm_assert(!NM_FLAGS_ANY(sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+    nm_assert(!NM_FLAGS_ANY(sett_flags, ~sett_mask));
+    nm_assert(NM_IN_SET(persist_mode,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+                        NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
+
+    cur_storage = g_object_ref(nm_settings_connection_get_storage(sett_conn));
+
+    uuid = nm_settings_storage_get_uuid(cur_storage);
+
+    nm_assert(NM_IS_SETTINGS_STORAGE(cur_storage));
+
+    sett_conn_entry = _sett_conn_entries_get(self, uuid);
+
+    nm_assert(_sett_conn_entry_get_conn(sett_conn_entry) == sett_conn);
+
+    if (connection) {
+        gs_free_error GError *local = NULL;
+
+        if (!_nm_connection_ensure_normalized(connection,
+                                              FALSE,
+                                              uuid,
+                                              TRUE,
+                                              &connection_cloned_1,
+                                              &local)) {
+            _LOGT("update[%s]: %s: failed because profile is invalid: %s",
+                  nm_settings_storage_get_uuid(cur_storage),
+                  log_context_name,
+                  local->message);
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "connection is invalid: %s",
+                        local->message);
+            return FALSE;
+        }
+        if (connection_cloned_1)
+            connection = connection_cloned_1;
+    } else
+        connection = nm_settings_connection_get_connection(sett_conn);
+
+    cur_in_memory = nm_settings_storage_is_keyfile_run(cur_storage);
+
+    if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP) {
+        persist_mode = cur_in_memory ? NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY
+                                     : NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+    }
+
+    if (NM_FLAGS_HAS(sett_mask, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
+        && !NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+        NMDevice *device;
+
+        /* The connection has been changed by the user, it should no longer be
+         * considered a default wired connection, and should no longer affect
+         * the no-auto-default configuration option.
+         */
+        device = nm_settings_connection_default_wired_get_device(sett_conn);
+        if (device) {
+            nm_assert(cur_in_memory);
+            nm_assert(NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn),
+                                   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED));
+
+            default_wired_clear_tag(self, device, sett_conn, FALSE);
+
+            if (NM_IN_SET(persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) {
+                /* making a default-wired-connection a regular connection implies persisting
+                 * it to disk (unless specified differently).
+                 *
+                 * Actually, this line is probably unreached, because we should not use
+                 * NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST to toggle the nm-generated
+                 * flag. */
+                nm_assert_not_reached();
+                persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+            }
+        }
+    }
+
+    if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST
+        && NM_FLAGS_ANY(sett_mask,
+                        NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                            | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                            | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)
+        && NM_FLAGS_ANY((sett_flags ^ nm_settings_connection_get_flags(sett_conn)) & sett_mask,
+                        NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                            | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                            | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) {
+        /* we update the nm-generated/volatile setting of a profile (which is inherently
+         * in-memory. The caller did not request to persist this to disk, however we need
+         * to store the flags in run. */
+        nm_assert(cur_in_memory);
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY;
+    }
+
+    if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK)
+        new_in_memory = FALSE;
+    else if (NM_IN_SET(persist_mode,
+                       NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+                       NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+                       NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY))
+        new_in_memory = TRUE;
+    else {
+        nm_assert(persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST);
+        new_in_memory = cur_in_memory;
+    }
+
+    if (!new_in_memory) {
+        /* Persistent connections cannot be volatile nor nm-generated.
+         *
+         * That is obviously true for volatile, as it is enforced by Update2() API.
+         *
+         * For nm-generated profiles also, because the nm-generated flag is only stored
+         * for in-memory profiles. If we would persist the profile to /etc it would loose
+         * the nm-generated flag after restart/reload, and that cannot be right. If a profile
+         * ends up on disk, the information who created it gets lost. */
+        nm_assert(!NM_FLAGS_ANY(sett_flags,
+                                NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                                    | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                                    | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL));
+        sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                     | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                     | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
+        sett_flags &= ~(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                        | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                        | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
+    }
+
+    if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) {
+        new_storage         = g_object_ref(cur_storage);
+        new_connection_real = connection;
+        _LOGT("update[%s]: %s: update profile \"%s\" (not persisted)",
+              nm_settings_storage_get_uuid(cur_storage),
+              log_context_name,
+              nm_connection_get_id(connection));
+    } else {
+        NMSettingsStorage *shadowed_storage;
+        const char *       cur_shadowed_storage_filename;
+        const char *       new_shadowed_storage_filename = NULL;
+        gboolean           cur_shadowed_owned;
+        gboolean           new_shadowed_owned = FALSE;
+        NMSettingsStorage *update_storage     = NULL;
+        gs_free_error GError *local           = NULL;
+        gboolean              success;
+
+        cur_shadowed_storage_filename =
+            nm_settings_storage_get_shadowed_storage(cur_storage, &cur_shadowed_owned);
+
+        shadowed_storage = _sett_conn_entry_find_shadowed_storage(sett_conn_entry,
+                                                                  cur_shadowed_storage_filename,
+                                                                  cur_storage);
+        if (!shadowed_storage) {
+            cur_shadowed_storage_filename = NULL;
+            cur_shadowed_owned            = FALSE;
+        }
+
+        if (new_in_memory && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) {
+            if (cur_in_memory) {
+                drop_storage   = shadowed_storage;
+                update_storage = cur_storage;
+            } else
+                drop_storage = cur_storage;
+        } else if (!new_in_memory && cur_in_memory && shadowed_storage) {
+            drop_storage   = cur_storage;
+            update_storage = shadowed_storage;
+        } else if (new_in_memory != cur_in_memory) {
+            if (!new_in_memory)
+                drop_storage = cur_storage;
+            else if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)
+                drop_storage = cur_storage;
+            else {
+                nm_assert(NM_IN_SET(persist_mode,
+                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED));
+            }
+        } else if (nm_settings_storage_is_keyfile_lib(cur_storage)) {
+            /* the profile is a keyfile in /usr/lib. It cannot be overwritten, we must migrate it
+             * from /usr/lib to /etc. */
+        } else
+            update_storage = cur_storage;
+
+        if (new_in_memory) {
+            if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) {
+                /* pass */
+            } else if (!cur_in_memory) {
+                new_shadowed_storage_filename =
+                    nm_settings_storage_get_filename_for_shadowed_storage(cur_storage);
+                if (new_shadowed_storage_filename
+                    && persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED)
+                    new_shadowed_owned = TRUE;
+            } else {
+                new_shadowed_storage_filename = cur_shadowed_storage_filename;
+                if (new_shadowed_storage_filename
+                    && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY)
+                    new_shadowed_owned = TRUE;
+            }
+        }
+
+        if (!update_storage) {
+            success = _add_connection_to_first_plugin(self,
+                                                      sett_conn_entry,
+                                                      connection,
+                                                      new_in_memory,
+                                                      sett_flags,
+                                                      new_shadowed_storage_filename,
+                                                      new_shadowed_owned,
+                                                      &new_storage,
+                                                      &new_connection,
+                                                      &local);
+        } else {
+            success = _update_connection_to_plugin(self,
+                                                   update_storage,
+                                                   connection,
+                                                   sett_flags,
+                                                   update_reason,
+                                                   new_shadowed_storage_filename,
+                                                   new_shadowed_owned,
+                                                   &new_storage,
+                                                   &new_connection,
+                                                   &local);
+        }
+        if (!success) {
+            gboolean ignore_failure;
+
+            ignore_failure =
+                NM_FLAGS_ANY(update_reason,
+                             NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE);
+
+            _LOGT("update[%s]: %s: %sfailure to %s connection \"%s\" on storage: %s",
+                  nm_settings_storage_get_uuid(cur_storage),
+                  log_context_name,
+                  ignore_failure ? "ignore " : "",
+                  update_storage ? "update" : "write",
+                  nm_connection_get_id(connection),
+                  local->message);
+            if (!ignore_failure) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "failed to %s connection: %s",
+                            update_storage ? "update" : "write",
+                            local->message);
+                return FALSE;
+            }
+
+            new_storage         = g_object_ref(cur_storage);
+            new_connection_real = connection;
+        } else {
+            gs_unref_variant GVariant *agent_owned_secrets = NULL;
+
+            _LOGT("update[%s]: %s: %s profile \"%s\"",
+                  nm_settings_storage_get_uuid(cur_storage),
+                  log_context_name,
+                  update_storage ? "update" : "write",
+                  nm_connection_get_id(connection));
+
+            nm_assert_valid_settings_storage(NULL, new_storage);
+            nm_assert(NM_IS_CONNECTION(new_connection));
+            nm_assert(nm_streq(uuid, nm_settings_storage_get_uuid(new_storage)));
+
+            agent_owned_secrets =
+                nm_connection_to_dbus(connection,
+                                      NM_CONNECTION_SERIALIZE_ONLY_SECRETS
+                                          | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
+            new_connection_real = _connection_changed_normalize_connection(new_storage,
+                                                                           new_connection,
+                                                                           agent_owned_secrets,
+                                                                           &new_connection_cloned);
+            if (!new_connection_real) {
+                nm_assert_not_reached();
+                new_connection_real = new_connection;
+            }
+        }
+    }
+
+    nm_assert(NM_IS_SETTINGS_STORAGE(new_storage));
+    nm_assert(NM_IS_CONNECTION(new_connection_real));
+
+    _connection_changed_track(self, new_storage, new_connection_real, TRUE);
+
+    if (drop_storage && drop_storage != new_storage) {
+        gs_free_error GError *local = NULL;
+
+        if (!nm_settings_plugin_delete_connection(nm_settings_storage_get_plugin(drop_storage),
+                                                  drop_storage,
+                                                  &local)) {
+            const char *filename;
+
+            filename = nm_settings_storage_get_filename(drop_storage);
+            _LOGT("update[%s]: failed to delete moved storage " NM_SETTINGS_STORAGE_PRINT_FMT
+                  "%s%s%s: %s",
+                  nm_settings_storage_get_uuid(drop_storage),
+                  NM_SETTINGS_STORAGE_PRINT_ARG(drop_storage),
+                  NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""),
+                  local->message);
+            /* there is no aborting back form this. We must get rid of the connection and
+             * cannot do better than log a message. Proceed, but remember to write tombstones. */
+            if (nm_settings_storage_is_keyfile_run(cur_storage))
+                tombstone_in_memory = TRUE;
+            else
+                tombstone_on_disk = TRUE;
+        } else
+            _connection_changed_track(self, drop_storage, NULL, FALSE);
+    }
+
+    _set_nmmeta_tombstone(self, uuid, tombstone_on_disk, tombstone_in_memory, NULL);
+
+    _connection_changed_process_all_dirty(self, FALSE, sett_flags, sett_mask, FALSE, update_reason);
+
+    return TRUE;
+}
+
+void
+nm_settings_delete_connection(NMSettings *          self,
+                              NMSettingsConnection *sett_conn,
+                              gboolean              allow_add_to_no_auto_default)
+{
+    NMSettingsStorage *cur_storage;
+    NMSettingsStorage *shadowed_storage;
+    NMSettingsStorage *shadowed_storage_unowned = NULL;
+    NMSettingsStorage *drop_storages[2]         = {};
+    gs_free_error GError *local                 = NULL;
+    SettConnEntry *       sett_conn_entry;
+    const char *          cur_shadowed_storage_filename;
+    const char *          new_shadowed_storage_filename = NULL;
+    gboolean              cur_shadowed_owned;
+    const char *          uuid;
+    gboolean              tombstone_in_memory = FALSE;
+    gboolean              tombstone_on_disk   = FALSE;
+    int                   i;
+
+    g_return_if_fail(NM_IS_SETTINGS(self));
+    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn));
+    g_return_if_fail(nm_settings_has_connection(self, sett_conn));
+
+    cur_storage = nm_settings_connection_get_storage(sett_conn);
+
+    nm_assert(NM_IS_SETTINGS_STORAGE(cur_storage));
+
+    uuid = nm_settings_storage_get_uuid(cur_storage);
+    nm_assert(nm_utils_is_uuid(uuid));
+
+    sett_conn_entry = _sett_conn_entries_get(self, uuid);
+
+    g_return_if_fail(sett_conn_entry);
+    nm_assert(sett_conn_entry->sett_conn == sett_conn);
+    g_return_if_fail(sett_conn_entry->storage == cur_storage);
+
+    if (NMS_IS_KEYFILE_STORAGE(cur_storage)) {
+        NMSKeyfileStorage *s = NMS_KEYFILE_STORAGE(cur_storage);
+
+        if (NM_IN_SET(s->storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC))
+            drop_storages[0] = cur_storage;
+        else
+            tombstone_on_disk = TRUE;
+    } else
+        drop_storages[0] = cur_storage;
+
+    cur_shadowed_storage_filename =
+        nm_settings_storage_get_shadowed_storage(cur_storage, &cur_shadowed_owned);
+
+    shadowed_storage = _sett_conn_entry_find_shadowed_storage(sett_conn_entry,
+                                                              cur_shadowed_storage_filename,
+                                                              cur_storage);
+    if (shadowed_storage) {
+        if (!cur_shadowed_owned)
+            shadowed_storage_unowned = g_steal_pointer(&shadowed_storage);
+    }
+    drop_storages[1] = shadowed_storage;
+
+    for (i = 0; i < (int) G_N_ELEMENTS(drop_storages); i++) {
+        NMSettingsStorage *storage;
+        StorageData *      sd;
+
+        storage = drop_storages[i];
+        if (!storage)
+            continue;
+
+        if (!nm_settings_plugin_delete_connection(nm_settings_storage_get_plugin(storage),
+                                                  storage,
+                                                  &local)) {
+            _LOGT("delete-connection: failed to delete storage " NM_SETTINGS_STORAGE_PRINT_FMT
+                  ": %s",
+                  NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+                  local->message);
+            g_clear_error(&local);
+            /* there is no aborting back form this. We must get rid of the connection and
+             * cannot do better than log a message. Proceed, but remember to write tombstones. */
+            if (nm_settings_storage_is_keyfile_run(cur_storage))
+                tombstone_in_memory = TRUE;
+            else
+                tombstone_on_disk = TRUE;
+            sett_conn_entry = _sett_conn_entries_get(self, uuid);
+        } else
+            sett_conn_entry = _connection_changed_track(self, storage, NULL, FALSE);
+
+        c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+            if (NM_IN_SET(sd->storage, drop_storages[0], drop_storages[1]))
+                continue;
+            if (!_storage_data_is_alive(sd))
+                continue;
+            if (nm_settings_storage_is_meta_data(sd->storage))
+                continue;
+
+            if (sd->storage == shadowed_storage_unowned) {
+                /* this only happens if we leak a profile on disk after NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED.
+                 * We need to write a tombstone and remember the shadowed-storage. */
+                tombstone_in_memory = TRUE;
+                new_shadowed_storage_filename =
+                    nm_settings_storage_get_filename(shadowed_storage_unowned);
+                continue;
+            }
+
+            /* we have still conflicting storages. We need to hide them with tombstones. */
+            if (nm_settings_storage_is_keyfile_run(sd->storage)) {
+                tombstone_in_memory = TRUE;
+                continue;
+            }
+            tombstone_on_disk = TRUE;
+        }
+    }
+
+    _set_nmmeta_tombstone(self,
+                          uuid,
+                          tombstone_on_disk,
+                          tombstone_in_memory,
+                          new_shadowed_storage_filename);
+
+    _connection_changed_process_all_dirty(self,
+                                          allow_add_to_no_auto_default,
+                                          NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                          NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                          FALSE,
+                                          NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE);
+}
+
+/*****************************************************************************/
+
+static void
+send_agent_owned_secrets(NMSettings *self, NMSettingsConnection *sett_conn, NMAuthSubject *subject)
+{
+    NMSettingsPrivate *priv                 = NM_SETTINGS_GET_PRIVATE(self);
+    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(sett_conn));
+    _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+    nm_agent_manager_save_secrets(priv->agent_mgr,
+                                  nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)),
+                                  for_agent,
+                                  subject);
+}
+
+static void
+pk_add_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data)
+{
+    NMSettings *     self = NM_SETTINGS(user_data);
+    NMAuthCallResult result;
+    gs_free_error GError *error                 = NULL;
+    NMConnection *        connection            = NULL;
+    gs_unref_object NMSettingsConnection *added = NULL;
+    NMSettingsAddCallback                 callback;
+    gpointer                              callback_data;
+    NMAuthSubject *                       subject;
+    const char *                          perm;
+
+    nm_assert(G_IS_DBUS_METHOD_INVOCATION(context));
+
+    c_list_unlink(nm_auth_chain_parent_lst_list(chain));
+
+    perm = nm_auth_chain_get_data(chain, "perm");
+    nm_assert(perm);
+
+    result = nm_auth_chain_get_result(chain, perm);
+
+    if (result != NM_AUTH_CALL_RESULT_YES) {
+        error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                    NM_UTILS_ERROR_MSG_INSUFF_PRIV);
+    } else {
+        /* Authorized */
+        connection = nm_auth_chain_get_data(chain, "connection");
+        nm_assert(NM_IS_CONNECTION(connection));
+
+        nm_settings_add_connection(self,
+                                   connection,
+                                   GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "persist-mode")),
+                                   GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "add-reason")),
+                                   GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "sett-flags")),
+                                   &added,
+                                   &error);
+
+        /* The callback may remove the connection from the settings manager (e.g.
+         * because it's found to be incompatible with the device on AddAndActivate).
+         * But we need to keep it alive for a bit longer, precisely to check wehther
+         * it's still known to the setting manager. */
+        nm_g_object_ref(added);
+    }
+
+    callback      = nm_auth_chain_get_data(chain, "callback");
+    callback_data = nm_auth_chain_get_data(chain, "callback-data");
+    subject       = nm_auth_chain_get_data(chain, "subject");
+
+    callback(self, added, error, context, subject, callback_data);
+
+    /* Send agent-owned secrets to the agents */
+    if (added && nm_settings_has_connection(self, added))
+        send_agent_owned_secrets(self, added, subject);
+}
+
+void
+nm_settings_add_connection_dbus(NMSettings *                    self,
+                                NMConnection *                  connection,
+                                NMSettingsConnectionPersistMode persist_mode,
+                                NMSettingsConnectionAddReason   add_reason,
+                                NMSettingsConnectionIntFlags    sett_flags,
+                                NMAuthSubject *                 subject,
+                                GDBusMethodInvocation *         context,
+                                NMSettingsAddCallback           callback,
+                                gpointer                        user_data)
+{
+    NMSettingsPrivate *  priv = NM_SETTINGS_GET_PRIVATE(self);
+    NMSettingConnection *s_con;
+    NMAuthChain *        chain;
+    GError *             error = NULL, *tmp_error = NULL;
+    const char *         perm;
+
+    g_return_if_fail(NM_IS_CONNECTION(connection));
+    g_return_if_fail(NM_IS_AUTH_SUBJECT(subject));
+    g_return_if_fail(G_IS_DBUS_METHOD_INVOCATION(context));
+
+    nm_assert(!NM_FLAGS_ANY(sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+
+    /* Connection must be valid, of course */
+    if (_nm_connection_verify(connection, &tmp_error) != NM_SETTING_VERIFY_SUCCESS) {
+        error = g_error_new(NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "The connection was invalid: %s",
+                            tmp_error->message);
+        g_error_free(tmp_error);
+        goto done;
+    }
+
+    if (!nm_auth_is_subject_in_acl_set_error(connection,
+                                             subject,
+                                             NM_SETTINGS_ERROR,
+                                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                             &error))
+        goto done;
+
+    /* If the caller is the only user in the connection's permissions, then
+     * we use the 'modify.own' permission instead of 'modify.system'.  If the
+     * request affects more than just the caller, require 'modify.system'.
+     */
+    s_con = nm_connection_get_setting_connection(connection);
+    nm_assert(s_con);
+    if (nm_setting_connection_get_num_permissions(s_con) == 1)
+        perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
+    else
+        perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
+
+    chain = nm_auth_chain_new_subject(subject, context, pk_add_cb, self);
+
+    c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain));
+    nm_auth_chain_set_data(chain, "perm", (gpointer) perm, NULL);
+    nm_auth_chain_set_data(chain, "connection", g_object_ref(connection), g_object_unref);
+    nm_auth_chain_set_data(chain, "callback", callback, NULL);
+    nm_auth_chain_set_data(chain, "callback-data", user_data, NULL);
+    nm_auth_chain_set_data(chain, "subject", g_object_ref(subject), g_object_unref);
+    nm_auth_chain_set_data(chain, "persist-mode", GUINT_TO_POINTER(persist_mode), NULL);
+    nm_auth_chain_set_data(chain, "add-reason", GUINT_TO_POINTER(add_reason), NULL);
+    nm_auth_chain_set_data(chain, "sett-flags", GUINT_TO_POINTER(sett_flags), NULL);
+    nm_auth_chain_add_call_unsafe(chain, perm, TRUE);
+    return;
+
+done:
+    nm_assert(error);
+    callback(self, NULL, error, context, subject, user_data);
+    g_error_free(error);
+}
+
+static void
+settings_add_connection_add_cb(NMSettings *           self,
+                               NMSettingsConnection * connection,
+                               GError *               error,
+                               GDBusMethodInvocation *context,
+                               NMAuthSubject *        subject,
+                               gpointer               user_data)
+{
+    gboolean is_add_connection_2 = GPOINTER_TO_INT(user_data);
+
+    if (error) {
+        g_dbus_method_invocation_return_gerror(context, error);
+        nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD,
+                                   NULL,
+                                   FALSE,
+                                   NULL,
+                                   subject,
+                                   error->message);
+        return;
+    }
+
+    if (is_add_connection_2) {
+        GVariantBuilder builder;
+
+        g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+        g_dbus_method_invocation_return_value(
+            context,
+            g_variant_new("(oa{sv})",
+                          nm_dbus_object_get_path(NM_DBUS_OBJECT(connection)),
+                          &builder));
+    } else {
+        g_dbus_method_invocation_return_value(
+            context,
+            g_variant_new("(o)", nm_dbus_object_get_path(NM_DBUS_OBJECT(connection))));
+    }
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD, connection, TRUE, NULL, subject, NULL);
+}
+
+static void
+settings_add_connection_helper(NMSettings *                  self,
+                               GDBusMethodInvocation *       context,
+                               gboolean                      is_add_connection_2,
+                               GVariant *                    settings,
+                               NMSettingsAddConnection2Flags flags)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    GError *                      error      = NULL;
+    gs_unref_object NMAuthSubject * subject  = NULL;
+    NMSettingsConnectionPersistMode persist_mode;
+
+    connection = _nm_simple_connection_new_from_dbus(settings,
+                                                     NM_SETTING_PARSE_FLAGS_STRICT
+                                                         | NM_SETTING_PARSE_FLAGS_NORMALIZE,
+                                                     &error);
+
+    if (!connection || !nm_connection_verify_secrets(connection, &error)) {
+        g_dbus_method_invocation_take_error(context, error);
+        return;
+    }
+
+    subject = nm_dbus_manager_new_auth_subject_from_context(context);
+    if (!subject) {
+        g_dbus_method_invocation_return_error_literal(context,
+                                                      NM_SETTINGS_ERROR,
+                                                      NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                                      NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
+        return;
+    }
+
+    if (NM_FLAGS_HAS(flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK))
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+    else {
+        nm_assert(NM_FLAGS_HAS(flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY));
+        persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+    }
+
+    nm_settings_add_connection_dbus(
+        self,
+        connection,
+        persist_mode,
+        NM_FLAGS_HAS(flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)
+            ? NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT
+            : NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+        NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+        subject,
+        context,
+        settings_add_connection_add_cb,
+        GINT_TO_POINTER(!!is_add_connection_2));
+}
+
+static void
+impl_settings_add_connection(NMDBusObject *                     obj,
+                             const NMDBusInterfaceInfoExtended *interface_info,
+                             const NMDBusMethodInfoExtended *   method_info,
+                             GDBusConnection *                  connection,
+                             const char *                       sender,
+                             GDBusMethodInvocation *            invocation,
+                             GVariant *                         parameters)
+{
+    NMSettings *     self               = NM_SETTINGS(obj);
+    gs_unref_variant GVariant *settings = NULL;
+
+    g_variant_get(parameters, "(@a{sa{sv}})", &settings);
+    settings_add_connection_helper(self,
+                                   invocation,
+                                   FALSE,
+                                   settings,
+                                   NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK);
+}
+
+static void
+impl_settings_add_connection_unsaved(NMDBusObject *                     obj,
+                                     const NMDBusInterfaceInfoExtended *interface_info,
+                                     const NMDBusMethodInfoExtended *   method_info,
+                                     GDBusConnection *                  connection,
+                                     const char *                       sender,
+                                     GDBusMethodInvocation *            invocation,
+                                     GVariant *                         parameters)
+{
+    NMSettings *     self               = NM_SETTINGS(obj);
+    gs_unref_variant GVariant *settings = NULL;
+
+    g_variant_get(parameters, "(@a{sa{sv}})", &settings);
+    settings_add_connection_helper(self,
+                                   invocation,
+                                   FALSE,
+                                   settings,
+                                   NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY);
+}
+
+static void
+impl_settings_add_connection2(NMDBusObject *                     obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended *   method_info,
+                              GDBusConnection *                  connection,
+                              const char *                       sender,
+                              GDBusMethodInvocation *            invocation,
+                              GVariant *                         parameters)
+{
+    NMSettings *     self               = NM_SETTINGS(obj);
+    gs_unref_variant GVariant *settings = NULL;
+    gs_unref_variant GVariant *   args  = NULL;
+    NMSettingsAddConnection2Flags flags;
+    const char *                  args_name;
+    GVariantIter                  iter;
+    guint32                       flags_u;
+
+    g_variant_get(parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args);
+
+    if (NM_FLAGS_ANY(flags_u,
+                     ~((guint32)(NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+                                 | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY
+                                 | NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)))) {
+        g_dbus_method_invocation_take_error(invocation,
+                                            g_error_new_literal(NM_SETTINGS_ERROR,
+                                                                NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                                                                "Unknown flags"));
+        return;
+    }
+
+    flags = flags_u;
+
+    if (!NM_FLAGS_ANY(flags,
+                      NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+                          | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
+        g_dbus_method_invocation_take_error(
+            invocation,
+            g_error_new_literal(NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                                "Requires either to-disk (0x1) or in-memory (0x2) flags"));
+        return;
+    }
+
+    if (NM_FLAGS_ALL(flags,
+                     NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+                         | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
+        g_dbus_method_invocation_take_error(
+            invocation,
+            g_error_new_literal(NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                                "Cannot set to-disk (0x1) and in-memory (0x2) flags together"));
+        return;
+    }
+
+    nm_assert(g_variant_is_of_type(args, G_VARIANT_TYPE("a{sv}")));
+
+    g_variant_iter_init(&iter, args);
+    while (g_variant_iter_next(&iter, "{&sv}", &args_name, NULL)) {
+        g_dbus_method_invocation_take_error(invocation,
+                                            g_error_new(NM_SETTINGS_ERROR,
+                                                        NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+                                                        "Unsupported argument '%s'",
+                                                        args_name));
+        return;
+    }
+
+    settings_add_connection_helper(self, invocation, TRUE, settings, flags);
+}
+
+/*****************************************************************************/
+
+static void
+impl_settings_load_connections(NMDBusObject *                     obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *   method_info,
+                               GDBusConnection *                  dbus_connection,
+                               const char *                       sender,
+                               GDBusMethodInvocation *            invocation,
+                               GVariant *                         parameters)
+{
+    NMSettings *       self                    = NM_SETTINGS(obj);
+    NMSettingsPrivate *priv                    = NM_SETTINGS_GET_PRIVATE(self);
+    gs_unref_ptrarray GPtrArray *failures      = NULL;
+    gs_free const char **        filenames     = NULL;
+    gs_free char *               op_result_str = NULL;
+
+    g_variant_get(parameters, "(^a&s)", &filenames);
+
+    /* The permission is already enforced by the D-Bus daemon, but we ensure
+     * that the caller is still alive so that clients are forced to wait and
+     * we'll be able to switch to polkit without breaking behavior.
+     */
+    if (!nm_dbus_manager_ensure_uid(nm_dbus_object_get_manager(obj),
+                                    invocation,
+                                    G_MAXULONG,
+                                    NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_PERMISSION_DENIED))
+        return;
+
+    if (filenames && filenames[0]) {
+        NMSettingsPluginConnectionLoadEntry *entries;
+        gsize                                n_entries;
+        gsize                                i;
+        GSList *                             iter;
+
+        entries = nm_settings_plugin_create_connection_load_entries(filenames, &n_entries);
+
+        for (iter = priv->plugins; iter; iter = iter->next) {
+            NMSettingsPlugin *plugin = iter->data;
+
+            nm_settings_plugin_load_connections(plugin,
+                                                entries,
+                                                n_entries,
+                                                _plugin_connections_reload_cb,
+                                                self);
+        }
+
+        for (i = 0; i < n_entries; i++) {
+            NMSettingsPluginConnectionLoadEntry *entry = &entries[i];
+
+            if (!entry->handled) {
+                _LOGW("load: no settings plugin could load \"%s\"", entry->filename);
+                nm_assert(!entry->error);
+            } else if (entry->error) {
+                _LOGW("load: failure to load \"%s\": %s", entry->filename, entry->error->message);
+                g_clear_error(&entry->error);
+            } else
+                continue;
+
+            if (!failures)
+                failures = g_ptr_array_new();
+            g_ptr_array_add(failures, (char *) entry->filename);
+        }
+
+        nm_clear_g_free(&entries);
+
+        _connection_changed_process_all_dirty(
+            self,
+            TRUE,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+            TRUE,
+            NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+                | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
+
+        for (iter = priv->plugins; iter; iter = iter->next)
+            nm_settings_plugin_load_connections_done(iter->data);
+    }
+
+    if (failures)
+        g_ptr_array_add(failures, NULL);
+
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONNS_LOAD,
+                               NULL,
+                               !failures,
+                               (op_result_str = g_strjoinv(",", (char **) filenames)),
+                               invocation,
+                               NULL);
+
+    g_dbus_method_invocation_return_value(invocation,
+                                          g_variant_new("(b^as)",
+                                                        (gboolean)(!failures),
+                                                        failures
+                                                            ? (const char **) failures->pdata
+                                                            : NM_PTRARRAY_EMPTY(const char *)));
+}
+
+static void
+impl_settings_reload_connections(NMDBusObject *                     obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *   method_info,
+                                 GDBusConnection *                  connection,
+                                 const char *                       sender,
+                                 GDBusMethodInvocation *            invocation,
+                                 GVariant *                         parameters)
+{
+    NMSettings *self = NM_SETTINGS(obj);
+
+    /* The permission is already enforced by the D-Bus daemon, but we ensure
+     * that the caller is still alive so that clients are forced to wait and
+     * we'll be able to switch to polkit without breaking behavior.
+     */
+    if (!nm_dbus_manager_ensure_uid(nm_dbus_object_get_manager(obj),
+                                    invocation,
+                                    G_MAXULONG,
+                                    NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_PERMISSION_DENIED))
+        return;
+
+    _plugin_connections_reload(self);
+
+    nm_audit_log_connection_op(NM_AUDIT_OP_CONNS_RELOAD, NULL, TRUE, NULL, invocation, NULL);
+
+    /* We MUST return %TRUE here, otherwise older libnm versions might misbehave. */
+    g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", TRUE));
+}
+
+/*****************************************************************************/
+
+static void
+_clear_connections_cached_list(NMSettingsPrivate *priv)
+{
+    if (!priv->connections_cached_list)
+        return;
+
+    nm_assert(priv->connections_len == NM_PTRARRAY_LEN(priv->connections_cached_list));
+
+#if NM_MORE_ASSERTS
+    /* set the pointer to a bogus value. This makes it more apparent
+     * if somebody has a reference to the cached list and still uses
+     * it. That is a bug, this code just tries to make it blow up
+     * more eagerly. */
+    memset(priv->connections_cached_list,
+           0x43,
+           sizeof(NMSettingsConnection *) * (priv->connections_len + 1));
+#endif
+
+    nm_clear_g_free(&priv->connections_cached_list);
+}
+
+static void
+impl_settings_list_connections(NMDBusObject *                     obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *   method_info,
+                               GDBusConnection *                  dbus_connection,
+                               const char *                       sender,
+                               GDBusMethodInvocation *            invocation,
+                               GVariant *                         parameters)
+{
+    NMSettings *         self = NM_SETTINGS(obj);
+    NMSettingsPrivate *  priv = NM_SETTINGS_GET_PRIVATE(self);
+    gs_free const char **strv = NULL;
+
+    strv =
+        nm_dbus_utils_get_paths_for_clist(&priv->connections_lst_head,
+                                          priv->connections_len,
+                                          G_STRUCT_OFFSET(NMSettingsConnection, _connections_lst),
+                                          TRUE);
+    g_dbus_method_invocation_return_value(invocation, g_variant_new("(^ao)", strv));
+}
+
+NMSettingsConnection *
+nm_settings_get_connection_by_uuid(NMSettings *self, const char *uuid)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS(self), NULL);
+    g_return_val_if_fail(uuid != NULL, NULL);
+
+    return _sett_conn_entry_get_conn(_sett_conn_entries_get(self, uuid));
+}
+
+const char *
+nm_settings_get_dbus_path_for_uuid(NMSettings *self, const char *uuid)
+{
+    NMSettingsConnection *sett_conn;
+
+    sett_conn = nm_settings_get_connection_by_uuid(self, uuid);
+
+    if (!sett_conn)
+        return NULL;
+
+    return nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn));
+}
+
+static void
+impl_settings_get_connection_by_uuid(NMDBusObject *                     obj,
+                                     const NMDBusInterfaceInfoExtended *interface_info,
+                                     const NMDBusMethodInfoExtended *   method_info,
+                                     GDBusConnection *                  dbus_connection,
+                                     const char *                       sender,
+                                     GDBusMethodInvocation *            invocation,
+                                     GVariant *                         parameters)
+{
+    NMSettings *          self = NM_SETTINGS(obj);
+    NMSettingsConnection *sett_conn;
+    gs_unref_object NMAuthSubject *subject = NULL;
+    GError *                       error   = NULL;
+    const char *                   uuid;
+
+    g_variant_get(parameters, "(&s)", &uuid);
+
+    sett_conn = nm_settings_get_connection_by_uuid(self, uuid);
+    if (!sett_conn) {
+        error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                    "No connection with the UUID was found.");
+        goto error;
+    }
+
+    subject = nm_dbus_manager_new_auth_subject_from_context(invocation);
+    if (!subject) {
+        error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                    NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
+        goto error;
+    }
+
+    if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(sett_conn),
+                                             subject,
+                                             NM_SETTINGS_ERROR,
+                                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                             &error))
+        goto error;
+
+    g_dbus_method_invocation_return_value(
+        invocation,
+        g_variant_new("(o)", nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn))));
+    return;
+
+error:
+    g_dbus_method_invocation_take_error(invocation, error);
+}
+
+/**
+ * nm_settings_get_connections:
+ * @self: the #NMSettings
+ * @out_len: (out) (allow-none): returns the number of returned
+ *   connections.
+ *
+ * Returns: (transfer none): a list of NMSettingsConnections. The list is
+ * unsorted and NULL terminated. The result is never %NULL, in case of no
+ * connections, it returns an empty list.
+ * The returned list is cached internally, only valid until the next
+ * NMSettings operation.
+ */
+NMSettingsConnection *const *
+nm_settings_get_connections(NMSettings *self, guint *out_len)
+{
+    NMSettingsPrivate *    priv;
+    NMSettingsConnection **v;
+    NMSettingsConnection * con;
+    guint                  i;
+
+    g_return_val_if_fail(NM_IS_SETTINGS(self), NULL);
+
+    priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    nm_assert(priv->connections_len == c_list_length(&priv->connections_lst_head));
+
+    if (G_UNLIKELY(!priv->connections_cached_list)) {
+        v = g_new(NMSettingsConnection *, priv->connections_len + 1);
+
+        i = 0;
+        c_list_for_each_entry (con, &priv->connections_lst_head, _connections_lst) {
+            nm_assert(i < priv->connections_len);
+            v[i++] = con;
+        }
+        nm_assert(i == priv->connections_len);
+        v[i] = NULL;
+
+        priv->connections_cached_list = v;
+    }
+
+    NM_SET_OUT(out_len, priv->connections_len);
+    return priv->connections_cached_list;
+}
+
+/**
+ * nm_settings_get_connections_clone:
+ * @self: the #NMSetting
+ * @out_len: (allow-none): 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
+ *   sorting the returned list.
+ * @sort_data: user data for @sort_compare_func.
+ *
+ * Returns: (transfer container) (element-type NMSettingsConnection):
+ *   an NULL terminated array of #NMSettingsConnection objects that were
+ *   filtered by @func (or all connections if no filter was specified).
+ *   The order is arbitrary.
+ *   Caller is responsible for freeing the returned array with free(),
+ *   the contained values do not need to be unrefed.
+ */
+NMSettingsConnection **
+nm_settings_get_connections_clone(NMSettings *                   self,
+                                  guint *                        out_len,
+                                  NMSettingsConnectionFilterFunc func,
+                                  gpointer                       func_data,
+                                  GCompareDataFunc               sort_compare_func,
+                                  gpointer                       sort_data)
+{
+    NMSettingsConnection *const *list_cached;
+    NMSettingsConnection **      list;
+    guint                        len, i, j;
+
+    g_return_val_if_fail(NM_IS_SETTINGS(self), NULL);
+
+    list_cached = nm_settings_get_connections(self, &len);
+
+#if NM_MORE_ASSERTS
+    nm_assert(list_cached);
+    for (i = 0; i < len; i++)
+        nm_assert(NM_IS_SETTINGS_CONNECTION(list_cached[i]));
+    nm_assert(!list_cached[i]);
+#endif
+
+    list = g_new(NMSettingsConnection *, ((gsize) len + 1));
+    if (func) {
+        for (i = 0, j = 0; i < len; i++) {
+            if (func(self, list_cached[i], func_data))
+                list[j++] = list_cached[i];
+        }
+        list[j] = NULL;
+        len     = j;
+    } else
+        memcpy(list, list_cached, sizeof(list[0]) * ((gsize) len + 1));
+
+    if (len > 1 && sort_compare_func) {
+        g_qsort_with_data(list, len, sizeof(NMSettingsConnection *), sort_compare_func, sort_data);
+    }
+    NM_SET_OUT(out_len, len);
+    return list;
+}
+
+NMSettingsConnection *
+nm_settings_get_connection_by_path(NMSettings *self, const char *path)
+{
+    NMSettingsPrivate *   priv;
+    NMSettingsConnection *connection;
+
+    g_return_val_if_fail(NM_IS_SETTINGS(self), NULL);
+    g_return_val_if_fail(path, NULL);
+
+    priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    connection =
+        nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), path);
+    if (!connection || !NM_IS_SETTINGS_CONNECTION(connection))
+        return NULL;
+
+    nm_assert(c_list_contains(&priv->connections_lst_head, &connection->_connections_lst));
+    return connection;
+}
+
+gboolean
+nm_settings_has_connection(NMSettings *self, NMSettingsConnection *connection)
+{
+    gboolean has;
+
+    g_return_val_if_fail(NM_IS_SETTINGS(self), FALSE);
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(connection), FALSE);
+
+    has = !c_list_is_empty(&connection->_connections_lst);
+
+    nm_assert(has
+              == nm_c_list_contains_entry(&NM_SETTINGS_GET_PRIVATE(self)->connections_lst_head,
+                                          connection,
+                                          _connections_lst));
+    nm_assert(({
+        NMSettingsConnection *candidate = NULL;
+        const char *          path;
+
+        path = nm_dbus_object_get_path(NM_DBUS_OBJECT(connection));
+        if (path)
+            candidate = nm_settings_get_connection_by_path(self, path);
+
+        (has == (connection == candidate));
+    }));
+
+    return has;
+}
+
+/*****************************************************************************/
+
+static void
+add_plugin(NMSettings *self, NMSettingsPlugin *plugin, const char *pname, const char *path)
+{
+    NMSettingsPrivate *priv;
+
+    nm_assert(NM_IS_SETTINGS(self));
+    nm_assert(NM_IS_SETTINGS_PLUGIN(plugin));
+
+    nm_assert(pname);
+    nm_assert(nm_streq0(pname, nm_settings_plugin_get_plugin_name(plugin)));
+
+    priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    nm_assert(!g_slist_find(priv->plugins, plugin));
+
+    priv->plugins = g_slist_append(priv->plugins, g_object_ref(plugin));
+
+    nm_shutdown_wait_obj_register_object_full(plugin,
+                                              g_strdup_printf("%s-settings-plugin", pname),
+                                              TRUE);
+
+    _LOGI("Loaded settings plugin: %s (%s%s%s)",
+          pname,
+          NM_PRINT_FMT_QUOTED(path, "\"", path, "\"", "internal"));
+}
+
+static gboolean
+add_plugin_load_file(NMSettings *self, const char *pname, GError **error)
+{
+    gs_free char *  full_name                = NULL;
+    gs_free char *  path                     = NULL;
+    gs_unref_object NMSettingsPlugin *plugin = NULL;
+    GModule *                         module;
+    NMSettingsPluginFactoryFunc       factory_func;
+    struct stat                       st;
+    int                               errsv;
+
+    full_name = g_strdup_printf("nm-settings-plugin-%s", pname);
+    path      = g_module_build_path(NMPLUGINDIR, full_name);
+
+    if (stat(path, &st) != 0) {
+        errsv = errno;
+        _LOGW("could not load plugin '%s' from file '%s': %s",
+              pname,
+              path,
+              nm_strerror_native(errsv));
+        return TRUE;
+    }
+    if (!S_ISREG(st.st_mode)) {
+        _LOGW("could not load plugin '%s' from file '%s': not a file", pname, path);
+        return TRUE;
+    }
+    if (st.st_uid != 0) {
+        _LOGW("could not load plugin '%s' from file '%s': file must be owned by root", pname, path);
+        return TRUE;
+    }
+    if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
+        _LOGW("could not load plugin '%s' from file '%s': invalid file permissions", pname, path);
+        return TRUE;
+    }
+
+    module = g_module_open(path, G_MODULE_BIND_LOCAL);
+    if (!module) {
+        _LOGW("could not load plugin '%s' from file '%s': %s", pname, path, g_module_error());
+        return TRUE;
+    }
+
+    /* errors after this point are fatal, because we loaded the shared library already. */
+
+    if (!g_module_symbol(module, "nm_settings_plugin_factory", (gpointer)(&factory_func))) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Could not find plugin '%s' factory function.",
+                    pname);
+        g_module_close(module);
+        return FALSE;
+    }
+
+    /* after accessing the plugin we cannot unload it anymore, because the glib
+     * types cannot be properly unregistered. */
+    g_module_make_resident(module);
+
+    plugin = (*factory_func)();
+    if (!NM_IS_SETTINGS_PLUGIN(plugin)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "plugin '%s' returned invalid settings plugin",
+                    pname);
+        return FALSE;
+    }
+
+    add_plugin(self, NM_SETTINGS_PLUGIN(plugin), pname, path);
+    return TRUE;
+}
+
+static void
+add_plugin_keyfile(NMSettings *self)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    if (priv->keyfile_plugin)
+        return;
+    priv->keyfile_plugin = nms_keyfile_plugin_new();
+    add_plugin(self, NM_SETTINGS_PLUGIN(priv->keyfile_plugin), "keyfile", NULL);
+}
+
+static gboolean
+load_plugins(NMSettings *self, const char *const *plugins, GError **error)
+{
+    const char *const *iter;
+    gboolean           success = TRUE;
+
+    for (iter = plugins; iter && *iter; iter++) {
+        const char *pname = *iter;
+
+        if (!*pname || strchr(pname, '/')) {
+            _LOGW("ignore invalid plugin \"%s\"", pname);
+            continue;
+        }
+
+        if (NM_IN_STRSET(pname, "ifcfg-suse", "ifnet", "ibft", "no-ibft")) {
+            _LOGW("skipping deprecated plugin %s", pname);
+            continue;
+        }
+
+        /* keyfile plugin is built-in now */
+        if (nm_streq(pname, "keyfile")) {
+            add_plugin_keyfile(self);
+            continue;
+        }
+
+        if (nm_utils_strv_find_first((char **) plugins, iter - plugins, pname) >= 0) {
+            /* the plugin is already mentioned in the list previously.
+             * Don't load a duplicate. */
+            continue;
+        }
+
+        success = add_plugin_load_file(self, pname, error);
+        if (!success)
+            break;
+    }
+
+    /* If keyfile plugin was not among configured plugins, add it as the last one */
+    if (success)
+        add_plugin_keyfile(self);
+
+    return success;
+}
+
+/*****************************************************************************/
+
+static void
+pk_hostname_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data)
+{
+    NMSettings *       self = NM_SETTINGS(user_data);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    NMAuthCallResult   result;
+    GError *           error = NULL;
+    const char *       hostname;
+
+    nm_assert(G_IS_DBUS_METHOD_INVOCATION(context));
+
+    c_list_unlink(nm_auth_chain_parent_lst_list(chain));
+
+    result   = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
+    hostname = nm_auth_chain_get_data(chain, "hostname");
+
+    /* If our NMSettingsConnection is already gone, do nothing */
+    if (result != NM_AUTH_CALL_RESULT_YES) {
+        error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_PERMISSION_DENIED,
+                                    NM_UTILS_ERROR_MSG_INSUFF_PRIV);
+    } else {
+        if (!nm_hostname_manager_write_hostname(priv->hostname_manager, hostname)) {
+            error = g_error_new_literal(NM_SETTINGS_ERROR,
+                                        NM_SETTINGS_ERROR_FAILED,
+                                        "Saving the hostname failed.");
+        }
+    }
+
+    nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE,
+                            hostname,
+                            !error,
+                            nm_auth_chain_get_subject(chain),
+                            error ? error->message : NULL);
+
+    if (error)
+        g_dbus_method_invocation_take_error(context, error);
+    else
+        g_dbus_method_invocation_return_value(context, NULL);
+}
+
+static void
+impl_settings_save_hostname(NMDBusObject *                     obj,
+                            const NMDBusInterfaceInfoExtended *interface_info,
+                            const NMDBusMethodInfoExtended *   method_info,
+                            GDBusConnection *                  connection,
+                            const char *                       sender,
+                            GDBusMethodInvocation *            invocation,
+                            GVariant *                         parameters)
+{
+    NMSettings *       self = NM_SETTINGS(obj);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    NMAuthChain *      chain;
+    const char *       hostname;
+    const char *       error_reason;
+    int                error_code;
+
+    g_variant_get(parameters, "(&s)", &hostname);
+
+    /* Minimal validation of the hostname */
+    if (!nm_hostname_manager_validate_hostname(hostname)) {
+        error_code   = NM_SETTINGS_ERROR_INVALID_HOSTNAME;
+        error_reason = "The hostname was too long or contained invalid characters";
+        goto err;
+    }
+
+    chain = nm_auth_chain_new_context(invocation, pk_hostname_cb, self);
+    if (!chain) {
+        error_code   = NM_SETTINGS_ERROR_PERMISSION_DENIED;
+        error_reason = NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED;
+        goto err;
+    }
+
+    c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain));
+    nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, TRUE);
+    nm_auth_chain_set_data(chain, "hostname", g_strdup(hostname), g_free);
+    return;
+err:
+    nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, hostname, FALSE, invocation, error_reason);
+    g_dbus_method_invocation_return_error_literal(invocation,
+                                                  NM_SETTINGS_ERROR,
+                                                  error_code,
+                                                  error_reason);
+}
+
+/*****************************************************************************/
+
+static void
+_hostname_changed_cb(NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointer user_data)
+{
+    _notify(user_data, PROP_HOSTNAME);
+}
+
+/*****************************************************************************/
+
+static gboolean
+have_connection_for_device(NMSettings *self, NMDevice *device)
+{
+    NMSettingsPrivate *   priv = NM_SETTINGS_GET_PRIVATE(self);
+    NMSettingsConnection *sett_conn;
+
+    g_return_val_if_fail(NM_IS_SETTINGS(self), FALSE);
+
+    /* Find a wired connection matching for the device, if any */
+    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))
+            continue;
+
+        if (nm_settings_connection_default_wired_get_device(sett_conn))
+            continue;
+
+        if (NM_FLAGS_ANY(nm_settings_connection_get_flags(sett_conn),
+                         NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                             | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
+            continue;
+
+        return TRUE;
+    }
+
+    /* See if there's a known non-NetworkManager configuration for the device */
+    if (nm_device_spec_match_list(device, priv->unrecognized_specs))
+        return TRUE;
+
+    return FALSE;
+}
+
+static void
+default_wired_clear_tag(NMSettings *          self,
+                        NMDevice *            device,
+                        NMSettingsConnection *sett_conn,
+                        gboolean              add_to_no_auto_default)
+{
+    nm_assert(NM_IS_SETTINGS(self));
+    nm_assert(NM_IS_DEVICE(device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+    nm_assert(device == nm_settings_connection_default_wired_get_device(sett_conn));
+    nm_assert(sett_conn == g_object_get_qdata(G_OBJECT(device), _default_wired_connection_quark()));
+
+    _LOGT("auto-default: forget association between %s (%s) and device %s (%s)",
+          nm_settings_connection_get_uuid(sett_conn),
+          nm_settings_connection_get_id(sett_conn),
+          nm_device_get_iface(device),
+          add_to_no_auto_default ? "persisted" : "temporary");
+
+    nm_settings_connection_default_wired_set_device(sett_conn, NULL);
+
+    g_object_set_qdata(G_OBJECT(device), _default_wired_connection_quark(), NULL);
+
+    if (add_to_no_auto_default)
+        nm_config_set_no_auto_default_for_device(NM_SETTINGS_GET_PRIVATE(self)->config, device);
+}
+
+static void
+device_realized(NMDevice *device, GParamSpec *pspec, NMSettings *self)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingsPrivate *           priv;
+    NMSettingsConnection *        added;
+    GError *                      error = NULL;
+
+    if (!nm_device_is_real(device))
+        return;
+
+    g_signal_handlers_disconnect_by_func(device, G_CALLBACK(device_realized), self);
+
+    priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    /* If the device isn't managed or it already has a default wired connection,
+     * ignore it.
+     */
+    if (!NM_DEVICE_GET_CLASS(device)->new_default_connection
+        || !nm_device_get_managed(device, FALSE)
+        || g_object_get_qdata(G_OBJECT(device), _default_wired_connection_blocked_quark()))
+        return;
+
+    /* we only check once whether to create the auto-default connection. If we reach this point,
+     * we mark the creation of the default-wired-connection as blocked. */
+    g_object_set_qdata(G_OBJECT(device), _default_wired_connection_blocked_quark(), device);
+
+    if (nm_config_get_no_auto_default_for_device(priv->config, device)) {
+        _LOGT("auto-default: cannot create auto-default connection for device %s: disabled by "
+              "\"no-auto-default\"",
+              nm_device_get_iface(device));
+        return;
+    }
+
+    if (have_connection_for_device(self, device)) {
+        _LOGT("auto-default: cannot create auto-default connection for device %s: already has a "
+              "profile",
+              nm_device_get_iface(device));
+        return;
+    }
+
+    connection = nm_device_new_default_connection(device);
+    if (!connection) {
+        _LOGT("auto-default: cannot create auto-default connection for device %s",
+              nm_device_get_iface(device));
+        return;
+    }
+
+    _LOGT("auto-default: creating in-memory connection %s (%s) for device %s",
+          nm_connection_get_uuid(connection),
+          nm_connection_get_id(connection),
+          nm_device_get_iface(device));
+
+    nm_settings_add_connection(self,
+                               connection,
+                               NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+                               NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+                               NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+                               &added,
+                               &error);
+    if (!added) {
+        if (!g_error_matches(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_UUID_EXISTS)) {
+            _LOGW("(%s) couldn't create default wired connection: %s",
+                  nm_device_get_iface(device),
+                  error->message);
+        }
+        g_clear_error(&error);
+        return;
+    }
+
+    nm_settings_connection_default_wired_set_device(added, device);
+
+    g_object_set_qdata(G_OBJECT(device), _default_wired_connection_quark(), added);
+
+    _LOGI("(%s): created default wired connection '%s'",
+          nm_device_get_iface(device),
+          nm_settings_connection_get_id(added));
+}
+
+void
+nm_settings_device_added(NMSettings *self, NMDevice *device)
+{
+    if (nm_device_is_real(device))
+        device_realized(device, NULL, self);
+    else {
+        /* FIXME(shutdown): we need to disconnect this signal handler during
+         *   shutdown. */
+        g_signal_connect_after(device,
+                               "notify::" NM_DEVICE_REAL,
+                               G_CALLBACK(device_realized),
+                               self);
+    }
+}
+
+void
+nm_settings_device_removed(NMSettings *self, NMDevice *device, gboolean quitting)
+{
+    NMSettingsConnection *connection;
+
+    g_signal_handlers_disconnect_by_func(device, G_CALLBACK(device_realized), self);
+
+    connection = g_object_get_qdata(G_OBJECT(device), _default_wired_connection_quark());
+    if (connection) {
+        default_wired_clear_tag(self, device, connection, FALSE);
+
+        /* Don't delete the default wired connection on shutdown, so that it
+         * remains up and can be assumed if NM starts again.
+         */
+        if (quitting == FALSE)
+            nm_settings_connection_delete(connection, TRUE);
+    }
+}
+
+/*****************************************************************************/
+
+static void
+session_monitor_changed_cb(NMSessionMonitor *session_monitor, NMSettings *self)
+{
+    NMSettingsPrivate *          priv = NM_SETTINGS_GET_PRIVATE(self);
+    NMSettingsConnection *const *list;
+    guint                        i, len;
+    guint                        generation;
+
+again:
+    list       = nm_settings_get_connections(self, &len);
+    generation = priv->connections_generation;
+    for (i = 0; i < len; i++) {
+        gboolean is_visible;
+
+        is_visible = nm_settings_connection_check_visibility(list[i], session_monitor);
+        nm_settings_connection_set_flags(list[i],
+                                         NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,
+                                         is_visible);
+        if (generation != priv->connections_generation) {
+            /* the cached list was invalidated. Start again.
+             *
+             * Note that nm_settings_connection_recheck_visibility() will do nothing
+             * if the visibility didn't change (including emitting no signals,
+             * and not invalidating the list).
+             *
+             * Hence, for this to be an endless loop, the settings would have
+             * to constantly change the visibility flag and also invalidate the list. */
+            goto again;
+        }
+    }
+}
+
+/*****************************************************************************/
+
+G_GNUC_PRINTF(4, 5)
+static void
+_kf_db_log_fcn(NMKeyFileDB *kf_db, int syslog_level, gpointer user_data, const char *fmt, ...)
+{
+    NMSettings *self  = user_data;
+    NMLogLevel  level = nm_log_level_from_syslog(syslog_level);
+
+    if (_NMLOG_ENABLED(level)) {
+        NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+        gs_free char *     msg  = NULL;
+        va_list            ap;
+        const char *       prefix;
+
+        va_start(ap, fmt);
+        msg = g_strdup_vprintf(fmt, ap);
+        va_end(ap);
+
+        if (priv->kf_db_timestamps == kf_db)
+            prefix = "timestamps";
+        else if (priv->kf_db_seen_bssids == kf_db)
+            prefix = "seen-bssids";
+        else {
+            nm_assert_not_reached();
+            prefix = "???";
+        }
+
+        _NMLOG(level, "[%s-keyfile]: %s", prefix, msg);
+    }
+}
+
+static gboolean
+_kf_db_got_dirty_flush(NMSettings *self, gboolean is_timestamps)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    const char *       prefix;
+    NMKeyFileDB *      kf_db;
+
+    if (is_timestamps) {
+        prefix                               = "timestamps";
+        kf_db                                = priv->kf_db_timestamps;
+        priv->kf_db_flush_idle_id_timestamps = 0;
+    } else {
+        prefix                                = "seen-bssids";
+        kf_db                                 = priv->kf_db_seen_bssids;
+        priv->kf_db_flush_idle_id_seen_bssids = 0;
+    }
+
+    if (nm_key_file_db_is_dirty(kf_db))
+        nm_key_file_db_to_file(kf_db, FALSE);
+    else {
+        _LOGT("[%s-keyfile]: skip saving changes to \"%s\"",
+              prefix,
+              nm_key_file_db_get_filename(kf_db));
+    }
+
+    return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_kf_db_got_dirty_flush_timestamps_cb(gpointer user_data)
+{
+    return _kf_db_got_dirty_flush(user_data, TRUE);
+}
+
+static gboolean
+_kf_db_got_dirty_flush_seen_bssids_cb(gpointer user_data)
+{
+    return _kf_db_got_dirty_flush(user_data, FALSE);
+}
+
+static void
+_kf_db_got_dirty_fcn(NMKeyFileDB *kf_db, gpointer user_data)
+{
+    NMSettings *       self = user_data;
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    GSourceFunc        idle_func;
+    guint *            p_id;
+    const char *       prefix;
+
+    if (priv->kf_db_timestamps == kf_db) {
+        prefix    = "timestamps";
+        p_id      = &priv->kf_db_flush_idle_id_timestamps;
+        idle_func = _kf_db_got_dirty_flush_timestamps_cb;
+    } else if (priv->kf_db_seen_bssids == kf_db) {
+        prefix    = "seen-bssids";
+        p_id      = &priv->kf_db_flush_idle_id_seen_bssids;
+        idle_func = _kf_db_got_dirty_flush_seen_bssids_cb;
+    } else {
+        nm_assert_not_reached();
+        return;
+    }
+
+    if (*p_id != 0)
+        return;
+    _LOGT("[%s-keyfile]: schedule flushing changes to disk", prefix);
+    *p_id = g_idle_add_full(G_PRIORITY_LOW, idle_func, self, NULL);
+}
+
+void
+nm_settings_kf_db_write(NMSettings *self)
+{
+    NMSettingsPrivate *priv;
+
+    g_return_if_fail(NM_IS_SETTINGS(self));
+
+    priv = NM_SETTINGS_GET_PRIVATE(self);
+    if (priv->kf_db_timestamps)
+        nm_key_file_db_to_file(priv->kf_db_timestamps, TRUE);
+    if (priv->kf_db_seen_bssids)
+        nm_key_file_db_to_file(priv->kf_db_seen_bssids, TRUE);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_start(NMSettings *self, GError **error)
+{
+    NMSettingsPrivate *priv;
+    gs_strfreev char **plugins = NULL;
+    GSList *           iter;
+
+    priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    nm_assert(!priv->started);
+
+    priv->startup_complete_start_timestamp_msec = nm_utils_get_monotonic_timestamp_msec();
+
+    priv->hostname_manager = g_object_ref(nm_hostname_manager_get());
+
+    priv->kf_db_timestamps  = nm_key_file_db_new(NMSTATEDIR "/timestamps",
+                                                "timestamps",
+                                                _kf_db_log_fcn,
+                                                _kf_db_got_dirty_fcn,
+                                                self);
+    priv->kf_db_seen_bssids = nm_key_file_db_new(NMSTATEDIR "/seen-bssids",
+                                                 "seen-bssids",
+                                                 _kf_db_log_fcn,
+                                                 _kf_db_got_dirty_fcn,
+                                                 self);
+    nm_key_file_db_start(priv->kf_db_timestamps);
+    nm_key_file_db_start(priv->kf_db_seen_bssids);
+
+    /* Load the plugins; fail if a plugin is not found. */
+    plugins = nm_config_data_get_plugins(nm_config_get_data_orig(priv->config), TRUE);
+
+    if (!load_plugins(self, (const char *const *) plugins, error))
+        return FALSE;
+
+    for (iter = priv->plugins; iter; iter = iter->next) {
+        NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN(iter->data);
+
+        g_signal_connect(plugin,
+                         NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
+                         G_CALLBACK(_plugin_unmanaged_specs_changed),
+                         self);
+        g_signal_connect(plugin,
+                         NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
+                         G_CALLBACK(_plugin_unrecognized_specs_changed),
+                         self);
+    }
+
+    _plugin_unmanaged_specs_changed(NULL, self);
+    _plugin_unrecognized_specs_changed(NULL, self);
+
+    _plugin_connections_reload(self);
+
+    g_signal_connect(priv->hostname_manager,
+                     "notify::" NM_HOSTNAME_MANAGER_HOSTNAME,
+                     G_CALLBACK(_hostname_changed_cb),
+                     self);
+    if (nm_hostname_manager_get_hostname(priv->hostname_manager))
+        _notify(self, PROP_HOSTNAME);
+
+    priv->started = TRUE;
+    _startup_complete_check(self, 0);
+
+    /* FIXME(shutdown): we also need a nm_settings_stop() during shutdown.
+     *
+     * In particular, we need to remove all in-memory keyfiles from /run that are nm-generated.
+     * alternatively, the nm-generated flag must also be persisted and loaded to /run. */
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMSettings *       self = NM_SETTINGS(object);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    const char **      strv;
+
+    switch (prop_id) {
+    case PROP_UNMANAGED_SPECS:
+        g_value_take_boxed(value,
+                           _nm_utils_slist_to_strv(nm_settings_get_unmanaged_specs(self), TRUE));
+        break;
+    case PROP_HOSTNAME:
+        g_value_set_string(value,
+                           priv->hostname_manager
+                               ? nm_hostname_manager_get_hostname(priv->hostname_manager)
+                               : NULL);
+        break;
+    case PROP_CAN_MODIFY:
+        g_value_set_boolean(value, TRUE);
+        break;
+    case PROP_CONNECTIONS:
+        strv = nm_dbus_utils_get_paths_for_clist(
+            &priv->connections_lst_head,
+            priv->connections_len,
+            G_STRUCT_OFFSET(NMSettingsConnection, _connections_lst),
+            TRUE);
+        g_value_take_boxed(value, nm_utils_strv_make_deep_copied(strv));
+        break;
+    case PROP_STARTUP_COMPLETE:
+        g_value_set_boolean(value, !nm_settings_get_startup_complete_blocked_reason(self, FALSE));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NMSettings *       self = NM_SETTINGS(object);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    switch (prop_id) {
+    case PROP_MANAGER:
+        /* construct-only */
+        priv->manager = g_value_get_pointer(value);
+        nm_assert(NM_IS_MANAGER(priv->manager));
+        g_object_add_weak_pointer(G_OBJECT(priv->manager), (gpointer *) &priv->manager);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_settings_init(NMSettings *self)
+{
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+
+    c_list_init(&priv->auth_lst_head);
+    c_list_init(&priv->connections_lst_head);
+    c_list_init(&priv->startup_complete_scd_lst_head);
+
+    c_list_init(&priv->sce_dirty_lst_head);
+    priv->sce_idx = g_hash_table_new_full(nm_pstr_hash,
+                                          nm_pstr_equal,
+                                          NULL,
+                                          (GDestroyNotify) _sett_conn_entry_free);
+
+    priv->config = g_object_ref(nm_config_get());
+
+    priv->agent_mgr = g_object_ref(nm_agent_manager_get());
+
+    priv->platform = g_object_ref(NM_PLATFORM_GET);
+
+    priv->session_monitor = g_object_ref(nm_session_monitor_get());
+    g_signal_connect(priv->session_monitor,
+                     NM_SESSION_MONITOR_CHANGED,
+                     G_CALLBACK(session_monitor_changed_cb),
+                     self);
+}
+
+NMSettings *
+nm_settings_new(NMManager *manager)
+{
+    nm_assert(NM_IS_MANAGER(manager));
+
+    return g_object_new(NM_TYPE_SETTINGS, NM_SETTINGS_MANAGER, manager, NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSettings *       self = NM_SETTINGS(object);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    CList *            iter;
+
+    nm_assert(c_list_is_empty(&priv->sce_dirty_lst_head));
+    nm_assert(g_hash_table_size(priv->sce_idx) == 0);
+
+    nm_clear_g_source(&priv->startup_complete_timeout_id);
+    nm_clear_pointer(&priv->startup_complete_idx, g_hash_table_destroy);
+    nm_assert(c_list_is_empty(&priv->startup_complete_scd_lst_head));
+
+    while ((iter = c_list_first(&priv->auth_lst_head)))
+        nm_auth_chain_destroy(nm_auth_chain_parent_lst_entry(iter));
+
+    if (priv->hostname_manager) {
+        g_signal_handlers_disconnect_by_func(priv->hostname_manager,
+                                             G_CALLBACK(_hostname_changed_cb),
+                                             self);
+        g_clear_object(&priv->hostname_manager);
+    }
+
+    if (priv->session_monitor) {
+        g_signal_handlers_disconnect_by_func(priv->session_monitor,
+                                             G_CALLBACK(session_monitor_changed_cb),
+                                             self);
+        g_clear_object(&priv->session_monitor);
+    }
+
+    G_OBJECT_CLASS(nm_settings_parent_class)->dispose(object);
+}
+
+static void
+finalize(GObject *object)
+{
+    NMSettings *       self = NM_SETTINGS(object);
+    NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
+    GSList *           iter;
+
+    _clear_connections_cached_list(priv);
+
+    nm_assert(c_list_is_empty(&priv->connections_lst_head));
+
+    nm_assert(c_list_is_empty(&priv->sce_dirty_lst_head));
+    nm_assert(g_hash_table_size(priv->sce_idx) == 0);
+
+    nm_clear_pointer(&priv->sce_idx, g_hash_table_destroy);
+
+    g_slist_free_full(priv->unmanaged_specs, g_free);
+    g_slist_free_full(priv->unrecognized_specs, g_free);
+
+    while ((iter = priv->plugins)) {
+        gs_unref_object NMSettingsPlugin *plugin = iter->data;
+
+        priv->plugins = g_slist_delete_link(priv->plugins, iter);
+        g_signal_handlers_disconnect_by_data(plugin, self);
+    }
+
+    g_clear_object(&priv->keyfile_plugin);
+
+    g_clear_object(&priv->agent_mgr);
+
+    nm_clear_g_source(&priv->kf_db_flush_idle_id_timestamps);
+    nm_clear_g_source(&priv->kf_db_flush_idle_id_seen_bssids);
+    nm_key_file_db_to_file(priv->kf_db_timestamps, FALSE);
+    nm_key_file_db_to_file(priv->kf_db_seen_bssids, FALSE);
+    nm_key_file_db_destroy(priv->kf_db_timestamps);
+    nm_key_file_db_destroy(priv->kf_db_seen_bssids);
+
+    G_OBJECT_CLASS(nm_settings_parent_class)->finalize(object);
+
+    g_clear_object(&priv->config);
+
+    g_clear_object(&priv->platform);
+
+    if (priv->manager) {
+        g_object_remove_weak_pointer(G_OBJECT(priv->manager), (gpointer *) &priv->manager);
+        priv->manager = NULL;
+    }
+}
+
+static const GDBusSignalInfo signal_info_new_connection = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(
+    "NewConnection",
+    .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), ), );
+
+static const GDBusSignalInfo signal_info_connection_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(
+    "ConnectionRemoved",
+    .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), ), );
+
+static const NMDBusInterfaceInfoExtended interface_info_settings = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_SETTINGS,
+        .methods = NM_DEFINE_GDBUS_METHOD_INFOS(
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "ListConnections",
+                    .out_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("connections", "ao"), ), ),
+                .handle = impl_settings_list_connections, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "GetConnectionByUuid",
+                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("uuid", "s"), ),
+                    .out_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), ), ),
+                .handle = impl_settings_get_connection_by_uuid, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "AddConnection",
+                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), ),
+                    .out_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("path", "o"), ), ),
+                .handle = impl_settings_add_connection, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "AddConnectionUnsaved",
+                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), ),
+                    .out_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("path", "o"), ), ),
+                .handle = impl_settings_add_connection_unsaved, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "AddConnection2",
+                    .in_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("settings", "a{sa{sv}}"),
+                                                  NM_DEFINE_GDBUS_ARG_INFO("flags", "u"),
+                                                  NM_DEFINE_GDBUS_ARG_INFO("args", "a{sv}"), ),
+                    .out_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("path", "o"),
+                                                  NM_DEFINE_GDBUS_ARG_INFO("result", "a{sv}"), ), ),
+                .handle = impl_settings_add_connection2, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "LoadConnections",
+                    .in_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("filenames", "as"), ),
+                    .out_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("status", "b"),
+                                                  NM_DEFINE_GDBUS_ARG_INFO("failures", "as"), ), ),
+                .handle = impl_settings_load_connections, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT("ReloadConnections",
+                                                 .out_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                                                     NM_DEFINE_GDBUS_ARG_INFO("status", "b"), ), ),
+                .handle = impl_settings_reload_connections, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "SaveHostname",
+                    .in_args =
+                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("hostname", "s"), ), ),
+                .handle = impl_settings_save_hostname, ), ),
+        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy,
+                                                &signal_info_new_connection,
+                                                &signal_info_connection_removed, ),
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Connections",
+                                                             "ao",
+                                                             NM_SETTINGS_CONNECTIONS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Hostname", "s", NM_SETTINGS_HOSTNAME),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("CanModify",
+                                                             "b",
+                                                             NM_SETTINGS_CAN_MODIFY), ), ),
+    .legacy_property_changed = TRUE,
+};
+
+static void
+nm_settings_class_init(NMSettingsClass *class)
+{
+    GObjectClass *     object_class      = G_OBJECT_CLASS(class);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(class);
+
+    dbus_object_class->export_path     = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH_SETTINGS);
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_settings);
+
+    object_class->get_property = get_property;
+    object_class->set_property = set_property;
+    object_class->dispose      = dispose;
+    object_class->finalize     = finalize;
+
+    obj_properties[PROP_MANAGER] =
+        g_param_spec_pointer(NM_SETTINGS_MANAGER,
+                             "",
+                             "",
+                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_UNMANAGED_SPECS] =
+        g_param_spec_boxed(NM_SETTINGS_UNMANAGED_SPECS,
+                           "",
+                           "",
+                           G_TYPE_STRV,
+                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_HOSTNAME] = g_param_spec_string(NM_SETTINGS_HOSTNAME,
+                                                        "",
+                                                        "",
+                                                        NULL,
+                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_CAN_MODIFY] =
+        g_param_spec_boolean(NM_SETTINGS_CAN_MODIFY,
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_CONNECTIONS] =
+        g_param_spec_boxed(NM_SETTINGS_CONNECTIONS,
+                           "",
+                           "",
+                           G_TYPE_STRV,
+                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_STARTUP_COMPLETE] =
+        g_param_spec_boolean(NM_SETTINGS_STARTUP_COMPLETE,
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    signals[CONNECTION_ADDED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
+                                             G_OBJECT_CLASS_TYPE(object_class),
+                                             G_SIGNAL_RUN_FIRST,
+                                             0,
+                                             NULL,
+                                             NULL,
+                                             g_cclosure_marshal_VOID__OBJECT,
+                                             G_TYPE_NONE,
+                                             1,
+                                             NM_TYPE_SETTINGS_CONNECTION);
+
+    signals[CONNECTION_UPDATED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
+                                               G_OBJECT_CLASS_TYPE(object_class),
+                                               G_SIGNAL_RUN_FIRST,
+                                               0,
+                                               NULL,
+                                               NULL,
+                                               NULL,
+                                               G_TYPE_NONE,
+                                               2,
+                                               NM_TYPE_SETTINGS_CONNECTION,
+                                               G_TYPE_UINT);
+
+    signals[CONNECTION_REMOVED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
+                                               G_OBJECT_CLASS_TYPE(object_class),
+                                               G_SIGNAL_RUN_FIRST,
+                                               0,
+                                               NULL,
+                                               NULL,
+                                               g_cclosure_marshal_VOID__OBJECT,
+                                               G_TYPE_NONE,
+                                               1,
+                                               NM_TYPE_SETTINGS_CONNECTION);
+
+    signals[CONNECTION_FLAGS_CHANGED] = g_signal_new(NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED,
+                                                     G_OBJECT_CLASS_TYPE(object_class),
+                                                     G_SIGNAL_RUN_FIRST,
+                                                     0,
+                                                     NULL,
+                                                     NULL,
+                                                     g_cclosure_marshal_VOID__OBJECT,
+                                                     G_TYPE_NONE,
+                                                     1,
+                                                     NM_TYPE_SETTINGS_CONNECTION);
+}
diff --git a/src/core/settings/nm-settings.h b/src/core/settings/nm-settings.h
new file mode 100644
index 00000000..09a0af57
--- /dev/null
+++ b/src/core/settings/nm-settings.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Søren Sandmann <sandmann@daimi.au.dk>
+ * Dan Williams <dcbw@redhat.com>
+ * Tambet Ingo <tambet@gmail.com>
+ * Copyright (C) 2007 - 2011 Red Hat, Inc.
+ * Copyright (C) 2008 Novell, Inc.
+ */
+
+#ifndef __NM_SETTINGS_H__
+#define __NM_SETTINGS_H__
+
+#include "nm-connection.h"
+
+#include "nm-settings-connection.h"
+
+#define NM_TYPE_SETTINGS (nm_settings_get_type())
+#define NM_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTINGS, NMSettings))
+#define NM_SETTINGS_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTINGS, NMSettingsClass))
+#define NM_IS_SETTINGS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTINGS))
+#define NM_IS_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTINGS))
+#define NM_SETTINGS_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTINGS, NMSettingsClass))
+
+#define NM_SETTINGS_UNMANAGED_SPECS  "unmanaged-specs"
+#define NM_SETTINGS_HOSTNAME         "hostname"
+#define NM_SETTINGS_CAN_MODIFY       "can-modify"
+#define NM_SETTINGS_CONNECTIONS      "connections"
+#define NM_SETTINGS_STARTUP_COMPLETE "startup-complete"
+#define NM_SETTINGS_MANAGER          "manager"
+
+#define NM_SETTINGS_SIGNAL_CONNECTION_ADDED         "connection-added"
+#define NM_SETTINGS_SIGNAL_CONNECTION_UPDATED       "connection-updated"
+#define NM_SETTINGS_SIGNAL_CONNECTION_REMOVED       "connection-removed"
+#define NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED "connection-flags-changed"
+
+/**
+ * NMConnectionFilterFunc:
+ * @settings: The #NMSettings requesting the filtering
+ * @connection: the connection to be filtered
+ * @func_data: the caller-provided data pointer
+ *
+ * Returns: %TRUE to allow the connection, %FALSE to ignore it
+ */
+typedef gboolean (*NMSettingsConnectionFilterFunc)(NMSettings *          settings,
+                                                   NMSettingsConnection *connection,
+                                                   gpointer              func_data);
+
+typedef struct _NMSettingsClass NMSettingsClass;
+
+typedef void (*NMSettingsSetHostnameCb)(const char *name, gboolean result, gpointer user_data);
+
+GType nm_settings_get_type(void);
+
+NMSettings *nm_settings_get(void);
+#define NM_SETTINGS_GET (nm_settings_get())
+
+NMSettings *nm_settings_new(NMManager *manager);
+
+gboolean nm_settings_start(NMSettings *self, GError **error);
+
+typedef void (*NMSettingsAddCallback)(NMSettings *           settings,
+                                      NMSettingsConnection * connection,
+                                      GError *               error,
+                                      GDBusMethodInvocation *context,
+                                      NMAuthSubject *        subject,
+                                      gpointer               user_data);
+
+void nm_settings_add_connection_dbus(NMSettings *                    self,
+                                     NMConnection *                  connection,
+                                     NMSettingsConnectionPersistMode persist_mode,
+                                     NMSettingsConnectionAddReason   add_reason,
+                                     NMSettingsConnectionIntFlags    sett_flags,
+                                     NMAuthSubject *                 subject,
+                                     GDBusMethodInvocation *         context,
+                                     NMSettingsAddCallback           callback,
+                                     gpointer                        user_data);
+
+NMSettingsConnection *const *nm_settings_get_connections(NMSettings *settings, guint *out_len);
+
+NMSettingsConnection **nm_settings_get_connections_clone(NMSettings *                   self,
+                                                         guint *                        out_len,
+                                                         NMSettingsConnectionFilterFunc func,
+                                                         gpointer                       func_data,
+                                                         GCompareDataFunc sort_compare_func,
+                                                         gpointer         sort_data);
+
+gboolean nm_settings_add_connection(NMSettings *                    settings,
+                                    NMConnection *                  connection,
+                                    NMSettingsConnectionPersistMode persist_mode,
+                                    NMSettingsConnectionAddReason   add_reason,
+                                    NMSettingsConnectionIntFlags    sett_flags,
+                                    NMSettingsConnection **         out_sett_conn,
+                                    GError **                       error);
+
+gboolean nm_settings_update_connection(NMSettings *                     self,
+                                       NMSettingsConnection *           sett_conn,
+                                       NMConnection *                   new_connection,
+                                       NMSettingsConnectionPersistMode  persist_mode,
+                                       NMSettingsConnectionIntFlags     sett_flags,
+                                       NMSettingsConnectionIntFlags     sett_mask,
+                                       NMSettingsConnectionUpdateReason update_reason,
+                                       const char *                     log_context_name,
+                                       GError **                        error);
+
+void nm_settings_delete_connection(NMSettings *          self,
+                                   NMSettingsConnection *sett_conn,
+                                   gboolean              allow_add_to_no_auto_default);
+
+NMSettingsConnection *nm_settings_get_connection_by_path(NMSettings *settings, const char *path);
+
+NMSettingsConnection *nm_settings_get_connection_by_uuid(NMSettings *settings, const char *uuid);
+
+const char *nm_settings_get_dbus_path_for_uuid(NMSettings *self, const char *uuid);
+
+gboolean nm_settings_has_connection(NMSettings *self, NMSettingsConnection *connection);
+
+const GSList *nm_settings_get_unmanaged_specs(NMSettings *self);
+
+void nm_settings_device_added(NMSettings *self, NMDevice *device);
+
+void nm_settings_device_removed(NMSettings *self, NMDevice *device, gboolean quitting);
+
+const char *nm_settings_get_startup_complete_blocked_reason(NMSettings *self,
+                                                            gboolean    force_reload);
+
+void nm_settings_kf_db_write(NMSettings *settings);
+
+#endif /* __NM_SETTINGS_H__ */
diff --git a/src/core/settings/plugins/ifcfg-rh/meson.build b/src/core/settings/plugins/ifcfg-rh/meson.build
new file mode 100644
index 00000000..a2f11a92
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/meson.build
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+install_data(
+  'nm-ifcfg-rh.conf',
+  install_dir: dbus_conf_dir,
+)
+
+dbus_sources = gnome.gdbus_codegen(
+  'nmdbus-ifcfg-rh',
+  'nm-ifcfg-rh.xml',
+  interface_prefix: 'com.redhat',
+  namespace: 'NMDBus',
+)
+
+libnmdbus_ifcfg_rh = static_library(
+  'nmdbus-ifcfg-rh',
+  sources: dbus_sources,
+  dependencies: glib_dep,
+  c_args: introspection_extra_cflags,
+)
+
+libnms_ifcfg_rh_core = static_library(
+  'nms-ifcfg-rh-core',
+  sources: files(
+    'nms-ifcfg-rh-reader.c',
+    'nms-ifcfg-rh-utils.c',
+    'nms-ifcfg-rh-writer.c',
+    'shvar.c',
+  ),
+  dependencies: core_default_dep,
+  c_args: daemon_c_flags,
+)
+
+libnm_settings_plugin_ifcfg_rh = shared_module(
+  'nm-settings-plugin-ifcfg-rh',
+  sources: files(
+    'nms-ifcfg-rh-storage.c',
+    'nms-ifcfg-rh-plugin.c',
+  ),
+  dependencies: core_plugin_dep,
+  c_args: daemon_c_flags,
+  link_with: libnms_ifcfg_rh_core,
+  link_args: ldflags_linker_script_settings,
+  link_depends: linker_script_settings,
+  install: true,
+  install_dir: nm_plugindir,
+)
+
+core_plugins += libnm_settings_plugin_ifcfg_rh
+
+install_data(
+  [
+    'nm-ifdown',
+    'nm-ifup',
+  ],
+  install_dir: nm_libexecdir,
+  install_mode: 'rwxr-xr-x',
+)
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
new file mode 100644
index 00000000..c7a1730a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
@@ -0,0 +1,11 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+    <policy context="default">
+        <allow send_destination="com.redhat.ifcfgrh1"/>
+    </policy>
+    <policy user="root">
+        <allow own="com.redhat.ifcfgrh1"/>
+    </policy>
+</busconfig>
diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml
new file mode 100644
index 00000000..033fbfaa
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/">
+
+  <!--
+      com.redhat.ifcfgrh1:
+
+      Utility methods for handling NM integration with standard Red Hat ifcfg
+      files.
+  -->
+  <interface name="com.redhat.ifcfgrh1">
+
+    <!--
+        GetIfcfgDetails:
+        @ifcfg: The full path to an ifcfg file.
+        @uuid: The UUID of the NM connection backed by this ifcfg file. If the ifcfg file does not contain a UUID tag, this UUID is generated by NM, otherwise the UUID from the ifcfg file is used.
+        @path: The object path of the NM connected backed by this ifcfg file.
+
+        Given an ifcfg file, return various internal information about it.
+    -->
+    <method name="GetIfcfgDetails">
+      <arg name="ifcfg" type="s" direction="in"/>
+      <arg name="uuid" type="s" direction="out"/>
+      <arg name="path" type="o" direction="out"/>
+    </method>
+  </interface>
+</node>
diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifdown b/src/core/settings/plugins/ifcfg-rh/nm-ifdown
new file mode 100644
index 00000000..e7bd73ae
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifdown
@@ -0,0 +1,3 @@
+#!/bin/sh
+nmcli connection load "/etc/sysconfig/network-scripts/ifcfg-$1" &&
+exec nmcli connection down filename "/etc/sysconfig/network-scripts/ifcfg-$1"
diff --git a/src/core/settings/plugins/ifcfg-rh/nm-ifup b/src/core/settings/plugins/ifcfg-rh/nm-ifup
new file mode 100644
index 00000000..96637a92
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifup
@@ -0,0 +1,3 @@
+#!/bin/sh
+nmcli connection load "/etc/sysconfig/network-scripts/ifcfg-$1" &&
+exec nmcli connection up filename "/etc/sysconfig/network-scripts/ifcfg-$1"
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
new file mode 100644
index 00000000..79c67e48
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 - 2013 Red Hat, Inc.
+ */
+
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#define IFCFG_TAG  "ifcfg-"
+#define KEYS_TAG   "keys-"
+#define ROUTE_TAG  "route-"
+#define RULE_TAG   "rule-"
+#define ROUTE6_TAG "route6-"
+#define RULE6_TAG  "rule6-"
+
+#define BAK_TAG    ".bak"
+#define TILDE_TAG  "~"
+#define ORIG_TAG   ".orig"
+#define REJ_TAG    ".rej"
+#define RPMNEW_TAG ".rpmnew"
+#define AUGNEW_TAG ".augnew"
+#define AUGTMP_TAG ".augtmp"
+
+#define IFCFG_DIR SYSCONFDIR "/sysconfig/network-scripts"
+
+#define TYPE_ETHERNET   "Ethernet"
+#define TYPE_WIRELESS   "Wireless"
+#define TYPE_INFINIBAND "InfiniBand"
+#define TYPE_BRIDGE     "Bridge"
+#define TYPE_BOND       "Bond"
+#define TYPE_VLAN       "Vlan"
+#define TYPE_TEAM       "Team"
+#define TYPE_TEAM_PORT  "TeamPort"
+
+#define SECRET_FLAG_AGENT        "user"
+#define SECRET_FLAG_NOT_SAVED    "ask"
+#define SECRET_FLAG_NOT_REQUIRED "unused"
+
+/* DCB key names */
+#define KEY_DCB_APP_FCOE_ENABLE     "DCB_APP_FCOE_ENABLE"
+#define KEY_DCB_APP_FCOE_ADVERTISE  "DCB_APP_FCOE_ADVERTISE"
+#define KEY_DCB_APP_FCOE_WILLING    "DCB_APP_FCOE_WILLING"
+#define KEY_DCB_APP_FCOE_MODE       "DCB_APP_FCOE_MODE"
+#define KEY_DCB_APP_ISCSI_ENABLE    "DCB_APP_ISCSI_ENABLE"
+#define KEY_DCB_APP_ISCSI_ADVERTISE "DCB_APP_ISCSI_ADVERTISE"
+#define KEY_DCB_APP_ISCSI_WILLING   "DCB_APP_ISCSI_WILLING"
+#define KEY_DCB_APP_FIP_ENABLE      "DCB_APP_FIP_ENABLE"
+#define KEY_DCB_APP_FIP_ADVERTISE   "DCB_APP_FIP_ADVERTISE"
+#define KEY_DCB_APP_FIP_WILLING     "DCB_APP_FIP_WILLING"
+#define KEY_DCB_PFC_ENABLE          "DCB_PFC_ENABLE"
+#define KEY_DCB_PFC_ADVERTISE       "DCB_PFC_ADVERTISE"
+#define KEY_DCB_PFC_WILLING         "DCB_PFC_WILLING"
+#define KEY_DCB_PFC_UP              "DCB_PFC_UP"
+#define KEY_DCB_PG_ENABLE           "DCB_PG_ENABLE"
+#define KEY_DCB_PG_ADVERTISE        "DCB_PG_ADVERTISE"
+#define KEY_DCB_PG_WILLING          "DCB_PG_WILLING"
+#define KEY_DCB_PG_ID               "DCB_PG_ID"
+#define KEY_DCB_PG_PCT              "DCB_PG_PCT"
+#define KEY_DCB_PG_UPPCT            "DCB_PG_UPPCT"
+#define KEY_DCB_PG_STRICT           "DCB_PG_STRICT"
+#define KEY_DCB_PG_UP2TC            "DCB_PG_UP2TC"
+
+#endif /* __COMMON_H__ */
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
new file mode 100644
index 00000000..d114db0f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -0,0 +1,1253 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Dan Williams <dcbw@redhat.com>
+ * Søren Sandmann <sandmann@daimi.au.dk>
+ * Copyright (C) 2007 - 2011 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifcfg-rh-plugin.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-io-utils.h"
+#include "nm-std-aux/nm-dbus-compat.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-config.h"
+#include "nm-dbus-manager.h"
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-utils.h"
+#include "NetworkManagerUtils.h"
+
+#include "nms-ifcfg-rh-storage.h"
+#include "nms-ifcfg-rh-common.h"
+#include "nms-ifcfg-rh-utils.h"
+#include "nms-ifcfg-rh-reader.h"
+#include "nms-ifcfg-rh-writer.h"
+
+#define IFCFGRH1_BUS_NAME                        "com.redhat.ifcfgrh1"
+#define IFCFGRH1_OBJECT_PATH                     "/com/redhat/ifcfgrh1"
+#define IFCFGRH1_IFACE1_NAME                     "com.redhat.ifcfgrh1"
+#define IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS "GetIfcfgDetails"
+
+/*****************************************************************************/
+
+typedef struct {
+    NMConfig *config;
+
+    struct {
+        GDBusConnection *connection;
+        GCancellable *   cancellable;
+        gulong           signal_id;
+        guint            regist_id;
+    } dbus;
+
+    NMSettUtilStorages storages;
+
+    GHashTable *unmanaged_specs;
+    GHashTable *unrecognized_specs;
+
+} NMSIfcfgRHPluginPrivate;
+
+struct _NMSIfcfgRHPlugin {
+    NMSettingsPlugin        parent;
+    NMSIfcfgRHPluginPrivate _priv;
+};
+
+struct _NMSIfcfgRHPluginClass {
+    NMSettingsPluginClass parent;
+};
+
+G_DEFINE_TYPE(NMSIfcfgRHPlugin, nms_ifcfg_rh_plugin, NM_TYPE_SETTINGS_PLUGIN)
+
+#define NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMSIfcfgRHPlugin, NMS_IS_IFCFG_RH_PLUGIN, NMSettingsPlugin)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_SETTINGS
+#define _NMLOG(level, ...)                                      \
+    G_STMT_START                                                \
+    {                                                           \
+        nm_log((level),                                         \
+               (_NMLOG_DOMAIN),                                 \
+               NULL,                                            \
+               NULL,                                            \
+               "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__),         \
+               "ifcfg-rh: " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                           \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static void _unhandled_specs_reset(NMSIfcfgRHPlugin *self);
+
+static void _unhandled_specs_merge_storages(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages);
+
+/*****************************************************************************/
+
+static void
+nm_assert_self(NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent)
+{
+    nm_assert(NMS_IS_IFCFG_RH_PLUGIN(self));
+
+#if NM_MORE_ASSERTS > 5
+    {
+        NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+        NMSIfcfgRHStorage *      storage;
+        gsize                    n_uuid;
+        gs_unref_hashtable GHashTable *h_unmanaged    = NULL;
+        gs_unref_hashtable GHashTable *h_unrecognized = NULL;
+
+        nm_assert(g_hash_table_size(priv->storages.idx_by_filename)
+                  == c_list_length(&priv->storages._storage_lst_head));
+
+        h_unmanaged    = g_hash_table_new(nm_str_hash, g_str_equal);
+        h_unrecognized = g_hash_table_new(nm_str_hash, g_str_equal);
+
+        n_uuid = 0;
+
+        c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) {
+            const char *uuid;
+            const char *filename;
+
+            filename = nms_ifcfg_rh_storage_get_filename(storage);
+
+            nm_assert(filename && NM_STR_HAS_PREFIX(filename, IFCFG_DIR "/"));
+
+            uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage);
+
+            nm_assert((!!uuid) + (!!storage->unmanaged_spec) + (!!storage->unrecognized_spec) == 1);
+
+            nm_assert(storage
+                      == nm_sett_util_storages_lookup_by_filename(&priv->storages, filename));
+
+            if (uuid) {
+                NMSettUtilStorageByUuidHead *sbuh;
+                NMSettUtilStorageByUuidHead *sbuh2;
+
+                if (storage->connection)
+                    nm_assert(nm_streq0(nm_connection_get_uuid(storage->connection), uuid));
+
+                if (!g_hash_table_lookup_extended(priv->storages.idx_by_uuid,
+                                                  &uuid,
+                                                  (gpointer *) &sbuh,
+                                                  (gpointer *) &sbuh2))
+                    nm_assert_not_reached();
+
+                nm_assert(sbuh);
+                nm_assert(nm_streq(uuid, sbuh->uuid));
+                nm_assert(sbuh == sbuh2);
+                nm_assert(c_list_contains(&sbuh->_storage_by_uuid_lst_head,
+                                          &storage->parent._storage_by_uuid_lst));
+
+                if (c_list_first(&sbuh->_storage_by_uuid_lst_head)
+                    == &storage->parent._storage_by_uuid_lst)
+                    n_uuid++;
+            } else if (storage->unmanaged_spec) {
+                nm_assert(strlen(storage->unmanaged_spec) > 0);
+                g_hash_table_add(h_unmanaged, storage->unmanaged_spec);
+            } else if (storage->unrecognized_spec) {
+                nm_assert(strlen(storage->unrecognized_spec) > 0);
+                g_hash_table_add(h_unrecognized, storage->unrecognized_spec);
+            } else
+                nm_assert_not_reached();
+
+            nm_assert(!storage->connection);
+        }
+
+        nm_assert(g_hash_table_size(priv->storages.idx_by_uuid) == n_uuid);
+
+        if (unhandled_specs_consistent) {
+            nm_assert(nm_utils_hashtable_same_keys(h_unmanaged, priv->unmanaged_specs));
+            nm_assert(nm_utils_hashtable_same_keys(h_unrecognized, priv->unrecognized_specs));
+        }
+    }
+#endif
+}
+
+/*****************************************************************************/
+
+static NMSIfcfgRHStorage *
+_load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    gs_free_error GError *load_error         = NULL;
+    gs_free char *        unhandled_spec     = NULL;
+    gboolean              load_error_ignore;
+    struct stat           st;
+
+    if (stat(filename, &st) != 0) {
+        int errsv = errno;
+
+        if (error) {
+            nm_utils_error_set_errno(error, errsv, "failure to stat file \%s\": %s", filename);
+        } else
+            _LOGT("load[%s]: failure to stat file: %s", filename, nm_strerror_native(errsv));
+        return NULL;
+    }
+
+    connection = connection_from_file(filename, &unhandled_spec, &load_error, &load_error_ignore);
+    if (load_error) {
+        if (error) {
+            nm_utils_error_set(error,
+                               NM_UTILS_ERROR_UNKNOWN,
+                               "failure to read file \"%s\": %s",
+                               filename,
+                               load_error->message);
+        } else {
+            _NMLOG(load_error_ignore ? LOGL_TRACE : LOGL_WARN,
+                   "load[%s]: failure to read file: %s",
+                   filename,
+                   load_error->message);
+        }
+        return NULL;
+    }
+
+    if (unhandled_spec) {
+        const char *unmanaged_spec;
+        const char *unrecognized_spec;
+
+        if (!nms_ifcfg_rh_utils_parse_unhandled_spec(unhandled_spec,
+                                                     &unmanaged_spec,
+                                                     &unrecognized_spec)) {
+            nm_utils_error_set(error,
+                               NM_UTILS_ERROR_UNKNOWN,
+                               "invalid unhandled spec \"%s\"",
+                               unhandled_spec);
+            nm_assert_not_reached();
+            return NULL;
+        }
+        return nms_ifcfg_rh_storage_new_unhandled(self,
+                                                  filename,
+                                                  unmanaged_spec,
+                                                  unrecognized_spec);
+    }
+
+    return nms_ifcfg_rh_storage_new_connection(self,
+                                               filename,
+                                               g_steal_pointer(&connection),
+                                               &st.st_mtim);
+}
+
+static void
+_load_dir(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages)
+{
+    gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+    gs_free_error GError *local                   = NULL;
+    const char *          f_filename;
+    GDir *                dir;
+
+    dir = g_dir_open(IFCFG_DIR, 0, &local);
+    if (!dir) {
+        _LOGT("Could not read directory '%s': %s", IFCFG_DIR, local->message);
+        return;
+    }
+
+    dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free);
+
+    while ((f_filename = g_dir_read_name(dir))) {
+        gs_free char *     full_path = NULL;
+        NMSIfcfgRHStorage *storage;
+        char *             full_filename;
+
+        full_path     = g_build_filename(IFCFG_DIR, f_filename, NULL);
+        full_filename = utils_detect_ifcfg_path(full_path, TRUE);
+        if (!full_filename)
+            continue;
+
+        if (!g_hash_table_add(dupl_filenames, full_filename))
+            continue;
+
+        nm_assert(!nm_sett_util_storages_lookup_by_filename(storages, full_filename));
+
+        storage = _load_file(self, full_filename, NULL);
+        if (storage)
+            nm_sett_util_storages_add_take(storages, storage);
+    }
+    g_dir_close(dir);
+}
+
+static void
+_storages_consolidate(NMSIfcfgRHPlugin *                     self,
+                      NMSettUtilStorages *                   storages_new,
+                      gboolean                               replace_all,
+                      GHashTable *                           storages_replaced,
+                      NMSettingsPluginConnectionLoadCallback callback,
+                      gpointer                               user_data)
+{
+    NMSIfcfgRHPluginPrivate *priv                  = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    CList                    lst_conn_info_deleted = C_LIST_INIT(lst_conn_info_deleted);
+    gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+    CList                        storages_deleted;
+    NMSIfcfgRHStorage *          storage_safe;
+    NMSIfcfgRHStorage *          storage_new;
+    NMSIfcfgRHStorage *          storage_old;
+    NMSIfcfgRHStorage *          storage;
+    guint                        i;
+
+    /* when we reload all files, we must signal add/update/modify of profiles one-by-one.
+     * NMSettings then goes ahead and emits further signals and a lot of things happen.
+     *
+     * So, first, emit an update of the unmanaged/unrecognized specs that contains *all*
+     * the unmanaged/unrecognized devices from before and after. Since both unmanaged/unrecognized
+     * specs have the meaning of "not doing something", it makes sense that we temporarily
+     * disable that action for the sum of before and after. */
+    _unhandled_specs_merge_storages(self, storages_new);
+
+    storages_modified = g_ptr_array_new_with_free_func(g_object_unref);
+    c_list_init(&storages_deleted);
+
+    c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst)
+        storage_old->dirty = TRUE;
+
+    c_list_for_each_entry_safe (storage_new,
+                                storage_safe,
+                                &storages_new->_storage_lst_head,
+                                parent._storage_lst) {
+        storage_old = nm_sett_util_storages_lookup_by_filename(
+            &priv->storages,
+            nms_ifcfg_rh_storage_get_filename(storage_new));
+
+        nm_sett_util_storages_steal(storages_new, storage_new);
+
+        if (!storage_old || !nms_ifcfg_rh_storage_equal_type(storage_new, storage_old)) {
+            if (storage_old) {
+                nm_sett_util_storages_steal(&priv->storages, storage_old);
+                if (nms_ifcfg_rh_storage_get_uuid_opt(storage_old))
+                    c_list_link_tail(&storages_deleted, &storage_old->parent._storage_lst);
+                else
+                    nms_ifcfg_rh_storage_destroy(storage_old);
+            }
+            storage_new->dirty = FALSE;
+            nm_sett_util_storages_add_take(&priv->storages, storage_new);
+            g_ptr_array_add(storages_modified, g_object_ref(storage_new));
+            continue;
+        }
+
+        storage_old->dirty = FALSE;
+        nms_ifcfg_rh_storage_copy_content(storage_old, storage_new);
+        nms_ifcfg_rh_storage_destroy(storage_new);
+        g_ptr_array_add(storages_modified, g_object_ref(storage_old));
+    }
+
+    c_list_for_each_entry_safe (storage_old,
+                                storage_safe,
+                                &priv->storages._storage_lst_head,
+                                parent._storage_lst) {
+        if (!storage_old->dirty)
+            continue;
+        if (replace_all
+            || (storages_replaced && g_hash_table_contains(storages_replaced, storage_old))) {
+            nm_sett_util_storages_steal(&priv->storages, storage_old);
+            if (nms_ifcfg_rh_storage_get_uuid_opt(storage_old))
+                c_list_link_tail(&storages_deleted, &storage_old->parent._storage_lst);
+            else
+                nms_ifcfg_rh_storage_destroy(storage_old);
+        }
+    }
+
+    /* raise events. */
+
+    for (i = 0; i < storages_modified->len; i++) {
+        storage        = storages_modified->pdata[i];
+        storage->dirty = TRUE;
+    }
+
+    for (i = 0; i < storages_modified->len; i++) {
+        gs_unref_object NMConnection *connection = NULL;
+        storage                                  = storages_modified->pdata[i];
+
+        if (!storage->dirty) {
+            /* the entry is no longer dirty. In the meantime we already emitted
+             * another signal for it. */
+            continue;
+        }
+        storage->dirty = FALSE;
+        if (storage
+            != nm_sett_util_storages_lookup_by_filename(
+                &priv->storages,
+                nms_ifcfg_rh_storage_get_filename(storage))) {
+            /* hm? The profile was deleted in the meantime? That is only possible
+             * if the signal handler called again into the plugin. In any case, the event
+             * was already emitted. Skip. */
+            continue;
+        }
+
+        connection = nms_ifcfg_rh_storage_steal_connection(storage);
+        if (!connection) {
+            nm_assert(!nms_ifcfg_rh_storage_get_uuid_opt(storage));
+            continue;
+        }
+
+        nm_assert(NM_IS_CONNECTION(connection));
+        nm_assert(nms_ifcfg_rh_storage_get_uuid_opt(storage));
+        callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), connection, user_data);
+    }
+
+    while (
+        (storage = c_list_first_entry(&storages_deleted, NMSIfcfgRHStorage, parent._storage_lst))) {
+        c_list_unlink(&storage->parent._storage_lst);
+        callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), NULL, user_data);
+        nms_ifcfg_rh_storage_destroy(storage);
+    }
+}
+
+/*****************************************************************************/
+
+static void
+load_connections(NMSettingsPlugin *                     plugin,
+                 NMSettingsPluginConnectionLoadEntry *  entries,
+                 gsize                                  n_entries,
+                 NMSettingsPluginConnectionLoadCallback callback,
+                 gpointer                               user_data)
+{
+    NMSIfcfgRHPlugin *       self = NMS_IFCFG_RH_PLUGIN(plugin);
+    NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new =
+        NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_ifcfg_rh_storage_destroy);
+    gs_unref_hashtable GHashTable *dupl_filenames    = NULL;
+    gs_unref_hashtable GHashTable *storages_replaced = NULL;
+    gs_unref_hashtable GHashTable *loaded_uuids      = NULL;
+    const char *                   loaded_uuid;
+    GHashTableIter                 h_iter;
+    gsize                          i;
+
+    if (n_entries == 0)
+        return;
+
+    dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+
+    loaded_uuids = g_hash_table_new(nm_str_hash, g_str_equal);
+
+    storages_replaced = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL);
+
+    for (i = 0; i < n_entries; i++) {
+        NMSettingsPluginConnectionLoadEntry *const entry = &entries[i];
+        gs_free_error GError *               local       = NULL;
+        const char *                         full_filename;
+        const char *                         uuid;
+        gs_free char *                       full_filename_keep = NULL;
+        NMSettingsPluginConnectionLoadEntry *dupl_content_entry;
+        gs_unref_object NMSIfcfgRHStorage *storage = NULL;
+
+        if (entry->handled)
+            continue;
+
+        if (entry->filename[0] != '/')
+            continue;
+
+        full_filename_keep = utils_detect_ifcfg_path(entry->filename, FALSE);
+
+        if (!full_filename_keep) {
+            if (nm_utils_file_is_in_path(entry->filename, IFCFG_DIR)) {
+                nm_utils_error_set(&entry->error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   ("path is not a valid name for an ifcfg-rh file"));
+                entry->handled = TRUE;
+            }
+            continue;
+        }
+
+        if ((dupl_content_entry = g_hash_table_lookup(dupl_filenames, full_filename_keep))) {
+            /* we already visited this file. */
+            entry->handled = dupl_content_entry->handled;
+            if (dupl_content_entry->error) {
+                g_set_error_literal(&entry->error,
+                                    dupl_content_entry->error->domain,
+                                    dupl_content_entry->error->code,
+                                    dupl_content_entry->error->message);
+            }
+            continue;
+        }
+
+        entry->handled = TRUE;
+
+        full_filename = full_filename_keep;
+        if (!g_hash_table_insert(dupl_filenames, g_steal_pointer(&full_filename_keep), entry))
+            nm_assert_not_reached();
+
+        storage = _load_file(self, full_filename, &local);
+        if (!storage) {
+            if (nm_utils_file_stat(full_filename, NULL) == -ENOENT) {
+                NMSIfcfgRHStorage *storage2;
+
+                /* the file does not exist. We take that as indication to unload the file
+                 * that was previously loaded... */
+                storage2 = nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename);
+                if (storage2)
+                    g_hash_table_add(storages_replaced, g_object_ref(storage2));
+                continue;
+            }
+            g_propagate_error(&entry->error, g_steal_pointer(&local));
+            continue;
+        }
+
+        uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage);
+        if (uuid)
+            g_hash_table_add(loaded_uuids, (char *) uuid);
+
+        nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage));
+    }
+
+    /* now we visit all UUIDs that are about to change... */
+    g_hash_table_iter_init(&h_iter, loaded_uuids);
+    while (g_hash_table_iter_next(&h_iter, (gpointer *) &loaded_uuid, NULL)) {
+        NMSIfcfgRHStorage *          storage;
+        NMSettUtilStorageByUuidHead *sbuh;
+
+        sbuh = nm_sett_util_storages_lookup_by_uuid(&priv->storages, loaded_uuid);
+        if (!sbuh)
+            continue;
+
+        c_list_for_each_entry (storage,
+                               &sbuh->_storage_by_uuid_lst_head,
+                               parent._storage_by_uuid_lst) {
+            const char *    full_filename = nms_ifcfg_rh_storage_get_filename(storage);
+            gs_unref_object NMSIfcfgRHStorage *storage_new = NULL;
+            gs_free_error GError *local                    = NULL;
+
+            if (g_hash_table_contains(dupl_filenames, full_filename)) {
+                /* already re-loaded. */
+                continue;
+            }
+
+            /* @storage has a UUID that was just loaded from disk, but we have an entry in cache.
+             * Reload that file too despite not being told to do so. The reason is to get
+             * the latest file timestamp so that we get the priorities right. */
+
+            storage_new = _load_file(self, full_filename, &local);
+            if (storage_new
+                && !nm_streq0(loaded_uuid, nms_ifcfg_rh_storage_get_uuid_opt(storage_new))) {
+                /* the file now references a different UUID. We are not told to reload
+                 * that file, so this means the existing storage (with the previous
+                 * filename and UUID tuple) is no longer valid. */
+                g_clear_object(&storage_new);
+            }
+
+            g_hash_table_add(storages_replaced, g_object_ref(storage));
+            if (storage_new)
+                nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage_new));
+        }
+    }
+
+    nm_clear_pointer(&loaded_uuids, g_hash_table_destroy);
+    nm_clear_pointer(&dupl_filenames, g_hash_table_destroy);
+
+    _storages_consolidate(self, &storages_new, FALSE, storages_replaced, callback, user_data);
+}
+
+static void
+reload_connections(NMSettingsPlugin *                     plugin,
+                   NMSettingsPluginConnectionLoadCallback callback,
+                   gpointer                               user_data)
+{
+    NMSIfcfgRHPlugin *                                  self = NMS_IFCFG_RH_PLUGIN(plugin);
+    nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new =
+        NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_ifcfg_rh_storage_destroy);
+
+    nm_assert_self(self, TRUE);
+
+    _load_dir(self, &storages_new);
+
+    _storages_consolidate(self, &storages_new, TRUE, NULL, callback, user_data);
+
+    nm_assert_self(self, FALSE);
+}
+
+static void
+load_connections_done(NMSettingsPlugin *plugin)
+{
+    NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin);
+
+    /* at the beginning of a load, we emit a change signal for unmanaged/unrecognized
+     * specs that contain the sum of before and after (_unhandled_specs_merge_storages()).
+     *
+     * The idea is that while we emit signals about changes to connection, we have
+     * the sum of all unmanaged/unrecognized devices from before and after.
+     *
+     * This if triggered at the end, to reset the specs. */
+    _unhandled_specs_reset(self);
+
+    nm_assert_self(self, TRUE);
+}
+
+/*****************************************************************************/
+
+static gboolean
+add_connection(NMSettingsPlugin *  plugin,
+               NMConnection *      connection,
+               NMSettingsStorage **out_storage,
+               NMConnection **     out_connection,
+               GError **           error)
+{
+    NMSIfcfgRHPlugin *       self               = NMS_IFCFG_RH_PLUGIN(plugin);
+    NMSIfcfgRHPluginPrivate *priv               = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    gs_unref_object NMSIfcfgRHStorage *storage  = NULL;
+    gs_unref_object NMConnection *reread        = NULL;
+    gs_free char *                full_filename = NULL;
+    GError *                      local         = NULL;
+    gboolean                      reread_same;
+    struct timespec               mtime;
+
+    nm_assert_self(self, TRUE);
+    nm_assert(NM_IS_CONNECTION(connection));
+    nm_assert(out_storage && !*out_storage);
+    nm_assert(out_connection && !*out_connection);
+
+    if (!nms_ifcfg_rh_writer_write_connection(
+            connection,
+            IFCFG_DIR,
+            NULL,
+            nm_sett_util_allow_filename_cb,
+            NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, NULL),
+            &full_filename,
+            &reread,
+            &reread_same,
+            &local)) {
+        _LOGT("commit: %s (%s): failed to add: %s",
+              nm_connection_get_uuid(connection),
+              nm_connection_get_id(connection),
+              local->message);
+        g_propagate_error(error, local);
+        return FALSE;
+    }
+
+    if (!reread || reread_same)
+        nm_g_object_ref_set(&reread, connection);
+
+    nm_assert(full_filename && full_filename[0] == '/');
+
+    _LOGT("commit: %s (%s) added as \"%s\"",
+          nm_connection_get_uuid(reread),
+          nm_connection_get_id(reread),
+          full_filename);
+
+    storage =
+        nms_ifcfg_rh_storage_new_connection(self,
+                                            full_filename,
+                                            g_steal_pointer(&reread),
+                                            nm_sett_util_stat_mtime(full_filename, FALSE, &mtime));
+
+    nm_sett_util_storages_add_take(&priv->storages, g_object_ref(storage));
+
+    *out_connection = nms_ifcfg_rh_storage_steal_connection(storage);
+    *out_storage    = NM_SETTINGS_STORAGE(g_steal_pointer(&storage));
+
+    nm_assert_self(self, TRUE);
+
+    return TRUE;
+}
+
+static gboolean
+update_connection(NMSettingsPlugin *  plugin,
+                  NMSettingsStorage * storage_x,
+                  NMConnection *      connection,
+                  NMSettingsStorage **out_storage,
+                  NMConnection **     out_connection,
+                  GError **           error)
+{
+    NMSIfcfgRHPlugin *       self    = NMS_IFCFG_RH_PLUGIN(plugin);
+    NMSIfcfgRHPluginPrivate *priv    = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    NMSIfcfgRHStorage *      storage = NMS_IFCFG_RH_STORAGE(storage_x);
+    const char *             full_filename;
+    const char *             uuid;
+    GError *                 local       = NULL;
+    gs_unref_object NMConnection *reread = NULL;
+    gboolean                      reread_same;
+    struct timespec               mtime;
+
+    nm_assert_self(self, TRUE);
+    nm_assert(NM_IS_CONNECTION(connection));
+    nm_assert(NMS_IS_IFCFG_RH_STORAGE(storage));
+    nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(!error || !*error);
+
+    uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage);
+
+    nm_assert(uuid && nm_streq0(uuid, nm_connection_get_uuid(connection)));
+
+    full_filename = nms_ifcfg_rh_storage_get_filename(storage);
+
+    nm_assert(full_filename);
+    nm_assert(storage == nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename));
+
+    if (!nms_ifcfg_rh_writer_write_connection(
+            connection,
+            IFCFG_DIR,
+            full_filename,
+            nm_sett_util_allow_filename_cb,
+            NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, full_filename),
+            NULL,
+            &reread,
+            &reread_same,
+            &local)) {
+        _LOGT("commit: failure to write %s (%s) to \"%s\": %s",
+              nm_connection_get_uuid(connection),
+              nm_connection_get_id(connection),
+              full_filename,
+              local->message);
+        g_propagate_error(error, local);
+        return FALSE;
+    }
+
+    if (!reread || reread_same)
+        nm_g_object_ref_set(&reread, connection);
+
+    _LOGT("commit: \"%s\": profile %s (%s) written",
+          full_filename,
+          uuid,
+          nm_connection_get_id(connection));
+
+    storage->stat_mtime = *nm_sett_util_stat_mtime(full_filename, FALSE, &mtime);
+
+    *out_storage    = NM_SETTINGS_STORAGE(g_object_ref(storage));
+    *out_connection = g_steal_pointer(&reread);
+
+    nm_assert_self(self, TRUE);
+
+    return TRUE;
+}
+
+static gboolean
+delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError **error)
+{
+    NMSIfcfgRHPlugin *       self    = NMS_IFCFG_RH_PLUGIN(plugin);
+    NMSIfcfgRHPluginPrivate *priv    = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    NMSIfcfgRHStorage *      storage = NMS_IFCFG_RH_STORAGE(storage_x);
+    const char *             operation_message;
+    const char *             full_filename;
+
+    nm_assert_self(self, TRUE);
+    nm_assert(!error || !*error);
+    nm_assert(NMS_IS_IFCFG_RH_STORAGE(storage));
+
+    full_filename = nms_ifcfg_rh_storage_get_filename(storage);
+    nm_assert(full_filename);
+
+    nm_assert(nms_ifcfg_rh_storage_get_uuid_opt(storage));
+
+    nm_assert(storage == nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename));
+
+    {
+        gs_free char *    keyfile     = utils_get_keys_path(full_filename);
+        gs_free char *    routefile   = utils_get_route_path(full_filename);
+        gs_free char *    route6file  = utils_get_route6_path(full_filename);
+        const char *const files[]     = {full_filename, keyfile, routefile, route6file};
+        gboolean          any_deleted = FALSE;
+        gboolean          any_failure = FALSE;
+        int               i;
+
+        for (i = 0; i < G_N_ELEMENTS(files); i++) {
+            int errsv;
+
+            if (unlink(files[i]) == 0) {
+                any_deleted = TRUE;
+                continue;
+            }
+            errsv = errno;
+            if (errsv == ENOENT)
+                continue;
+
+            _LOGW("commit: failure to delete file \"%s\": %s", files[i], nm_strerror_native(errsv));
+            any_failure = TRUE;
+        }
+        if (any_failure)
+            operation_message = "failed to delete files from disk";
+        else if (any_deleted)
+            operation_message = "deleted from disk";
+        else
+            operation_message = "does not exist on disk";
+    }
+
+    _LOGT("commit: deleted \"%s\", profile %s (%s)",
+          full_filename,
+          nms_ifcfg_rh_storage_get_uuid_opt(storage),
+          operation_message);
+
+    nm_sett_util_storages_steal(&priv->storages, storage);
+    nms_ifcfg_rh_storage_destroy(storage);
+
+    nm_assert_self(self, TRUE);
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+_unhandled_specs_reset(NMSIfcfgRHPlugin *self)
+{
+    NMSIfcfgRHPluginPrivate *priv                     = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    gs_unref_hashtable GHashTable *unmanaged_specs    = NULL;
+    gs_unref_hashtable GHashTable *unrecognized_specs = NULL;
+    NMSIfcfgRHStorage *            storage;
+
+    unmanaged_specs    = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+    unrecognized_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+
+    c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) {
+        if (storage->unmanaged_spec)
+            g_hash_table_add(unmanaged_specs, g_strdup(storage->unmanaged_spec));
+        if (storage->unrecognized_spec)
+            g_hash_table_add(unrecognized_specs, g_strdup(storage->unrecognized_spec));
+    }
+
+    if (!nm_utils_hashtable_same_keys(unmanaged_specs, priv->unmanaged_specs)) {
+        g_hash_table_unref(priv->unmanaged_specs);
+        priv->unmanaged_specs = g_steal_pointer(&unmanaged_specs);
+    }
+    if (!nm_utils_hashtable_same_keys(unrecognized_specs, priv->unrecognized_specs)) {
+        g_hash_table_unref(priv->unrecognized_specs);
+        priv->unrecognized_specs = g_steal_pointer(&unrecognized_specs);
+    }
+
+    if (!unmanaged_specs)
+        _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self));
+    if (!unrecognized_specs)
+        _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NM_SETTINGS_PLUGIN(self));
+}
+
+static void
+_unhandled_specs_merge_storages(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages)
+{
+    NMSIfcfgRHPluginPrivate *priv                 = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    gboolean                 unmanaged_changed    = FALSE;
+    gboolean                 unrecognized_changed = FALSE;
+    NMSIfcfgRHStorage *      storage;
+
+    c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst) {
+        if (storage->unmanaged_spec
+            && !g_hash_table_contains(priv->unmanaged_specs, storage->unmanaged_spec)) {
+            unmanaged_changed = TRUE;
+            g_hash_table_add(priv->unmanaged_specs, g_strdup(storage->unmanaged_spec));
+        }
+        if (storage->unrecognized_spec
+            && !g_hash_table_contains(priv->unrecognized_specs, storage->unrecognized_spec)) {
+            unrecognized_changed = TRUE;
+            g_hash_table_add(priv->unrecognized_specs, g_strdup(storage->unrecognized_spec));
+        }
+    }
+
+    if (unmanaged_changed)
+        _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self));
+    if (unrecognized_changed)
+        _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NM_SETTINGS_PLUGIN(self));
+}
+
+static GSList *
+_unhandled_specs_from_hashtable(GHashTable *hash)
+{
+    gs_free const char **keys = NULL;
+    GSList *             list = NULL;
+    guint                i, l;
+
+    keys = nm_utils_strdict_get_keys(hash, TRUE, &l);
+    for (i = l; i > 0;) {
+        i--;
+        list = g_slist_prepend(list, g_strdup(keys[i]));
+    }
+    return list;
+}
+
+static GSList *
+get_unmanaged_specs(NMSettingsPlugin *plugin)
+{
+    return _unhandled_specs_from_hashtable(
+        NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(plugin)->unmanaged_specs);
+}
+
+static GSList *
+get_unrecognized_specs(NMSettingsPlugin *plugin)
+{
+    return _unhandled_specs_from_hashtable(
+        NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(plugin)->unrecognized_specs);
+}
+
+/*****************************************************************************/
+
+static void
+impl_ifcfgrh_get_ifcfg_details(NMSIfcfgRHPlugin *     self,
+                               GDBusMethodInvocation *context,
+                               const char *           in_ifcfg)
+{
+    NMSIfcfgRHPluginPrivate *priv       = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    gs_free char *           ifcfg_path = NULL;
+    NMSIfcfgRHStorage *      storage;
+    const char *             uuid;
+    const char *             path;
+
+    if (in_ifcfg[0] != '/') {
+        g_dbus_method_invocation_return_error(context,
+                                              NM_SETTINGS_ERROR,
+                                              NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                              "ifcfg path '%s' is not absolute",
+                                              in_ifcfg);
+        return;
+    }
+
+    ifcfg_path = utils_detect_ifcfg_path(in_ifcfg, TRUE);
+    if (!ifcfg_path) {
+        g_dbus_method_invocation_return_error(context,
+                                              NM_SETTINGS_ERROR,
+                                              NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                              "ifcfg path '%s' is not an ifcfg base file",
+                                              in_ifcfg);
+        return;
+    }
+
+    storage = nm_sett_util_storages_lookup_by_filename(&priv->storages, ifcfg_path);
+    if (!storage) {
+        g_dbus_method_invocation_return_error(context,
+                                              NM_SETTINGS_ERROR,
+                                              NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                              "ifcfg file '%s' unknown",
+                                              in_ifcfg);
+        return;
+    }
+
+    uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage);
+    if (!uuid) {
+        g_dbus_method_invocation_return_error(context,
+                                              NM_SETTINGS_ERROR,
+                                              NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                              "ifcfg file '%s' not managed by NetworkManager",
+                                              in_ifcfg);
+        return;
+    }
+
+    /* It is ugly that the ifcfg-rh plugin needs to call back into NMSettings this
+     * way.
+     * There are alternatives (like invoking a signal), but they are all significant
+     * extra code (and performance overhead). So the quick and dirty solution here
+     * is likely to be simpler than getting this right (also from point of readability!).
+     */
+    path = nm_settings_get_dbus_path_for_uuid(nm_settings_get(), uuid);
+
+    if (!path) {
+        g_dbus_method_invocation_return_error(context,
+                                              NM_SETTINGS_ERROR,
+                                              NM_SETTINGS_ERROR_FAILED,
+                                              "unable to get the connection D-Bus path");
+        return;
+    }
+
+    g_dbus_method_invocation_return_value(context, g_variant_new("(so)", uuid, path));
+}
+
+/*****************************************************************************/
+
+static void
+_dbus_clear(NMSIfcfgRHPlugin *self)
+{
+    NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    guint                    id;
+
+    nm_clear_g_signal_handler(priv->dbus.connection, &priv->dbus.signal_id);
+
+    nm_clear_g_cancellable(&priv->dbus.cancellable);
+
+    if ((id = nm_steal_int(&priv->dbus.regist_id))) {
+        if (!g_dbus_connection_unregister_object(priv->dbus.connection, id))
+            _LOGW("dbus: unexpected failure to unregister object");
+    }
+
+    g_clear_object(&priv->dbus.connection);
+}
+
+static void
+_dbus_connection_closed(GDBusConnection *connection,
+                        gboolean         remote_peer_vanished,
+                        GError *         error,
+                        gpointer         user_data)
+{
+    _LOGW("dbus: %s bus closed", IFCFGRH1_BUS_NAME);
+    _dbus_clear(NMS_IFCFG_RH_PLUGIN(user_data));
+
+    /* Retry or recover? */
+}
+
+static void
+_method_call(GDBusConnection *      connection,
+             const char *           sender,
+             const char *           object_path,
+             const char *           interface_name,
+             const char *           method_name,
+             GVariant *             parameters,
+             GDBusMethodInvocation *invocation,
+             gpointer               user_data)
+{
+    NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(user_data);
+
+    if (nm_streq(interface_name, IFCFGRH1_IFACE1_NAME)) {
+        if (nm_streq(method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) {
+            const char *ifcfg;
+
+            g_variant_get(parameters, "(&s)", &ifcfg);
+            impl_ifcfgrh_get_ifcfg_details(self, invocation, ifcfg);
+            return;
+        }
+    }
+
+    g_dbus_method_invocation_return_error(invocation,
+                                          G_DBUS_ERROR,
+                                          G_DBUS_ERROR_UNKNOWN_METHOD,
+                                          "Unknown method %s",
+                                          method_name);
+}
+
+static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO(
+    IFCFGRH1_IFACE1_NAME,
+    .methods = NM_DEFINE_GDBUS_METHOD_INFOS(
+        NM_DEFINE_GDBUS_METHOD_INFO(
+            IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS,
+            .in_args  = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("ifcfg", "s"), ),
+            .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("uuid", "s"),
+                                                  NM_DEFINE_GDBUS_ARG_INFO("path", "o"), ), ), ), );
+
+static void
+_dbus_request_name_done(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    GDBusConnection *        connection = G_DBUS_CONNECTION(source_object);
+    NMSIfcfgRHPlugin *       self;
+    NMSIfcfgRHPluginPrivate *priv;
+    gs_free_error GError *error    = NULL;
+    gs_unref_variant GVariant *ret = NULL;
+    guint32                    result;
+
+    ret = g_dbus_connection_call_finish(connection, res, &error);
+    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+    self = NMS_IFCFG_RH_PLUGIN(user_data);
+    priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+
+    g_clear_object(&priv->dbus.cancellable);
+
+    if (!ret) {
+        _LOGW("dbus: couldn't acquire D-Bus service: %s", error->message);
+        _dbus_clear(self);
+        return;
+    }
+
+    g_variant_get(ret, "(u)", &result);
+
+    if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+        _LOGW("dbus: couldn't acquire ifcfgrh1 D-Bus service (already taken)");
+        _dbus_clear(self);
+        return;
+    }
+
+    {
+        static const GDBusInterfaceVTable interface_vtable = {
+            .method_call = _method_call,
+        };
+
+        priv->dbus.regist_id = g_dbus_connection_register_object(
+            connection,
+            IFCFGRH1_OBJECT_PATH,
+            interface_info,
+            NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable),
+            self,
+            NULL,
+            &error);
+        if (!priv->dbus.regist_id) {
+            _LOGW("dbus: couldn't register D-Bus service: %s", error->message);
+            _dbus_clear(self);
+            return;
+        }
+    }
+
+    _LOGD("dbus: acquired D-Bus service %s and exported %s object",
+          IFCFGRH1_BUS_NAME,
+          IFCFGRH1_OBJECT_PATH);
+}
+
+static void
+_dbus_create_done(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    NMSIfcfgRHPlugin *       self;
+    NMSIfcfgRHPluginPrivate *priv;
+    gs_free_error GError *error = NULL;
+    GDBusConnection *     connection;
+
+    connection = g_dbus_connection_new_for_address_finish(res, &error);
+    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+    self = NMS_IFCFG_RH_PLUGIN(user_data);
+    priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+
+    g_clear_object(&priv->dbus.cancellable);
+
+    if (!connection) {
+        _LOGW("dbus: couldn't initialize system bus: %s", error->message);
+        return;
+    }
+
+    priv->dbus.connection  = connection;
+    priv->dbus.cancellable = g_cancellable_new();
+
+    priv->dbus.signal_id = g_signal_connect(priv->dbus.connection,
+                                            "closed",
+                                            G_CALLBACK(_dbus_connection_closed),
+                                            self);
+
+    g_dbus_connection_call(priv->dbus.connection,
+                           DBUS_SERVICE_DBUS,
+                           DBUS_PATH_DBUS,
+                           DBUS_INTERFACE_DBUS,
+                           "RequestName",
+                           g_variant_new("(su)", IFCFGRH1_BUS_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE),
+                           G_VARIANT_TYPE("(u)"),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           priv->dbus.cancellable,
+                           _dbus_request_name_done,
+                           self);
+}
+
+static void
+_dbus_setup(NMSIfcfgRHPlugin *self)
+{
+    NMSIfcfgRHPluginPrivate *priv    = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    gs_free char *           address = NULL;
+    gs_free_error GError *error      = NULL;
+
+    _dbus_clear(self);
+
+    if (!NM_MAIN_DBUS_CONNECTION_GET) {
+        _LOGW("dbus: don't use D-Bus for %s service", IFCFGRH1_BUS_NAME);
+        return;
+    }
+
+    /* We use a separate D-Bus connection so that org.freedesktop.NetworkManager and com.redhat.ifcfgrh1
+     * are exported by different connections. */
+    address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+    if (address == NULL) {
+        _LOGW("dbus: failed getting address for system bus: %s", error->message);
+        return;
+    }
+
+    priv->dbus.cancellable = g_cancellable_new();
+
+    g_dbus_connection_new_for_address(address,
+                                      G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
+                                          | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                                      NULL,
+                                      priv->dbus.cancellable,
+                                      _dbus_create_done,
+                                      self);
+}
+
+static void
+config_changed_cb(NMConfig *          config,
+                  NMConfigData *      config_data,
+                  NMConfigChangeFlags changes,
+                  NMConfigData *      old_data,
+                  NMSIfcfgRHPlugin *  self)
+{
+    NMSIfcfgRHPluginPrivate *priv;
+
+    /* If the dbus connection for some reason is borked the D-Bus service
+     * won't be offered.
+     *
+     * On SIGHUP and SIGUSR1 try to re-connect to D-Bus. So in the unlikely
+     * event that the D-Bus connection is broken, that allows for recovery
+     * without need for restarting NetworkManager. */
+    if (!NM_FLAGS_ANY(changes, NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_SIGUSR1))
+        return;
+
+    priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+    if (!priv->dbus.connection && !priv->dbus.cancellable)
+        _dbus_setup(self);
+}
+
+/*****************************************************************************/
+
+static void
+nms_ifcfg_rh_plugin_init(NMSIfcfgRHPlugin *self)
+{
+    NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+
+    priv->config = g_object_ref(nm_config_get());
+
+    priv->unmanaged_specs    = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+    priv->unrecognized_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+
+    priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT(priv->storages,
+                                                                     nms_ifcfg_rh_storage_destroy);
+}
+
+static void
+constructed(GObject *object)
+{
+    NMSIfcfgRHPlugin *       self = NMS_IFCFG_RH_PLUGIN(object);
+    NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+
+    G_OBJECT_CLASS(nms_ifcfg_rh_plugin_parent_class)->constructed(object);
+
+    g_signal_connect(priv->config,
+                     NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+                     G_CALLBACK(config_changed_cb),
+                     self);
+
+    _dbus_setup(self);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSIfcfgRHPlugin *       self = NMS_IFCFG_RH_PLUGIN(object);
+    NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self);
+
+    if (priv->config)
+        g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self);
+
+    /* FIXME(shutdown) we need a stop method so that we can unregistering the D-Bus service
+     * when NMSettings is shutting down, and not when the instance gets destroyed. */
+    _dbus_clear(self);
+
+    nm_sett_util_storages_clear(&priv->storages);
+
+    g_clear_object(&priv->config);
+
+    G_OBJECT_CLASS(nms_ifcfg_rh_plugin_parent_class)->dispose(object);
+
+    nm_clear_pointer(&priv->unmanaged_specs, g_hash_table_destroy);
+    nm_clear_pointer(&priv->unrecognized_specs, g_hash_table_destroy);
+}
+
+static void
+nms_ifcfg_rh_plugin_class_init(NMSIfcfgRHPluginClass *klass)
+{
+    GObjectClass *         object_class = G_OBJECT_CLASS(klass);
+    NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass);
+
+    object_class->constructed = constructed;
+    object_class->dispose     = dispose;
+
+    plugin_class->plugin_name            = "ifcfg-rh";
+    plugin_class->get_unmanaged_specs    = get_unmanaged_specs;
+    plugin_class->get_unrecognized_specs = get_unrecognized_specs;
+    plugin_class->reload_connections     = reload_connections;
+    plugin_class->load_connections       = load_connections;
+    plugin_class->load_connections_done  = load_connections_done;
+    plugin_class->add_connection         = add_connection;
+    plugin_class->update_connection      = update_connection;
+    plugin_class->delete_connection      = delete_connection;
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT NMSettingsPlugin *
+                nm_settings_plugin_factory(void)
+{
+    return g_object_new(NMS_TYPE_IFCFG_RH_PLUGIN, NULL);
+}
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
new file mode 100644
index 00000000..dc1a01c8
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Dan Williams <dcbw@redhat.com>
+ * Søren Sandmann <sandmann@daimi.au.dk>
+ * Copyright (C) 2007 - 2008 Red Hat, Inc.
+ */
+
+#ifndef __NMS_IFCFG_RH_PLUGIN_H__
+#define __NMS_IFCFG_RH_PLUGIN_H__
+
+#define NMS_TYPE_IFCFG_RH_PLUGIN (nms_ifcfg_rh_plugin_get_type())
+#define NMS_IFCFG_RH_PLUGIN(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPlugin))
+#define NMS_IFCFG_RH_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass))
+#define NMS_IS_IFCFG_RH_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_IFCFG_RH_PLUGIN))
+#define NMS_IS_IFCFG_RH_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_IFCFG_RH_PLUGIN))
+#define NMS_IFCFG_RH_PLUGIN_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass))
+
+typedef struct _NMSIfcfgRHPlugin      NMSIfcfgRHPlugin;
+typedef struct _NMSIfcfgRHPluginClass NMSIfcfgRHPluginClass;
+
+GType nms_ifcfg_rh_plugin_get_type(void);
+
+#endif /* __NMS_IFCFG_RH_PLUGIN_H__ */
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
new file mode 100644
index 00000000..209957d9
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -0,0 +1,6642 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifcfg-rh-reader.h"
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/wait.h>
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_ether.h>
+
+#include "nm-glib-aux/nm-secret-utils.h"
+#include "nm-connection.h"
+#include "nm-dbus-interface.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-vlan.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-ethtool.h"
+#include "nm-setting-8021x.h"
+#include "nm-setting-bond.h"
+#include "nm-setting-team.h"
+#include "nm-setting-team-port.h"
+#include "nm-setting-bridge.h"
+#include "nm-setting-bridge-port.h"
+#include "nm-setting-dcb.h"
+#include "nm-setting-user.h"
+#include "nm-setting-proxy.h"
+#include "nm-setting-generic.h"
+#include "nm-core-internal.h"
+#include "nm-utils.h"
+#include "nm-base/nm-ethtool-base.h"
+
+#include "platform/nm-platform.h"
+#include "NetworkManagerUtils.h"
+
+#include "nms-ifcfg-rh-common.h"
+#include "nms-ifcfg-rh-utils.h"
+#include "shvar.h"
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG_PREFIX_NAME "ifcfg-rh"
+#define _NMLOG(level, ...)                                                 \
+    G_STMT_START                                                           \
+    {                                                                      \
+        nm_log((level),                                                    \
+               (_NMLOG_DOMAIN),                                            \
+               NULL,                                                       \
+               NULL,                                                       \
+               "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                    \
+               _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                      \
+    G_STMT_END
+
+#define PARSE_WARNING(...) \
+    _LOGW("%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), "    " _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+static char *
+get_full_file_path(const char *ifcfg_path, const char *file_path)
+{
+    const char *  base    = file_path;
+    gs_free char *dirname = NULL;
+    char *        p;
+
+    g_return_val_if_fail(ifcfg_path != NULL, NULL);
+    g_return_val_if_fail(file_path != NULL, NULL);
+
+    if (file_path[0] == '/')
+        return g_strdup(file_path);
+
+    p = strrchr(file_path, '/');
+    if (p)
+        base = p + 1;
+
+    dirname = g_path_get_dirname(ifcfg_path);
+    return g_build_path("/", dirname, base, NULL);
+}
+
+/*****************************************************************************/
+
+static NMSettingSecretFlags
+_secret_read_ifcfg_flags(shvarFile *ifcfg, const char *flags_key)
+{
+    NMSettingSecretFlags flags    = NM_SETTING_SECRET_FLAG_NONE;
+    gs_free char *       val_free = NULL;
+    const char *         val;
+
+    nm_assert(flags_key);
+    nm_assert(g_str_has_suffix(flags_key, "_FLAGS"));
+
+    val = svGetValueStr(ifcfg, flags_key, &val_free);
+    if (val) {
+        if (strstr(val, SECRET_FLAG_AGENT))
+            flags |= NM_SETTING_SECRET_FLAG_AGENT_OWNED;
+        if (strstr(val, SECRET_FLAG_NOT_SAVED))
+            flags |= NM_SETTING_SECRET_FLAG_NOT_SAVED;
+        if (strstr(val, SECRET_FLAG_NOT_REQUIRED))
+            flags |= NM_SETTING_SECRET_FLAG_NOT_REQUIRED;
+    }
+    return flags;
+}
+
+static void
+_secret_read_ifcfg(shvarFile *           ifcfg,
+                   shvarFile *           keys_ifcfg,
+                   const char *          name,
+                   char **               value,
+                   NMSettingSecretFlags *flags)
+{
+    char flags_key[250];
+
+    nm_sprintf_buf(flags_key, "%s_FLAGS", name);
+
+    *flags = _secret_read_ifcfg_flags(ifcfg, flags_key);
+
+    if (*flags != NM_SETTING_SECRET_FLAG_NONE)
+        *value = NULL;
+    else {
+        *value = svGetValue_cp(ifcfg, name);
+        if (!*value && keys_ifcfg)
+            *value = svGetValue_cp(keys_ifcfg, name);
+    }
+}
+
+static void
+_secret_set_from_ifcfg(gpointer    setting,
+                       shvarFile * ifcfg,
+                       shvarFile * keys_ifcfg,
+                       const char *ifcfg_key,
+                       const char *property_name)
+{
+    nm_auto_free_secret char *secret = NULL;
+    NMSettingSecretFlags      flags;
+    char                      flags_key[250];
+
+    nm_assert(NM_IS_SETTING(setting));
+
+    _secret_read_ifcfg(ifcfg, keys_ifcfg, ifcfg_key, &secret, &flags);
+
+    g_object_set(setting,
+                 property_name,
+                 secret,
+                 nm_sprintf_buf(flags_key, "%s-flags", property_name),
+                 flags,
+                 NULL);
+}
+
+static gboolean
+_secret_password_raw_to_bytes(const char *ifcfg_key,
+                              const char *password_raw,
+                              GBytes **   out_bytes,
+                              GError **   error)
+{
+    nm_auto_free_secret_buf NMSecretBuf *secret = NULL;
+    gsize                                len;
+
+    if (!password_raw) {
+        NM_SET_OUT(out_bytes, NULL);
+        return TRUE;
+    }
+
+    if (password_raw[0] == '0' && password_raw[1] == 'x')
+        password_raw += 2;
+
+    secret = nm_secret_buf_new(strlen(password_raw) / 2 + 3);
+    if (!nm_utils_hexstr2bin_full(password_raw,
+                                  FALSE,
+                                  FALSE,
+                                  FALSE,
+                                  ":",
+                                  0,
+                                  secret->bin,
+                                  secret->len,
+                                  &len)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Invalid hex password in %s",
+                    ifcfg_key);
+        return FALSE;
+    }
+
+    NM_SET_OUT(out_bytes, nm_secret_buf_to_gbytes_take(g_steal_pointer(&secret), len));
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static GBytes *
+_cert_get_cert_bytes(const char *ifcfg_path, const char *value, GError **error)
+{
+    gs_free char *path = NULL;
+
+    if (NM_STR_HAS_PREFIX(value, "pkcs11:"))
+        return _nm_setting_802_1x_cert_value_to_bytes(NM_SETTING_802_1X_CK_SCHEME_PKCS11,
+                                                      (guint8 *) value,
+                                                      -1,
+                                                      error);
+
+    path = get_full_file_path(ifcfg_path, value);
+    return _nm_setting_802_1x_cert_value_to_bytes(NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                  (guint8 *) path,
+                                                  -1,
+                                                  error);
+}
+
+static gboolean
+_cert_get_cert(shvarFile *             ifcfg,
+               const char *            ifcfg_key,
+               GBytes **               out_cert,
+               NMSetting8021xCKScheme *out_scheme,
+               GError **               error)
+{
+    nm_auto_free_secret char *val_free = NULL;
+    const char *              val;
+    gs_unref_bytes GBytes *cert  = NULL;
+    GError *               local = NULL;
+    NMSetting8021xCKScheme scheme;
+
+    val = svGetValueStr(ifcfg, ifcfg_key, &val_free);
+    if (!val) {
+        NM_SET_OUT(out_cert, NULL);
+        NM_SET_OUT(out_scheme, NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
+        return TRUE;
+    }
+
+    cert = _cert_get_cert_bytes(svFileGetName(ifcfg), val, &local);
+    if (!cert)
+        goto err;
+
+    scheme = _nm_setting_802_1x_cert_get_scheme(cert, &local);
+    if (scheme == NM_SETTING_802_1X_CK_SCHEME_UNKNOWN)
+        goto err;
+
+    NM_SET_OUT(out_cert, g_steal_pointer(&cert));
+    NM_SET_OUT(out_scheme, scheme);
+    return TRUE;
+
+err:
+    g_set_error(error,
+                NM_SETTINGS_ERROR,
+                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                "invalid certificate %s: %s",
+                ifcfg_key,
+                local->message);
+    g_error_free(local);
+    return FALSE;
+}
+
+static gboolean
+_cert_set_from_ifcfg(gpointer    setting,
+                     shvarFile * ifcfg,
+                     const char *ifcfg_key,
+                     const char *property_name,
+                     GBytes **   out_cert,
+                     GError **   error)
+{
+    gs_unref_bytes GBytes *cert = NULL;
+
+    if (!_cert_get_cert(ifcfg, ifcfg_key, &cert, NULL, error))
+        return FALSE;
+
+    g_object_set(setting, property_name, cert, NULL);
+
+    NM_SET_OUT(out_cert, g_steal_pointer(&cert));
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+check_if_bond_slave(shvarFile *ifcfg, NMSettingConnection *s_con)
+{
+    gs_free char *value = NULL;
+    const char *  v;
+    const char *  master;
+
+    v = svGetValueStr(ifcfg, "MASTER_UUID", &value);
+    if (!v)
+        v = svGetValueStr(ifcfg, "MASTER", &value);
+
+    if (v) {
+        master = nm_setting_connection_get_master(s_con);
+        if (master) {
+            PARSE_WARNING("Already configured as slave of %s. Ignoring MASTER{_UUID}=\"%s\"",
+                          master,
+                          v);
+            return;
+        }
+
+        g_object_set(s_con,
+                     NM_SETTING_CONNECTION_MASTER,
+                     v,
+                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_BOND_SETTING_NAME,
+                     NULL);
+    }
+
+    /* We should be checking for SLAVE=yes as well, but NM used to not set that,
+     * so for backward-compatibility, we don't check.
+     */
+}
+
+static void
+check_if_team_slave(shvarFile *ifcfg, NMSettingConnection *s_con)
+{
+    gs_free char *value = NULL;
+    const char *  v;
+    const char *  master;
+
+    v = svGetValueStr(ifcfg, "TEAM_MASTER_UUID", &value);
+    if (!v)
+        v = svGetValueStr(ifcfg, "TEAM_MASTER", &value);
+    if (!v)
+        return;
+
+    master = nm_setting_connection_get_master(s_con);
+    if (master) {
+        PARSE_WARNING("Already configured as slave of %s. Ignoring TEAM_MASTER{_UUID}=\"%s\"",
+                      master,
+                      v);
+        return;
+    }
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_MASTER,
+                 v,
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_TEAM_SETTING_NAME,
+                 NULL);
+}
+
+static char *
+make_connection_name(shvarFile * ifcfg,
+                     const char *ifcfg_name,
+                     const char *suggested,
+                     const char *prefix)
+{
+    char *full_name = NULL, *name;
+
+    /* If the ifcfg file already has a NAME, always use that */
+    name = svGetValueStr_cp(ifcfg, "NAME");
+    if (name)
+        return name;
+
+    /* Otherwise, construct a new NAME */
+    if (!prefix)
+        prefix = "System";
+
+    /* For cosmetic reasons, if the suggested name is the same as
+     * the ifcfg files name, don't use it.  Mainly for wifi so that
+     * the SSID is shown in the connection ID instead of just "wlan0".
+     */
+    if (suggested && strcmp(ifcfg_name, suggested))
+        full_name = g_strdup_printf("%s %s (%s)", prefix, suggested, ifcfg_name);
+    else
+        full_name = g_strdup_printf("%s %s", prefix, ifcfg_name);
+
+    return full_name;
+}
+
+static NMSetting *
+make_connection_setting(const char *file,
+                        shvarFile * ifcfg,
+                        const char *type,
+                        const char *suggested,
+                        const char *prefix)
+{
+    NMSettingConnection *   s_con;
+    NMSettingConnectionLldp lldp;
+    const char *            ifcfg_name = NULL;
+    gs_free char *          new_id     = NULL;
+    const char *            uuid;
+    gs_free char *          uuid_free = NULL;
+    gs_free char *          value     = NULL;
+    const char *            v;
+    gs_free char *          stable_id = NULL;
+    const char *const *     iter;
+    int                     vint64, i_val;
+
+    ifcfg_name = utils_get_ifcfg_name(file, TRUE);
+    if (!ifcfg_name)
+        return NULL;
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+
+    new_id = make_connection_name(ifcfg, ifcfg_name, suggested, prefix);
+    g_object_set(s_con, NM_SETTING_CONNECTION_ID, new_id, NULL);
+
+    /* Try for a UUID key before falling back to hashing the file name */
+    uuid = svGetValueStr(ifcfg, "UUID", &uuid_free);
+    if (!uuid) {
+        uuid_free = nm_utils_uuid_generate_from_string(svFileGetName(ifcfg),
+                                                       -1,
+                                                       NM_UTILS_UUID_TYPE_LEGACY,
+                                                       NULL);
+        uuid      = uuid_free;
+    }
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_TYPE,
+                 type,
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_STABLE_ID,
+                 svGetValue(ifcfg, "STABLE_ID", &stable_id),
+                 NULL);
+
+    v = svGetValueStr(ifcfg, "DEVICE", &value);
+    if (v) {
+        GError *error = NULL;
+
+        /* Only validate for NMU_IFACE_KERNEL, because ifcfg plugin anyway
+         * doesn't support OVS types. */
+        if (nm_utils_ifname_valid(v, NMU_IFACE_KERNEL, &error)) {
+            g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, v, NULL);
+        } else {
+            PARSE_WARNING("invalid DEVICE name '%s': %s", v, error->message);
+            g_error_free(error);
+        }
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "LLDP", &value);
+    if (nm_streq0(v, "rx"))
+        lldp = NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
+    else
+        lldp = svParseBoolean(v, NM_SETTING_CONNECTION_LLDP_DEFAULT);
+
+    /* Missing ONBOOT is treated as "ONBOOT=true" by the old network service */
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 svGetValueBoolean(ifcfg, "ONBOOT", TRUE),
+                 NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
+                 (int) svGetValueInt64(ifcfg,
+                                       "AUTOCONNECT_PRIORITY",
+                                       10,
+                                       NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN,
+                                       NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX,
+                                       NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT),
+                 NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES,
+                 (int) svGetValueInt64(ifcfg, "AUTOCONNECT_RETRIES", 10, -1, G_MAXINT32, -1),
+                 NM_SETTING_CONNECTION_MULTI_CONNECT,
+                 (int) svGetValueInt64(ifcfg,
+                                       "MULTI_CONNECT",
+                                       10,
+                                       G_MININT32,
+                                       G_MAXINT32,
+                                       NM_CONNECTION_MULTI_CONNECT_DEFAULT),
+                 NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
+                 svGetValueBoolean(ifcfg,
+                                   "AUTOCONNECT_SLAVES",
+                                   NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT),
+                 NM_SETTING_CONNECTION_LLDP,
+                 lldp,
+                 NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "USERS", &value);
+    if (v) {
+        gs_free const char **items = NULL;
+
+        items = nm_utils_strsplit_set(v, " ");
+        for (iter = items; iter && *iter; iter++) {
+            if (!nm_setting_connection_add_permission(s_con, "user", *iter, NULL))
+                PARSE_WARNING("invalid USERS item '%s'", *iter);
+        }
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "ZONE", &value);
+    g_object_set(s_con, NM_SETTING_CONNECTION_ZONE, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "SECONDARY_UUIDS", &value);
+    if (v) {
+        gs_free const char **items = NULL;
+
+        items = nm_utils_strsplit_set(v, " \t");
+        for (iter = items; iter && *iter; iter++) {
+            if (!nm_setting_connection_add_secondary(s_con, *iter))
+                PARSE_WARNING("secondary connection UUID '%s' already added", *iter);
+        }
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "BRIDGE_UUID", &value);
+    if (!v)
+        v = svGetValueStr(ifcfg, "BRIDGE", &value);
+    if (v) {
+        const char *old_value;
+
+        if ((old_value = nm_setting_connection_get_master(s_con))) {
+            PARSE_WARNING("Already configured as slave of %s. Ignoring BRIDGE=\"%s\"",
+                          old_value,
+                          v);
+        } else {
+            g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
+            g_object_set(s_con,
+                         NM_SETTING_CONNECTION_SLAVE_TYPE,
+                         NM_SETTING_BRIDGE_SETTING_NAME,
+                         NULL);
+        }
+    }
+
+    check_if_bond_slave(ifcfg, s_con);
+    check_if_team_slave(ifcfg, s_con);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "OVS_PORT_UUID", &value);
+    if (!v)
+        v = svGetValueStr(ifcfg, "OVS_PORT", &value);
+    if (v) {
+        const char *old_value;
+
+        if ((old_value = nm_setting_connection_get_master(s_con))) {
+            PARSE_WARNING("Already configured as slave of %s. Ignoring OVS_PORT=\"%s\"",
+                          old_value,
+                          v);
+        } else {
+            g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
+            g_object_set(s_con,
+                         NM_SETTING_CONNECTION_SLAVE_TYPE,
+                         NM_SETTING_OVS_PORT_SETTING_NAME,
+                         NULL);
+        }
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "VRF_UUID", &value);
+    if (!v)
+        v = svGetValueStr(ifcfg, "VRF", &value);
+    if (v) {
+        const char *old_value;
+
+        if ((old_value = nm_setting_connection_get_master(s_con))) {
+            PARSE_WARNING("Already configured as slave of %s. Ignoring VRF{_UUID}=\"%s\"",
+                          old_value,
+                          v);
+        } else {
+            g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
+            g_object_set(s_con,
+                         NM_SETTING_CONNECTION_SLAVE_TYPE,
+                         NM_SETTING_VRF_SETTING_NAME,
+                         NULL);
+        }
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "GATEWAY_PING_TIMEOUT", &value);
+    if (v) {
+        gint64 tmp;
+
+        tmp = _nm_utils_ascii_str_to_int64(v, 10, 0, G_MAXINT32 - 1, -1);
+        if (tmp >= 0) {
+            if (tmp > 600) {
+                tmp = 600;
+                PARSE_WARNING("invalid GATEWAY_PING_TIMEOUT time");
+            }
+            g_object_set(s_con, NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, (guint) tmp, NULL);
+        } else
+            PARSE_WARNING("invalid GATEWAY_PING_TIMEOUT time");
+    }
+
+    switch (svGetValueBoolean(ifcfg, "CONNECTION_METERED", -1)) {
+    case TRUE:
+        g_object_set(s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_YES, NULL);
+        break;
+    case FALSE:
+        g_object_set(s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_NO, NULL);
+        break;
+    }
+
+    vint64 = svGetValueInt64(ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1);
+    g_object_set(s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (int) vint64, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValue(ifcfg, "DEVTIMEOUT", &value);
+    if (v) {
+        v      = nm_str_skip_leading_spaces(v);
+        vint64 = _nm_utils_ascii_str_to_int64(v, 10, 0, ((gint64) G_MAXINT32) / 1000, -1);
+        if (vint64 != -1)
+            vint64 *= 1000;
+        else if (v[0] != '\0') {
+            char * endptr;
+            double d;
+
+            d      = nm_g_ascii_strtod(v, &endptr);
+            endptr = nm_str_skip_leading_spaces(endptr);
+            if (errno == 0 && endptr[0] == '\0' && d >= 0.0) {
+                d *= 1000.0;
+
+                /* We round. Yes, this is not correct to round IEEE 754 floats in general,
+                 * but sufficient for our case where we know that NetworkManager wrote the
+                 * setting with up to 3 digits for the milliseconds. */
+                d += 0.5;
+                if (d >= 0.0 && d <= (double) G_MAXINT32)
+                    vint64 = (gint64) d;
+            }
+        }
+        if (vint64 == -1)
+            PARSE_WARNING("invalid DEVTIMEOUT setting");
+        else
+            g_object_set(s_con, NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, (int) vint64, NULL);
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValue(ifcfg, "MUD_URL", &value);
+    if (v)
+        g_object_set(s_con, NM_SETTING_CONNECTION_MUD_URL, v, NULL);
+
+    i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+    if (!svGetValueEnum(ifcfg, "MDNS", nm_setting_connection_mdns_get_type(), &i_val, NULL))
+        PARSE_WARNING("invalid MDNS setting");
+    g_object_set(s_con, NM_SETTING_CONNECTION_MDNS, i_val, NULL);
+
+    i_val = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+    if (!svGetValueEnum(ifcfg, "LLMNR", nm_setting_connection_llmnr_get_type(), &i_val, NULL))
+        PARSE_WARNING("invalid LLMNR setting");
+    g_object_set(s_con, NM_SETTING_CONNECTION_LLMNR, i_val, NULL);
+
+    return NM_SETTING(s_con);
+}
+
+static gboolean
+read_ip4_address(shvarFile * ifcfg,
+                 const char *tag,
+                 gboolean *  out_has_key,
+                 guint32 *   out_addr,
+                 GError **   error)
+{
+    gs_free char *value_to_free = NULL;
+    const char *  value;
+    in_addr_t     a;
+
+    nm_assert(ifcfg);
+    nm_assert(tag);
+    nm_assert(!error || !*error);
+
+    value = svGetValueStr(ifcfg, tag, &value_to_free);
+    if (!value) {
+        NM_SET_OUT(out_has_key, FALSE);
+        NM_SET_OUT(out_addr, 0);
+        return TRUE;
+    }
+
+    if (inet_pton(AF_INET, value, &a) != 1) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Invalid %s IP4 address '%s'",
+                    tag,
+                    value);
+        return FALSE;
+    }
+
+    NM_SET_OUT(out_has_key, TRUE);
+    NM_SET_OUT(out_addr, a);
+    return TRUE;
+}
+
+static gboolean
+is_any_ip4_address_defined(shvarFile *ifcfg, int *idx)
+{
+    int i, ignore, *ret_idx;
+
+    ret_idx = idx ?: &ignore;
+
+    for (i = -1; i <= 2; i++) {
+        gs_free char *value = NULL;
+        char          tag[256];
+
+        if (svGetValueStr(ifcfg, numbered_tag(tag, "IPADDR", i), &value)) {
+            *ret_idx = i;
+            return TRUE;
+        }
+
+        if (svGetValueStr(ifcfg, numbered_tag(tag, "PREFIX", i), &value)) {
+            *ret_idx = i;
+            return TRUE;
+        }
+
+        if (svGetValueStr(ifcfg, numbered_tag(tag, "NETMASK", i), &value)) {
+            *ret_idx = i;
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+/* Returns TRUE on missing address or valid address */
+static gboolean
+read_full_ip4_address(shvarFile *   ifcfg,
+                      gint32        which,
+                      NMIPAddress * base_addr,
+                      NMIPAddress **out_address,
+                      char **       out_gateway,
+                      GError **     error)
+{
+    char          tag[256];
+    char          prefix_tag[256];
+    guint32       ipaddr;
+    gs_free char *value = NULL;
+    const char *  v;
+    int           prefix = 0;
+    gboolean      has_key;
+    guint32       a;
+    char          inet_buf[NM_UTILS_INET_ADDRSTRLEN];
+
+    g_return_val_if_fail(which >= -1, FALSE);
+    g_return_val_if_fail(ifcfg != NULL, FALSE);
+    g_return_val_if_fail(out_address != NULL, FALSE);
+    g_return_val_if_fail(*out_address == NULL, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    /* IP address */
+    if (!read_ip4_address(ifcfg, numbered_tag(tag, "IPADDR", which), &has_key, &ipaddr, error))
+        return FALSE;
+    if (!has_key) {
+        if (!base_addr)
+            return TRUE;
+        nm_ip_address_get_address_binary(base_addr, &ipaddr);
+    }
+
+    /* Gateway */
+    if (out_gateway && !*out_gateway) {
+        if (!read_ip4_address(ifcfg, numbered_tag(tag, "GATEWAY", which), &has_key, &a, error))
+            return FALSE;
+        if (has_key)
+            *out_gateway = nm_utils_inet4_ntop_dup(a);
+    }
+
+    /* Prefix */
+    numbered_tag(prefix_tag, "PREFIX", which);
+    v = svGetValueStr(ifcfg, prefix_tag, &value);
+    if (v) {
+        prefix = _nm_utils_ascii_str_to_int64(v, 10, 0, 32, -1);
+        if (prefix < 0) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid IP4 prefix '%s'",
+                        v);
+            return FALSE;
+        }
+    } else {
+        /* Fall back to NETMASK if no PREFIX was specified */
+        if (!read_ip4_address(ifcfg, numbered_tag(tag, "NETMASK", which), &has_key, &a, error))
+            return FALSE;
+        if (has_key)
+            prefix = nm_utils_ip4_netmask_to_prefix(a);
+        else {
+            if (base_addr)
+                prefix = nm_ip_address_get_prefix(base_addr);
+            else {
+                /* Try to autodetermine the prefix for the address' class */
+                prefix = _nm_utils_ip4_get_default_prefix(ipaddr);
+                PARSE_WARNING("missing %s, assuming %s/%d",
+                              prefix_tag,
+                              _nm_utils_inet4_ntop(ipaddr, inet_buf),
+                              prefix);
+            }
+        }
+    }
+
+    *out_address = nm_ip_address_new_binary(AF_INET, &ipaddr, prefix, error);
+    if (*out_address)
+        return TRUE;
+
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+parse_route_line_is_comment(const char *line)
+{
+    /* we obtained the line from a legacy route file. Here we skip
+     * empty lines and comments.
+     *
+     * initscripts compares: "$line" =~ '^[[:space:]]*(\#.*)?$'
+     */
+    while (nm_utils_is_separator(line[0]))
+        line++;
+    if (NM_IN_SET(line[0], '\0', '#'))
+        return TRUE;
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+typedef enum {
+    PARSE_LINE_AF_FLAG_FOR_IPV4 = 0x01,
+    PARSE_LINE_AF_FLAG_FOR_IPV6 = 0x02,
+} ParseLineAFFlag;
+
+typedef struct {
+    const char *key;
+
+    /* the element is not available in this case. */
+    ParseLineAFFlag disabled : 3;
+
+    bool disabled_with_options_route : 1;
+
+    /* whether the element is to be ignored. Ignord is different from
+     * "disabled", because we still parse the option, but don't use it. */
+    ParseLineAFFlag ignore : 3;
+
+    bool int_base_16 : 1;
+
+    /* the type, one of PARSE_LINE_TYPE_* */
+    char type;
+
+} ParseLineInfo;
+
+typedef struct {
+    /* whether the command line option was found, and @v is
+     * initialized. */
+    bool has : 1;
+
+    union {
+        guint8      uint8;
+        guint32     uint32;
+        const char *str;
+        struct {
+            guint32 uint32;
+            bool    lock : 1;
+        } uint32_with_lock;
+        struct {
+            NMIPAddr addr;
+            guint8   plen;
+            bool     has_plen : 1;
+        } addr;
+    } v;
+
+} ParseLineData;
+
+enum {
+    /* route attributes */
+    PARSE_LINE_ATTR_ROUTE_TYPE,
+    PARSE_LINE_ATTR_ROUTE_TABLE,
+    PARSE_LINE_ATTR_ROUTE_SRC,
+    PARSE_LINE_ATTR_ROUTE_FROM,
+    PARSE_LINE_ATTR_ROUTE_TOS,
+    PARSE_LINE_ATTR_ROUTE_SCOPE,
+    PARSE_LINE_ATTR_ROUTE_ONLINK,
+    PARSE_LINE_ATTR_ROUTE_WINDOW,
+    PARSE_LINE_ATTR_ROUTE_CWND,
+    PARSE_LINE_ATTR_ROUTE_INITCWND,
+    PARSE_LINE_ATTR_ROUTE_INITRWND,
+    PARSE_LINE_ATTR_ROUTE_MTU,
+
+    /* iproute2 arguments that only matter when parsing the file. */
+    PARSE_LINE_ATTR_ROUTE_TO,
+    PARSE_LINE_ATTR_ROUTE_VIA,
+    PARSE_LINE_ATTR_ROUTE_METRIC,
+
+    /* iproute2 parameters that are well known and that we silently ignore. */
+    PARSE_LINE_ATTR_ROUTE_DEV,
+};
+
+#define PARSE_LINE_TYPE_UINT8            '8'
+#define PARSE_LINE_TYPE_UINT32           'u'
+#define PARSE_LINE_TYPE_UINT32_WITH_LOCK 'l'
+#define PARSE_LINE_TYPE_ADDR             'a'
+#define PARSE_LINE_TYPE_ADDR_WITH_PREFIX 'p'
+#define PARSE_LINE_TYPE_IFNAME           'i'
+#define PARSE_LINE_TYPE_FLAG             'f'
+#define PARSE_LINE_TYPE_ROUTE_SCOPE      'S'
+#define PARSE_LINE_TYPE_STRING           's'
+
+/**
+ * parse_route_line:
+ * @line: the line to parse. This is either a line from the route-* or route6-* file,
+ *   or the numbered OPTIONS setting.
+ * @addr_family: the address family.
+ * @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.
+ *   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.
+ *
+ * Parsing the route options line has two modes: one for the numbered OPTIONS
+ * setting, and one for initscript's handle_ip_file(), which takes the lines
+ * and passes them to `ip route add`. The modes are similar, but certain properties
+ * are not allowed for OPTIONS.
+ * The mode is differentiated by having an @options_route argument.
+ *
+ * Returns: returns a negative errno on failure. On success, it returns 0
+ *   and @out_route.
+ */
+static int
+parse_route_line(const char *line,
+                 int         addr_family,
+                 NMIPRoute * options_route,
+                 NMIPRoute **out_route,
+                 GError **   error)
+{
+    static const ParseLineInfo parse_infos[] = {
+        [PARSE_LINE_ATTR_ROUTE_TYPE] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_TYPE,
+                .type = PARSE_LINE_TYPE_STRING,
+            },
+        [PARSE_LINE_ATTR_ROUTE_TABLE] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_TABLE,
+                .type = PARSE_LINE_TYPE_UINT32,
+            },
+        [PARSE_LINE_ATTR_ROUTE_SRC] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_SRC,
+                .type = PARSE_LINE_TYPE_ADDR,
+            },
+        [PARSE_LINE_ATTR_ROUTE_FROM] =
+            {
+                .key      = NM_IP_ROUTE_ATTRIBUTE_FROM,
+                .type     = PARSE_LINE_TYPE_ADDR_WITH_PREFIX,
+                .disabled = PARSE_LINE_AF_FLAG_FOR_IPV4,
+            },
+        [PARSE_LINE_ATTR_ROUTE_TOS] =
+            {
+                .key         = NM_IP_ROUTE_ATTRIBUTE_TOS,
+                .type        = PARSE_LINE_TYPE_UINT8,
+                .int_base_16 = TRUE,
+                .ignore      = PARSE_LINE_AF_FLAG_FOR_IPV6,
+            },
+        [PARSE_LINE_ATTR_ROUTE_SCOPE] =
+            {
+                .key    = NM_IP_ROUTE_ATTRIBUTE_SCOPE,
+                .type   = PARSE_LINE_TYPE_ROUTE_SCOPE,
+                .ignore = PARSE_LINE_AF_FLAG_FOR_IPV6,
+            },
+        [PARSE_LINE_ATTR_ROUTE_ONLINK] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_ONLINK,
+                .type = PARSE_LINE_TYPE_FLAG,
+            },
+        [PARSE_LINE_ATTR_ROUTE_WINDOW] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_WINDOW,
+                .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK,
+            },
+        [PARSE_LINE_ATTR_ROUTE_CWND] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_CWND,
+                .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK,
+            },
+        [PARSE_LINE_ATTR_ROUTE_INITCWND] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_INITCWND,
+                .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK,
+            },
+        [PARSE_LINE_ATTR_ROUTE_INITRWND] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_INITRWND,
+                .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK,
+            },
+        [PARSE_LINE_ATTR_ROUTE_MTU] =
+            {
+                .key  = NM_IP_ROUTE_ATTRIBUTE_MTU,
+                .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK,
+            },
+
+        [PARSE_LINE_ATTR_ROUTE_TO] =
+            {
+                .key                         = "to",
+                .type                        = PARSE_LINE_TYPE_ADDR_WITH_PREFIX,
+                .disabled_with_options_route = TRUE,
+            },
+        [PARSE_LINE_ATTR_ROUTE_VIA] =
+            {
+                .key                         = "via",
+                .type                        = PARSE_LINE_TYPE_ADDR,
+                .disabled_with_options_route = TRUE,
+            },
+        [PARSE_LINE_ATTR_ROUTE_METRIC] =
+            {
+                .key                         = "metric",
+                .type                        = PARSE_LINE_TYPE_UINT32,
+                .disabled_with_options_route = TRUE,
+            },
+
+        [PARSE_LINE_ATTR_ROUTE_DEV] =
+            {
+                .key    = "dev",
+                .type   = PARSE_LINE_TYPE_IFNAME,
+                .ignore = PARSE_LINE_AF_FLAG_FOR_IPV4 | PARSE_LINE_AF_FLAG_FOR_IPV6,
+                .disabled_with_options_route = TRUE,
+            },
+    };
+    nm_auto_unref_ip_route NMIPRoute *route      = NULL;
+    gs_free const char **             words_free = NULL;
+    const char *const *               words;
+    const char *                      s;
+    gsize                             i_words;
+    guint                             i;
+    char                              buf1[256];
+    char                              buf2[256];
+    ParseLineData                     parse_datas[G_N_ELEMENTS(parse_infos)] = {};
+    const ParseLineAFFlag             af_flag =
+        (addr_family == AF_INET) ? PARSE_LINE_AF_FLAG_FOR_IPV4 : PARSE_LINE_AF_FLAG_FOR_IPV6;
+
+    nm_assert(line);
+    nm_assert_addr_family(addr_family);
+    nm_assert(!options_route || nm_ip_route_get_family(options_route) == addr_family);
+
+    /* initscripts read the legacy route file line-by-line and
+     * use it as `ip route add $line`, thus doing split+glob.
+     * Splitting on IFS (which we consider '<space><tab><newline>')
+     * and globbing (which we obviously don't do).
+     *
+     * I think it's a mess, because it doesn't support escaping or
+     * quoting. In fact, it can only encode benign values.
+     *
+     * We also use the same form for the numbered OPTIONS
+     * variable. I think it's bad not to support any form of
+     * escaping. But do that for now.
+     *
+     * Maybe later we want to support some form of quotation here.
+     * Which of course, would be incompatible with initscripts.
+     */
+    words_free = nm_utils_strsplit_set(line, " \t\n");
+
+    words = words_free ?: NM_PTRARRAY_EMPTY(const char *);
+
+    for (i_words = 0; words[i_words];) {
+        const gsize          i_words0 = i_words;
+        const char *const    w        = words[i_words0];
+        const ParseLineInfo *p_info;
+        ParseLineData *      p_data;
+        gboolean             unqualified_addr = FALSE;
+
+        for (i = 0; i < G_N_ELEMENTS(parse_infos); i++) {
+            p_info = &parse_infos[i];
+            p_data = &parse_datas[i];
+
+            if ((p_info->disabled & af_flag)
+                || (p_info->disabled_with_options_route && options_route))
+                continue;
+
+            if (!nm_streq(w, p_info->key))
+                continue;
+
+            if (p_data->has) {
+                /* iproute2 for most arguments allows specifying them multiple times.
+                 * Let's not do that. */
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Duplicate option \"%s\"",
+                            w);
+                return -EINVAL;
+            }
+
+            p_data->has = TRUE;
+            switch (p_info->type) {
+            case PARSE_LINE_TYPE_UINT8:
+                i_words++;
+                goto parse_line_type_uint8;
+            case PARSE_LINE_TYPE_UINT32:
+                i_words++;
+                goto parse_line_type_uint32;
+            case PARSE_LINE_TYPE_UINT32_WITH_LOCK:
+                i_words++;
+                goto parse_line_type_uint32_with_lock;
+            case PARSE_LINE_TYPE_ADDR:
+                i_words++;
+                goto parse_line_type_addr;
+            case PARSE_LINE_TYPE_ADDR_WITH_PREFIX:
+                i_words++;
+                goto parse_line_type_addr_with_prefix;
+            case PARSE_LINE_TYPE_IFNAME:
+                i_words++;
+                goto parse_line_type_ifname;
+            case PARSE_LINE_TYPE_FLAG:
+                i_words++;
+                goto next;
+            case PARSE_LINE_TYPE_ROUTE_SCOPE:
+                i_words++;
+                goto parse_line_type_route_scope;
+            default:
+                nm_assert_not_reached();
+            }
+        }
+
+        p_info = &parse_infos[PARSE_LINE_ATTR_ROUTE_TYPE];
+        p_data = &parse_datas[PARSE_LINE_ATTR_ROUTE_TYPE];
+        if (!p_data->has
+            && NM_IN_STRSET(w,
+                            "local",
+                            "unicast",
+                            "broadcast"
+                            "multicast",
+                            "throw",
+                            "unreachable",
+                            "prohibit",
+                            "blackhole",
+                            "nat")) {
+            p_data->has = TRUE;
+            goto parse_line_type_string;
+        }
+
+        /* "to" is also accepted unqualified... (once) */
+        p_info = &parse_infos[PARSE_LINE_ATTR_ROUTE_TO];
+        p_data = &parse_datas[PARSE_LINE_ATTR_ROUTE_TO];
+        if (!p_data->has && !(p_info->disabled & af_flag)
+            && !(p_info->disabled_with_options_route && options_route)) {
+            unqualified_addr = TRUE;
+            p_data->has      = TRUE;
+            goto parse_line_type_addr;
+        }
+
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Unrecognized argument (\"to\" is duplicate or \"%s\" is garbage)",
+                    w);
+        return -EINVAL;
+
+parse_line_type_route_scope:
+        s = words[i_words];
+        if (!s)
+            goto err_word_missing_argument;
+        if (nm_streq(s, "global"))
+            p_data->v.uint8 = RT_SCOPE_UNIVERSE;
+        else if (nm_streq(s, "nowhere"))
+            p_data->v.uint8 = RT_SCOPE_NOWHERE;
+        else if (nm_streq(s, "host"))
+            p_data->v.uint8 = RT_SCOPE_HOST;
+        else if (nm_streq(s, "link"))
+            p_data->v.uint8 = RT_SCOPE_LINK;
+        else if (nm_streq(s, "site"))
+            p_data->v.uint8 = RT_SCOPE_SITE;
+        else {
+            p_data->v.uint8 = _nm_utils_ascii_str_to_int64(s, 0, 0, G_MAXUINT8, 0);
+            if (errno) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Argument for \"%s\" is not a valid number",
+                            w);
+                return -EINVAL;
+            }
+        }
+        i_words++;
+        goto next;
+
+parse_line_type_uint8:
+        s = words[i_words];
+        if (!s)
+            goto err_word_missing_argument;
+        p_data->v.uint8 =
+            _nm_utils_ascii_str_to_int64(s, p_info->int_base_16 ? 16 : 10, 0, G_MAXUINT8, 0);
+        if (errno) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Argument for \"%s\" is not a valid number",
+                        w);
+            return -EINVAL;
+        }
+        i_words++;
+        goto next;
+
+parse_line_type_uint32:
+parse_line_type_uint32_with_lock:
+        s = words[i_words];
+        if (!s)
+            goto err_word_missing_argument;
+        if (p_info->type == PARSE_LINE_TYPE_UINT32_WITH_LOCK) {
+            if (nm_streq(s, "lock")) {
+                s = words[++i_words];
+                if (!s)
+                    goto err_word_missing_argument;
+                p_data->v.uint32_with_lock.lock = TRUE;
+            } else
+                p_data->v.uint32_with_lock.lock = FALSE;
+            p_data->v.uint32_with_lock.uint32 =
+                _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT32, 0);
+        } else {
+            p_data->v.uint32 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT32, 0);
+        }
+        if (errno) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Argument for \"%s\" is not a valid number",
+                        w);
+            return -EINVAL;
+        }
+        i_words++;
+        goto next;
+
+parse_line_type_ifname:
+        s = words[i_words];
+        if (!s)
+            goto err_word_missing_argument;
+        i_words++;
+        goto next;
+
+parse_line_type_addr:
+parse_line_type_addr_with_prefix:
+        s = words[i_words];
+        if (!s)
+            goto err_word_missing_argument;
+        {
+            int prefix = -1;
+
+            if (p_info->type == PARSE_LINE_TYPE_ADDR) {
+                if (!nm_utils_parse_inaddr_bin(addr_family, s, NULL, &p_data->v.addr.addr)) {
+                    if (p_info == &parse_infos[PARSE_LINE_ATTR_ROUTE_VIA]
+                        && nm_streq(s, "(null)")) {
+                        /* Due to a bug, would older versions of NM write "via (null)"
+                         * (rh#1452648). Workaround that, and accept it.*/
+                        memset(&p_data->v.addr.addr, 0, sizeof(p_data->v.addr.addr));
+                    } else {
+                        if (unqualified_addr) {
+                            g_set_error(error,
+                                        NM_SETTINGS_ERROR,
+                                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                        "Unrecognized argument (inet prefix is expected rather "
+                                        "then \"%s\")",
+                                        w);
+                            return -EINVAL;
+                        } else {
+                            g_set_error(error,
+                                        NM_SETTINGS_ERROR,
+                                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                        "Argument for \"%s\" is not a valid IPv%c address",
+                                        w,
+                                        addr_family == AF_INET ? '4' : '6');
+                        }
+                        return -EINVAL;
+                    }
+                }
+            } else {
+                nm_assert(p_info->type == PARSE_LINE_TYPE_ADDR_WITH_PREFIX);
+                if (p_info == &parse_infos[PARSE_LINE_ATTR_ROUTE_TO] && nm_streq(s, "default")) {
+                    memset(&p_data->v.addr.addr, 0, sizeof(p_data->v.addr.addr));
+                    prefix = 0;
+                } else if (!nm_utils_parse_inaddr_prefix_bin(addr_family,
+                                                             s,
+                                                             NULL,
+                                                             &p_data->v.addr.addr,
+                                                             &prefix)) {
+                    g_set_error(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "Argument for \"%s\" is not ADDR/PREFIX format",
+                                w);
+                    return -EINVAL;
+                }
+            }
+            if (prefix == -1)
+                p_data->v.addr.has_plen = FALSE;
+            else {
+                p_data->v.addr.has_plen = TRUE;
+                p_data->v.addr.plen     = prefix;
+            }
+        }
+        i_words++;
+        goto next;
+
+parse_line_type_string:
+        s = words[i_words];
+        if (!s)
+            goto err_word_missing_argument;
+
+        p_data->v.str = s;
+        i_words++;
+        goto next;
+
+err_word_missing_argument:
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Missing argument for \"%s\"",
+                    w);
+        return -EINVAL;
+next:;
+    }
+
+    if (options_route) {
+        route = options_route;
+        nm_ip_route_ref(route);
+    } else {
+        ParseLineData *data_to     = &parse_datas[PARSE_LINE_ATTR_ROUTE_TO];
+        ParseLineData *data_via    = &parse_datas[PARSE_LINE_ATTR_ROUTE_VIA];
+        ParseLineData *data_metric = &parse_datas[PARSE_LINE_ATTR_ROUTE_METRIC];
+        guint          prefix;
+
+        if (!data_to->has) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Missing destination prefix");
+            return -EINVAL;
+        }
+
+        prefix =
+            data_to->v.addr.has_plen ? data_to->v.addr.plen : (addr_family == AF_INET ? 32 : 128);
+
+        route =
+            nm_ip_route_new_binary(addr_family,
+                                   &data_to->v.addr.addr,
+                                   prefix,
+                                   data_via->has ? &data_via->v.addr.addr : NULL,
+                                   data_metric->has ? (gint64) data_metric->v.uint32 : (gint64) -1,
+                                   error);
+        data_to->has     = FALSE;
+        data_via->has    = FALSE;
+        data_metric->has = FALSE;
+        if (!route)
+            return -EINVAL;
+    }
+
+    for (i = 0; i < G_N_ELEMENTS(parse_infos); i++) {
+        const ParseLineInfo *p_info = &parse_infos[i];
+        ParseLineData *      p_data = &parse_datas[i];
+
+        if (!p_data->has)
+            continue;
+
+        if ((p_info->ignore & af_flag) || (p_info->disabled & af_flag)
+            || (p_info->disabled_with_options_route && options_route))
+            continue;
+
+        switch (p_info->type) {
+        case PARSE_LINE_TYPE_UINT8:
+        case PARSE_LINE_TYPE_ROUTE_SCOPE:
+            nm_ip_route_set_attribute(route, p_info->key, g_variant_new_byte(p_data->v.uint8));
+            break;
+        case PARSE_LINE_TYPE_UINT32:
+            nm_ip_route_set_attribute(route, p_info->key, g_variant_new_uint32(p_data->v.uint32));
+            break;
+        case PARSE_LINE_TYPE_UINT32_WITH_LOCK:
+            if (p_data->v.uint32_with_lock.lock) {
+                nm_ip_route_set_attribute(route,
+                                          nm_sprintf_buf(buf1, "lock-%s", p_info->key),
+                                          g_variant_new_boolean(TRUE));
+            }
+            nm_ip_route_set_attribute(route,
+                                      p_info->key,
+                                      g_variant_new_uint32(p_data->v.uint32_with_lock.uint32));
+            break;
+        case PARSE_LINE_TYPE_ADDR:
+        case PARSE_LINE_TYPE_ADDR_WITH_PREFIX:
+            nm_ip_route_set_attribute(
+                route,
+                p_info->key,
+                g_variant_new_printf(
+                    "%s%s",
+                    inet_ntop(addr_family, &p_data->v.addr.addr, buf1, sizeof(buf1)),
+                    p_data->v.addr.has_plen
+                        ? nm_sprintf_buf(buf2, "/%u", (unsigned) p_data->v.addr.plen)
+                        : ""));
+            break;
+        case PARSE_LINE_TYPE_FLAG:
+            /* NOTE: the flag (for "onlink") only allows to explicitly set "TRUE".
+             * There is no way to express an explicit "FALSE" setting
+             * of this attribute, hence, the file format cannot encode
+             * that configuration. */
+            nm_ip_route_set_attribute(route, p_info->key, g_variant_new_boolean(TRUE));
+            break;
+        case PARSE_LINE_TYPE_STRING:
+            nm_ip_route_set_attribute(route, p_info->key, g_variant_new_string(p_data->v.str));
+            break;
+        default:
+            nm_assert_not_reached();
+            break;
+        }
+    }
+
+    nm_assert(_nm_ip_route_attribute_validate_all(route, NULL));
+
+    NM_SET_OUT(out_route, g_steal_pointer(&route));
+    return 0;
+}
+
+/* Returns TRUE on missing route or valid route */
+static gboolean
+read_one_ip4_route(shvarFile *ifcfg, guint32 which, NMIPRoute **out_route, GError **error)
+{
+    char          tag[256];
+    char          netmask_tag[256];
+    guint32       dest;
+    guint32       next_hop;
+    guint32       netmask;
+    gboolean      has_key;
+    const char *  v;
+    gs_free char *value = NULL;
+    gint64        prefix, metric;
+    char          inet_buf[NM_UTILS_INET_ADDRSTRLEN];
+
+    g_return_val_if_fail(ifcfg != NULL, FALSE);
+    g_return_val_if_fail(out_route && !*out_route, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    /* Destination */
+    if (!read_ip4_address(ifcfg, numbered_tag(tag, "ADDRESS", which), &has_key, &dest, error))
+        return FALSE;
+    if (!has_key) {
+        /* missing route = success */
+        *out_route = NULL;
+        return TRUE;
+    }
+
+    /* Next hop */
+    if (!read_ip4_address(ifcfg, numbered_tag(tag, "GATEWAY", which), NULL, &next_hop, error))
+        return FALSE;
+    /* We don't make distinction between missing GATEWAY IP and 0.0.0.0 */
+
+    /* Prefix */
+    if (!read_ip4_address(ifcfg,
+                          numbered_tag(netmask_tag, "NETMASK", which),
+                          &has_key,
+                          &netmask,
+                          error))
+        return FALSE;
+    if (has_key) {
+        prefix = nm_utils_ip4_netmask_to_prefix(netmask);
+        if (netmask != _nm_utils_ip4_prefix_to_netmask(prefix)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid IP4 netmask '%s' \"%s\"",
+                        netmask_tag,
+                        _nm_utils_inet4_ntop(netmask, inet_buf));
+            return FALSE;
+        }
+    } else {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Missing IP4 route element '%s'",
+                    netmask_tag);
+        return FALSE;
+    }
+
+    /* Metric */
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, numbered_tag(tag, "METRIC", which), &value);
+    if (v) {
+        metric = _nm_utils_ascii_str_to_int64(v, 10, 0, G_MAXUINT32, -1);
+        if (metric < 0) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid IP4 route metric '%s'",
+                        v);
+            return FALSE;
+        }
+    } else
+        metric = -1;
+
+    *out_route = nm_ip_route_new_binary(AF_INET, &dest, prefix, &next_hop, metric, error);
+    if (!*out_route)
+        return FALSE;
+
+    /* Options */
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, numbered_tag(tag, "OPTIONS", which), &value);
+    if (v) {
+        if (parse_route_line(v, AF_INET, *out_route, NULL, error) < 0) {
+            nm_clear_pointer(out_route, nm_ip_route_unref);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean
+read_route_file_parse(int                addr_family,
+                      const char *       filename,
+                      const char *       contents,
+                      gsize              len,
+                      NMSettingIPConfig *s_ip,
+                      GError **          error)
+{
+    gsize line_num;
+
+    nm_assert(filename);
+    nm_assert(addr_family == nm_setting_ip_config_get_addr_family(s_ip));
+    nm_assert(!error || !*error);
+
+    if (len <= 0)
+        return TRUE; /* missing/empty = success */
+
+    line_num = 0;
+    while (TRUE) {
+        nm_auto_unref_ip_route NMIPRoute *route = NULL;
+        gs_free_error GError *local             = NULL;
+        const char *          line              = contents;
+        char *                eol;
+        int                   e;
+
+        eol = strchr(contents, '\n');
+        if (eol) {
+            eol[0]   = '\0';
+            contents = &eol[1];
+        }
+
+        line_num++;
+
+        if (parse_route_line_is_comment(line))
+            goto next;
+
+        e = parse_route_line(line, addr_family, NULL, &route, &local);
+
+        if (e < 0) {
+            if (e == -ERANGE)
+                PARSE_WARNING("ignoring manual default route: '%s' (%s)", line, filename);
+            else {
+                /* we accept all unrecognized lines, because otherwise we would reject the
+                 * entire connection. */
+                PARSE_WARNING("ignoring invalid route at \"%s\" (%s:%lu): %s",
+                              line,
+                              filename,
+                              (long unsigned) line_num,
+                              local->message);
+            }
+            goto next;
+        }
+
+        if (!nm_setting_ip_config_add_route(s_ip, route))
+            PARSE_WARNING("duplicate IPv%c route", addr_family == AF_INET ? '4' : '6');
+
+next:
+        if (!eol)
+            return TRUE;
+
+        /* restore original content. */
+        eol[0] = '\n';
+    }
+}
+
+static gboolean
+read_route_file(int addr_family, const char *filename, NMSettingIPConfig *s_ip, GError **error)
+{
+    gs_free char *contents = NULL;
+    gsize         len;
+
+    nm_assert(filename);
+    nm_assert(addr_family == nm_setting_ip_config_get_addr_family(s_ip));
+    nm_assert(!error || !*error);
+
+    if (!g_file_get_contents(filename, &contents, &len, NULL))
+        return TRUE; /* missing/empty = success */
+
+    return read_route_file_parse(addr_family, filename, contents, len, s_ip, error);
+}
+
+static void
+parse_dns_options(NMSettingIPConfig *ip_config, const char *value)
+{
+    gs_free const char **options = NULL;
+    const char *const *  item;
+
+    g_return_if_fail(ip_config);
+
+    if (!value)
+        return;
+
+    if (!nm_setting_ip_config_has_dns_options(ip_config))
+        nm_setting_ip_config_clear_dns_options(ip_config, TRUE);
+
+    options = nm_utils_strsplit_set(value, " ");
+    if (options) {
+        for (item = options; *item; item++) {
+            if (!nm_setting_ip_config_add_dns_option(ip_config, *item))
+                PARSE_WARNING("can't add DNS option '%s'", *item);
+        }
+    }
+}
+
+static gboolean
+parse_full_ip6_address(shvarFile *   ifcfg,
+                       const char *  addr_str,
+                       int           i,
+                       NMIPAddress **out_address,
+                       GError **     error)
+{
+    NMIPAddress *addr;
+    NMIPAddr     addr_bin;
+    int          prefix;
+
+    nm_assert(addr_str);
+    nm_assert(out_address && !*out_address);
+    nm_assert(!error || !*error);
+
+    if (!nm_utils_parse_inaddr_prefix_bin(AF_INET6, addr_str, NULL, &addr_bin, &prefix)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Invalid IP6 address '%s'",
+                    addr_str);
+        return FALSE;
+    }
+
+    if (prefix < 0)
+        prefix = 64;
+
+    addr = nm_ip_address_new_binary(AF_INET6, &addr_bin, prefix, error);
+    if (!addr)
+        return FALSE;
+
+    *out_address = addr;
+    return TRUE;
+}
+
+static NMSetting *
+make_user_setting(shvarFile *ifcfg)
+{
+    gboolean        has_user_data         = FALSE;
+    gs_unref_object NMSettingUser *s_user = NULL;
+    gs_unref_hashtable GHashTable *keys   = NULL;
+    GHashTableIter                 iter;
+    const char *                   key;
+    nm_auto_free_gstring GString *str = NULL;
+
+    keys = svGetKeys(ifcfg, SV_KEY_TYPE_USER);
+    if (!keys)
+        return NULL;
+
+    g_hash_table_iter_init(&iter, keys);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) {
+        const char *  value;
+        gs_free char *value_to_free = NULL;
+
+        value = svGetValue(ifcfg, key, &value_to_free);
+
+        if (!value)
+            continue;
+
+        if (!str)
+            str = g_string_sized_new(100);
+        else
+            g_string_set_size(str, 0);
+
+        if (!nms_ifcfg_rh_utils_user_key_decode(key + NM_STRLEN("NM_USER_"), str))
+            continue;
+
+        if (!s_user)
+            s_user = NM_SETTING_USER(nm_setting_user_new());
+
+        if (nm_setting_user_set_data(s_user, str->str, value, NULL))
+            has_user_data = TRUE;
+    }
+
+    return has_user_data ? NM_SETTING(g_steal_pointer(&s_user)) : NULL;
+}
+
+static void
+make_match_setting_prop(const char *     v,
+                        NMSettingMatch **s_match,
+                        void (*add_fcn)(NMSettingMatch *s_match, const char *value))
+{
+    gs_free const char **strv = NULL;
+    gsize                i;
+
+    strv = nm_utils_escaped_tokens_split(v, NM_ASCII_SPACES);
+    if (strv) {
+        for (i = 0; strv[i]; i++) {
+            if (!(*s_match))
+                *s_match = NM_SETTING_MATCH(nm_setting_match_new());
+            add_fcn(*s_match, strv[i]);
+        }
+    }
+}
+
+static NMSetting *
+make_match_setting(shvarFile *ifcfg)
+{
+    NMSettingMatch *s_match   = NULL;
+    gs_free char *  value_ifn = NULL;
+    gs_free char *  value_kcl = NULL;
+    gs_free char *  value_d   = NULL;
+    gs_free char *  value_p   = NULL;
+    const char *    v;
+
+    v = svGetValueStr(ifcfg, "MATCH_INTERFACE_NAME", &value_ifn);
+    make_match_setting_prop(v, &s_match, nm_setting_match_add_interface_name);
+    v = svGetValueStr(ifcfg, "MATCH_KERNEL_COMMAND_LINE", &value_kcl);
+    make_match_setting_prop(v, &s_match, nm_setting_match_add_kernel_command_line);
+    v = svGetValueStr(ifcfg, "MATCH_DRIVER", &value_d);
+    make_match_setting_prop(v, &s_match, nm_setting_match_add_driver);
+    v = svGetValueStr(ifcfg, "MATCH_PATH", &value_p);
+    make_match_setting_prop(v, &s_match, nm_setting_match_add_path);
+
+    return NM_SETTING(s_match);
+}
+
+static NMSetting *
+make_proxy_setting(shvarFile *ifcfg)
+{
+    NMSettingProxy *     s_proxy = NULL;
+    gs_free char *       value   = NULL;
+    const char *         v;
+    NMSettingProxyMethod method;
+
+    v = svGetValueStr(ifcfg, "PROXY_METHOD", &value);
+    if (!v)
+        return NULL;
+
+    if (!g_ascii_strcasecmp(v, "auto"))
+        method = NM_SETTING_PROXY_METHOD_AUTO;
+    else
+        method = NM_SETTING_PROXY_METHOD_NONE;
+
+    s_proxy = (NMSettingProxy *) nm_setting_proxy_new();
+
+    switch (method) {
+    case NM_SETTING_PROXY_METHOD_AUTO:
+        g_object_set(s_proxy, NM_SETTING_PROXY_METHOD, (int) NM_SETTING_PROXY_METHOD_AUTO, NULL);
+
+        nm_clear_g_free(&value);
+        v = svGetValueStr(ifcfg, "PAC_URL", &value);
+        if (v)
+            g_object_set(s_proxy, NM_SETTING_PROXY_PAC_URL, v, NULL);
+
+        nm_clear_g_free(&value);
+        v = svGetValueStr(ifcfg, "PAC_SCRIPT", &value);
+        if (v)
+            g_object_set(s_proxy, NM_SETTING_PROXY_PAC_SCRIPT, v, NULL);
+
+        break;
+    case NM_SETTING_PROXY_METHOD_NONE:
+        g_object_set(s_proxy, NM_SETTING_PROXY_METHOD, (int) NM_SETTING_PROXY_METHOD_NONE, NULL);
+        break;
+    }
+
+    if (svGetValueBoolean(ifcfg, "BROWSER_ONLY", FALSE))
+        g_object_set(s_proxy, NM_SETTING_PROXY_BROWSER_ONLY, TRUE, NULL);
+
+    return NM_SETTING(s_proxy);
+}
+
+static NMSetting *
+make_ip4_setting(shvarFile *ifcfg,
+                 shvarFile *network_ifcfg,
+                 gboolean   routes_read,
+                 gboolean * out_has_defroute,
+                 GError **  error)
+{
+    gs_unref_object NMSettingIPConfig *s_ip4      = NULL;
+    gs_free char *                     route_path = NULL;
+    gs_free char *                     value      = NULL;
+    const char *                       v;
+    char *                             method;
+    gs_free char *                     dns_options_free = NULL;
+    const char *                       dns_options      = NULL;
+    gs_free char *                     gateway          = NULL;
+    int                                i;
+    guint32                            a;
+    gboolean                           has_key;
+    gboolean                           never_default;
+    gint64                             i64;
+    int                                priority;
+    const char *const *                item;
+    guint32                            route_table;
+
+    nm_assert(out_has_defroute && !*out_has_defroute);
+
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+
+    /* First check if DEFROUTE is set for this device; DEFROUTE has the
+     * opposite meaning from never-default. The default if DEFROUTE is not
+     * specified is DEFROUTE=yes which means that this connection can be used
+     * as a default route
+     */
+    i = svGetValueBoolean(ifcfg, "DEFROUTE", -1);
+    if (i == -1)
+        never_default = FALSE;
+    else {
+        never_default     = !i;
+        *out_has_defroute = TRUE;
+    }
+
+    /* Then check if GATEWAYDEV; it's global and overrides DEFROUTE */
+    if (network_ifcfg) {
+        gs_free char *gatewaydev_value = NULL;
+        const char *  gatewaydev;
+
+        /* Get the connection ifcfg device name and the global gateway device */
+        v           = svGetValueStr(ifcfg, "DEVICE", &value);
+        gatewaydev  = svGetValueStr(network_ifcfg, "GATEWAYDEV", &gatewaydev_value);
+        dns_options = svGetValue(network_ifcfg, "RES_OPTIONS", &dns_options_free);
+
+        /* If there was a global gateway device specified, then only connections
+         * for that device can be the default connection.
+         */
+        if (gatewaydev && v)
+            never_default = !!strcmp(v, gatewaydev);
+
+        nm_clear_g_free(&value);
+    }
+
+    v = svGetValueStr(ifcfg, "BOOTPROTO", &value);
+
+    if (!v || !*v || !g_ascii_strcasecmp(v, "none")) {
+        if (is_any_ip4_address_defined(ifcfg, NULL))
+            method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
+        else
+            method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+    } else if (!g_ascii_strcasecmp(v, "bootp") || !g_ascii_strcasecmp(v, "dhcp")) {
+        method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+    } else if (!g_ascii_strcasecmp(v, "static")) {
+        if (is_any_ip4_address_defined(ifcfg, NULL))
+            method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
+        else
+            method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+    } else if (!g_ascii_strcasecmp(v, "autoip")) {
+        method = NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL;
+    } else if (!g_ascii_strcasecmp(v, "shared")) {
+        method = NM_SETTING_IP4_CONFIG_METHOD_SHARED;
+    } else {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Unknown BOOTPROTO '%s'",
+                    v);
+        return NULL;
+    }
+
+    /* the route table (policy routing) is ignored if we don't handle routes. */
+    route_table = svGetValueInt64(ifcfg, "IPV4_ROUTE_TABLE", 10, 0, G_MAXUINT32, 0);
+    if (route_table != 0 && !routes_read) {
+        PARSE_WARNING(
+            "'rule-' or 'rule6-' files are present; Policy routing (IPV4_ROUTE_TABLE) is ignored");
+        route_table = 0;
+    }
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 method,
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
+                 !svGetValueBoolean(ifcfg, "PEERDNS", TRUE),
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
+                 !svGetValueBoolean(ifcfg, "PEERROUTES", TRUE),
+                 NM_SETTING_IP_CONFIG_NEVER_DEFAULT,
+                 never_default,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 !svGetValueBoolean(ifcfg, "IPV4_FAILURE_FATAL", FALSE),
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 svGetValueInt64(ifcfg, "IPV4_ROUTE_METRIC", 10, -1, G_MAXUINT32, -1),
+                 NM_SETTING_IP_CONFIG_ROUTE_TABLE,
+                 (guint) route_table,
+                 NULL);
+
+    if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+        return NM_SETTING(g_steal_pointer(&s_ip4));
+
+    /* Handle DHCP settings */
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCP_HOSTNAME", &value);
+    if (v)
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCP_FQDN", &value);
+    if (v) {
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_DHCP_HOSTNAME,
+                     NULL,
+                     NM_SETTING_IP4_CONFIG_DHCP_FQDN,
+                     v,
+                     NULL);
+    }
+
+    i64 = svGetValueInt64(ifcfg, "DHCP_HOSTNAME_FLAGS", 10, 0, G_MAXUINT32, -1);
+    if (i64 > -1) {
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, (guint) i64, NULL);
+    }
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
+                 svGetValueBoolean(ifcfg, "DHCP_SEND_HOSTNAME", TRUE),
+                 NM_SETTING_IP_CONFIG_DHCP_TIMEOUT,
+                 (int) svGetValueInt64(ifcfg, "IPV4_DHCP_TIMEOUT", 10, 0, G_MAXINT32, 0),
+                 NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCP_CLIENT_ID", &value);
+    if (v)
+        g_object_set(s_ip4, NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCP_VENDOR_CLASS_IDENTIFIER", &value);
+    if (v)
+        g_object_set(s_ip4, NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCP_IAID", &value);
+    if (v)
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_IAID, v, NULL);
+
+    /* Read static IP addresses.
+     * Read them even for AUTO method - in this case the addresses are
+     * added to the automatic ones. Note that this is not currently supported by
+     * the legacy 'network' service (ifup-eth).
+     */
+    for (i = -1;; i++) {
+        NMIPAddress *addr = NULL;
+
+        /* gateway will only be set if still unset. Hence, we don't leak gateway
+         * here by calling read_full_ip4_address() repeatedly */
+        if (!read_full_ip4_address(ifcfg, i, NULL, &addr, &gateway, error))
+            return NULL;
+
+        if (!addr) {
+            /* The first mandatory variable is 2-indexed (IPADDR2)
+             * Variables IPADDR, IPADDR0 and IPADDR1 are optional */
+            if (i > 1)
+                break;
+            continue;
+        }
+
+        if (!nm_setting_ip_config_add_address(s_ip4, addr))
+            PARSE_WARNING("duplicate IP4 address");
+        nm_ip_address_unref(addr);
+    }
+
+    /* Gateway */
+    if (!gateway) {
+        if (network_ifcfg) {
+            gboolean read_success;
+
+            read_success = read_ip4_address(network_ifcfg, "GATEWAY", &has_key, &a, error);
+            if (!read_success)
+                return NULL;
+            if (has_key) {
+                if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) {
+                    gs_free char *f = g_path_get_basename(svFileGetName(ifcfg));
+                    PARSE_WARNING("ignoring GATEWAY (/etc/sysconfig/network) for %s "
+                                  "because the connection has no static addresses",
+                                  f);
+                } else
+                    gateway = nm_utils_inet4_ntop_dup(a);
+            }
+        }
+    }
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway, NULL);
+
+    if (gateway && never_default)
+        PARSE_WARNING("GATEWAY will be ignored when DEFROUTE is disabled");
+
+    /* We used to skip saving a lot of unused properties for the ipv4 shared method.
+     * We want now to persist them but... unfortunately loading DNS or DOMAIN options
+     * would cause a fail in the ipv4 verify() function. As we don't want any regression
+     * in the unlikely event that someone has a working ifcfg file for an IPv4 shared ip
+     * connection with a crafted "DNS" entry... don't load it. So we will avoid failing
+     * the connection) */
+    if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+        /* DNS servers
+         * Pick up just IPv4 addresses (IPv6 addresses are taken by make_ip6_setting())
+         */
+        for (i = 1; i <= 10; i++) {
+            char tag[256];
+
+            numbered_tag(tag, "DNS", i);
+            nm_clear_g_free(&value);
+            v = svGetValueStr(ifcfg, tag, &value);
+            if (v) {
+                if (nm_utils_ipaddr_is_valid(AF_INET, v)) {
+                    if (!nm_setting_ip_config_add_dns(s_ip4, v))
+                        PARSE_WARNING("duplicate DNS server %s", tag);
+                } else if (nm_utils_ipaddr_is_valid(AF_INET6, v)) {
+                    /* Ignore IPv6 addresses */
+                } else {
+                    PARSE_WARNING("invalid DNS server address %s", v);
+                    return NULL;
+                }
+            }
+        }
+
+        /* DNS searches */
+        nm_clear_g_free(&value);
+        v = svGetValueStr(ifcfg, "DOMAIN", &value);
+        if (v) {
+            gs_free const char **searches = NULL;
+
+            searches = nm_utils_strsplit_set(v, " ");
+            if (searches) {
+                for (item = searches; *item; item++) {
+                    if (!nm_setting_ip_config_add_dns_search(s_ip4, *item))
+                        PARSE_WARNING("duplicate DNS domain '%s'", *item);
+                }
+            }
+        }
+    }
+
+    /* DNS options */
+    nm_clear_g_free(&value);
+    parse_dns_options(s_ip4, svGetValue(ifcfg, "RES_OPTIONS", &value));
+    parse_dns_options(s_ip4, dns_options);
+
+    /* DNS priority */
+    priority = svGetValueInt64(ifcfg, "IPV4_DNS_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DNS_PRIORITY, priority, NULL);
+
+    /* Static routes  - route-<name> file */
+    route_path = utils_get_route_path(svFileGetName(ifcfg));
+
+    if (routes_read) {
+        gs_free char *contents = NULL;
+        gsize         len;
+
+        if (!g_file_get_contents(route_path, &contents, &len, NULL))
+            len = 0;
+
+        if (utils_has_route_file_new_syntax_content(contents, len)) {
+            nm_auto_shvar_file_close shvarFile *route_ifcfg = NULL;
+
+            /* Parse route file in new syntax */
+            route_ifcfg = svFile_new(route_path, -1, contents);
+            for (i = 0;; i++) {
+                nm_auto_unref_ip_route NMIPRoute *route = NULL;
+
+                if (!read_one_ip4_route(route_ifcfg, i, &route, error))
+                    return NULL;
+
+                if (!route)
+                    break;
+
+                if (!nm_setting_ip_config_add_route(s_ip4, route))
+                    PARSE_WARNING("duplicate IP4 route");
+            }
+        } else {
+            if (!read_route_file_parse(AF_INET, route_path, contents, len, s_ip4, error))
+                return NULL;
+        }
+    }
+
+    /* Legacy value NM used for a while but is incorrect (rh #459370) */
+    if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)
+        && !nm_setting_ip_config_get_num_dns_searches(s_ip4)) {
+        nm_clear_g_free(&value);
+        v = svGetValueStr(ifcfg, "SEARCH", &value);
+        if (v) {
+            gs_free const char **searches = NULL;
+
+            searches = nm_utils_strsplit_set(v, " ");
+            if (searches) {
+                for (item = searches; *item; item++) {
+                    if (!nm_setting_ip_config_add_dns_search(s_ip4, *item))
+                        PARSE_WARNING("duplicate DNS search '%s'", *item);
+                }
+            }
+        }
+    }
+
+    i64 = svGetValueInt64(ifcfg, "ACD_TIMEOUT", 10, -1, NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX, -2);
+    if (i64 == -2) {
+        i64 = svGetValueInt64(ifcfg,
+                              "ARPING_WAIT",
+                              10,
+                              -1,
+                              NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX / 1000,
+                              -1);
+        if (i64 > 0)
+            i64 *= 1000;
+    }
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT, (int) i64, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCP_REJECT_SERVERS", &value);
+    if (v) {
+        gs_free const char **strv = NULL;
+
+        strv = nm_utils_escaped_tokens_split(v, NM_ASCII_SPACES);
+        if (strv) {
+            for (item = strv; *item; item++)
+                nm_setting_ip_config_add_dhcp_reject_server(s_ip4, *item);
+        }
+    }
+
+    return NM_SETTING(g_steal_pointer(&s_ip4));
+}
+
+static void
+read_aliases(NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *filename)
+{
+    GDir *        dir;
+    gs_free char *dirname   = NULL;
+    gs_free char *base      = NULL;
+    NMIPAddress * base_addr = NULL;
+    GError *      err       = NULL;
+
+    g_return_if_fail(s_ip4 != NULL);
+    g_return_if_fail(filename != NULL);
+
+    if (nm_setting_ip_config_get_num_addresses(s_ip4) > 0)
+        base_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+
+    dirname = g_path_get_dirname(filename);
+    nm_assert(dirname != NULL);
+    base = g_path_get_basename(filename);
+    nm_assert(base != NULL);
+
+    dir = g_dir_open(dirname, 0, &err);
+    if (dir) {
+        const char * item;
+        NMIPAddress *addr;
+        gboolean     ok;
+
+        while ((item = g_dir_read_name(dir))) {
+            nm_auto_shvar_file_close shvarFile *parsed       = NULL;
+            gs_free char *                      gateway      = NULL;
+            gs_free char *                      device_value = NULL;
+            gs_free char *                      full_path    = NULL;
+            const char *                        device;
+            const char *                        p;
+
+            if (!utils_is_ifcfg_alias_file(item, base))
+                continue;
+
+            full_path = g_build_filename(dirname, item, NULL);
+
+            p = strchr(item, ':');
+            g_assert(p != NULL); /* we know this is true from utils_is_ifcfg_alias_file() */
+            for (p++; *p; p++) {
+                if (!g_ascii_isalnum(*p) && *p != '_')
+                    break;
+            }
+            if (*p) {
+                PARSE_WARNING("ignoring alias file '%s' with invalid name", full_path);
+                continue;
+            }
+
+            parsed = svOpenFile(full_path, &err);
+            if (!parsed) {
+                PARSE_WARNING("couldn't parse alias file '%s': %s", full_path, err->message);
+                g_clear_error(&err);
+                continue;
+            }
+
+            device = svGetValueStr(parsed, "DEVICE", &device_value);
+            if (!device) {
+                PARSE_WARNING("alias file '%s' has no DEVICE", full_path);
+                continue;
+            }
+            /* We know that item starts with IFCFG_TAG from utils_is_ifcfg_alias_file() */
+            if (strcmp(device, item + strlen(IFCFG_TAG)) != 0) {
+                PARSE_WARNING("alias file '%s' has invalid DEVICE (%s) for filename",
+                              full_path,
+                              device);
+                continue;
+            }
+
+            addr = NULL;
+            ok   = read_full_ip4_address(parsed,
+                                       -1,
+                                       base_addr,
+                                       &addr,
+                                       read_defroute ? &gateway : NULL,
+                                       &err);
+            if (ok) {
+                nm_ip_address_set_attribute(addr,
+                                            NM_IP_ADDRESS_ATTRIBUTE_LABEL,
+                                            g_variant_new_string(device));
+                if (!nm_setting_ip_config_add_address(s_ip4, addr))
+                    PARSE_WARNING("duplicate IP4 address in alias file %s", item);
+                if (nm_streq0(nm_setting_ip_config_get_method(s_ip4),
+                              NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+                    g_object_set(s_ip4,
+                                 NM_SETTING_IP_CONFIG_METHOD,
+                                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                                 NULL);
+                if (read_defroute) {
+                    int i;
+
+                    if (gateway) {
+                        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway, NULL);
+                        read_defroute = FALSE;
+                    }
+                    i = svGetValueBoolean(parsed, "DEFROUTE", -1);
+                    if (i != -1) {
+                        g_object_set(s_ip4,
+                                     NM_SETTING_IP_CONFIG_NEVER_DEFAULT,
+                                     (gboolean) !i,
+                                     NULL);
+                        read_defroute = FALSE;
+                    }
+                }
+            } else {
+                PARSE_WARNING("error reading IP4 address from alias file '%s': %s",
+                              full_path,
+                              err ? err->message : "no address");
+                g_clear_error(&err);
+            }
+            nm_ip_address_unref(addr);
+        }
+
+        g_dir_close(dir);
+    } else {
+        PARSE_WARNING("can not read directory '%s': %s", dirname, err->message);
+        g_error_free(err);
+    }
+}
+
+static NMSetting *
+make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_read, GError **error)
+{
+    gs_unref_object NMSettingIPConfig *s_ip6 = NULL;
+    const char *                       v;
+    gs_free char *                     value = NULL;
+    gboolean                           ipv6init;
+    gboolean                           ipv6forwarding;
+    gboolean                           disabled;
+    gboolean                           dhcp6  = FALSE;
+    char *                             method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
+    const char *                       ipv6addr, *ipv6addr_secondaries;
+    gs_free char *                     ipv6addr_to_free             = NULL;
+    gs_free char *                     ipv6addr_secondaries_to_free = NULL;
+    gs_free const char **              list                         = NULL;
+    const char *const *                iter;
+    guint32                            i;
+    gint64                             i64;
+    int                                i_val;
+    GError *                           local = NULL;
+    int                                priority;
+    gboolean                           never_default = FALSE;
+    gboolean                           ip6_privacy   = FALSE, ip6_privacy_prefer_public_ip;
+    NMSettingIP6ConfigPrivacy          ip6_privacy_val;
+    guint32                            route_table;
+
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+
+    /* First check if IPV6_DEFROUTE is set for this device; IPV6_DEFROUTE has the
+     * opposite meaning from never-default. The default if IPV6_DEFROUTE is not
+     * specified is IPV6_DEFROUTE=yes which means that this connection can be used
+     * as a default route
+     */
+    never_default = !svGetValueBoolean(ifcfg, "IPV6_DEFROUTE", TRUE);
+
+    /* Then check if IPV6_DEFAULTGW or IPV6_DEFAULTDEV is specified;
+     * they are global and override IPV6_DEFROUTE
+     * When both are set, the device specified in IPV6_DEFAULTGW takes preference.
+     */
+    if (network_ifcfg) {
+        const char *  ipv6_defaultgw, *ipv6_defaultdev;
+        gs_free char *ipv6_defaultgw_to_free  = NULL;
+        gs_free char *ipv6_defaultdev_to_free = NULL;
+        const char *  default_dev             = NULL;
+
+        /* Get the connection ifcfg device name and the global default route device */
+        nm_clear_g_free(&value);
+        v               = svGetValueStr(ifcfg, "DEVICE", &value);
+        ipv6_defaultgw  = svGetValueStr(network_ifcfg, "IPV6_DEFAULTGW", &ipv6_defaultgw_to_free);
+        ipv6_defaultdev = svGetValueStr(network_ifcfg, "IPV6_DEFAULTDEV", &ipv6_defaultdev_to_free);
+
+        if (ipv6_defaultgw) {
+            default_dev = strchr(ipv6_defaultgw, '%');
+            if (default_dev)
+                default_dev++;
+        }
+        if (!default_dev)
+            default_dev = ipv6_defaultdev;
+
+        /* If there was a global default route device specified, then only connections
+         * for that device can be the default connection.
+         */
+        if (default_dev && v)
+            never_default = !!strcmp(v, default_dev);
+    }
+
+    /* Find out method property */
+    /* Is IPV6 enabled? Set method to "ignored", when not enabled */
+    disabled = svGetValueBoolean(ifcfg, "IPV6_DISABLED", FALSE);
+    nm_clear_g_free(&value);
+    v        = svGetValueStr(ifcfg, "IPV6INIT", &value);
+    ipv6init = svGetValueBoolean(ifcfg, "IPV6INIT", FALSE);
+    if (!v) {
+        if (network_ifcfg)
+            ipv6init = svGetValueBoolean(network_ifcfg, "IPV6INIT", FALSE);
+    }
+
+    if (disabled)
+        method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+    else if (!ipv6init)
+        method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
+    else {
+        ipv6forwarding = svGetValueBoolean(ifcfg, "IPV6FORWARDING", FALSE);
+        nm_clear_g_free(&value);
+        v     = svGetValueStr(ifcfg, "IPV6_AUTOCONF", &value);
+        dhcp6 = svGetValueBoolean(ifcfg, "DHCPV6C", FALSE);
+
+        if (!g_strcmp0(v, "shared"))
+            method = NM_SETTING_IP6_CONFIG_METHOD_SHARED;
+        else if (svParseBoolean(v, !ipv6forwarding))
+            method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+        else if (dhcp6)
+            method = NM_SETTING_IP6_CONFIG_METHOD_DHCP;
+        else {
+            /* IPV6_AUTOCONF=no and no IPv6 address -> method 'link-local' */
+            nm_clear_g_free(&value);
+            v = svGetValueStr(ifcfg, "IPV6ADDR", &value);
+            if (!v) {
+                nm_clear_g_free(&value);
+                v = svGetValueStr(ifcfg, "IPV6ADDR_SECONDARIES", &value);
+            }
+
+            if (!v)
+                method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL;
+        }
+    }
+    /* TODO - handle other methods */
+
+    /* Read IPv6 Privacy Extensions configuration */
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IPV6_PRIVACY", &value);
+    if (v) {
+        ip6_privacy = svParseBoolean(v, FALSE);
+        if (!ip6_privacy)
+            ip6_privacy = (g_strcmp0(v, "rfc4941") == 0) || (g_strcmp0(v, "rfc3041") == 0);
+    }
+    ip6_privacy_prefer_public_ip = svGetValueBoolean(ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP", FALSE);
+    ip6_privacy_val              = v ? (ip6_privacy ? (ip6_privacy_prefer_public_ip
+                                                           ? NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR
+                                                           : NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR)
+                                                    : NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED)
+                                     : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+
+    /* the route table (policy routing) is ignored if we don't handle routes. */
+    route_table = svGetValueInt64(ifcfg, "IPV6_ROUTE_TABLE", 10, 0, G_MAXUINT32, 0);
+    if (route_table != 0 && !routes_read) {
+        PARSE_WARNING(
+            "'rule-' or 'rule6-' files are present; Policy routing (IPV6_ROUTE_TABLE) is ignored");
+        route_table = 0;
+    }
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 method,
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
+                 !svGetValueBoolean(ifcfg, "IPV6_PEERDNS", TRUE),
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
+                 !svGetValueBoolean(ifcfg, "IPV6_PEERROUTES", TRUE),
+                 NM_SETTING_IP_CONFIG_NEVER_DEFAULT,
+                 never_default,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 !svGetValueBoolean(ifcfg, "IPV6_FAILURE_FATAL", FALSE),
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 svGetValueInt64(ifcfg, "IPV6_ROUTE_METRIC", 10, -1, G_MAXUINT32, -1),
+                 NM_SETTING_IP_CONFIG_ROUTE_TABLE,
+                 (guint) route_table,
+                 NM_SETTING_IP6_CONFIG_IP6_PRIVACY,
+                 ip6_privacy_val,
+                 NULL);
+
+    /* Don't bother to read IP, DNS and routes when IPv6 is disabled */
+    if (NM_IN_STRSET(method,
+                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
+        return NM_SETTING(g_steal_pointer(&s_ip6));
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCPV6_DUID", &value);
+    if (v)
+        g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_DHCP_DUID, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCPV6_IAID", &value);
+    if (v)
+        g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_IAID, 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
+     */
+    if (!v
+        && (!strcmp(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+            || !strcmp(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP))) {
+        nm_clear_g_free(&value);
+        v = svGetValueStr(ifcfg, "DHCP_HOSTNAME", &value);
+        if (v && !strchr(v, '.'))
+            v = NULL;
+    }
+    if (v)
+        g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL);
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
+                 svGetValueBoolean(ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE),
+                 NM_SETTING_IP_CONFIG_DHCP_TIMEOUT,
+                 (int) svGetValueInt64(ifcfg, "IPV6_DHCP_TIMEOUT", 10, 0, G_MAXINT32, 0),
+                 NM_SETTING_IP6_CONFIG_RA_TIMEOUT,
+                 (int) svGetValueInt64(ifcfg, "IPV6_RA_TIMEOUT", 10, 0, G_MAXINT32, 0),
+                 NULL);
+
+    i64 = svGetValueInt64(ifcfg, "DHCPV6_HOSTNAME_FLAGS", 10, 0, G_MAXUINT32, -1);
+    if (i64 > -1) {
+        g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, (guint) i64, NULL);
+    }
+
+    /* Read static IP addresses.
+     * Read them even for AUTO and DHCP methods - in this case the addresses are
+     * added to the automatic ones. Note that this is not currently supported by
+     * the legacy 'network' service (ifup-eth).
+     */
+    ipv6addr = svGetValueStr(ifcfg, "IPV6ADDR", &ipv6addr_to_free);
+    ipv6addr_secondaries =
+        svGetValueStr(ifcfg, "IPV6ADDR_SECONDARIES", &ipv6addr_secondaries_to_free);
+
+    nm_clear_g_free(&value);
+    value = g_strjoin(ipv6addr && ipv6addr_secondaries ? " " : NULL,
+                      ipv6addr ?: "",
+                      ipv6addr_secondaries ?: "",
+                      NULL);
+
+    list = nm_utils_strsplit_set(value, " ");
+    for (iter = list, i = 0; iter && *iter; iter++, i++) {
+        NMIPAddress *addr = NULL;
+
+        if (!parse_full_ip6_address(ifcfg, *iter, i, &addr, error))
+            return NULL;
+
+        if (!nm_setting_ip_config_add_address(s_ip6, addr))
+            PARSE_WARNING("duplicate IP6 address");
+        nm_ip_address_unref(addr);
+    }
+
+    /* Gateway */
+    if (nm_setting_ip_config_get_num_addresses(s_ip6)) {
+        nm_clear_g_free(&value);
+        v = svGetValueStr(ifcfg, "IPV6_DEFAULTGW", &value);
+        if (!v) {
+            /* If no gateway in the ifcfg, try global /etc/sysconfig/network instead */
+            if (network_ifcfg) {
+                nm_clear_g_free(&value);
+                v = svGetValueStr(network_ifcfg, "IPV6_DEFAULTGW", &value);
+            }
+        }
+        if (v) {
+            char *ptr;
+            if ((ptr = strchr(v, '%')) != NULL)
+                *ptr = '\0'; /* remove %interface prefix if present */
+            if (!nm_utils_ipaddr_is_valid(AF_INET6, v)) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Invalid IP6 address '%s'",
+                            v);
+                return NULL;
+            }
+
+            g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, v, NULL);
+        }
+    }
+
+    i_val = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
+    if (!svGetValueEnum(ifcfg,
+                        "IPV6_ADDR_GEN_MODE",
+                        nm_setting_ip6_config_addr_gen_mode_get_type(),
+                        &i_val,
+                        &local)) {
+        PARSE_WARNING("%s", local->message);
+        g_clear_error(&local);
+    }
+    g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, i_val, NULL);
+
+    /* IPv6 tokenized interface identifier */
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IPV6_TOKEN", &value);
+    if (v)
+        g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_TOKEN, v, NULL);
+
+    /* DNS servers
+     * Pick up just IPv6 addresses (IPv4 addresses are taken by make_ip4_setting())
+     */
+    for (i = 1; i <= 10; i++) {
+        char tag[256];
+
+        numbered_tag(tag, "DNS", i);
+        nm_clear_g_free(&value);
+        v = svGetValueStr(ifcfg, tag, &value);
+        if (!v) {
+            /* all done */
+            break;
+        }
+
+        if (nm_utils_ipaddr_is_valid(AF_INET6, v)) {
+            if (!nm_setting_ip_config_add_dns(s_ip6, v))
+                PARSE_WARNING("duplicate DNS server %s", tag);
+        } else if (nm_utils_ipaddr_is_valid(AF_INET, v)) {
+            /* Ignore IPv4 addresses */
+        } else {
+            PARSE_WARNING("invalid DNS server address %s", v);
+            return NULL;
+        }
+    }
+
+    if (!routes_read) {
+        /* NOP */
+    } else {
+        gs_free char *route6_path = NULL;
+
+        /* Read static routes from route6-<interface> file */
+        route6_path = utils_get_route6_path(svFileGetName(ifcfg));
+        if (!read_route_file(AF_INET6, route6_path, s_ip6, error))
+            return NULL;
+    }
+
+    /* DNS searches */
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IPV6_DOMAIN", &value);
+    if (v) {
+        gs_free const char **searches = NULL;
+
+        searches = nm_utils_strsplit_set(v, " ");
+        if (searches) {
+            for (iter = searches; *iter; iter++) {
+                if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter))
+                    PARSE_WARNING("duplicate DNS domain '%s'", *iter);
+            }
+        }
+    }
+
+    /* DNS options */
+    nm_clear_g_free(&value);
+    parse_dns_options(s_ip6, svGetValue(ifcfg, "IPV6_RES_OPTIONS", &value));
+
+    /* DNS priority */
+    priority = svGetValueInt64(ifcfg, "IPV6_DNS_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DNS_PRIORITY, priority, NULL);
+
+    return NM_SETTING(g_steal_pointer(&s_ip6));
+}
+
+static NMSetting *
+make_hostname_setting(shvarFile *ifcfg)
+{
+    NMSetting *setting;
+    NMTernary  from_dhcp;
+    NMTernary  from_dns_lookup;
+    NMTernary  only_from_default;
+    int        priority;
+
+    priority = svGetValueInt64(ifcfg, "HOSTNAME_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
+
+    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");
+
+    /* 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)
+        return NULL;
+
+    setting = nm_setting_hostname_new();
+
+    g_object_set(setting,
+                 NM_SETTING_HOSTNAME_PRIORITY,
+                 priority,
+                 NM_SETTING_HOSTNAME_FROM_DHCP,
+                 from_dhcp,
+                 NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP,
+                 from_dns_lookup,
+                 NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT,
+                 only_from_default,
+                 NULL);
+
+    return setting;
+}
+
+static NMSetting *
+make_sriov_setting(shvarFile *ifcfg)
+{
+    gs_unref_hashtable GHashTable *keys = NULL;
+    gs_unref_ptrarray GPtrArray *vfs    = NULL;
+    int                          autoprobe_drivers;
+    NMSettingSriov *             s_sriov;
+    gint64                       total_vfs;
+
+    total_vfs = svGetValueInt64(ifcfg, "SRIOV_TOTAL_VFS", 10, 0, G_MAXUINT32, -1);
+
+    autoprobe_drivers = svGetValueInt64(ifcfg,
+                                        "SRIOV_AUTOPROBE_DRIVERS",
+                                        10,
+                                        NM_TERNARY_DEFAULT,
+                                        NM_TERNARY_TRUE,
+                                        -2);
+
+    keys = svGetKeys(ifcfg, SV_KEY_TYPE_SRIOV_VF);
+    if (keys) {
+        GHashTableIter iter;
+        const char *   key;
+
+        g_hash_table_iter_init(&iter, keys);
+        while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) {
+            gs_free_error GError *error         = NULL;
+            gs_free char *        value_to_free = NULL;
+            const char *          value;
+            NMSriovVF *           vf;
+
+            nm_assert(g_str_has_prefix(key, "SRIOV_VF"));
+
+            value = svGetValue(ifcfg, key, &value_to_free);
+            if (!value)
+                continue;
+
+            key += NM_STRLEN("SRIOV_VF");
+
+            vf = _nm_utils_sriov_vf_from_strparts(key, value, TRUE, &error);
+            if (!vf) {
+                PARSE_WARNING("ignoring invalid SR-IOV VF '%s %s': %s", key, value, error->message);
+                continue;
+            }
+            if (!vfs)
+                vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref);
+            g_ptr_array_add(vfs, vf);
+        }
+    }
+
+    /* Create the setting when at least one key is set */
+    if (total_vfs < 0 && !vfs && autoprobe_drivers < NM_TERNARY_DEFAULT)
+        return NULL;
+
+    s_sriov = (NMSettingSriov *) nm_setting_sriov_new();
+
+    autoprobe_drivers = NM_MAX(autoprobe_drivers, NM_TERNARY_DEFAULT);
+    total_vfs         = NM_MAX(total_vfs, 0);
+
+    g_object_set(s_sriov,
+                 NM_SETTING_SRIOV_TOTAL_VFS,
+                 (guint) total_vfs,
+                 NM_SETTING_SRIOV_VFS,
+                 vfs,
+                 NM_SETTING_SRIOV_AUTOPROBE_DRIVERS,
+                 autoprobe_drivers,
+                 NULL);
+
+    return (NMSetting *) s_sriov;
+}
+
+static NMSetting *
+make_tc_setting(shvarFile *ifcfg)
+{
+    NMSettingTCConfig *s_tc = NULL;
+    char               tag[256];
+    int                i;
+
+    s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new();
+
+    for (i = 1;; i++) {
+        NMTCQdisc *   qdisc         = NULL;
+        gs_free char *value_to_free = NULL;
+        const char *  value         = NULL;
+        GError *      local         = NULL;
+
+        value = svGetValueStr(ifcfg, numbered_tag(tag, "QDISC", i), &value_to_free);
+        if (!value)
+            break;
+
+        qdisc = nm_utils_tc_qdisc_from_str(value, &local);
+        if (!qdisc) {
+            PARSE_WARNING("ignoring bad tc qdisc: '%s': %s", value, local->message);
+            continue;
+        }
+
+        if (!nm_setting_tc_config_add_qdisc(s_tc, qdisc))
+            PARSE_WARNING("duplicate tc qdisc");
+
+        nm_tc_qdisc_unref(qdisc);
+    }
+
+    for (i = 1;; i++) {
+        NMTCTfilter * tfilter       = NULL;
+        gs_free char *value_to_free = NULL;
+        const char *  value         = NULL;
+        gs_free_error GError *local = NULL;
+
+        value = svGetValueStr(ifcfg, numbered_tag(tag, "FILTER", i), &value_to_free);
+        if (!value)
+            break;
+
+        tfilter = nm_utils_tc_tfilter_from_str(value, &local);
+        if (!tfilter) {
+            PARSE_WARNING("ignoring bad tc filter: '%s': %s", value, local->message);
+            continue;
+        }
+
+        if (!nm_setting_tc_config_add_tfilter(s_tc, tfilter))
+            PARSE_WARNING("duplicate tc filter");
+
+        nm_tc_tfilter_unref(tfilter);
+    }
+
+    if (nm_setting_tc_config_get_num_qdiscs(s_tc) > 0
+        || nm_setting_tc_config_get_num_tfilters(s_tc) > 0)
+        return NM_SETTING(s_tc);
+
+    g_object_unref(s_tc);
+    return NULL;
+}
+
+typedef struct {
+    const char *enable_key;
+    const char *advertise_key;
+    const char *willing_key;
+    const char *flags_prop;
+} DcbFlagsProperty;
+
+enum {
+    DCB_APP_FCOE_FLAGS  = 0,
+    DCB_APP_ISCSI_FLAGS = 1,
+    DCB_APP_FIP_FLAGS   = 2,
+    DCB_PFC_FLAGS       = 3,
+    DCB_PG_FLAGS        = 4,
+};
+
+static DcbFlagsProperty dcb_flags_props[] = {
+    {KEY_DCB_APP_FCOE_ENABLE,
+     KEY_DCB_APP_FCOE_ADVERTISE,
+     KEY_DCB_APP_FCOE_WILLING,
+     NM_SETTING_DCB_APP_FCOE_FLAGS},
+    {KEY_DCB_APP_ISCSI_ENABLE,
+     KEY_DCB_APP_ISCSI_ADVERTISE,
+     KEY_DCB_APP_ISCSI_WILLING,
+     NM_SETTING_DCB_APP_ISCSI_FLAGS},
+    {KEY_DCB_APP_FIP_ENABLE,
+     KEY_DCB_APP_FIP_ADVERTISE,
+     KEY_DCB_APP_FIP_WILLING,
+     NM_SETTING_DCB_APP_FIP_FLAGS},
+    {KEY_DCB_PFC_ENABLE,
+     KEY_DCB_PFC_ADVERTISE,
+     KEY_DCB_PFC_WILLING,
+     NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS},
+    {KEY_DCB_PG_ENABLE,
+     KEY_DCB_PG_ADVERTISE,
+     KEY_DCB_PG_WILLING,
+     NM_SETTING_DCB_PRIORITY_GROUP_FLAGS},
+    {NULL},
+};
+
+static NMSettingDcbFlags
+read_dcb_flags(shvarFile *ifcfg, DcbFlagsProperty *property)
+{
+    NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE;
+
+    if (svGetValueBoolean(ifcfg, property->enable_key, FALSE))
+        flags |= NM_SETTING_DCB_FLAG_ENABLE;
+    if (svGetValueBoolean(ifcfg, property->advertise_key, FALSE))
+        flags |= NM_SETTING_DCB_FLAG_ADVERTISE;
+    if (svGetValueBoolean(ifcfg, property->willing_key, FALSE))
+        flags |= NM_SETTING_DCB_FLAG_WILLING;
+
+    return flags;
+}
+
+static gboolean
+read_dcb_app(shvarFile *       ifcfg,
+             NMSettingDcb *    s_dcb,
+             const char *      app,
+             DcbFlagsProperty *flags_prop,
+             const char *      priority_prop,
+             GError **         error)
+{
+    NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE;
+    gs_free char *    value = NULL;
+    const char *      v;
+    gboolean          success  = TRUE;
+    int               priority = -1;
+    char              key[255];
+
+    flags = read_dcb_flags(ifcfg, flags_prop);
+
+    /* Priority */
+    nm_sprintf_buf(key, "DCB_APP_%s_PRIORITY", app);
+    v = svGetValueStr(ifcfg, key, &value);
+    if (v) {
+        priority = _nm_utils_ascii_str_to_int64(v, 0, 0, 7, -1);
+        if (priority < 0) {
+            success = FALSE;
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid %s value '%s' (expected 0 - 7)",
+                        key,
+                        v);
+        }
+
+        if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
+            PARSE_WARNING("ignoring DCB %s priority; app not enabled", app);
+    }
+
+    if (success) {
+        g_object_set(G_OBJECT(s_dcb),
+                     flags_prop->flags_prop,
+                     flags,
+                     priority_prop,
+                     (guint) priority,
+                     NULL);
+    }
+
+    return success;
+}
+
+typedef void (*DcbSetBoolFunc)(NMSettingDcb *, guint, gboolean);
+
+static gboolean
+read_dcb_bool_array(shvarFile *       ifcfg,
+                    NMSettingDcb *    s_dcb,
+                    NMSettingDcbFlags flags,
+                    const char *      prop,
+                    const char *      desc,
+                    DcbSetBoolFunc    set_func,
+                    GError **         error)
+{
+    gs_free char *value = NULL;
+    const char *  v;
+    guint         i;
+
+    v = svGetValueStr(ifcfg, prop, &value);
+    if (!v)
+        return TRUE;
+
+    if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) {
+        PARSE_WARNING("ignoring %s; %s is not enabled", prop, desc);
+        return TRUE;
+    }
+
+    if (strlen(v) != 8) {
+        PARSE_WARNING("%s value '%s' must be 8 characters long", prop, v);
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "boolean array must be 8 characters");
+        return FALSE;
+    }
+
+    /* All characters must be either 0 or 1 */
+    for (i = 0; i < 8; i++) {
+        if (v[i] != '0' && v[i] != '1') {
+            PARSE_WARNING("invalid %s value '%s': not all 0s and 1s", prop, v);
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "invalid boolean digit");
+            return FALSE;
+        }
+        set_func(s_dcb, i, (v[i] == '1'));
+    }
+    return TRUE;
+}
+
+typedef void (*DcbSetUintFunc)(NMSettingDcb *, guint, guint);
+
+static gboolean
+read_dcb_uint_array(shvarFile *       ifcfg,
+                    NMSettingDcb *    s_dcb,
+                    NMSettingDcbFlags flags,
+                    const char *      prop,
+                    const char *      desc,
+                    gboolean          f_allowed,
+                    DcbSetUintFunc    set_func,
+                    GError **         error)
+{
+    gs_free char *val = NULL;
+    guint         i;
+
+    val = svGetValueStr_cp(ifcfg, prop);
+    if (!val)
+        return TRUE;
+
+    if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) {
+        PARSE_WARNING("ignoring %s; %s is not enabled", prop, desc);
+        return TRUE;
+    }
+
+    if (strlen(val) != 8) {
+        PARSE_WARNING("%s value '%s' must be 8 characters long", prop, val);
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "uint array must be 8 characters");
+        return FALSE;
+    }
+
+    /* All characters must be either 0 - 7 or (optionally) f */
+    for (i = 0; i < 8; i++) {
+        if (val[i] >= '0' && val[i] <= '7')
+            set_func(s_dcb, i, val[i] - '0');
+        else if (f_allowed && (val[i] == 'f' || val[i] == 'F'))
+            set_func(s_dcb, i, 15);
+        else {
+            PARSE_WARNING("invalid %s value '%s': not 0 - 7%s",
+                          prop,
+                          val,
+                          f_allowed ? " or 'f'" : "");
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "invalid uint digit");
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean
+read_dcb_percent_array(shvarFile *       ifcfg,
+                       NMSettingDcb *    s_dcb,
+                       NMSettingDcbFlags flags,
+                       const char *      prop,
+                       const char *      desc,
+                       gboolean          sum_pct,
+                       DcbSetUintFunc    set_func,
+                       GError **         error)
+{
+    gs_free char *       val   = NULL;
+    gs_free const char **split = NULL;
+    const char *const *  iter;
+    guint                i, sum = 0;
+
+    val = svGetValueStr_cp(ifcfg, prop);
+    if (!val)
+        return TRUE;
+
+    if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) {
+        PARSE_WARNING("ignoring %s; %s is not enabled", prop, desc);
+        return TRUE;
+    }
+
+    split = nm_utils_strsplit_set(val, ",");
+    if (NM_PTRARRAY_LEN(split) != 8) {
+        PARSE_WARNING("invalid %s percentage list value '%s'", prop, val);
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "percent array must be 8 elements");
+        return FALSE;
+    }
+
+    for (iter = split, i = 0; iter && *iter; iter++, i++) {
+        int tmp;
+
+        tmp = _nm_utils_ascii_str_to_int64(*iter, 0, 0, 100, -1);
+        if (tmp < 0) {
+            PARSE_WARNING("invalid %s percentage value '%s'", prop, *iter);
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "invalid percent element");
+            return FALSE;
+        }
+        set_func(s_dcb, i, (guint) tmp);
+        sum += (guint) tmp;
+    }
+
+    if (sum_pct && (sum != 100)) {
+        PARSE_WARNING("%s percentages do not equal 100%%", prop);
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "invalid percentage sum");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+make_dcb_setting(shvarFile *ifcfg, NMSetting **out_setting, GError **error)
+{
+    gs_unref_object NMSettingDcb *s_dcb = NULL;
+    gboolean                      dcb_on;
+    NMSettingDcbFlags             flags = NM_SETTING_DCB_FLAG_NONE;
+
+    g_return_val_if_fail(out_setting, FALSE);
+    *out_setting = NULL;
+
+    dcb_on = !!svGetValueBoolean(ifcfg, "DCB", FALSE);
+    if (!dcb_on)
+        return TRUE;
+
+    s_dcb = (NMSettingDcb *) nm_setting_dcb_new();
+
+    /* FCOE */
+    if (!read_dcb_app(ifcfg,
+                      s_dcb,
+                      "FCOE",
+                      &dcb_flags_props[DCB_APP_FCOE_FLAGS],
+                      NM_SETTING_DCB_APP_FCOE_PRIORITY,
+                      error)) {
+        return FALSE;
+    }
+    if (nm_setting_dcb_get_app_fcoe_flags(s_dcb) & NM_SETTING_DCB_FLAG_ENABLE) {
+        gs_free char *val = NULL;
+
+        val = svGetValueStr_cp(ifcfg, KEY_DCB_APP_FCOE_MODE);
+        if (val) {
+            if (NM_IN_STRSET(val, NM_SETTING_DCB_FCOE_MODE_FABRIC, NM_SETTING_DCB_FCOE_MODE_VN2VN))
+                g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_APP_FCOE_MODE, val, NULL);
+            else {
+                PARSE_WARNING("invalid FCoE mode '%s'", val);
+                g_set_error_literal(error,
+                                    NM_SETTINGS_ERROR,
+                                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                    "invalid FCoE mode");
+                return FALSE;
+            }
+        }
+    }
+
+    /* iSCSI */
+    if (!read_dcb_app(ifcfg,
+                      s_dcb,
+                      "ISCSI",
+                      &dcb_flags_props[DCB_APP_ISCSI_FLAGS],
+                      NM_SETTING_DCB_APP_ISCSI_PRIORITY,
+                      error)) {
+        return FALSE;
+    }
+
+    /* FIP */
+    if (!read_dcb_app(ifcfg,
+                      s_dcb,
+                      "FIP",
+                      &dcb_flags_props[DCB_APP_FIP_FLAGS],
+                      NM_SETTING_DCB_APP_FIP_PRIORITY,
+                      error)) {
+        return FALSE;
+    }
+
+    /* Priority Flow Control */
+    flags = read_dcb_flags(ifcfg, &dcb_flags_props[DCB_PFC_FLAGS]);
+    g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, flags, NULL);
+
+    if (!read_dcb_bool_array(ifcfg,
+                             s_dcb,
+                             flags,
+                             KEY_DCB_PFC_UP,
+                             "PFC",
+                             nm_setting_dcb_set_priority_flow_control,
+                             error)) {
+        return FALSE;
+    }
+
+    /* Priority Groups */
+    flags = read_dcb_flags(ifcfg, &dcb_flags_props[DCB_PG_FLAGS]);
+    g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, flags, NULL);
+
+    if (!read_dcb_uint_array(ifcfg,
+                             s_dcb,
+                             flags,
+                             KEY_DCB_PG_ID,
+                             "PGID",
+                             TRUE,
+                             nm_setting_dcb_set_priority_group_id,
+                             error)) {
+        return FALSE;
+    }
+
+    /* Group bandwidth */
+    if (!read_dcb_percent_array(ifcfg,
+                                s_dcb,
+                                flags,
+                                KEY_DCB_PG_PCT,
+                                "PGPCT",
+                                TRUE,
+                                nm_setting_dcb_set_priority_group_bandwidth,
+                                error)) {
+        return FALSE;
+    }
+
+    /* Priority bandwidth */
+    if (!read_dcb_percent_array(ifcfg,
+                                s_dcb,
+                                flags,
+                                KEY_DCB_PG_UPPCT,
+                                "UPPCT",
+                                FALSE,
+                                nm_setting_dcb_set_priority_bandwidth,
+                                error)) {
+        return FALSE;
+    }
+
+    /* Strict Bandwidth */
+    if (!read_dcb_bool_array(ifcfg,
+                             s_dcb,
+                             flags,
+                             KEY_DCB_PG_STRICT,
+                             "STRICT",
+                             nm_setting_dcb_set_priority_strict_bandwidth,
+                             error)) {
+        return FALSE;
+    }
+
+    if (!read_dcb_uint_array(ifcfg,
+                             s_dcb,
+                             flags,
+                             KEY_DCB_PG_UP2TC,
+                             "UP2TC",
+                             FALSE,
+                             nm_setting_dcb_set_priority_traffic_class,
+                             error)) {
+        return FALSE;
+    }
+
+    *out_setting = NM_SETTING(g_steal_pointer(&s_dcb));
+    return TRUE;
+}
+
+static gboolean
+add_one_wep_key(shvarFile *                ifcfg,
+                const char *               shvar_key,
+                guint8                     key_idx,
+                gboolean                   passphrase,
+                NMSettingWirelessSecurity *s_wsec,
+                GError **                  error)
+{
+    gs_free char *value_free = NULL;
+    const char *  value;
+    const char *  key = NULL;
+
+    g_return_val_if_fail(ifcfg != NULL, FALSE);
+    g_return_val_if_fail(shvar_key != NULL, FALSE);
+    g_return_val_if_fail(key_idx <= 3, FALSE);
+    g_return_val_if_fail(s_wsec != NULL, FALSE);
+
+    value = svGetValueStr(ifcfg, shvar_key, &value_free);
+    if (!value)
+        return TRUE;
+
+    /* Validate keys */
+    if (passphrase) {
+        if (value[0] && strlen(value) < 64)
+            key = value;
+    } else {
+        if (NM_IN_SET(strlen(value), 10, 26)) {
+            /* Hexadecimal WEP key */
+            if (NM_STRCHAR_ANY(value, ch, !g_ascii_isxdigit(ch))) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Invalid hexadecimal WEP key");
+                return FALSE;
+            }
+            key = value;
+        } else if (!strncmp(value, "s:", 2) && NM_IN_SET(strlen(value), 7, 15)) {
+            /* ASCII key */
+            if (NM_STRCHAR_ANY(value + 2, ch, !g_ascii_isprint(ch))) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Invalid ASCII WEP key");
+                return FALSE;
+            }
+
+            /* Remove 's:' prefix.
+             * Don't convert to hex string. wpa_supplicant takes 'wep_key0' option over D-Bus as byte array
+             * and converts it to hex string itself. Even though we convert hex string keys into a bin string
+             * before passing to wpa_supplicant, this prevents two unnecessary conversions. And mainly,
+             * ASCII WEP key doesn't change to HEX WEP key in UI, which could confuse users.
+             */
+            key = value + 2;
+        }
+    }
+
+    if (!key) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Invalid WEP key length");
+        return FALSE;
+    }
+
+    nm_setting_wireless_security_set_wep_key(s_wsec, key_idx, key);
+    return TRUE;
+}
+
+static gboolean
+read_wep_keys(shvarFile *                ifcfg,
+              NMWepKeyType               key_type,
+              guint8                     def_idx,
+              NMSettingWirelessSecurity *s_wsec,
+              GError **                  error)
+{
+    if (key_type != NM_WEP_KEY_TYPE_PASSPHRASE) {
+        if (!add_one_wep_key(ifcfg, "KEY1", 0, FALSE, s_wsec, error))
+            return FALSE;
+        if (!add_one_wep_key(ifcfg, "KEY2", 1, FALSE, s_wsec, error))
+            return FALSE;
+        if (!add_one_wep_key(ifcfg, "KEY3", 2, FALSE, s_wsec, error))
+            return FALSE;
+        if (!add_one_wep_key(ifcfg, "KEY4", 3, FALSE, s_wsec, error))
+            return FALSE;
+        if (!add_one_wep_key(ifcfg, "KEY", def_idx, FALSE, s_wsec, error))
+            return FALSE;
+    }
+
+    if (key_type != NM_WEP_KEY_TYPE_KEY) {
+        if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE1", 0, TRUE, s_wsec, error))
+            return FALSE;
+        if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE2", 1, TRUE, s_wsec, error))
+            return FALSE;
+        if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE3", 2, TRUE, s_wsec, error))
+            return FALSE;
+        if (!add_one_wep_key(ifcfg, "KEY_PASSPHRASE4", 3, TRUE, s_wsec, error))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+static NMSetting *
+make_wep_setting(shvarFile *ifcfg, const char *file, GError **error)
+{
+    gs_unref_object NMSettingWirelessSecurity *s_wsec          = NULL;
+    gs_free char *                             value           = NULL;
+    shvarFile *                                keys_ifcfg      = NULL;
+    int                                        default_key_idx = 0;
+    gboolean                                   has_default_key = FALSE;
+    NMSettingSecretFlags                       key_flags;
+
+    s_wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new());
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL);
+
+    value = svGetValueStr_cp(ifcfg, "DEFAULTKEY");
+    if (value) {
+        default_key_idx = _nm_utils_ascii_str_to_int64(value, 0, 1, 4, 0);
+        if (default_key_idx == 0) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid default WEP key '%s'",
+                        value);
+            return NULL;
+        }
+        has_default_key = TRUE;
+        default_key_idx--; /* convert to [0...3] */
+        g_object_set(s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
+                     (guint) default_key_idx,
+                     NULL);
+        nm_clear_g_free(&value);
+    }
+
+    /* Read WEP key flags */
+    key_flags = _secret_read_ifcfg_flags(ifcfg, "WEP_KEY_FLAGS");
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, key_flags, NULL);
+
+    /* Read keys in the ifcfg file if they are system-owned */
+    if (key_flags == NM_SETTING_SECRET_FLAG_NONE) {
+        NMWepKeyType  key_type;
+        const char *  v;
+        gs_free char *to_free = NULL;
+
+        v = svGetValueStr(ifcfg, "KEY_TYPE", &to_free);
+        if (!v)
+            key_type = NM_WEP_KEY_TYPE_UNKNOWN;
+        else if (nm_streq(v, "key"))
+            key_type = NM_WEP_KEY_TYPE_KEY;
+        else if (nm_streq(v, "passphrase"))
+            key_type = NM_WEP_KEY_TYPE_PASSPHRASE;
+        else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid KEY_TYPE value '%s'",
+                        v);
+            return FALSE;
+        }
+
+        if (!read_wep_keys(ifcfg, key_type, default_key_idx, s_wsec, error))
+            return NULL;
+
+        /* Try to get keys from the "shadow" key file */
+        keys_ifcfg = utils_get_keys_ifcfg(file, FALSE);
+        if (keys_ifcfg) {
+            if (!read_wep_keys(keys_ifcfg, key_type, default_key_idx, s_wsec, error)) {
+                svCloseFile(keys_ifcfg);
+                return NULL;
+            }
+            svCloseFile(keys_ifcfg);
+            g_assert(error == NULL || *error == NULL);
+        }
+
+        g_object_set(G_OBJECT(s_wsec), NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, key_type, NULL);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "SECURITYMODE");
+    if (value) {
+        gs_free char *lcase = NULL;
+
+        lcase = g_ascii_strdown(value, -1);
+        nm_clear_g_free(&value);
+
+        if (nm_streq(lcase, "open")) {
+            g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL);
+        } else if (nm_streq(lcase, "restricted")) {
+            g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", NULL);
+        } else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid WEP authentication algorithm '%s'",
+                        lcase);
+            return NULL;
+        }
+    }
+
+    /* If no WEP keys were given, and the keys are not agent-owned, and no
+     * default WEP key index was given, then the connection is unencrypted.
+     */
+    if (!nm_setting_wireless_security_get_wep_key(s_wsec, 0)
+        && !nm_setting_wireless_security_get_wep_key(s_wsec, 1)
+        && !nm_setting_wireless_security_get_wep_key(s_wsec, 2)
+        && !nm_setting_wireless_security_get_wep_key(s_wsec, 3) && (has_default_key == FALSE)
+        && (key_flags == NM_SETTING_SECRET_FLAG_NONE)) {
+        const char *auth_alg;
+
+        auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec);
+        if (auth_alg && !strcmp(auth_alg, "shared")) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "WEP Shared Key authentication is invalid for "
+                        "unencrypted connections");
+            return NULL;
+        }
+
+        /* Unencrypted */
+        return NULL;
+    }
+
+    return NM_SETTING(g_steal_pointer(&s_wsec));
+}
+
+static gboolean
+fill_wpa_ciphers(shvarFile *ifcfg, NMSettingWirelessSecurity *wsec, gboolean group, gboolean adhoc)
+{
+    gs_free char *       value = NULL;
+    const char *         p;
+    gs_free const char **list = NULL;
+    const char *const *  iter;
+    int                  i = 0;
+
+    p = svGetValueStr(ifcfg, group ? "CIPHER_GROUP" : "CIPHER_PAIRWISE", &value);
+    if (!p)
+        return TRUE;
+
+    list = nm_utils_strsplit_set(p, " ");
+    for (iter = list; iter && *iter; iter++, i++) {
+        if (!strcmp(*iter, "CCMP")) {
+            if (group)
+                nm_setting_wireless_security_add_group(wsec, "ccmp");
+            else
+                nm_setting_wireless_security_add_pairwise(wsec, "ccmp");
+        } else if (!strcmp(*iter, "TKIP")) {
+            if (group)
+                nm_setting_wireless_security_add_group(wsec, "tkip");
+            else
+                nm_setting_wireless_security_add_pairwise(wsec, "tkip");
+        } else if (group && !strcmp(*iter, "WEP104"))
+            nm_setting_wireless_security_add_group(wsec, "wep104");
+        else if (group && !strcmp(*iter, "WEP40"))
+            nm_setting_wireless_security_add_group(wsec, "wep40");
+        else {
+            PARSE_WARNING("ignoring invalid %s cipher '%s'",
+                          group ? "CIPHER_GROUP" : "CIPHER_PAIRWISE",
+                          *iter);
+        }
+    }
+
+    return TRUE;
+}
+
+#define WPA_PMK_LEN 32
+
+static char *
+parse_wpa_psk(shvarFile *ifcfg, const char *file, GBytes *ssid, GError **error)
+{
+    shvarFile *   keys_ifcfg;
+    gs_free char *psk = NULL;
+    size_t        plen;
+
+    /* Passphrase must be between 10 and 66 characters in length because WPA
+     * hex keys are exactly 64 characters (no quoting), and WPA passphrases
+     * are between 8 and 63 characters (inclusive), plus optional quoting if
+     * the passphrase contains spaces.
+     */
+
+    /* Try to get keys from the "shadow" key file */
+    keys_ifcfg = utils_get_keys_ifcfg(file, FALSE);
+    if (keys_ifcfg) {
+        psk = svGetValueStr_cp(keys_ifcfg, "WPA_PSK");
+        svCloseFile(keys_ifcfg);
+    }
+
+    /* Fall back to the original ifcfg */
+    if (!psk)
+        psk = svGetValueStr_cp(ifcfg, "WPA_PSK");
+
+    if (!psk)
+        return NULL;
+
+    plen = strlen(psk);
+
+    if (plen == 64) {
+        /* Verify the hex PSK; 64 digits */
+        if (!NM_STRCHAR_ALL(psk, ch, g_ascii_isxdigit(ch))) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid WPA_PSK (contains non-hexadecimal characters)");
+            return NULL;
+        }
+    } else {
+        if (plen < 8 || plen > 63) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid WPA_PSK (passphrases must be between "
+                        "8 and 63 characters long (inclusive))");
+            return NULL;
+        }
+    }
+
+    return g_steal_pointer(&psk);
+}
+
+static gboolean
+eap_simple_reader(const char *    eap_method,
+                  shvarFile *     ifcfg,
+                  shvarFile *     keys_ifcfg,
+                  NMSetting8021x *s_8021x,
+                  gboolean        phase2,
+                  GError **       error)
+{
+    NMSettingSecretFlags      flags;
+    gs_free char *            identity_free    = NULL;
+    nm_auto_free_secret char *password_raw_str = NULL;
+    gs_unref_bytes GBytes *password_raw_bytes  = NULL;
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_IDENTITY,
+                 svGetValueStr(ifcfg, "IEEE_8021X_IDENTITY", &identity_free),
+                 NULL);
+
+    _secret_set_from_ifcfg(s_8021x,
+                           ifcfg,
+                           keys_ifcfg,
+                           "IEEE_8021X_PASSWORD",
+                           NM_SETTING_802_1X_PASSWORD);
+
+    _secret_read_ifcfg(ifcfg, keys_ifcfg, "IEEE_8021X_PASSWORD_RAW", &password_raw_str, &flags);
+    if (!_secret_password_raw_to_bytes("IEEE_8021X_PASSWORD_RAW",
+                                       password_raw_str,
+                                       &password_raw_bytes,
+                                       error))
+        return FALSE;
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_PASSWORD_RAW_FLAGS,
+                 flags,
+                 NM_SETTING_802_1X_PASSWORD_RAW,
+                 password_raw_bytes,
+                 NULL);
+
+    return TRUE;
+}
+
+static gboolean
+eap_tls_reader(const char *    eap_method,
+               shvarFile *     ifcfg,
+               shvarFile *     keys_ifcfg,
+               NMSetting8021x *s_8021x,
+               gboolean        phase2,
+               GError **       error)
+{
+    gs_unref_bytes GBytes *privkey       = NULL;
+    gs_unref_bytes GBytes *client_cert   = NULL;
+    gs_free char *         identity_free = NULL;
+    gs_free char *         value_to_free = NULL;
+    const char *           client_cert_var;
+    const char *           client_cert_prop;
+    NMSetting8021xCKFormat format;
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_IDENTITY,
+                 svGetValueStr(ifcfg, "IEEE_8021X_IDENTITY", &identity_free),
+                 NULL);
+
+    /* CA certificate */
+    if (!_cert_set_from_ifcfg(s_8021x,
+                              ifcfg,
+                              phase2 ? "IEEE_8021X_INNER_CA_CERT" : "IEEE_8021X_CA_CERT",
+                              phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT : NM_SETTING_802_1X_CA_CERT,
+                              NULL,
+                              error))
+        return FALSE;
+    _secret_set_from_ifcfg(
+        s_8021x,
+        ifcfg,
+        keys_ifcfg,
+        phase2 ? "IEEE_8021X_INNER_CA_CERT_PASSWORD" : "IEEE_8021X_CA_CERT_PASSWORD",
+        phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD : NM_SETTING_802_1X_CA_CERT_PASSWORD);
+
+    /* Private key */
+    if (!_cert_set_from_ifcfg(s_8021x,
+                              ifcfg,
+                              phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY",
+                              phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY
+                                     : NM_SETTING_802_1X_PRIVATE_KEY,
+                              &privkey,
+                              error))
+        return FALSE;
+    _secret_set_from_ifcfg(s_8021x,
+                           ifcfg,
+                           keys_ifcfg,
+                           phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD"
+                                  : "IEEE_8021X_PRIVATE_KEY_PASSWORD",
+                           phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD
+                                  : NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
+
+    /* Client certificate */
+    client_cert_var = phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT";
+    client_cert_prop =
+        phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT;
+    if (!_cert_set_from_ifcfg(s_8021x,
+                              ifcfg,
+                              client_cert_var,
+                              client_cert_prop,
+                              &client_cert,
+                              error))
+        return FALSE;
+    _secret_set_from_ifcfg(s_8021x,
+                           ifcfg,
+                           keys_ifcfg,
+                           phase2 ? "IEEE_8021X_INNER_CLIENT_CERT_PASSWORD"
+                                  : "IEEE_8021X_CLIENT_CERT_PASSWORD",
+                           phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD
+                                  : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD);
+
+    /* In the past when the private key and client certificate
+     * were the same PKCS #12 file we used to write only the
+     * private key variable. Still support that even if it means
+     * that we have to look into the file content, which makes
+     * the connection not self-contained.
+     */
+    if (!client_cert && privkey && !svGetValue(ifcfg, client_cert_var, &value_to_free)) {
+        if (phase2)
+            format = nm_setting_802_1x_get_phase2_private_key_format(s_8021x);
+        else
+            format = nm_setting_802_1x_get_private_key_format(s_8021x);
+
+        if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
+            g_object_set(s_8021x, client_cert_prop, privkey, NULL);
+    }
+
+    return TRUE;
+}
+
+static gboolean
+parse_8021x_phase2_auth(shvarFile *     ifcfg,
+                        shvarFile *     keys_ifcfg,
+                        NMSetting8021x *s_8021x,
+                        GError **       error)
+{
+    gs_free char *       inner_auth = NULL;
+    gs_free char *       v_free     = NULL;
+    const char *         v;
+    gs_free const char **list = NULL;
+    const char *const *  iter;
+    guint                num_auth    = 0;
+    guint                num_autheap = 0;
+
+    v = svGetValueStr(ifcfg, "IEEE_8021X_INNER_AUTH_METHODS", &v_free);
+    if (!v) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Missing IEEE_8021X_INNER_AUTH_METHODS");
+        return FALSE;
+    }
+
+    inner_auth = g_ascii_strdown(v, -1);
+    list       = nm_utils_strsplit_set(inner_auth, " ");
+    for (iter = list; iter && *iter; iter++) {
+        if (NM_IN_STRSET(*iter, "pap", "chap", "mschap", "mschapv2", "gtc", "otp", "md5")) {
+            if (num_auth == 0) {
+                if (!eap_simple_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
+                    return FALSE;
+                g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, *iter, NULL);
+            }
+            num_auth++;
+        } else if (nm_streq(*iter, "tls")) {
+            if (num_auth == 0) {
+                if (!eap_tls_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
+                    return FALSE;
+                g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, "tls", NULL);
+            }
+            num_auth++;
+        } else if (NM_IN_STRSET(*iter, "eap-md5", "eap-mschapv2", "eap-otp", "eap-gtc")) {
+            if (num_autheap == 0) {
+                if (!eap_simple_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
+                    return FALSE;
+                g_object_set(s_8021x,
+                             NM_SETTING_802_1X_PHASE2_AUTHEAP,
+                             (*iter + NM_STRLEN("eap-")),
+                             NULL);
+            }
+            num_autheap++;
+        } else if (nm_streq(*iter, "eap-tls")) {
+            if (num_autheap == 0) {
+                if (!eap_tls_reader(*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
+                    return FALSE;
+                g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, "tls", NULL);
+            }
+            num_autheap++;
+        } else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Unknown IEEE_8021X_INNER_AUTH_METHOD '%s'",
+                        *iter);
+            return FALSE;
+        }
+    }
+
+    if (num_auth > 1)
+        PARSE_WARNING("Discarded extra phase2 authentication methods");
+    if (num_auth > 1)
+        PARSE_WARNING("Discarded extra phase2 EAP authentication methods");
+
+    if (!num_auth && !num_autheap) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "No phase2 authentication method found");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+eap_peap_reader(const char *    eap_method,
+                shvarFile *     ifcfg,
+                shvarFile *     keys_ifcfg,
+                NMSetting8021x *s_8021x,
+                gboolean        phase2,
+                GError **       error)
+{
+    gs_free char *value = NULL;
+    const char *  v;
+
+    if (!_cert_set_from_ifcfg(s_8021x,
+                              ifcfg,
+                              "IEEE_8021X_CA_CERT",
+                              NM_SETTING_802_1X_CA_CERT,
+                              NULL,
+                              error))
+        return FALSE;
+    _secret_set_from_ifcfg(s_8021x,
+                           ifcfg,
+                           keys_ifcfg,
+                           "IEEE_8021X_CA_CERT_PASSWORD",
+                           NM_SETTING_802_1X_CA_CERT_PASSWORD);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_PEAP_VERSION", &value);
+    if (v) {
+        if (!strcmp(v, "0"))
+            g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "0", NULL);
+        else if (!strcmp(v, "1"))
+            g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "1", NULL);
+        else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Unknown IEEE_8021X_PEAP_VERSION value '%s'",
+                        v);
+            return FALSE;
+        }
+    }
+
+    if (svGetValueBoolean(ifcfg, "IEEE_8021X_PEAP_FORCE_NEW_LABEL", FALSE))
+        g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_PEAPLABEL, "1", NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_ANON_IDENTITY", &value);
+    if (v)
+        g_object_set(s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, v, NULL);
+
+    if (!parse_8021x_phase2_auth(ifcfg, keys_ifcfg, s_8021x, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+static gboolean
+eap_ttls_reader(const char *    eap_method,
+                shvarFile *     ifcfg,
+                shvarFile *     keys_ifcfg,
+                NMSetting8021x *s_8021x,
+                gboolean        phase2,
+                GError **       error)
+{
+    gs_free char *value = NULL;
+    const char *  v;
+
+    if (!_cert_set_from_ifcfg(s_8021x,
+                              ifcfg,
+                              "IEEE_8021X_CA_CERT",
+                              NM_SETTING_802_1X_CA_CERT,
+                              NULL,
+                              error))
+        return FALSE;
+    _secret_set_from_ifcfg(s_8021x,
+                           ifcfg,
+                           keys_ifcfg,
+                           "IEEE_8021X_CA_CERT_PASSWORD",
+                           NM_SETTING_802_1X_CA_CERT_PASSWORD);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_ANON_IDENTITY", &value);
+    if (v)
+        g_object_set(s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, v, NULL);
+
+    if (!parse_8021x_phase2_auth(ifcfg, keys_ifcfg, s_8021x, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+static gboolean
+eap_fast_reader(const char *    eap_method,
+                shvarFile *     ifcfg,
+                shvarFile *     keys_ifcfg,
+                NMSetting8021x *s_8021x,
+                gboolean        phase2,
+                GError **       error)
+{
+    gs_free char *     anon_ident        = NULL;
+    gs_free char *     pac_file          = NULL;
+    gs_free char *     real_pac_path     = NULL;
+    gs_free char *     fast_provisioning = NULL;
+    const char *const *iter;
+    const char *       pac_prov_str;
+    gboolean           allow_unauth = FALSE, allow_auth = FALSE;
+
+    pac_file = svGetValueStr_cp(ifcfg, "IEEE_8021X_PAC_FILE");
+    if (pac_file) {
+        real_pac_path = get_full_file_path(svFileGetName(ifcfg), pac_file);
+        g_object_set(s_8021x, NM_SETTING_802_1X_PAC_FILE, real_pac_path, NULL);
+    }
+
+    fast_provisioning = svGetValueStr_cp(ifcfg, "IEEE_8021X_FAST_PROVISIONING");
+    if (fast_provisioning) {
+        gs_free const char **list = NULL;
+
+        list = nm_utils_strsplit_set(fast_provisioning, " \t");
+        for (iter = list; iter && *iter; iter++) {
+            if (strcmp(*iter, "allow-unauth") == 0)
+                allow_unauth = TRUE;
+            else if (strcmp(*iter, "allow-auth") == 0)
+                allow_auth = TRUE;
+            else {
+                PARSE_WARNING(
+                    "invalid IEEE_8021X_FAST_PROVISIONING '%s' "
+                    "(space-separated list of these values [allow-auth, allow-unauth] expected)",
+                    *iter);
+            }
+        }
+    }
+    pac_prov_str = allow_unauth ? (allow_auth ? "3" : "1") : (allow_auth ? "2" : "0");
+    g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, pac_prov_str, NULL);
+
+    if (!pac_file && !(allow_unauth || allow_auth)) {
+        g_set_error(
+            error,
+            NM_SETTINGS_ERROR,
+            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+            "IEEE_8021X_PAC_FILE not provided and EAP-FAST automatic PAC provisioning disabled");
+        return FALSE;
+    }
+
+    anon_ident = svGetValueStr_cp(ifcfg, "IEEE_8021X_ANON_IDENTITY");
+    if (anon_ident)
+        g_object_set(s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, anon_ident, NULL);
+
+    if (!parse_8021x_phase2_auth(ifcfg, keys_ifcfg, s_8021x, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+typedef struct {
+    const char *method;
+    gboolean (*reader)(const char *    eap_method,
+                       shvarFile *     ifcfg,
+                       shvarFile *     keys_ifcfg,
+                       NMSetting8021x *s_8021x,
+                       gboolean        phase2,
+                       GError **       error);
+    gboolean wifi_phase2_only;
+} EAPReader;
+
+static EAPReader eap_readers[] = {{"md5", eap_simple_reader, TRUE},
+                                  {"pap", eap_simple_reader, TRUE},
+                                  {"chap", eap_simple_reader, TRUE},
+                                  {"mschap", eap_simple_reader, TRUE},
+                                  {"mschapv2", eap_simple_reader, TRUE},
+                                  {"leap", eap_simple_reader, FALSE},
+                                  {"pwd", eap_simple_reader, FALSE},
+                                  {"tls", eap_tls_reader, FALSE},
+                                  {"peap", eap_peap_reader, FALSE},
+                                  {"ttls", eap_ttls_reader, FALSE},
+                                  {"fast", eap_fast_reader, FALSE},
+                                  {NULL, NULL}};
+
+static void
+read_8021x_list_value(shvarFile *     ifcfg,
+                      const char *    ifcfg_var_name,
+                      NMSetting8021x *setting,
+                      const char *    prop_name)
+{
+    gs_free char *       value = NULL;
+    gs_free const char **strv  = NULL;
+    const char *         v;
+
+    g_return_if_fail(ifcfg != NULL);
+    g_return_if_fail(ifcfg_var_name != NULL);
+    g_return_if_fail(prop_name != NULL);
+
+    v = svGetValueStr(ifcfg, ifcfg_var_name, &value);
+    if (!v)
+        return;
+
+    strv = nm_utils_strsplit_set(v, " \t");
+    if (strv)
+        g_object_set(setting, prop_name, strv, NULL);
+}
+
+static NMSetting8021x *
+fill_8021x(shvarFile *ifcfg, const char *file, const char *key_mgmt, gboolean wifi, GError **error)
+{
+    nm_auto_shvar_file_close shvarFile *keys_ifcfg = NULL;
+    gs_unref_object NMSetting8021x *s_8021x        = NULL;
+    gs_free char *                  value          = NULL;
+    const char *                    v;
+    gs_free const char **           list = NULL;
+    const char *const *             iter;
+    gint64                          timeout;
+    int                             i_val;
+
+    v = svGetValueStr(ifcfg, "IEEE_8021X_EAP_METHODS", &value);
+    if (!v) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Missing IEEE_8021X_EAP_METHODS for key management '%s'",
+                    key_mgmt);
+        return NULL;
+    }
+
+    list = nm_utils_strsplit_set(v, " ");
+
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+
+    /* Read in the lookaside keys_ifcfg file, if present */
+    keys_ifcfg = utils_get_keys_ifcfg(file, FALSE);
+
+    /* Validate and handle each EAP method */
+    for (iter = list; iter && *iter; iter++) {
+        EAPReader *   eap   = &eap_readers[0];
+        gboolean      found = FALSE;
+        gs_free char *lower = NULL;
+
+        lower = g_ascii_strdown(*iter, -1);
+        while (eap->method) {
+            if (strcmp(eap->method, lower))
+                goto next;
+
+            /* Some EAP methods don't provide keying material, thus they
+             * cannot be used with Wi-Fi unless they are an inner method
+             * used with TTLS or PEAP or whatever.
+             */
+            if (wifi && eap->wifi_phase2_only) {
+                PARSE_WARNING("ignored invalid IEEE_8021X_EAP_METHOD '%s'; not allowed for wifi",
+                              lower);
+                goto next;
+            }
+
+            /* Parse EAP method specific options */
+            if (!(*eap->reader)(lower, ifcfg, keys_ifcfg, s_8021x, FALSE, error))
+                return NULL;
+
+            nm_setting_802_1x_add_eap_method(s_8021x, lower);
+            found = TRUE;
+            break;
+
+next:
+            eap++;
+        }
+
+        if (!found)
+            PARSE_WARNING("ignored unknown IEEE_8021X_EAP_METHOD '%s'", lower);
+    }
+
+    if (nm_setting_802_1x_get_num_eap_methods(s_8021x) == 0) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "No valid EAP methods found in IEEE_8021X_EAP_METHODS");
+        return NULL;
+    }
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_SYSTEM_CA_CERTS,
+                 svGetValueBoolean(ifcfg, "IEEE_8021X_SYSTEM_CA_CERTS", FALSE),
+                 NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_SUBJECT_MATCH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_SUBJECT_MATCH, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_SUBJECT_MATCH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH, v, NULL);
+
+    i_val = NM_SETTING_802_1X_AUTH_FLAGS_NONE;
+    if (!svGetValueEnum(ifcfg,
+                        "IEEE_8021X_PHASE1_AUTH_FLAGS",
+                        nm_setting_802_1x_auth_flags_get_type(),
+                        &i_val,
+                        error))
+        return NULL;
+    g_object_set(s_8021x, NM_SETTING_802_1X_PHASE1_AUTH_FLAGS, (guint) i_val, NULL);
+
+    read_8021x_list_value(ifcfg,
+                          "IEEE_8021X_ALTSUBJECT_MATCHES",
+                          s_8021x,
+                          NM_SETTING_802_1X_ALTSUBJECT_MATCHES);
+    read_8021x_list_value(ifcfg,
+                          "IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES",
+                          s_8021x,
+                          NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_DOMAIN_SUFFIX_MATCH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_DOMAIN_MATCH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_DOMAIN_MATCH, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_DOMAIN_MATCH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH, v, NULL);
+
+    timeout = svGetValueInt64(ifcfg, "IEEE_8021X_AUTH_TIMEOUT", 10, 0, G_MAXINT32, 0);
+    g_object_set(s_8021x, NM_SETTING_802_1X_AUTH_TIMEOUT, (int) timeout, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_CA_PATH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_CA_PATH, v, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "IEEE_8021X_PHASE2_CA_PATH", &value);
+    g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_CA_PATH, v, NULL);
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_OPTIONAL,
+                 svGetValueBoolean(ifcfg, "IEEE_8021X_OPTIONAL", FALSE),
+                 NULL);
+
+    _secret_set_from_ifcfg(s_8021x, ifcfg, keys_ifcfg, "IEEE_8021X_PIN", NM_SETTING_802_1X_PIN);
+
+    return g_steal_pointer(&s_8021x);
+}
+
+static NMSetting *
+make_wpa_setting(shvarFile *      ifcfg,
+                 const char *     file,
+                 GBytes *         ssid,
+                 gboolean         adhoc,
+                 NMSetting8021x **s_8021x,
+                 GError **        error)
+{
+    gs_unref_object NMSettingWirelessSecurity *wsec  = NULL;
+    gs_free char *                             value = NULL;
+    const char *                               v;
+    gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_owe = FALSE, wpa_eap = FALSE, ieee8021x = FALSE,
+             wpa3_eap = FALSE;
+    int     i_val;
+    GError *local = NULL;
+
+    wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new());
+
+    v         = svGetValueStr(ifcfg, "KEY_MGMT", &value);
+    wpa_psk   = nm_streq0(v, "WPA-PSK");
+    wpa_sae   = nm_streq0(v, "SAE");
+    wpa_owe   = nm_streq0(v, "OWE");
+    wpa_eap   = nm_streq0(v, "WPA-EAP");
+    wpa3_eap  = nm_streq0(v, "WPA-EAP-SUITE-B-192");
+    ieee8021x = nm_streq0(v, "IEEE8021X");
+    if (!wpa_psk && !wpa_sae && !wpa_owe && !wpa_eap && !wpa3_eap && !ieee8021x)
+        return NULL; /* Not WPA or Dynamic WEP */
+
+    /* WPS */
+    i_val = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT;
+    if (!svGetValueEnum(ifcfg,
+                        "WPS_METHOD",
+                        nm_setting_wireless_security_wps_method_get_type(),
+                        &i_val,
+                        error))
+        return NULL;
+    g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD, (guint) i_val, NULL);
+
+    /* Pairwise and Group ciphers (only relevant for WPA/RSN) */
+    if (wpa_psk || wpa_sae || wpa_owe || wpa_eap) {
+        fill_wpa_ciphers(ifcfg, wsec, FALSE, adhoc);
+        fill_wpa_ciphers(ifcfg, wsec, TRUE, adhoc);
+    }
+
+    /* WPA and/or RSN */
+    if (adhoc) {
+        /* Ad-Hoc mode only supports RSN proto */
+        nm_setting_wireless_security_add_proto(wsec, "rsn");
+    } else {
+        gs_free char *value2 = NULL;
+        const char *  v2;
+
+        v2 = svGetValueStr(ifcfg, "WPA_ALLOW_WPA", &value2);
+        if (v2 && svParseBoolean(v2, TRUE))
+            nm_setting_wireless_security_add_proto(wsec, "wpa");
+
+        nm_clear_g_free(&value2);
+        v2 = svGetValueStr(ifcfg, "WPA_ALLOW_WPA2", &value2);
+        if (v2 && svParseBoolean(v2, TRUE))
+            nm_setting_wireless_security_add_proto(wsec, "rsn");
+    }
+
+    if (wpa_psk || wpa_sae) {
+        NMSettingSecretFlags psk_flags;
+
+        psk_flags = _secret_read_ifcfg_flags(ifcfg, "WPA_PSK_FLAGS");
+        g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS, psk_flags, NULL);
+
+        /* Read PSK if it's system-owned */
+        if (psk_flags == NM_SETTING_SECRET_FLAG_NONE) {
+            gs_free char *psk = NULL;
+
+            psk = parse_wpa_psk(ifcfg, file, ssid, &local);
+            if (psk)
+                g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_PSK, psk, NULL);
+            else if (local) {
+                g_propagate_error(error, local);
+                return NULL;
+            }
+        }
+
+        if (wpa_psk)
+            g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NULL);
+        else {
+            nm_assert(wpa_sae);
+            g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
+        }
+    } else {
+        nm_assert(wpa_eap || wpa3_eap || ieee8021x || wpa_owe);
+
+        /* Adhoc mode is mutually exclusive with any 802.1x-based authentication */
+        if (adhoc) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Ad-Hoc mode cannot be used with KEY_MGMT type '%s'",
+                        v);
+            return NULL;
+        }
+
+        if (wpa_owe) {
+            g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL);
+        } else {
+            *s_8021x = fill_8021x(ifcfg, file, v, TRUE, error);
+            if (!*s_8021x)
+                return NULL;
+
+            {
+                gs_free char *lower = g_ascii_strdown(v, -1);
+                g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, lower, NULL);
+            }
+        }
+    }
+
+    i_val = NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT;
+    if (!svGetValueEnum(ifcfg, "PMF", nm_setting_wireless_security_pmf_get_type(), &i_val, error))
+        return NULL;
+    g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_PMF, i_val, NULL);
+
+    i_val = NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT;
+    if (!svGetValueEnum(ifcfg, "FILS", nm_setting_wireless_security_fils_get_type(), &i_val, error))
+        return NULL;
+    g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_FILS, i_val, NULL);
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "SECURITYMODE", &value);
+    if (NM_IN_STRSET(v, NULL, "open"))
+        g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, v, NULL);
+
+    return (NMSetting *) g_steal_pointer(&wsec);
+}
+
+static NMSetting *
+make_leap_setting(shvarFile *ifcfg, const char *file, GError **error)
+{
+    gs_unref_object NMSettingWirelessSecurity *wsec = NULL;
+    shvarFile *                                keys_ifcfg;
+    gs_free char *                             value = NULL;
+    NMSettingSecretFlags                       flags;
+
+    wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new());
+
+    value = svGetValueStr_cp(ifcfg, "KEY_MGMT");
+    if (!value || strcmp(value, "IEEE8021X"))
+        return NULL;
+    nm_clear_g_free(&value);
+
+    value = svGetValueStr_cp(ifcfg, "SECURITYMODE");
+    if (!value || g_ascii_strcasecmp(value, "leap"))
+        return NULL; /* Not LEAP */
+    nm_clear_g_free(&value);
+
+    flags = _secret_read_ifcfg_flags(ifcfg, "IEEE_8021X_PASSWORD_FLAGS");
+    g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, flags, NULL);
+
+    /* Read LEAP password if it's system-owned */
+    if (flags == NM_SETTING_SECRET_FLAG_NONE) {
+        value = svGetValueStr_cp(ifcfg, "IEEE_8021X_PASSWORD");
+        if (!value) {
+            /* Try to get keys from the "shadow" key file */
+            keys_ifcfg = utils_get_keys_ifcfg(file, FALSE);
+            if (keys_ifcfg) {
+                value = svGetValueStr_cp(keys_ifcfg, "IEEE_8021X_PASSWORD");
+                svCloseFile(keys_ifcfg);
+            }
+        }
+        if (value && strlen(value))
+            g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, value, NULL);
+        nm_clear_g_free(&value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "IEEE_8021X_IDENTITY");
+    if (!value) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Missing LEAP identity");
+        return NULL;
+    }
+    g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, value, NULL);
+    nm_clear_g_free(&value);
+
+    g_object_set(wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "ieee8021x",
+                 NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                 "leap",
+                 NULL);
+
+    return (NMSetting *) g_steal_pointer(&wsec);
+}
+
+static NMSetting *
+make_wireless_security_setting(shvarFile *      ifcfg,
+                               const char *     file,
+                               GBytes *         ssid,
+                               gboolean         adhoc,
+                               NMSetting8021x **s_8021x,
+                               GError **        error)
+{
+    NMSetting *wsec;
+
+    g_return_val_if_fail(error && !*error, NULL);
+
+    if (!adhoc) {
+        wsec = make_leap_setting(ifcfg, file, error);
+        if (wsec)
+            return wsec;
+        else if (*error)
+            return NULL;
+    }
+
+    wsec = make_wpa_setting(ifcfg, file, ssid, adhoc, s_8021x, error);
+    if (wsec)
+        return wsec;
+    else if (*error)
+        return NULL;
+
+    wsec = make_wep_setting(ifcfg, file, error);
+    if (wsec)
+        return wsec;
+    else if (*error)
+        return NULL;
+
+    return NULL; /* unencrypted */
+}
+
+static const char **
+transform_hwaddr_blacklist(const char *blacklist)
+{
+    const char **strv;
+    gsize        i, j;
+
+    strv = nm_utils_strsplit_set(blacklist, " \t");
+    if (!strv)
+        return NULL;
+    for (i = 0, j = 0; strv[j]; j++) {
+        const char *s = strv[j];
+
+        if (!nm_utils_hwaddr_valid(s, ETH_ALEN)) {
+            PARSE_WARNING("invalid MAC in HWADDR_BLACKLIST '%s'", s);
+            continue;
+        }
+        strv[i++] = s;
+    }
+    strv[i] = NULL;
+    return strv;
+}
+
+static NMSetting *
+make_wireless_setting(shvarFile *ifcfg, GError **error)
+{
+    NMSettingWireless *        s_wireless;
+    const char *               cvalue;
+    char *                     value = NULL;
+    gint64                     chan  = 0;
+    NMSettingMacRandomization  mac_randomization;
+    NMSettingWirelessPowersave powersave = NM_SETTING_WIRELESS_POWERSAVE_DEFAULT;
+    NMTernary                  ternary;
+
+    s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+
+    value = svGetValueStr_cp(ifcfg, "HWADDR");
+    if (value) {
+        value = g_strstrip(value);
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_MAC_ADDRESS, value, NULL);
+        g_free(value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "MACADDR");
+    if (value) {
+        value = g_strstrip(value);
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, value, NULL);
+        g_free(value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "GENERATE_MAC_ADDRESS_MASK");
+    g_object_set(s_wireless, NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK, value, NULL);
+    g_free(value);
+
+    cvalue = svGetValueStr(ifcfg, "HWADDR_BLACKLIST", &value);
+    if (cvalue) {
+        gs_free const char **strv = NULL;
+
+        strv = transform_hwaddr_blacklist(cvalue);
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST, strv, NULL);
+        g_free(value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "ESSID");
+    if (value) {
+        gs_unref_bytes GBytes *bytes     = NULL;
+        gsize                  ssid_len  = 0;
+        gsize                  value_len = strlen(value);
+
+        if (value_len > 2 && (value_len % 2) == 0 && g_str_has_prefix(value, "0x")
+            && NM_STRCHAR_ALL(&value[2], ch, g_ascii_isxdigit(ch))) {
+            /* interpret the value as hex-digits iff value starts
+             * with "0x" followed by pairs of hex digits */
+            bytes = nm_utils_hexstr2bin(&value[2]);
+        } else
+            bytes = g_bytes_new(value, value_len);
+
+        ssid_len = g_bytes_get_size(bytes);
+        if (ssid_len > 32 || ssid_len == 0) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid SSID '%s' (size %zu not between 1 and 32 inclusive)",
+                        value,
+                        ssid_len);
+            g_free(value);
+            goto error;
+        }
+
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_SSID, bytes, NULL);
+        g_free(value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "MODE");
+    if (value) {
+        char *      lcase;
+        const char *mode = NULL;
+
+        lcase = g_ascii_strdown(value, -1);
+        g_free(value);
+
+        if (!strcmp(lcase, "ad-hoc")) {
+            mode = "adhoc";
+        } else if (!strcmp(lcase, "ap")) {
+            mode = "ap";
+        } else if (!strcmp(lcase, "managed") || !strcmp(lcase, "auto")) {
+            mode = "infrastructure";
+        } else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid mode '%s' (not 'Ad-Hoc', 'Ap', 'Managed', or 'Auto')",
+                        lcase);
+            g_free(lcase);
+            goto error;
+        }
+        g_free(lcase);
+
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_MODE, mode, NULL);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "BSSID");
+    if (value) {
+        value = g_strstrip(value);
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_BSSID, value, NULL);
+        g_free(value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "CHANNEL");
+    if (value) {
+        chan = _nm_utils_ascii_str_to_int64(value, 10, 1, 196, 0);
+        if (chan == 0) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid wireless channel '%s'",
+                        value);
+            g_free(value);
+            goto error;
+        }
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_CHANNEL, (guint32) chan, NULL);
+        g_free(value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "BAND");
+    if (value) {
+        if (!strcmp(value, "a")) {
+            if (chan && chan <= 14) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Band '%s' invalid for channel %u",
+                            value,
+                            (guint32) chan);
+                g_free(value);
+                goto error;
+            }
+        } else if (!strcmp(value, "bg")) {
+            if (chan && chan > 14) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Band '%s' invalid for channel %u",
+                            value,
+                            (guint32) chan);
+                g_free(value);
+                goto error;
+            }
+        } else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid wireless band '%s'",
+                        value);
+            g_free(value);
+            goto error;
+        }
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, value, NULL);
+        g_free(value);
+    } else if (chan > 0) {
+        if (chan > 14)
+            g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, "a", NULL);
+        else
+            g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, "bg", NULL);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "MTU");
+    if (value) {
+        int mtu;
+
+        mtu = _nm_utils_ascii_str_to_int64(value, 10, 0, 50000, -1);
+        if (mtu == -1) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid wireless MTU '%s'",
+                        value);
+            g_free(value);
+            goto error;
+        }
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_MTU, (guint) mtu, NULL);
+        g_free(value);
+    }
+
+    g_object_set(s_wireless,
+                 NM_SETTING_WIRELESS_HIDDEN,
+                 svGetValueBoolean(ifcfg, "SSID_HIDDEN", FALSE),
+                 NULL);
+
+    cvalue = svGetValue(ifcfg, "POWERSAVE", &value);
+    if (cvalue) {
+        if (!strcmp(cvalue, "default"))
+            powersave = NM_SETTING_WIRELESS_POWERSAVE_DEFAULT;
+        else if (!strcmp(cvalue, "ignore"))
+            powersave = NM_SETTING_WIRELESS_POWERSAVE_IGNORE;
+        else if (!strcmp(cvalue, "disable") || !strcmp(cvalue, "no"))
+            powersave = NM_SETTING_WIRELESS_POWERSAVE_DISABLE;
+        else if (!strcmp(cvalue, "enable") || !strcmp(cvalue, "yes"))
+            powersave = NM_SETTING_WIRELESS_POWERSAVE_ENABLE;
+        else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid POWERSAVE value '%s'",
+                        cvalue);
+            g_free(value);
+            goto error;
+        }
+        g_free(value);
+    }
+
+    g_object_set(s_wireless, NM_SETTING_WIRELESS_POWERSAVE, powersave, NULL);
+
+    cvalue = svGetValue(ifcfg, "MAC_ADDRESS_RANDOMIZATION", &value);
+    if (cvalue) {
+        if (strcmp(cvalue, "default") == 0)
+            mac_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT;
+        else if (strcmp(cvalue, "never") == 0)
+            mac_randomization = NM_SETTING_MAC_RANDOMIZATION_NEVER;
+        else if (strcmp(cvalue, "always") == 0)
+            mac_randomization = NM_SETTING_MAC_RANDOMIZATION_ALWAYS;
+        else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid MAC_ADDRESS_RANDOMIZATION value '%s'",
+                        cvalue);
+            g_free(value);
+            goto error;
+        }
+        g_free(value);
+    } else
+        mac_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT;
+
+    g_object_set(s_wireless,
+                 NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
+                 mac_randomization,
+                 NULL);
+
+    ternary = svGetValueTernary(ifcfg, "AP_ISOLATION");
+    if (ternary != NM_TERNARY_DEFAULT) {
+        g_object_set(s_wireless, NM_SETTING_WIRELESS_AP_ISOLATION, ternary, NULL);
+    }
+
+    return NM_SETTING(s_wireless);
+
+error:
+    if (s_wireless)
+        g_object_unref(s_wireless);
+    return NULL;
+}
+
+static NMConnection *
+wireless_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
+{
+    NMConnection *  connection       = NULL;
+    NMSetting *     con_setting      = NULL;
+    NMSetting *     wireless_setting = NULL;
+    NMSetting8021x *s_8021x          = NULL;
+    GBytes *        ssid;
+    NMSetting *     security_setting = NULL;
+    gs_free char *  ssid_utf8        = NULL;
+    const char *    mode;
+    gboolean        adhoc = FALSE;
+    GError *        local = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+    g_return_val_if_fail(ifcfg != NULL, NULL);
+    g_return_val_if_fail(!error || !*error, NULL);
+
+    connection = nm_simple_connection_new();
+
+    /* Wireless */
+    wireless_setting = make_wireless_setting(ifcfg, error);
+    if (!wireless_setting) {
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, wireless_setting);
+
+    ssid = nm_setting_wireless_get_ssid(NM_SETTING_WIRELESS(wireless_setting));
+    mode = nm_setting_wireless_get_mode(NM_SETTING_WIRELESS(wireless_setting));
+    if (mode && !strcmp(mode, "adhoc"))
+        adhoc = TRUE;
+
+    /* Wireless security */
+    security_setting = make_wireless_security_setting(ifcfg, file, ssid, adhoc, &s_8021x, &local);
+    if (local) {
+        g_object_unref(connection);
+        g_propagate_error(error, local);
+        return NULL;
+    }
+    if (security_setting) {
+        nm_connection_add_setting(connection, security_setting);
+        if (s_8021x)
+            nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+    }
+
+    if (ssid)
+        ssid_utf8 = _nm_utils_ssid_to_utf8(ssid);
+
+    /* Connection */
+    con_setting = make_connection_setting(file,
+                                          ifcfg,
+                                          NM_SETTING_WIRELESS_SETTING_NAME,
+                                          nm_str_not_empty(ssid_utf8) ?: "unmanaged",
+                                          NULL);
+
+    if (!con_setting) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create connection setting");
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, con_setting);
+
+    return connection;
+}
+
+typedef struct {
+    const char *optname;
+    union {
+        guint32   u32;
+        NMTernary nmternary;
+    } v;
+    gboolean has_value;
+} NMEthtoolIfcfgOption;
+
+/* returns an 'iterator' to words
+ * pointing to the next unprocessed option or NULL
+ * in case of failure */
+static const char **
+_next_ethtool_options_nmternary(const char **         words,
+                                NMEthtoolType         ethtool_type,
+                                NMEthtoolIfcfgOption *out_value)
+{
+    const char *         opt;
+    const char *         opt_val;
+    const NMEthtoolData *d     = NULL;
+    NMTernary            onoff = NM_TERNARY_DEFAULT;
+
+    nm_assert(out_value);
+
+    out_value->has_value = FALSE;
+    out_value->optname   = NULL;
+
+    if (!words || !words[0] || !words[1])
+        return NULL;
+
+    opt     = *words;
+    opt_val = *(++words);
+
+    if (nm_streq0(opt_val, "on"))
+        onoff = NM_TERNARY_TRUE;
+    else if (nm_streq0(opt_val, "off"))
+        onoff = NM_TERNARY_FALSE;
+
+    d = nms_ifcfg_rh_utils_get_ethtool_by_name(opt, ethtool_type);
+    if (!d) {
+        if (onoff != NM_TERNARY_DEFAULT) {
+            /* the next value is just the on/off argument. Skip it too. */
+            ++words;
+        }
+
+        /* silently ignore unsupported offloading features. */
+        return words;
+    }
+
+    if (onoff == NM_TERNARY_DEFAULT) {
+        PARSE_WARNING("Expects on/off argument for feature '%s'", opt);
+        return words;
+    }
+
+    out_value->has_value   = TRUE;
+    out_value->optname     = d->optname;
+    out_value->v.nmternary = onoff;
+
+    return ++words;
+}
+
+/* returns an 'iterator' to words
+ * pointing to the next unprocessed option or NULL
+ * in case of failure */
+static const char **
+_next_ethtool_options_uint32(const char **         words,
+                             NMEthtoolType         ethtool_type,
+                             NMEthtoolIfcfgOption *out_value)
+{
+    gint64               i64;
+    const char *         opt;
+    const char *         opt_val;
+    const NMEthtoolData *d = NULL;
+
+    nm_assert(out_value);
+
+    out_value->has_value = FALSE;
+    out_value->optname   = NULL;
+
+    if (!words || !words[0] || !words[1])
+        return NULL;
+
+    opt     = *words;
+    opt_val = *(++words);
+
+    i64 = _nm_utils_ascii_str_to_int64(opt_val, 10, 0, G_MAXUINT32, -1);
+
+    d = nms_ifcfg_rh_utils_get_ethtool_by_name(opt, ethtool_type);
+    if (!d) {
+        if (i64 != -1) {
+            /* the next value is just the on/off argument. Skip it too. */
+            ++words;
+        }
+
+        /* silently ignore unsupported offloading features. */
+        return words;
+    }
+
+    out_value->has_value = TRUE;
+    out_value->optname   = d->optname;
+    out_value->v.u32     = (guint32) i64;
+
+    return ++words;
+}
+
+static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
+    _get_ethtool_type_by_name,
+    NMEthtoolType,
+    { nm_assert(name); },
+    { return NM_ETHTOOL_TYPE_UNKNOWN; },
+    {"--coalesce", NM_ETHTOOL_TYPE_COALESCE},
+    {"--features", NM_ETHTOOL_TYPE_FEATURE},
+    {"--offload", NM_ETHTOOL_TYPE_FEATURE},
+    {"--set-ring", NM_ETHTOOL_TYPE_RING},
+    {"-C", NM_ETHTOOL_TYPE_COALESCE},
+    {"-G", NM_ETHTOOL_TYPE_RING},
+    {"-K", NM_ETHTOOL_TYPE_FEATURE}, );
+
+static void
+parse_ethtool_option(const char *             value,
+                     NMSettingWiredWakeOnLan *out_flags,
+                     char **                  out_password,
+                     gboolean *               out_autoneg,
+                     guint32 *                out_speed,
+                     const char **            out_duplex,
+                     NMSettingEthtool **      out_s_ethtool)
+{
+    guint                i;
+    const char **        w_iter;
+    NMEthtoolIfcfgOption ifcfg_option;
+    gs_free const char **words        = NULL;
+    NMEthtoolType        ethtool_type = NM_ETHTOOL_TYPE_UNKNOWN;
+
+    words = nm_utils_strsplit_set(value, " \t\n");
+    if (!words)
+        return;
+
+    if (words[0])
+        ethtool_type = _get_ethtool_type_by_name(words[0]);
+
+    if (ethtool_type != NM_ETHTOOL_TYPE_UNKNOWN) {
+        if (!words[1]) {
+            /* first argument must be the interface name. This is invalid. */
+            return;
+        }
+
+        if (!*out_s_ethtool)
+            *out_s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new());
+
+        /* skip ethtool type && interface name */
+        w_iter = &words[2];
+
+        while (w_iter && *w_iter) {
+            if (ethtool_type == NM_ETHTOOL_TYPE_FEATURE) {
+                w_iter = _next_ethtool_options_nmternary(w_iter, ethtool_type, &ifcfg_option);
+
+                if (ifcfg_option.has_value) {
+                    nm_setting_option_set_boolean(NM_SETTING(*out_s_ethtool),
+                                                  ifcfg_option.optname,
+                                                  ifcfg_option.v.nmternary != NM_TERNARY_FALSE);
+                }
+            }
+            if (NM_IN_SET(ethtool_type, NM_ETHTOOL_TYPE_COALESCE, NM_ETHTOOL_TYPE_RING)) {
+                w_iter = _next_ethtool_options_uint32(w_iter, ethtool_type, &ifcfg_option);
+
+                if (ifcfg_option.has_value) {
+                    nm_setting_option_set_uint32(NM_SETTING(*out_s_ethtool),
+                                                 ifcfg_option.optname,
+                                                 ifcfg_option.v.u32);
+                }
+            }
+        }
+
+        return;
+    }
+
+    /* /sbin/ethtool -s ${REALDEVICE} $opts */
+    for (i = 0; words[i];) {
+        const char *opt     = words[i];
+        const char *opt_val = words[++i];
+
+        if (nm_streq(opt, "autoneg")) {
+            if (!opt_val) {
+                PARSE_WARNING("Auto-negotiation option missing");
+                break;
+            }
+            i++;
+
+            if (nm_streq(opt_val, "off"))
+                *out_autoneg = FALSE;
+            else if (nm_streq(opt_val, "on"))
+                *out_autoneg = TRUE;
+            else
+                PARSE_WARNING("Auto-negotiation unknown value: %s", opt_val);
+            continue;
+        }
+
+        if (nm_streq(opt, "speed")) {
+            guint32 speed;
+
+            if (!opt_val) {
+                PARSE_WARNING("Speed option missing");
+                break;
+            }
+            i++;
+
+            speed = _nm_utils_ascii_str_to_int64(opt_val, 10, 0, G_MAXUINT32, 0);
+            if (errno == 0)
+                *out_speed = speed;
+            else
+                PARSE_WARNING("Speed value '%s' is invalid", opt_val);
+            continue;
+        }
+
+        if (nm_streq(opt, "duplex")) {
+            if (!opt_val) {
+                PARSE_WARNING("Duplex option missing");
+                break;
+            }
+            i++;
+
+            if (nm_streq(opt_val, "half"))
+                *out_duplex = "half";
+            else if (nm_streq(opt_val, "full"))
+                *out_duplex = "full";
+            else
+                PARSE_WARNING("Duplex unknown value: %s", opt_val);
+            continue;
+        }
+
+        if (nm_streq(opt, "wol")) {
+            NMSettingWiredWakeOnLan wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE;
+
+            if (!opt_val) {
+                PARSE_WARNING("Wake-on-LAN options missing");
+                break;
+            }
+            i++;
+
+            for (; *opt_val; opt_val++) {
+                switch (*opt_val) {
+                case 'p':
+                    wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_PHY;
+                    break;
+                case 'u':
+                    wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST;
+                    break;
+                case 'm':
+                    wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST;
+                    break;
+                case 'b':
+                    wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST;
+                    break;
+                case 'a':
+                    wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_ARP;
+                    break;
+                case 'g':
+                    wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC;
+                    break;
+                case 's':
+                    break;
+                case 'd':
+                    wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE;
+                    break;
+                default:
+                    PARSE_WARNING("unrecognized Wake-on-LAN option '%c'", *opt_val);
+                }
+            }
+
+            *out_flags = wol_flags;
+            continue;
+        }
+
+        if (nm_streq(opt, "sopass")) {
+            if (!opt_val) {
+                PARSE_WARNING("Wake-on-LAN password missing");
+                break;
+            }
+            i++;
+
+            if (nm_utils_hwaddr_valid(opt_val, ETH_ALEN)) {
+                nm_clear_g_free(out_password);
+                *out_password = g_strdup(opt_val);
+            } else
+                PARSE_WARNING("Wake-on-LAN password '%s' is invalid", opt_val);
+            continue;
+        }
+
+        /* Silently skip unknown options */
+    }
+}
+
+static GPtrArray *
+read_routing_rules_parse(shvarFile *ifcfg, gboolean routes_read)
+{
+    gs_unref_ptrarray GPtrArray *arr  = NULL;
+    gs_free const char **        keys = NULL;
+    guint                        i, len;
+
+    keys = svGetKeysSorted(ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6, &len);
+    if (len == 0)
+        return NULL;
+
+    if (!routes_read) {
+        PARSE_WARNING("'rule-' or 'rule6-' files are present; Policy routing rules (ROUTING_RULE*) "
+                      "settings are ignored");
+        return NULL;
+    }
+
+    arr = g_ptr_array_new_full(len, (GDestroyNotify) nm_ip_routing_rule_unref);
+    for (i = 0; i < len; i++) {
+        const char *                  key                   = keys[i];
+        nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+        gs_free_error GError *local                         = NULL;
+        gs_free char *        value_to_free                 = NULL;
+        const char *          value;
+        gboolean              key_is_ipv4;
+
+        key_is_ipv4 = (key[NM_STRLEN("ROUTING_RULE")] == '_');
+        nm_assert(key_is_ipv4 == NM_STR_HAS_PREFIX(key, "ROUTING_RULE_"));
+        nm_assert((!key_is_ipv4) == NM_STR_HAS_PREFIX(key, "ROUTING_RULE6_"));
+
+        value = svGetValueStr(ifcfg, key, &value_to_free);
+        if (!value)
+            continue;
+
+        rule = nm_ip_routing_rule_from_string(
+            value,
+            NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
+                | (key_is_ipv4 ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET
+                               : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6),
+            NULL,
+            &local);
+        if (!rule) {
+            PARSE_WARNING("invalid routing rule %s=\"%s\": %s", key, value, local->message);
+            continue;
+        }
+
+        g_ptr_array_add(arr, g_steal_pointer(&rule));
+    }
+
+    if (arr->len == 0)
+        return NULL;
+
+    return g_steal_pointer(&arr);
+}
+
+static void
+read_routing_rules(shvarFile *        ifcfg,
+                   gboolean           routes_read,
+                   NMSettingIPConfig *s_ip4,
+                   NMSettingIPConfig *s_ip6)
+{
+    gs_unref_ptrarray GPtrArray *routing_rules = NULL;
+    guint                        i;
+
+    routing_rules = read_routing_rules_parse(ifcfg, routes_read);
+    if (!routing_rules)
+        return;
+
+    for (i = 0; i < routing_rules->len; i++) {
+        NMIPRoutingRule *rule = routing_rules->pdata[i];
+
+        nm_setting_ip_config_add_routing_rule(
+            (nm_ip_routing_rule_get_addr_family(rule) == AF_INET) ? s_ip4 : s_ip6,
+            rule);
+    }
+}
+
+static void
+parse_ethtool_options(shvarFile *ifcfg, NMConnection *connection)
+{
+    NMSettingWired *s_wired;
+    gs_unref_object NMSettingEthtool *s_ethtool         = NULL;
+    NMSettingWiredWakeOnLan           wol_flags         = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT;
+    gs_free char *                    ethtool_opts_free = NULL;
+    const char *                      ethtool_opts;
+    gs_free char *                    wol_password   = NULL;
+    gs_free char *                    wol_value_free = NULL;
+    const char *                      tmp;
+    gboolean                          autoneg     = FALSE;
+    guint32                           speed       = 0;
+    const char *                      duplex      = NULL;
+    gboolean                          wired_found = FALSE;
+
+    ethtool_opts = svGetValue(ifcfg, "ETHTOOL_OPTS", &ethtool_opts_free);
+    if (ethtool_opts) {
+        wired_found = TRUE;
+        /* WAKE_ON_LAN_IGNORE is inferred from a specified but empty ETHTOOL_OPTS */
+        if (!ethtool_opts[0])
+            wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
+        else {
+            gs_free const char **opts = NULL;
+            const char *const *  iter;
+
+            opts = nm_utils_strsplit_set(ethtool_opts, ";");
+            for (iter = opts; iter && iter[0]; iter++) {
+                /* in case of repeated wol_passwords, parse_ethtool_option()
+                 * will do the right thing and clear wol_password before resetting. */
+                parse_ethtool_option(iter[0],
+                                     &wol_flags,
+                                     &wol_password,
+                                     &autoneg,
+                                     &speed,
+                                     &duplex,
+                                     &s_ethtool);
+            }
+        }
+    }
+
+    /* ETHTOOL_WAKE_ON_LAN = ignore overrides WoL settings in ETHTOOL_OPTS */
+    tmp = svGetValue(ifcfg, "ETHTOOL_WAKE_ON_LAN", &wol_value_free);
+    if (tmp)
+        wired_found = TRUE;
+    if (nm_streq0(tmp, "ignore"))
+        wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
+    else if (tmp)
+        PARSE_WARNING("invalid ETHTOOL_WAKE_ON_LAN value '%s'", tmp);
+
+    if (wol_password && !NM_FLAGS_HAS(wol_flags, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) {
+        PARSE_WARNING("Wake-on-LAN password not expected");
+        nm_clear_g_free(&wol_password);
+    }
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    if (!s_wired && wired_found) {
+        s_wired = (NMSettingWired *) nm_setting_wired_new();
+        nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    }
+    if (s_wired) {
+        g_object_set(s_wired,
+                     NM_SETTING_WIRED_WAKE_ON_LAN,
+                     wol_flags,
+                     NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD,
+                     wol_password,
+                     NM_SETTING_WIRED_AUTO_NEGOTIATE,
+                     autoneg,
+                     NM_SETTING_WIRED_SPEED,
+                     speed,
+                     NM_SETTING_WIRED_DUPLEX,
+                     duplex,
+                     NULL);
+    }
+
+    if (s_ethtool) {
+        nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ethtool)));
+    }
+}
+
+static NMSetting *
+make_wired_setting(shvarFile *ifcfg, const char *file, NMSetting8021x **s_8021x, GError **error)
+{
+    gs_unref_object NMSettingWired *s_wired = NULL;
+    const char *                    cvalue;
+    gs_free char *                  value = NULL;
+    gboolean                        found = FALSE;
+
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+
+    cvalue = svGetValue(ifcfg, "MTU", &value);
+    if (cvalue) {
+        int mtu;
+
+        mtu = _nm_utils_ascii_str_to_int64(cvalue, 0, 0, 65535, -1);
+        if (mtu >= 0)
+            g_object_set(s_wired, NM_SETTING_WIRED_MTU, (guint) mtu, NULL);
+        else
+            PARSE_WARNING("invalid MTU '%s'", cvalue);
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    value = svGetValue_cp(ifcfg, "HWADDR");
+    if (value) {
+        if (value[0] != '\0') {
+            value = g_strstrip(value);
+            g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, value, NULL);
+        }
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    cvalue = svGetValue(ifcfg, "SUBCHANNELS", &value);
+    if (cvalue) {
+        if (cvalue[0] != '\0') {
+            const char *p       = cvalue;
+            gboolean    success = TRUE;
+
+            /* basic sanity checks */
+            while (*p) {
+                if (!g_ascii_isxdigit(*p) && (*p != ',') && (*p != '.')) {
+                    PARSE_WARNING("invalid SUBCHANNELS '%s'", cvalue);
+                    success = FALSE;
+                    break;
+                }
+                p++;
+            }
+
+            if (success) {
+                gs_free const char **chans = NULL;
+                guint32              num_chans;
+
+                chans     = nm_utils_strsplit_set(cvalue, ",");
+                num_chans = NM_PTRARRAY_LEN(chans);
+                if (num_chans < 2 || num_chans > 3) {
+                    PARSE_WARNING("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
+                                  cvalue,
+                                  (unsigned) NM_PTRARRAY_LEN(chans));
+                } else
+                    g_object_set(s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, chans, NULL);
+            }
+        }
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    cvalue = svGetValue(ifcfg, "PORTNAME", &value);
+    if (cvalue) {
+        if (cvalue[0] != '\0')
+            nm_setting_wired_add_s390_option(s_wired, "portname", cvalue);
+        found = TRUE;
+        nm_clear_g_free(&value);
+    }
+
+    cvalue = svGetValue(ifcfg, "CTCPROT", &value);
+    if (cvalue) {
+        if (cvalue[0] != '\0')
+            nm_setting_wired_add_s390_option(s_wired, "ctcprot", cvalue);
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    cvalue = svGetValue(ifcfg, "NETTYPE", &value);
+    if (cvalue) {
+        if (NM_IN_STRSET(cvalue, "qeth", "lcs", "ctc"))
+            g_object_set(s_wired, NM_SETTING_WIRED_S390_NETTYPE, cvalue, NULL);
+        else
+            PARSE_WARNING("unknown s390 NETTYPE '%s'", cvalue);
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    cvalue = svGetValue(ifcfg, "OPTIONS", &value);
+    if (cvalue)
+        found = TRUE;
+    if (cvalue && cvalue[0]) {
+        gs_free const char **options = NULL;
+        gsize                i;
+
+        options = nm_utils_escaped_tokens_split(cvalue, NM_ASCII_SPACES);
+        for (i = 0; options && options[i]; i++) {
+            const char *line = options[i];
+            const char *equals;
+            gboolean    valid = FALSE;
+
+            equals = strchr(line, '=');
+            if (equals) {
+                ((char *) equals)[0] = '\0';
+                valid                = nm_setting_wired_add_s390_option(s_wired, line, equals + 1);
+            }
+            if (!valid)
+                PARSE_WARNING("invalid s390 OPTION '%s'", line);
+        }
+        found = TRUE;
+    }
+    nm_clear_g_free(&value);
+
+    cvalue = svGetValueStr(ifcfg, "MACADDR", &value);
+    if (cvalue) {
+        if (cvalue[0] != '\0') {
+            g_object_set(s_wired, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, cvalue, NULL);
+        }
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    cvalue = svGetValueStr(ifcfg, "GENERATE_MAC_ADDRESS_MASK", &value);
+    if (cvalue) {
+        if (cvalue[0] != '\0') {
+            g_object_set(s_wired, NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, cvalue, NULL);
+        }
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    cvalue = svGetValueStr(ifcfg, "HWADDR_BLACKLIST", &value);
+    if (cvalue) {
+        gs_free const char **strv = NULL;
+
+        strv = transform_hwaddr_blacklist(cvalue);
+        g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, strv, NULL);
+        nm_clear_g_free(&value);
+        found = TRUE;
+    }
+
+    cvalue = svGetValue(ifcfg, "KEY_MGMT", &value);
+    if (cvalue)
+        found = TRUE;
+    if (cvalue && cvalue[0] != '\0') {
+        if (!strcmp(cvalue, "IEEE8021X")) {
+            *s_8021x = fill_8021x(ifcfg, file, cvalue, FALSE, error);
+            if (!*s_8021x)
+                return NULL;
+        } else {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Unknown wired KEY_MGMT type '%s'",
+                        cvalue);
+            return NULL;
+        }
+    }
+    nm_clear_g_free(&value);
+
+    if (!found) {
+        g_set_error(error,
+                    NM_UTILS_ERROR,
+                    NM_UTILS_ERROR_SETTING_MISSING,
+                    "The setting is missing");
+        return NULL;
+    }
+
+    return (NMSetting *) g_steal_pointer(&s_wired);
+}
+
+static NMConnection *
+wired_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
+{
+    NMConnection *  connection    = NULL;
+    NMSetting *     con_setting   = NULL;
+    NMSetting *     wired_setting = NULL;
+    NMSetting8021x *s_8021x       = NULL;
+    GError *        local         = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+    g_return_val_if_fail(ifcfg != NULL, NULL);
+
+    connection = nm_simple_connection_new();
+
+    con_setting = make_connection_setting(file, ifcfg, NM_SETTING_WIRED_SETTING_NAME, NULL, NULL);
+    if (!con_setting) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create connection setting");
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, con_setting);
+
+    wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local);
+    if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+        g_propagate_error(error, local);
+        g_object_unref(connection);
+        return NULL;
+    }
+    g_clear_error(&local);
+
+    if (wired_setting)
+        nm_connection_add_setting(connection, wired_setting);
+
+    if (s_8021x)
+        nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    return connection;
+}
+
+static gboolean
+parse_infiniband_p_key(shvarFile *ifcfg, int *out_p_key, char **out_parent, GError **error)
+{
+    char *   device = NULL, *physdev = NULL, *pkey_id = NULL;
+    char *   ifname = NULL;
+    int      id;
+    gboolean ret = FALSE;
+
+    device = svGetValueStr_cp(ifcfg, "DEVICE");
+    if (!device) {
+        PARSE_WARNING("InfiniBand connection specified PKEY but not DEVICE");
+        goto done;
+    }
+
+    physdev = svGetValueStr_cp(ifcfg, "PHYSDEV");
+    if (!physdev) {
+        PARSE_WARNING("InfiniBand connection specified PKEY but not PHYSDEV");
+        goto done;
+    }
+
+    pkey_id = svGetValueStr_cp(ifcfg, "PKEY_ID");
+    if (!pkey_id) {
+        PARSE_WARNING("InfiniBand connection specified PKEY but not PKEY_ID");
+        goto done;
+    }
+
+    id = _nm_utils_ascii_str_to_int64(pkey_id, 0, 0, 0xFFFF, -1);
+    if (id == -1) {
+        PARSE_WARNING("invalid InfiniBand PKEY_ID '%s'", pkey_id);
+        goto done;
+    }
+    id = (id | 0x8000);
+
+    ifname = g_strdup_printf("%s.%04x", physdev, (unsigned) id);
+    if (strcmp(device, ifname) != 0) {
+        PARSE_WARNING("InfiniBand DEVICE (%s) does not match PHYSDEV+PKEY_ID (%s)", device, ifname);
+        goto done;
+    }
+
+    *out_p_key  = id;
+    *out_parent = g_strdup(physdev);
+    ret         = TRUE;
+
+done:
+    g_free(device);
+    g_free(physdev);
+    g_free(pkey_id);
+    g_free(ifname);
+
+    if (!ret) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create InfiniBand setting");
+    }
+    return ret;
+}
+
+static NMSetting *
+make_infiniband_setting(shvarFile *ifcfg, const char *file, GError **error)
+{
+    NMSettingInfiniband *s_infiniband;
+    char *               value = NULL;
+
+    s_infiniband = NM_SETTING_INFINIBAND(nm_setting_infiniband_new());
+
+    value = svGetValueStr_cp(ifcfg, "MTU");
+    if (value) {
+        int mtu;
+
+        mtu = _nm_utils_ascii_str_to_int64(value, 0, 0, 65535, -1);
+        if (mtu >= 0)
+            g_object_set(s_infiniband, NM_SETTING_INFINIBAND_MTU, (guint) mtu, NULL);
+        else
+            PARSE_WARNING("invalid MTU '%s'", value);
+        g_free(value);
+    }
+
+    value = svGetValueStr_cp(ifcfg, "HWADDR");
+    if (value) {
+        value = g_strstrip(value);
+        g_object_set(s_infiniband, NM_SETTING_INFINIBAND_MAC_ADDRESS, value, NULL);
+        g_free(value);
+    }
+
+    if (svGetValueBoolean(ifcfg, "CONNECTED_MODE", FALSE))
+        g_object_set(s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "connected", NULL);
+    else
+        g_object_set(s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
+
+    if (svGetValueBoolean(ifcfg, "PKEY", FALSE)) {
+        gs_free char *parent = NULL;
+        int           p_key;
+
+        if (!parse_infiniband_p_key(ifcfg, &p_key, &parent, error)) {
+            g_object_unref(s_infiniband);
+            return NULL;
+        }
+
+        g_object_set(s_infiniband,
+                     NM_SETTING_INFINIBAND_P_KEY,
+                     p_key,
+                     NM_SETTING_INFINIBAND_PARENT,
+                     parent,
+                     NULL);
+    }
+
+    return (NMSetting *) s_infiniband;
+}
+
+static NMConnection *
+infiniband_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
+{
+    NMConnection *connection         = NULL;
+    NMSetting *   con_setting        = NULL;
+    NMSetting *   infiniband_setting = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+    g_return_val_if_fail(ifcfg != NULL, NULL);
+
+    connection = nm_simple_connection_new();
+
+    con_setting =
+        make_connection_setting(file, ifcfg, NM_SETTING_INFINIBAND_SETTING_NAME, NULL, NULL);
+    if (!con_setting) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create connection setting");
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, con_setting);
+
+    infiniband_setting = make_infiniband_setting(ifcfg, file, error);
+    if (!infiniband_setting) {
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, infiniband_setting);
+
+    return connection;
+}
+
+static void
+handle_bond_option(NMSettingBond *s_bond, const char *key, const char *value)
+{
+    gs_free char *sanitized = NULL;
+    const char *  p         = value;
+
+    /* Remove any quotes or +/- from arp_ip_target */
+    if (nm_streq0(key, NM_SETTING_BOND_OPTION_ARP_IP_TARGET) && value && value[0]) {
+        char *j;
+
+        if (*p == '\'' || *p == '"')
+            p++;
+        j = sanitized = g_malloc(strlen(p) + 1);
+        while (*p) {
+            if (*p != '+' && *p != '-' && *p != '\'' && *p != '"')
+                *j++ = *p;
+            p++;
+        }
+        *j++  = '\0';
+        value = sanitized;
+    }
+
+    if (!_nm_setting_bond_validate_option(key, value, NULL)) {
+        PARSE_WARNING("invalid bonding option '%s' = %s", key, value);
+        return;
+    }
+
+    nm_setting_bond_add_option(s_bond, key, value);
+}
+
+static NMSetting *
+make_bond_setting(shvarFile *ifcfg, const char *file, GError **error)
+{
+    NMSettingBond *s_bond;
+    gs_free char * value = NULL;
+    const char *   v;
+
+    v = svGetValueStr(ifcfg, "DEVICE", &value);
+    if (!v) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "mandatory DEVICE keyword missing");
+        return NULL;
+    }
+
+    s_bond = NM_SETTING_BOND(nm_setting_bond_new());
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "BONDING_OPTS", &value);
+    if (v) {
+        gs_free const char **items = NULL;
+        const char *const *  iter;
+
+        items = nm_utils_strsplit_set(v, " ");
+        for (iter = items; iter && *iter; iter++) {
+            gs_free char *key = NULL;
+            const char *  val;
+
+            val = strchr(*iter, '=');
+            if (!val)
+                continue;
+            key = g_strndup(*iter, val - *iter);
+            val++;
+            if (key[0] && val[0])
+                handle_bond_option(s_bond, key, val);
+        }
+    }
+
+    return (NMSetting *) s_bond;
+}
+
+static NMConnection *
+bond_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
+{
+    NMConnection *  connection    = NULL;
+    NMSetting *     con_setting   = NULL;
+    NMSetting *     bond_setting  = NULL;
+    NMSetting *     wired_setting = NULL;
+    NMSetting8021x *s_8021x       = NULL;
+    GError *        local         = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+    g_return_val_if_fail(ifcfg != NULL, NULL);
+
+    connection = nm_simple_connection_new();
+
+    con_setting =
+        make_connection_setting(file, ifcfg, NM_SETTING_BOND_SETTING_NAME, NULL, _("Bond"));
+    if (!con_setting) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create connection setting");
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, con_setting);
+
+    bond_setting = make_bond_setting(ifcfg, file, error);
+    if (!bond_setting) {
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, bond_setting);
+
+    wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local);
+    if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+        g_propagate_error(error, local);
+        g_object_unref(connection);
+        return NULL;
+    }
+    g_clear_error(&local);
+
+    if (wired_setting)
+        nm_connection_add_setting(connection, wired_setting);
+
+    if (s_8021x)
+        nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    return connection;
+}
+
+static NMSetting *
+make_team_setting(shvarFile *ifcfg, const char *file, GError **error)
+{
+    NMSetting *   s_team;
+    gs_free char *value_device = NULL;
+    gs_free char *value        = NULL;
+
+    if (!svGetValueStr(ifcfg, "DEVICE", &value_device)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "mandatory DEVICE keyword missing");
+        return NULL;
+    }
+
+    s_team = nm_setting_team_new();
+    g_object_set(s_team, NM_SETTING_TEAM_CONFIG, svGetValue(ifcfg, "TEAM_CONFIG", &value), NULL);
+    return s_team;
+}
+
+static NMConnection *
+team_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
+{
+    NMConnection *  connection    = NULL;
+    NMSetting *     con_setting   = NULL;
+    NMSetting *     team_setting  = NULL;
+    NMSetting *     wired_setting = NULL;
+    NMSetting8021x *s_8021x       = NULL;
+    GError *        local         = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+    g_return_val_if_fail(ifcfg != NULL, NULL);
+
+    connection = nm_simple_connection_new();
+
+    con_setting =
+        make_connection_setting(file, ifcfg, NM_SETTING_TEAM_SETTING_NAME, NULL, _("Team"));
+    if (!con_setting) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create connection setting");
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, con_setting);
+
+    team_setting = make_team_setting(ifcfg, file, error);
+    if (!team_setting) {
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, team_setting);
+
+    wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local);
+    if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+        g_propagate_error(error, local);
+        g_object_unref(connection);
+        return NULL;
+    }
+    g_clear_error(&local);
+
+    if (wired_setting)
+        nm_connection_add_setting(connection, wired_setting);
+
+    if (s_8021x)
+        nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    return connection;
+}
+
+typedef enum {
+    BRIDGE_OPT_TYPE_MAIN,
+    BRIDGE_OPT_TYPE_OPTION,
+    BRIDGE_OPT_TYPE_PORT_MAIN,
+    BRIDGE_OPT_TYPE_PORT_OPTION,
+} BridgeOptType;
+
+typedef void (*BridgeOptFunc)(NMSetting *   setting,
+                              gboolean      stp,
+                              const char *  key,
+                              const char *  value,
+                              BridgeOptType opt_type);
+
+static void
+handle_bridge_option(NMSetting *   setting,
+                     gboolean      stp,
+                     const char *  key,
+                     const char *  value,
+                     BridgeOptType opt_type)
+{
+    static const struct {
+        const char *  key;
+        const char *  property_name;
+        BridgeOptType opt_type;
+        gboolean      only_with_stp;
+        gboolean      extended_bool;
+    } m /*etadata*/[] = {
+        {"DELAY", NM_SETTING_BRIDGE_FORWARD_DELAY, BRIDGE_OPT_TYPE_MAIN, .only_with_stp = TRUE},
+        {"priority", NM_SETTING_BRIDGE_PRIORITY, BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE},
+        {"hello_time", NM_SETTING_BRIDGE_HELLO_TIME, BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE},
+        {"max_age", NM_SETTING_BRIDGE_MAX_AGE, BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE},
+        {"ageing_time", NM_SETTING_BRIDGE_AGEING_TIME, BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_last_member_count",
+         NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_last_member_interval",
+         NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_membership_interval",
+         NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_hash_max", NM_SETTING_BRIDGE_MULTICAST_HASH_MAX, BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_querier", NM_SETTING_BRIDGE_MULTICAST_QUERIER, BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_querier_interval",
+         NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_query_interval",
+         NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_query_response_interval",
+         NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_query_use_ifaddr",
+         NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_snooping", NM_SETTING_BRIDGE_MULTICAST_SNOOPING, BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_router", NM_SETTING_BRIDGE_MULTICAST_ROUTER, BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_startup_query_count",
+         NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"multicast_startup_query_interval",
+         NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL,
+         BRIDGE_OPT_TYPE_OPTION},
+        {"vlan_filtering", NM_SETTING_BRIDGE_VLAN_FILTERING, BRIDGE_OPT_TYPE_OPTION},
+        {"default_pvid", NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, BRIDGE_OPT_TYPE_OPTION},
+        {"group_address", NM_SETTING_BRIDGE_GROUP_ADDRESS, BRIDGE_OPT_TYPE_OPTION},
+        {"group_fwd_mask", NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, BRIDGE_OPT_TYPE_OPTION},
+        {"vlan_protocol", NM_SETTING_BRIDGE_VLAN_PROTOCOL, BRIDGE_OPT_TYPE_OPTION},
+        {"vlan_stats_enabled", NM_SETTING_BRIDGE_VLAN_STATS_ENABLED, BRIDGE_OPT_TYPE_OPTION},
+        {"priority", NM_SETTING_BRIDGE_PORT_PRIORITY, BRIDGE_OPT_TYPE_PORT_OPTION},
+        {"path_cost", NM_SETTING_BRIDGE_PORT_PATH_COST, BRIDGE_OPT_TYPE_PORT_OPTION},
+        {
+            "hairpin_mode",
+            NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,
+            BRIDGE_OPT_TYPE_PORT_OPTION,
+            .extended_bool = TRUE,
+        },
+    };
+    const char *error_message = NULL;
+    int         i;
+    gint64      v;
+
+    for (i = 0; i < G_N_ELEMENTS(m); i++) {
+        GParamSpec *param_spec;
+
+        if (opt_type != m[i].opt_type)
+            continue;
+        if (!nm_streq(key, m[i].key))
+            continue;
+        if (m[i].only_with_stp && !stp) {
+            PARSE_WARNING("'%s' invalid when STP is disabled", key);
+            return;
+        }
+
+        param_spec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), m[i].property_name);
+        switch (param_spec->value_type) {
+        case G_TYPE_BOOLEAN:
+            if (m[i].extended_bool) {
+                if (!g_ascii_strcasecmp(value, "on") || !g_ascii_strcasecmp(value, "yes")
+                    || !strcmp(value, "1"))
+                    v = TRUE;
+                else if (!g_ascii_strcasecmp(value, "off") || !g_ascii_strcasecmp(value, "no"))
+                    v = FALSE;
+                else {
+                    error_message = "is not a boolean";
+                    goto warn;
+                }
+            } else {
+                v = _nm_utils_ascii_str_to_int64(value, 10, 0, 1, -1);
+                if (v == -1) {
+                    error_message = nm_strerror_native(errno);
+                    goto warn;
+                }
+            }
+            if (!nm_g_object_set_property_boolean(G_OBJECT(setting), m[i].property_name, v, NULL)) {
+                error_message = "number is out of range";
+                goto warn;
+            }
+            return;
+        case G_TYPE_UINT:
+            v = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT, -1);
+            if (v == -1) {
+                error_message = nm_strerror_native(errno);
+                goto warn;
+            }
+            if (!nm_g_object_set_property_uint(G_OBJECT(setting), m[i].property_name, v, NULL)) {
+                error_message = "number is out of range";
+                goto warn;
+            }
+            return;
+        case G_TYPE_UINT64:
+        {
+            guint64 vu64;
+
+            vu64 = _nm_utils_ascii_str_to_uint64(value, 10, 0, G_MAXUINT64, 0);
+            if (!nm_g_object_set_property_uint64(G_OBJECT(setting),
+                                                 m[i].property_name,
+                                                 vu64,
+                                                 NULL)) {
+                error_message = "number is out of range";
+                goto warn;
+            }
+        }
+            return;
+        case G_TYPE_STRING:
+            nm_g_object_set_property_string(G_OBJECT(setting), m[i].property_name, value, NULL);
+            return;
+        default:
+            nm_assert_not_reached();
+            continue;
+        }
+
+warn:
+        PARSE_WARNING("invalid %s value '%s': %s", key, value, error_message);
+        return;
+    }
+
+    PARSE_WARNING("unhandled bridge option '%s'", key);
+}
+
+static void
+handle_bridging_opts(NMSetting *   setting,
+                     gboolean      stp,
+                     const char *  value,
+                     BridgeOptFunc func,
+                     BridgeOptType opt_type)
+{
+    gs_free const char **items = NULL;
+    const char *const *  iter;
+
+    items = nm_utils_strsplit_set(value, " ");
+    for (iter = items; iter && *iter; iter++) {
+        gs_free char *key = NULL;
+        const char *  val;
+
+        val = strchr(*iter, '=');
+        if (!val)
+            continue;
+        key = g_strndup(*iter, val - *iter);
+        val++;
+        if (key[0] && val[0])
+            func(setting, stp, key, val, opt_type);
+    }
+}
+
+static void
+read_bridge_vlans(shvarFile *ifcfg, const char *key, NMSetting *setting, const char *property)
+{
+    gs_unref_ptrarray GPtrArray *array         = NULL;
+    gs_free char *               value_to_free = NULL;
+    const char *                 value;
+
+    value = svGetValueStr(ifcfg, key, &value_to_free);
+    if (value) {
+        gs_free const char **strv = NULL;
+        const char *const *  iter;
+        GError *             local = NULL;
+        NMBridgeVlan *       vlan;
+
+        array = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref);
+
+        strv = nm_utils_escaped_tokens_split(value, ",");
+        if (strv) {
+            for (iter = strv; *iter; iter++) {
+                vlan = nm_bridge_vlan_from_str(*iter, &local);
+                if (!vlan) {
+                    PARSE_WARNING("invalid bridge VLAN: %s", local->message);
+                    g_clear_error(&local);
+                    continue;
+                }
+                g_ptr_array_add(array, vlan);
+            }
+        }
+        nm_clear_g_free(&value_to_free);
+    }
+
+    g_object_set(setting, property, array, NULL);
+}
+
+static NMSetting *
+make_bridge_setting(shvarFile *ifcfg, const char *file, GError **error)
+{
+    gs_unref_object NMSettingBridge *s_bridge      = NULL;
+    gs_free char *                   value_to_free = NULL;
+    const char *                     value;
+    gboolean                         stp     = FALSE;
+    gboolean                         stp_set = FALSE;
+
+    value = svGetValueStr(ifcfg, "DEVICE", &value_to_free);
+    if (!value) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "mandatory DEVICE keyword missing");
+        return NULL;
+    }
+    nm_clear_g_free(&value_to_free);
+
+    s_bridge = NM_SETTING_BRIDGE(nm_setting_bridge_new());
+
+    value = svGetValueStr(ifcfg, "BRIDGE_MACADDR", &value_to_free);
+    if (value) {
+        g_object_set(s_bridge, NM_SETTING_BRIDGE_MAC_ADDRESS, value, NULL);
+        nm_clear_g_free(&value_to_free);
+    }
+
+    value = svGetValueStr(ifcfg, "STP", &value_to_free);
+    if (value) {
+        if (!g_ascii_strcasecmp(value, "on") || !g_ascii_strcasecmp(value, "yes")) {
+            g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, TRUE, NULL);
+            stp     = TRUE;
+            stp_set = TRUE;
+        } else if (!g_ascii_strcasecmp(value, "off") || !g_ascii_strcasecmp(value, "no")) {
+            g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, FALSE, NULL);
+            stp_set = TRUE;
+        } else
+            PARSE_WARNING("invalid STP value '%s'", value);
+        nm_clear_g_free(&value_to_free);
+    }
+
+    if (!stp_set) {
+        /* Missing or invalid STP property means "no" */
+        g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, FALSE, NULL);
+    }
+
+    value = svGetValueStr(ifcfg, "DELAY", &value_to_free);
+    if (value) {
+        handle_bridge_option(NM_SETTING(s_bridge), stp, "DELAY", value, BRIDGE_OPT_TYPE_MAIN);
+        nm_clear_g_free(&value_to_free);
+    }
+
+    value = svGetValueStr(ifcfg, "BRIDGING_OPTS", &value_to_free);
+    if (value) {
+        handle_bridging_opts(NM_SETTING(s_bridge),
+                             stp,
+                             value,
+                             handle_bridge_option,
+                             BRIDGE_OPT_TYPE_OPTION);
+        nm_clear_g_free(&value_to_free);
+    }
+
+    read_bridge_vlans(ifcfg, "BRIDGE_VLANS", NM_SETTING(s_bridge), NM_SETTING_BRIDGE_VLANS);
+
+    return (NMSetting *) g_steal_pointer(&s_bridge);
+}
+
+static NMConnection *
+bridge_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
+{
+    NMConnection *  connection     = NULL;
+    NMSetting *     con_setting    = NULL;
+    NMSetting *     bridge_setting = NULL;
+    NMSetting *     wired_setting  = NULL;
+    NMSetting8021x *s_8021x        = NULL;
+    GError *        local          = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+    g_return_val_if_fail(ifcfg != NULL, NULL);
+
+    connection = nm_simple_connection_new();
+
+    con_setting =
+        make_connection_setting(file, ifcfg, NM_SETTING_BRIDGE_SETTING_NAME, NULL, _("Bridge"));
+    if (!con_setting) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create connection setting");
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, con_setting);
+
+    bridge_setting = make_bridge_setting(ifcfg, file, error);
+    if (!bridge_setting) {
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, bridge_setting);
+
+    wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local);
+    if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+        g_propagate_error(error, local);
+        g_object_unref(connection);
+        return NULL;
+    }
+    g_clear_error(&local);
+
+    if (wired_setting)
+        nm_connection_add_setting(connection, wired_setting);
+
+    if (s_8021x)
+        nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    return connection;
+}
+
+static NMSetting *
+make_bridge_port_setting(shvarFile *ifcfg)
+{
+    NMSetting *   s_port        = NULL;
+    gs_free char *value_to_free = NULL;
+    const char *  value;
+
+    g_return_val_if_fail(ifcfg != NULL, FALSE);
+
+    value = svGetValueStr(ifcfg, "BRIDGE_UUID", &value_to_free);
+    if (!value)
+        value = svGetValueStr(ifcfg, "BRIDGE", &value_to_free);
+    if (value) {
+        nm_clear_g_free(&value_to_free);
+
+        s_port = nm_setting_bridge_port_new();
+        value  = svGetValueStr(ifcfg, "BRIDGING_OPTS", &value_to_free);
+        if (value) {
+            handle_bridging_opts(s_port,
+                                 FALSE,
+                                 value,
+                                 handle_bridge_option,
+                                 BRIDGE_OPT_TYPE_PORT_OPTION);
+            nm_clear_g_free(&value_to_free);
+        }
+
+        read_bridge_vlans(ifcfg, "BRIDGE_PORT_VLANS", s_port, NM_SETTING_BRIDGE_PORT_VLANS);
+    }
+
+    return s_port;
+}
+
+static NMSetting *
+make_team_port_setting(shvarFile *ifcfg)
+{
+    NMSetting *   s_port;
+    gs_free char *value = NULL;
+
+    value = svGetValueStr_cp(ifcfg, "TEAM_PORT_CONFIG");
+    if (!value)
+        return NULL;
+
+    s_port = nm_setting_team_port_new();
+    g_object_set(s_port, NM_SETTING_TEAM_PORT_CONFIG, value, NULL);
+    return s_port;
+}
+
+static gboolean
+is_bond_device(const char *name, shvarFile *parsed)
+{
+    g_return_val_if_fail(name != NULL, FALSE);
+    g_return_val_if_fail(parsed != NULL, FALSE);
+
+    if (svGetValueBoolean(parsed, "BONDING_MASTER", FALSE))
+        return TRUE;
+
+    return FALSE;
+}
+
+static gboolean
+is_vlan_device(const char *name, shvarFile *parsed)
+{
+    g_return_val_if_fail(name != NULL, FALSE);
+    g_return_val_if_fail(parsed != NULL, FALSE);
+
+    if (svGetValueBoolean(parsed, "VLAN", FALSE))
+        return TRUE;
+
+    return FALSE;
+}
+
+static gboolean
+is_wifi_device(const char *name, shvarFile *parsed)
+{
+    const NMPlatformLink *pllink;
+
+    g_return_val_if_fail(name != NULL, FALSE);
+    g_return_val_if_fail(parsed != NULL, FALSE);
+
+    pllink = nm_platform_link_get_by_ifname(NM_PLATFORM_GET, name);
+    return pllink && pllink->type == NM_LINK_TYPE_WIFI;
+}
+
+static void
+parse_prio_map_list(NMSettingVlan *s_vlan, shvarFile *ifcfg, const char *key, NMVlanPriorityMap map)
+{
+    gs_free char *       value = NULL;
+    gs_free const char **list  = NULL;
+    const char *const *  iter;
+    const char *         v;
+
+    v = svGetValueStr(ifcfg, key, &value);
+    if (!v)
+        return;
+    list = nm_utils_strsplit_set(v, ",");
+
+    for (iter = list; iter && *iter; iter++) {
+        if (!strchr(*iter, ':'))
+            continue;
+        if (!nm_setting_vlan_add_priority_str(s_vlan, map, *iter))
+            PARSE_WARNING("invalid %s priority map item '%s'", key, *iter);
+    }
+}
+
+static NMSetting *
+make_vlan_setting(shvarFile *ifcfg, const char *file, GError **error)
+{
+    gs_unref_object NMSettingVlan *s_vlan     = NULL;
+    gs_free char *                 parent     = NULL;
+    gs_free char *                 iface_name = NULL;
+    gs_free char *                 value      = NULL;
+    const char *                   v          = NULL;
+    int                            vlan_id    = -1;
+    guint32                        vlan_flags = 0;
+    int                            gvrp, reorder_hdr;
+
+    v = svGetValueStr(ifcfg, "VLAN_ID", &value);
+    if (v) {
+        vlan_id = _nm_utils_ascii_str_to_int64(v, 10, 0, 4095, -1);
+        if (vlan_id == -1) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Invalid VLAN_ID '%s'",
+                        v);
+            return NULL;
+        }
+    }
+
+    /* Need DEVICE if we don't have a separate VLAN_ID property */
+    iface_name = svGetValueStr_cp(ifcfg, "DEVICE");
+    if (!iface_name && vlan_id < 0) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Missing DEVICE property; cannot determine VLAN ID");
+        return NULL;
+    }
+
+    s_vlan = NM_SETTING_VLAN(nm_setting_vlan_new());
+
+    /* Parent interface from PHYSDEV takes precedence if it exists */
+    parent = svGetValueStr_cp(ifcfg, "PHYSDEV");
+
+    if (iface_name) {
+        v = strchr(iface_name, '.');
+        if (v) {
+            /* eth0.43; PHYSDEV is assumed from it if unknown */
+            if (!parent) {
+                parent = g_strndup(iface_name, v - iface_name);
+                if (g_str_has_prefix(parent, "vlan")) {
+                    /* Like initscripts, if no PHYSDEV and we get an obviously
+                     * invalid parent interface from DEVICE, fail.
+                     */
+                    nm_clear_g_free(&parent);
+                }
+            }
+            v++;
+        } else {
+            /* format like vlan43; PHYSDEV must be set */
+            if (g_str_has_prefix(iface_name, "vlan"))
+                v = iface_name + 4;
+        }
+
+        if (v) {
+            int device_vlan_id;
+
+            /* Grab VLAN ID from interface name; this takes precedence over the
+             * separate VLAN_ID property for backwards compat.
+             */
+            device_vlan_id = _nm_utils_ascii_str_to_int64(v, 10, 0, 4095, -1);
+            if (device_vlan_id != -1)
+                vlan_id = device_vlan_id;
+        }
+    }
+
+    if (vlan_id < 0) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Failed to determine VLAN ID from DEVICE or VLAN_ID");
+        return NULL;
+    }
+    g_object_set(s_vlan, NM_SETTING_VLAN_ID, vlan_id, NULL);
+
+    if (parent == NULL) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Failed to determine VLAN parent from DEVICE or PHYSDEV");
+        return NULL;
+    }
+    g_object_set(s_vlan, NM_SETTING_VLAN_PARENT, parent, NULL);
+
+    vlan_flags |= NM_VLAN_FLAG_REORDER_HEADERS;
+
+    gvrp = svGetValueBoolean(ifcfg, "GVRP", -1);
+    if (gvrp > 0)
+        vlan_flags |= NM_VLAN_FLAG_GVRP;
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "VLAN_FLAGS", &value);
+    if (v) {
+        gs_free const char **strv = NULL;
+        const char *const *  ptr;
+
+        strv = nm_utils_strsplit_set(v, ", ");
+        for (ptr = strv; ptr && *ptr; ptr++) {
+            if (nm_streq(*ptr, "GVRP") && gvrp == -1)
+                vlan_flags |= NM_VLAN_FLAG_GVRP;
+            if (nm_streq(*ptr, "LOOSE_BINDING"))
+                vlan_flags |= NM_VLAN_FLAG_LOOSE_BINDING;
+            if (nm_streq(*ptr, "NO_REORDER_HDR"))
+                vlan_flags &= ~NM_VLAN_FLAG_REORDER_HEADERS;
+        }
+    }
+
+    reorder_hdr = svGetValueBoolean(ifcfg, "REORDER_HDR", -1);
+    if (reorder_hdr != -1 && reorder_hdr != NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS))
+        PARSE_WARNING("REORDER_HDR key is deprecated, use VLAN_FLAGS");
+
+    if (svGetValueBoolean(ifcfg, "MVRP", FALSE))
+        vlan_flags |= NM_VLAN_FLAG_MVRP;
+
+    g_object_set(s_vlan, NM_SETTING_VLAN_FLAGS, vlan_flags, NULL);
+
+    parse_prio_map_list(s_vlan, ifcfg, "VLAN_INGRESS_PRIORITY_MAP", NM_VLAN_INGRESS_MAP);
+    parse_prio_map_list(s_vlan, ifcfg, "VLAN_EGRESS_PRIORITY_MAP", NM_VLAN_EGRESS_MAP);
+
+    return NM_SETTING(g_steal_pointer(&s_vlan));
+}
+
+static NMConnection *
+vlan_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
+{
+    NMConnection *  connection    = NULL;
+    NMSetting *     con_setting   = NULL;
+    NMSetting *     wired_setting = NULL;
+    NMSetting *     vlan_setting  = NULL;
+    NMSetting8021x *s_8021x       = NULL;
+    GError *        local         = NULL;
+
+    g_return_val_if_fail(file != NULL, NULL);
+    g_return_val_if_fail(ifcfg != NULL, NULL);
+
+    connection = nm_simple_connection_new();
+
+    con_setting = make_connection_setting(file, ifcfg, NM_SETTING_VLAN_SETTING_NAME, NULL, "Vlan");
+    if (!con_setting) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Failed to create connection setting");
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, con_setting);
+
+    vlan_setting = make_vlan_setting(ifcfg, file, error);
+    if (!vlan_setting) {
+        g_object_unref(connection);
+        return NULL;
+    }
+    nm_connection_add_setting(connection, vlan_setting);
+
+    wired_setting = make_wired_setting(ifcfg, file, &s_8021x, &local);
+    if (local && !g_error_matches(local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+        g_propagate_error(error, local);
+        g_object_unref(connection);
+        return NULL;
+    }
+    g_clear_error(&local);
+
+    if (wired_setting)
+        nm_connection_add_setting(connection, wired_setting);
+
+    if (s_8021x)
+        nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    return connection;
+}
+
+static NMConnection *
+create_unhandled_connection(const char *filename,
+                            shvarFile * ifcfg,
+                            const char *type,
+                            char **     out_spec)
+{
+    NMConnection *connection;
+    NMSetting *   s_con;
+    gs_free char *value = NULL;
+    const char *  v;
+
+    nm_assert(out_spec && !*out_spec);
+
+    connection = nm_simple_connection_new();
+
+    /* Get NAME, UUID, etc. We need to set a connection type (generic) and add
+     * an empty type-specific setting as well, to make sure it passes
+     * nm_connection_verify() later.
+     */
+    s_con = make_connection_setting(filename, ifcfg, NM_SETTING_GENERIC_SETTING_NAME, NULL, NULL);
+    nm_connection_add_setting(connection, s_con);
+
+    nm_connection_add_setting(connection, nm_setting_generic_new());
+
+    /* Get a spec */
+    v = svGetValueStr(ifcfg, "HWADDR", &value);
+    if (v) {
+        gs_free char *lower = g_ascii_strdown(v, -1);
+
+        *out_spec = g_strdup_printf("%s:" NM_MATCH_SPEC_MAC_TAG "%s", type, lower);
+        return connection;
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "SUBCHANNELS", &value);
+    if (v) {
+        *out_spec = g_strdup_printf("%s:" NM_MATCH_SPEC_S390_SUBCHANNELS_TAG "%s", type, v);
+        return connection;
+    }
+
+    nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DEVICE", &value);
+    if (v) {
+        *out_spec = g_strdup_printf("%s:" NM_MATCH_SPEC_INTERFACE_NAME_TAG "=%s", type, v);
+        return connection;
+    }
+
+    g_object_unref(connection);
+    return NULL;
+}
+
+static void
+check_dns_search_domains(shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6)
+{
+    if (!s_ip6)
+        return;
+
+    /* If there is no IPv4 config or it doesn't contain DNS searches,
+     * read DOMAIN and put the domains into IPv6.
+     */
+    if (!s_ip4 || nm_setting_ip_config_get_num_dns_searches(NM_SETTING_IP_CONFIG(s_ip4)) == 0) {
+        /* DNS searches */
+        gs_free char *value = NULL;
+        const char *  v;
+
+        v = svGetValueStr(ifcfg, "DOMAIN", &value);
+        if (v) {
+            gs_free const char **searches = NULL;
+            const char *const *  item;
+
+            searches = nm_utils_strsplit_set(v, " ");
+            if (searches) {
+                for (item = searches; *item; item++) {
+                    if (!nm_setting_ip_config_add_dns_search(NM_SETTING_IP_CONFIG(s_ip6), *item))
+                        PARSE_WARNING("duplicate DNS domain '%s'", *item);
+                }
+            }
+        }
+    }
+}
+
+static NMConnection *
+connection_from_file_full(const char *filename,
+                          const char *network_file, /* for unit tests only */
+                          const char *test_type,    /* for unit tests only */
+                          char **     out_unhandled,
+                          GError **   error,
+                          gboolean *  out_ignore_error)
+{
+    nm_auto_shvar_file_close shvarFile *main_ifcfg    = NULL;
+    nm_auto_shvar_file_close shvarFile *network_ifcfg = NULL;
+    gs_unref_object NMConnection *connection          = NULL;
+    gs_free char *                type                = NULL;
+    char *                        devtype, *bootproto;
+    NMSetting *                   setting;
+    NMSetting *                   s_ip4;
+    NMSetting *                   s_ip6;
+    const char *                  ifcfg_name       = NULL;
+    gboolean                      has_ip4_defroute = FALSE;
+    gboolean                      has_complex_routes_v4;
+    gboolean                      has_complex_routes_v6;
+
+    g_return_val_if_fail(filename != NULL, NULL);
+    g_return_val_if_fail(out_unhandled && !*out_unhandled, NULL);
+
+    NM_SET_OUT(out_ignore_error, FALSE);
+
+    /* Non-NULL only for unit tests; normally use /etc/sysconfig/network */
+    if (!network_file)
+        network_file = SYSCONFDIR "/sysconfig/network";
+
+    ifcfg_name = utils_get_ifcfg_name(filename, TRUE);
+    if (!ifcfg_name) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Ignoring connection '%s' because it's not an ifcfg file",
+                    filename);
+        return NULL;
+    }
+
+    main_ifcfg = svOpenFile(filename, error);
+    if (!main_ifcfg)
+        return NULL;
+
+    network_ifcfg = svOpenFile(network_file, NULL);
+
+    if (!svGetValueBoolean(main_ifcfg, "NM_CONTROLLED", TRUE)) {
+        connection = create_unhandled_connection(filename, main_ifcfg, "unmanaged", out_unhandled);
+        if (!connection) {
+            NM_SET_OUT(out_ignore_error, TRUE);
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "NM_CONTROLLED was false but device was not uniquely identified; device "
+                        "will be managed");
+        }
+        return g_steal_pointer(&connection);
+    }
+
+    /* iBFT is handled by nm-initrd-generator during boot. */
+    bootproto = svGetValueStr_cp(main_ifcfg, "BOOTPROTO");
+    if (bootproto && !g_ascii_strcasecmp(bootproto, "ibft")) {
+        NM_SET_OUT(out_ignore_error, TRUE);
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "Ignoring iBFT configuration");
+        g_free(bootproto);
+        return NULL;
+    }
+    g_free(bootproto);
+
+    devtype = svGetValueStr_cp(main_ifcfg, "DEVICETYPE");
+    if (devtype) {
+        if (!g_ascii_strcasecmp(devtype, TYPE_TEAM))
+            type = g_strdup(TYPE_TEAM);
+        else if (!g_ascii_strcasecmp(devtype, TYPE_TEAM_PORT)) {
+            gs_free char *device = NULL;
+
+            type   = svGetValueStr_cp(main_ifcfg, "TYPE");
+            device = svGetValueStr_cp(main_ifcfg, "DEVICE");
+
+            if (type) {
+                /* nothing to do */
+            } else if (device && is_vlan_device(device, main_ifcfg))
+                type = g_strdup(TYPE_VLAN);
+            else
+                type = g_strdup(TYPE_ETHERNET);
+        }
+        g_free(devtype);
+    }
+    if (!type) {
+        gs_free char *t = NULL;
+
+        /* Team and TeamPort types are also accepted by the mere
+         * presence of TEAM_CONFIG/TEAM_MASTER. They don't require
+         * DEVICETYPE. */
+        t = svGetValueStr_cp(main_ifcfg, "TEAM_CONFIG");
+        if (t)
+            type = g_strdup(TYPE_TEAM);
+    }
+
+    if (!type)
+        type = svGetValueStr_cp(main_ifcfg, "TYPE");
+
+    if (!type) {
+        gs_free char *tmp = NULL;
+        char *        device;
+
+        if ((tmp = svGetValueStr_cp(main_ifcfg, "IPV6TUNNELIPV4"))) {
+            NM_SET_OUT(out_ignore_error, TRUE);
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Ignoring unsupported connection due to IPV6TUNNELIPV4");
+            return NULL;
+        }
+
+        device = svGetValueStr_cp(main_ifcfg, "DEVICE");
+        if (!device) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "File '%s' had neither TYPE nor DEVICE keys",
+                        filename);
+            return NULL;
+        }
+
+        if (!strcmp(device, "lo")) {
+            NM_SET_OUT(out_ignore_error, TRUE);
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Ignoring loopback device config");
+            g_free(device);
+            return NULL;
+        }
+
+        if (!test_type) {
+            if (is_bond_device(device, main_ifcfg))
+                type = g_strdup(TYPE_BOND);
+            else if (is_vlan_device(device, main_ifcfg))
+                type = g_strdup(TYPE_VLAN);
+            else if (is_wifi_device(device, main_ifcfg))
+                type = g_strdup(TYPE_WIRELESS);
+            else {
+                gs_free char *p_path = NULL;
+                char *        p_device;
+                gsize         i;
+
+                /* network-functions detects DEVICETYPE based on the ifcfg-* name and the existence
+                 * of a ifup script:
+                 *    [ -z "$DEVICETYPE" ] && DEVICETYPE=$(echo ${DEVICE} | sed "s/[0-9]*$//")
+                 * later...
+                 *    OTHERSCRIPT="/etc/sysconfig/network-scripts/ifup-${DEVICETYPE}"
+                 * */
+#define IFUP_PATH_PREFIX "/etc/sysconfig/network-scripts/ifup-"
+                i        = strlen(device);
+                p_path   = g_malloc(NM_STRLEN(IFUP_PATH_PREFIX) + i + 1);
+                p_device = &p_path[NM_STRLEN(IFUP_PATH_PREFIX)];
+                memcpy(p_device, device, i + 1);
+
+                /* strip trailing numbers */
+                while (i >= 1) {
+                    i--;
+                    if (p_device[i] < '0' || p_device[i] > '9')
+                        break;
+                    p_device[i] = '\0';
+                }
+
+                if (nm_streq(p_device, "eth"))
+                    type = g_strdup(TYPE_ETHERNET);
+                else if (nm_streq(p_device, "wireless"))
+                    type = g_strdup(TYPE_WIRELESS);
+                else if (p_device[0]) {
+                    memcpy(p_path, IFUP_PATH_PREFIX, NM_STRLEN(IFUP_PATH_PREFIX));
+                    if (access(p_path, X_OK) == 0) {
+                        /* for all other types, this is not something we want to handle. */
+                        NM_SET_OUT(out_ignore_error, TRUE);
+                        g_set_error(
+                            error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Ignore script for unknown device type which has a matching %s script",
+                            p_path);
+                        return NULL;
+                    }
+                }
+
+                if (!type)
+                    type = g_strdup(TYPE_ETHERNET);
+            }
+        } else {
+            /* For the unit tests, there won't necessarily be any
+             * adapters of the connection's type in the system so the
+             * type can't be tested with ioctls.
+             */
+            type = g_strdup(test_type);
+        }
+
+        g_free(device);
+    } else {
+        /* Check for IBM s390 CTC devices and call them Ethernet */
+        if (g_strcmp0(type, "CTC") == 0) {
+            g_free(type);
+            type = g_strdup(TYPE_ETHERNET);
+        }
+    }
+
+    if (nm_streq0(type, TYPE_ETHERNET)) {
+        gs_free char *bond_options = NULL;
+
+        if (svGetValueStr(main_ifcfg, "BONDING_OPTS", &bond_options)) {
+            /* initscripts consider these as bond masters */
+            g_free(type);
+            type = g_strdup(TYPE_BOND);
+        }
+    }
+
+    if (svGetValueBoolean(main_ifcfg, "BONDING_MASTER", FALSE)
+        && g_ascii_strcasecmp(type, TYPE_BOND)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "BONDING_MASTER=yes key only allowed in TYPE=bond connections");
+        return NULL;
+    }
+
+    /* Construct the connection */
+    if (!g_ascii_strcasecmp(type, TYPE_ETHERNET))
+        connection = wired_connection_from_ifcfg(filename, main_ifcfg, error);
+    else if (!g_ascii_strcasecmp(type, TYPE_WIRELESS))
+        connection = wireless_connection_from_ifcfg(filename, main_ifcfg, error);
+    else if (!g_ascii_strcasecmp(type, TYPE_INFINIBAND))
+        connection = infiniband_connection_from_ifcfg(filename, main_ifcfg, error);
+    else if (!g_ascii_strcasecmp(type, TYPE_BOND))
+        connection = bond_connection_from_ifcfg(filename, main_ifcfg, error);
+    else if (!g_ascii_strcasecmp(type, TYPE_TEAM))
+        connection = team_connection_from_ifcfg(filename, main_ifcfg, error);
+    else if (!g_ascii_strcasecmp(type, TYPE_VLAN))
+        connection = vlan_connection_from_ifcfg(filename, main_ifcfg, error);
+    else if (!g_ascii_strcasecmp(type, TYPE_BRIDGE))
+        connection = bridge_connection_from_ifcfg(filename, main_ifcfg, error);
+    else {
+        connection =
+            create_unhandled_connection(filename, main_ifcfg, "unrecognized", out_unhandled);
+        if (!connection) {
+            PARSE_WARNING("connection type was unrecognized but device was not uniquely "
+                          "identified; device may be managed");
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Failed to read unrecognized connection");
+        }
+        return g_steal_pointer(&connection);
+    }
+
+    if (!connection)
+        return NULL;
+
+    parse_ethtool_options(main_ifcfg, connection);
+
+    has_complex_routes_v4 = utils_has_complex_routes(filename, AF_INET);
+    has_complex_routes_v6 = utils_has_complex_routes(filename, AF_INET6);
+
+    if (has_complex_routes_v4 || has_complex_routes_v6) {
+        if (has_complex_routes_v4 && !has_complex_routes_v6)
+            PARSE_WARNING("'rule-' file is present; you will need to use a dispatcher script to "
+                          "apply these routes");
+        else if (has_complex_routes_v6 && !has_complex_routes_v4)
+            PARSE_WARNING("'rule6-' file is present; you will need to use a dispatcher script to "
+                          "apply these routes");
+        else
+            PARSE_WARNING("'rule-' and 'rule6-' files are present; you will need to use a "
+                          "dispatcher script to apply these routes");
+    }
+
+    s_ip6 = make_ip6_setting(main_ifcfg,
+                             network_ifcfg,
+                             !has_complex_routes_v4 && !has_complex_routes_v6,
+                             error);
+    if (!s_ip6)
+        return NULL;
+    nm_connection_add_setting(connection, s_ip6);
+
+    s_ip4 = make_ip4_setting(main_ifcfg,
+                             network_ifcfg,
+                             !has_complex_routes_v4 && !has_complex_routes_v6,
+                             &has_ip4_defroute,
+                             error);
+    if (!s_ip4)
+        return NULL;
+    read_aliases(NM_SETTING_IP_CONFIG(s_ip4),
+                 !has_ip4_defroute
+                     && !nm_setting_ip_config_get_gateway(NM_SETTING_IP_CONFIG(s_ip4)),
+                 filename);
+    nm_connection_add_setting(connection, s_ip4);
+
+    read_routing_rules(main_ifcfg,
+                       !has_complex_routes_v4 && !has_complex_routes_v6,
+                       NM_SETTING_IP_CONFIG(s_ip4),
+                       NM_SETTING_IP_CONFIG(s_ip6));
+
+    setting = make_sriov_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    setting = make_tc_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    /* For backwards compatibility, if IPv4 is disabled or the
+     * config fails for some reason, we read DOMAIN and put the
+     * values into IPv6 config instead of IPv4.
+     */
+    check_dns_search_domains(main_ifcfg, s_ip4, s_ip6);
+
+    setting = make_proxy_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    setting = make_hostname_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    setting = make_user_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    setting = make_match_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    setting = make_bridge_port_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    setting = make_team_port_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    if (!make_dcb_setting(main_ifcfg, &setting, error))
+        return NULL;
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    if (!nm_connection_normalize(connection, NULL, NULL, error))
+        return NULL;
+
+    return g_steal_pointer(&connection);
+}
+
+NMConnection *
+connection_from_file(const char *filename,
+                     char **     out_unhandled,
+                     GError **   error,
+                     gboolean *  out_ignore_error)
+{
+    return connection_from_file_full(filename, NULL, NULL, out_unhandled, error, out_ignore_error);
+}
+
+NMConnection *
+nmtst_connection_from_file(const char *filename,
+                           const char *network_file,
+                           const char *test_type,
+                           char **     out_unhandled,
+                           GError **   error)
+{
+    return connection_from_file_full(filename, network_file, test_type, out_unhandled, error, NULL);
+}
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
new file mode 100644
index 00000000..c7dc2240
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef __NMS_IFCFG_RH_READER_H__
+#define __NMS_IFCFG_RH_READER_H__
+
+#include "nm-connection.h"
+
+NMConnection *connection_from_file(const char *filename,
+                                   char **     out_unhandled,
+                                   GError **   error,
+                                   gboolean *  out_ignore_error);
+
+NMConnection *nmtst_connection_from_file(const char *filename,
+                                         const char *network_file,
+                                         const char *test_type,
+                                         char **     out_unhandled,
+                                         GError **   error);
+
+#endif /* __NMS_IFCFG_RH_READER_H__ */
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
new file mode 100644
index 00000000..3f674dea
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifcfg-rh-storage.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-connection.h"
+#include "nms-ifcfg-rh-plugin.h"
+
+/*****************************************************************************/
+
+struct _NMSIfcfgRHStorageClass {
+    NMSettingsStorageClass parent;
+};
+
+G_DEFINE_TYPE(NMSIfcfgRHStorage, nms_ifcfg_rh_storage, NM_TYPE_SETTINGS_STORAGE)
+
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_storage_equal_type(const NMSIfcfgRHStorage *self_a, const NMSIfcfgRHStorage *self_b)
+{
+    return (self_a == self_b)
+           || (self_a && self_b
+               && nm_streq0(nms_ifcfg_rh_storage_get_uuid_opt(self_a),
+                            nms_ifcfg_rh_storage_get_uuid_opt(self_b))
+               && nm_streq0(self_a->unmanaged_spec, self_b->unmanaged_spec)
+               && nm_streq0(self_a->unrecognized_spec, self_b->unrecognized_spec));
+}
+
+void
+nms_ifcfg_rh_storage_copy_content(NMSIfcfgRHStorage *dst, const NMSIfcfgRHStorage *src)
+{
+    nm_assert(src != dst);
+    nm_assert(src && dst);
+    nm_assert(nms_ifcfg_rh_storage_equal_type(dst, src));
+    nm_assert(nms_ifcfg_rh_storage_get_filename(dst)
+              && nm_streq(nms_ifcfg_rh_storage_get_filename(dst),
+                          nms_ifcfg_rh_storage_get_filename(src)));
+
+    nm_g_object_ref_set(&dst->connection, src->connection);
+    g_free(dst->unmanaged_spec);
+    g_free(dst->unrecognized_spec);
+    dst->unmanaged_spec    = g_strdup(src->unmanaged_spec);
+    dst->unrecognized_spec = g_strdup(src->unrecognized_spec);
+    dst->stat_mtime        = src->stat_mtime;
+}
+
+NMConnection *
+nms_ifcfg_rh_storage_steal_connection(NMSIfcfgRHStorage *self)
+{
+    nm_assert(NMS_IS_IFCFG_RH_STORAGE(self));
+
+    return g_steal_pointer(&self->connection);
+}
+
+/*****************************************************************************/
+
+static int
+cmp_fcn(const NMSIfcfgRHStorage *a, const NMSIfcfgRHStorage *b)
+{
+    nm_assert(NMS_IS_IFCFG_RH_STORAGE(a));
+    nm_assert(NMS_IS_IFCFG_RH_STORAGE(b));
+    nm_assert(a != b);
+
+    /* newer files are more important. */
+    NM_CMP_FIELD(a, b, stat_mtime.tv_sec);
+    NM_CMP_FIELD(a, b, stat_mtime.tv_nsec);
+
+    NM_CMP_DIRECT_STRCMP(nms_ifcfg_rh_storage_get_filename(a),
+                         nms_ifcfg_rh_storage_get_filename(b));
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+static void
+nms_ifcfg_rh_storage_init(NMSIfcfgRHStorage *self)
+{}
+
+static NMSIfcfgRHStorage *
+_storage_new(NMSIfcfgRHPlugin *plugin, const char *uuid, const char *filename)
+{
+    nm_assert(NMS_IS_IFCFG_RH_PLUGIN(plugin));
+    nm_assert(!uuid || nm_utils_is_uuid(uuid));
+    nm_assert(filename && filename[0] == '/');
+
+    return g_object_new(NMS_TYPE_IFCFG_RH_STORAGE,
+                        NM_SETTINGS_STORAGE_PLUGIN,
+                        plugin,
+                        NM_SETTINGS_STORAGE_UUID,
+                        uuid,
+                        NM_SETTINGS_STORAGE_FILENAME,
+                        filename,
+                        NULL);
+}
+
+NMSIfcfgRHStorage *
+nms_ifcfg_rh_storage_new_connection(NMSIfcfgRHPlugin *     plugin,
+                                    const char *           filename,
+                                    NMConnection *         connection_take,
+                                    const struct timespec *mtime)
+{
+    NMSIfcfgRHStorage *self;
+
+    nm_assert(NM_IS_CONNECTION(connection_take));
+    nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nmtst_connection_assert_unchanging(connection_take);
+
+    self             = _storage_new(plugin, nm_connection_get_uuid(connection_take), filename);
+    self->connection = connection_take;
+    if (mtime)
+        self->stat_mtime = *mtime;
+    return self;
+}
+
+NMSIfcfgRHStorage *
+nms_ifcfg_rh_storage_new_unhandled(NMSIfcfgRHPlugin *plugin,
+                                   const char *      filename,
+                                   const char *      unmanaged_spec,
+                                   const char *      unrecognized_spec)
+{
+    NMSIfcfgRHStorage *self;
+
+    nm_assert(unmanaged_spec || unrecognized_spec);
+
+    self                    = _storage_new(plugin, NULL, filename);
+    self->unmanaged_spec    = g_strdup(unmanaged_spec);
+    self->unrecognized_spec = g_strdup(unrecognized_spec);
+    return self;
+}
+
+static void
+_storage_clear(NMSIfcfgRHStorage *self)
+{
+    c_list_unlink(&self->parent._storage_lst);
+    c_list_unlink(&self->parent._storage_by_uuid_lst);
+    nm_clear_g_free(&self->unmanaged_spec);
+    nm_clear_g_free(&self->unrecognized_spec);
+    g_clear_object(&self->connection);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSIfcfgRHStorage *self = NMS_IFCFG_RH_STORAGE(object);
+
+    _storage_clear(self);
+
+    G_OBJECT_CLASS(nms_ifcfg_rh_storage_parent_class)->dispose(object);
+}
+
+void
+nms_ifcfg_rh_storage_destroy(NMSIfcfgRHStorage *self)
+{
+    _storage_clear(self);
+    g_object_unref(self);
+}
+
+static void
+nms_ifcfg_rh_storage_class_init(NMSIfcfgRHStorageClass *klass)
+{
+    GObjectClass *          object_class  = G_OBJECT_CLASS(klass);
+    NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS(klass);
+
+    object_class->dispose = dispose;
+
+    storage_class->cmp_fcn = (int (*)(NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
+}
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
new file mode 100644
index 00000000..6fb9838f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef __NMS_IFCFG_RH_STORAGE_H__
+#define __NMS_IFCFG_RH_STORAGE_H__
+
+#include "c-list/src/c-list.h"
+#include "settings/nm-settings-storage.h"
+
+/*****************************************************************************/
+
+#define NMS_TYPE_IFCFG_RH_STORAGE (nms_ifcfg_rh_storage_get_type())
+#define NMS_IFCFG_RH_STORAGE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorage))
+#define NMS_IFCFG_RH_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass))
+#define NMS_IS_IFCFG_RH_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_IFCFG_RH_STORAGE))
+#define NMS_IS_IFCFG_RH_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_IFCFG_RH_STORAGE))
+#define NMS_IFCFG_RH_STORAGE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass))
+
+typedef struct {
+    NMSettingsStorage parent;
+
+    NMConnection *connection;
+
+    char *unmanaged_spec;
+    char *unrecognized_spec;
+
+    /* The timestamp (stat's mtime) of the file. Newer files have
+     * higher priority. */
+    struct timespec stat_mtime;
+
+    bool dirty : 1;
+
+} NMSIfcfgRHStorage;
+
+typedef struct _NMSIfcfgRHStorageClass NMSIfcfgRHStorageClass;
+
+GType nms_ifcfg_rh_storage_get_type(void);
+
+struct _NMSIfcfgRHPlugin;
+
+NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_connection(struct _NMSIfcfgRHPlugin *plugin,
+                                                       const char *              filename,
+                                                       NMConnection *            connection_take,
+                                                       const struct timespec *   mtime);
+
+NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_unhandled(struct _NMSIfcfgRHPlugin *plugin,
+                                                      const char *              filename,
+                                                      const char *              unmanaged_spec,
+                                                      const char *              unrecognized_spec);
+
+void nms_ifcfg_rh_storage_destroy(NMSIfcfgRHStorage *self);
+
+/*****************************************************************************/
+
+gboolean nms_ifcfg_rh_storage_equal_type(const NMSIfcfgRHStorage *self_a,
+                                         const NMSIfcfgRHStorage *self_b);
+
+void nms_ifcfg_rh_storage_copy_content(NMSIfcfgRHStorage *dst, const NMSIfcfgRHStorage *src);
+
+NMConnection *nms_ifcfg_rh_storage_steal_connection(NMSIfcfgRHStorage *self);
+
+/*****************************************************************************/
+
+static inline const char *
+nms_ifcfg_rh_storage_get_uuid_opt(const NMSIfcfgRHStorage *self)
+{
+    return nm_settings_storage_get_uuid_opt((const NMSettingsStorage *) self);
+}
+
+static inline const char *
+nms_ifcfg_rh_storage_get_filename(const NMSIfcfgRHStorage *self)
+{
+    return nm_settings_storage_get_filename((const NMSettingsStorage *) self);
+}
+
+#endif /* __NMS_IFCFG_RH_STORAGE_H__ */
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
new file mode 100644
index 00000000..8da5de47
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -0,0 +1,1119 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifcfg-rh-utils.h"
+
+#include <stdlib.h>
+
+#include "nm-core-internal.h"
+#include "NetworkManagerUtils.h"
+
+#include "nms-ifcfg-rh-common.h"
+
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_utils_parse_unhandled_spec(const char * unhandled_spec,
+                                        const char **out_unmanaged_spec,
+                                        const char **out_unrecognized_spec)
+{
+    if (unhandled_spec) {
+        if (NM_STR_HAS_PREFIX(unhandled_spec, "unmanaged:")) {
+            NM_SET_OUT(out_unmanaged_spec, &unhandled_spec[NM_STRLEN("unmanaged:")]);
+            NM_SET_OUT(out_unrecognized_spec, NULL);
+            return TRUE;
+        }
+        if (NM_STR_HAS_PREFIX(unhandled_spec, "unrecognized:")) {
+            NM_SET_OUT(out_unmanaged_spec, NULL);
+            NM_SET_OUT(out_unrecognized_spec, &unhandled_spec[NM_STRLEN("unrecognized:")]);
+            return TRUE;
+        }
+    }
+    NM_SET_OUT(out_unmanaged_spec, NULL);
+    NM_SET_OUT(out_unrecognized_spec, NULL);
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+/*
+ * Check ';[a-fA-F0-9]{8}' file suffix used for temporary files by rpm when
+ * installing packages.
+ *
+ * Implementation taken from upstart.
+ */
+static gboolean
+check_rpm_temp_suffix(const char *path)
+{
+    const char *ptr;
+
+    g_return_val_if_fail(path != NULL, FALSE);
+
+    /* Matches *;[a-fA-F0-9]{8}; used by rpm */
+    ptr = strrchr(path, ';');
+    if (ptr && strspn(ptr + 1, "abcdefABCDEF0123456789") == 8 && !ptr[9])
+        return TRUE;
+    return FALSE;
+}
+
+static gboolean
+check_suffix(const char *base, const char *tag)
+{
+    int len, tag_len;
+
+    g_return_val_if_fail(base != NULL, TRUE);
+    g_return_val_if_fail(tag != NULL, TRUE);
+
+    len     = strlen(base);
+    tag_len = strlen(tag);
+    if ((len > tag_len) && !g_ascii_strcasecmp(base + len - tag_len, tag))
+        return TRUE;
+    return FALSE;
+}
+
+gboolean
+utils_should_ignore_file(const char *filename, gboolean only_ifcfg)
+{
+    gs_free char *base = NULL;
+
+    g_return_val_if_fail(filename != NULL, TRUE);
+
+    base = g_path_get_basename(filename);
+
+    /* Only handle ifcfg, keys, and routes files */
+    if (strncmp(base, IFCFG_TAG, strlen(IFCFG_TAG)) != 0) {
+        if (only_ifcfg)
+            return TRUE;
+        else if (strncmp(base, KEYS_TAG, strlen(KEYS_TAG)) != 0
+                 && strncmp(base, ROUTE_TAG, strlen(ROUTE_TAG)) != 0
+                 && strncmp(base, ROUTE6_TAG, strlen(ROUTE6_TAG)) != 0)
+            return TRUE;
+    }
+
+    /* But not those that have certain suffixes */
+    if (check_suffix(base, BAK_TAG) || check_suffix(base, TILDE_TAG) || check_suffix(base, ORIG_TAG)
+        || check_suffix(base, REJ_TAG) || check_suffix(base, RPMNEW_TAG)
+        || check_suffix(base, AUGNEW_TAG) || check_suffix(base, AUGTMP_TAG)
+        || check_rpm_temp_suffix(base))
+        return TRUE;
+
+    return FALSE;
+}
+
+char *
+utils_cert_path(const char *parent, const char *suffix, const char *extension)
+{
+    gs_free char *dir = NULL;
+    const char *  name;
+
+    g_return_val_if_fail(parent, NULL);
+    g_return_val_if_fail(suffix, NULL);
+    g_return_val_if_fail(extension, NULL);
+
+    name = utils_get_ifcfg_name(parent, FALSE);
+    g_return_val_if_fail(name, NULL);
+
+    dir = g_path_get_dirname(parent);
+    return g_strdup_printf("%s/%s-%s.%s", dir, name, suffix, extension);
+}
+
+const char *
+utils_get_ifcfg_name(const char *file, gboolean only_ifcfg)
+{
+    const char *name;
+
+    g_return_val_if_fail(file != NULL, NULL);
+
+    name = strrchr(file, '/');
+    if (!name)
+        name = file;
+    else
+        name++;
+    if (!*name)
+        return NULL;
+
+#define MATCH_TAG_AND_RETURN(name, TAG)                \
+    G_STMT_START                                       \
+    {                                                  \
+        if (strncmp(name, TAG, NM_STRLEN(TAG)) == 0) { \
+            name += NM_STRLEN(TAG);                    \
+            if (name[0] == '\0')                       \
+                return NULL;                           \
+            else                                       \
+                return name;                           \
+        }                                              \
+    }                                                  \
+    G_STMT_END
+
+    /* Do not detect alias files and return 'eth0:0' instead of 'eth0'.
+     * Unfortunately, we cannot be sure that our files don't contain colons,
+     * so we cannot reject files with colons.
+     *
+     * Instead, you must not call utils_get_ifcfg_name() with an alias file
+     * or files that are ignored. */
+    MATCH_TAG_AND_RETURN(name, IFCFG_TAG);
+    if (!only_ifcfg) {
+        MATCH_TAG_AND_RETURN(name, KEYS_TAG);
+        MATCH_TAG_AND_RETURN(name, ROUTE_TAG);
+        MATCH_TAG_AND_RETURN(name, ROUTE6_TAG);
+    }
+
+    return NULL;
+}
+
+/* Used to get any ifcfg/extra file path from any other ifcfg/extra path
+ * in the form <tag><name>.
+ */
+static char *
+utils_get_extra_path(const char *parent, const char *tag)
+{
+    char *      item_path = NULL, *dirname;
+    const char *name;
+
+    g_return_val_if_fail(parent != NULL, NULL);
+    g_return_val_if_fail(tag != NULL, NULL);
+
+    dirname = g_path_get_dirname(parent);
+    if (!dirname)
+        g_return_val_if_reached(NULL);
+
+    name = utils_get_ifcfg_name(parent, FALSE);
+    if (name) {
+        if (!strcmp(dirname, "."))
+            item_path = g_strdup_printf("%s%s", tag, name);
+        else
+            item_path = g_strdup_printf("%s/%s%s", dirname, tag, name);
+    }
+    g_free(dirname);
+
+    return item_path;
+}
+
+char *
+utils_get_ifcfg_path(const char *parent)
+{
+    return utils_get_extra_path(parent, IFCFG_TAG);
+}
+
+char *
+utils_get_keys_path(const char *parent)
+{
+    return utils_get_extra_path(parent, KEYS_TAG);
+}
+
+char *
+utils_get_route_path(const char *parent)
+{
+    return utils_get_extra_path(parent, ROUTE_TAG);
+}
+
+char *
+utils_get_route6_path(const char *parent)
+{
+    return utils_get_extra_path(parent, ROUTE6_TAG);
+}
+
+shvarFile *
+utils_get_extra_ifcfg(const char *parent, const char *tag, gboolean should_create)
+{
+    shvarFile *ifcfg = NULL;
+    char *     path;
+
+    path = utils_get_extra_path(parent, tag);
+    if (!path)
+        return NULL;
+
+    if (should_create && !g_file_test(path, G_FILE_TEST_EXISTS))
+        ifcfg = svCreateFile(path);
+
+    if (!ifcfg)
+        ifcfg = svOpenFile(path, NULL);
+
+    g_free(path);
+    return ifcfg;
+}
+
+shvarFile *
+utils_get_keys_ifcfg(const char *parent, gboolean should_create)
+{
+    return utils_get_extra_ifcfg(parent, KEYS_TAG, should_create);
+}
+
+shvarFile *
+utils_get_route_ifcfg(const char *parent, gboolean should_create)
+{
+    return utils_get_extra_ifcfg(parent, ROUTE_TAG, should_create);
+}
+
+/* Finds out if route file has new or older format
+ * Returns TRUE  - new syntax (ADDRESS<n>=a.b.c.d ...), error opening file or empty
+ *         FALSE - older syntax, i.e. argument to 'ip route add' (1.2.3.0/24 via 11.22.33.44)
+ */
+gboolean
+utils_has_route_file_new_syntax(const char *filename)
+{
+    gs_free char *contents_data = NULL;
+    gsize         len;
+
+    g_return_val_if_fail(filename != NULL, TRUE);
+
+    if (!g_file_get_contents(filename, &contents_data, &len, NULL))
+        return TRUE;
+
+    return utils_has_route_file_new_syntax_content(contents_data, len);
+}
+
+gboolean
+utils_has_route_file_new_syntax_content(const char *contents, gsize len)
+{
+    if (len <= 0)
+        return TRUE;
+
+    while (TRUE) {
+        const char *line = contents;
+        char *      eol;
+        gboolean    found = FALSE;
+
+        /* matches regex "^[[:space:]]*ADDRESS[0-9]+=" */
+
+        eol = (char *) strchr(contents, '\n');
+        if (eol) {
+            eol[0]   = '\0';
+            contents = &eol[1];
+        }
+
+        line = nm_str_skip_leading_spaces(line);
+        if (NM_STR_HAS_PREFIX(line, "ADDRESS")) {
+            line += NM_STRLEN("ADDRESS");
+            if (g_ascii_isdigit(line[0])) {
+                while (g_ascii_isdigit((++line)[0])) {
+                    /* pass */
+                }
+                if (line[0] == '=')
+                    found = TRUE;
+            }
+        }
+
+        if (eol) {
+            /* restore the line ending. We don't want to mangle the content from
+             * POV of the caller. */
+            eol[0] = '\n';
+        }
+
+        if (found)
+            return TRUE;
+        if (!eol)
+            return FALSE;
+    }
+}
+
+gboolean
+utils_has_complex_routes(const char *filename, int addr_family)
+{
+    g_return_val_if_fail(filename, TRUE);
+
+    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) {
+        gs_free char *rules = utils_get_extra_path(filename, RULE_TAG);
+
+        if (g_file_test(rules, G_FILE_TEST_EXISTS))
+            return TRUE;
+    }
+
+    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) {
+        gs_free char *rules = utils_get_extra_path(filename, RULE6_TAG);
+        if (g_file_test(rules, G_FILE_TEST_EXISTS))
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+/* Find out if the 'alias' file name might be an alias file for 'ifcfg' file name,
+ * or any alias when 'ifcfg' is NULL. Does not check that it's actually a valid
+ * alias name; that happens in reader.c
+ */
+gboolean
+utils_is_ifcfg_alias_file(const char *alias, const char *ifcfg)
+{
+    g_return_val_if_fail(alias != NULL, FALSE);
+
+    if (strncmp(alias, IFCFG_TAG, strlen(IFCFG_TAG)))
+        return FALSE;
+
+    if (ifcfg) {
+        size_t len = strlen(ifcfg);
+
+        return (strncmp(alias, ifcfg, len) == 0 && alias[len] == ':');
+    } else {
+        return (strchr(alias, ':') != NULL);
+    }
+}
+
+char *
+utils_detect_ifcfg_path(const char *path, gboolean only_ifcfg)
+{
+    const char *base;
+
+    g_return_val_if_fail(path != NULL, NULL);
+
+    if (utils_should_ignore_file(path, only_ifcfg))
+        return NULL;
+
+    base = strrchr(path, '/');
+    if (!base)
+        base = path;
+    else
+        base += 1;
+
+    if (NM_STR_HAS_PREFIX(base, IFCFG_TAG)) {
+        if (base[NM_STRLEN(IFCFG_TAG)] == '\0')
+            return NULL;
+        if (utils_is_ifcfg_alias_file(base, NULL)) {
+            gs_free char *ifcfg = NULL;
+            char *        ptr;
+
+            ifcfg = g_strdup(path);
+            ptr   = strrchr(ifcfg, ':');
+            if (ptr && ptr > ifcfg && !strchr(ptr, '/')) {
+                *ptr = '\0';
+                if (g_file_test(ifcfg, G_FILE_TEST_EXISTS)) {
+                    /* the file has a colon, so it is probably an alias.
+                     * To be ~more~ certain that this is an alias file,
+                     * check whether a corresponding base file exists. */
+                    if (only_ifcfg)
+                        return NULL;
+                    return g_steal_pointer(&ifcfg);
+                }
+            }
+        }
+        return g_strdup(path);
+    }
+
+    if (only_ifcfg)
+        return NULL;
+    return utils_get_ifcfg_path(path);
+}
+
+void
+nms_ifcfg_rh_utils_user_key_encode(const char *key, GString *str_buffer)
+{
+    gsize i;
+
+    nm_assert(key);
+    nm_assert(str_buffer);
+
+    for (i = 0; key[i]; i++) {
+        char ch = key[i];
+
+        /* we encode the key in only upper case letters, digits, and underscore.
+         * As we expect lower-case letters to be more common, we encode lower-case
+         * letters as upper case, and upper-case letters with a leading underscore. */
+
+        if (ch >= '0' && ch <= '9') {
+            g_string_append_c(str_buffer, ch);
+            continue;
+        }
+        if (ch >= 'a' && ch <= 'z') {
+            g_string_append_c(str_buffer, ch - 'a' + 'A');
+            continue;
+        }
+        if (ch == '.') {
+            g_string_append(str_buffer, "__");
+            continue;
+        }
+        if (ch >= 'A' && ch <= 'Z') {
+            g_string_append_c(str_buffer, '_');
+            g_string_append_c(str_buffer, ch);
+            continue;
+        }
+        g_string_append_printf(str_buffer, "_%03o", (unsigned) ch);
+    }
+}
+
+gboolean
+nms_ifcfg_rh_utils_user_key_decode(const char *name, GString *str_buffer)
+{
+    gsize i;
+
+    nm_assert(name);
+    nm_assert(str_buffer);
+
+    if (!name[0])
+        return FALSE;
+
+    for (i = 0; name[i];) {
+        char ch = name[i];
+
+        if (ch >= '0' && ch <= '9') {
+            g_string_append_c(str_buffer, ch);
+            i++;
+            continue;
+        }
+        if (ch >= 'A' && ch <= 'Z') {
+            g_string_append_c(str_buffer, ch - 'A' + 'a');
+            i++;
+            continue;
+        }
+
+        if (ch == '_') {
+            ch = name[i + 1];
+            if (ch == '_') {
+                g_string_append_c(str_buffer, '.');
+                i += 2;
+                continue;
+            }
+            if (ch >= 'A' && ch <= 'Z') {
+                g_string_append_c(str_buffer, ch);
+                i += 2;
+                continue;
+            }
+            if (ch >= '0' && ch <= '7') {
+                char     ch2, ch3;
+                unsigned v;
+
+                ch2 = name[i + 2];
+                if (!(ch2 >= '0' && ch2 <= '7'))
+                    return FALSE;
+
+                ch3 = name[i + 3];
+                if (!(ch3 >= '0' && ch3 <= '7'))
+                    return FALSE;
+
+#define OCTAL_VALUE(ch) ((unsigned) ((ch) - '0'))
+                v = (OCTAL_VALUE(ch) << 6) + (OCTAL_VALUE(ch2) << 3) + OCTAL_VALUE(ch3);
+                if (v > 0xFF || v == 0)
+                    return FALSE;
+                ch = (char) v;
+                if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || (ch == '.')
+                    || (ch >= 'a' && ch <= 'z')) {
+                    /* such characters are not expected to be encoded via
+                     * octal representation. The encoding is invalid. */
+                    return FALSE;
+                }
+                g_string_append_c(str_buffer, ch);
+                i += 4;
+                continue;
+            }
+            return FALSE;
+        }
+
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+const char *const _nm_ethtool_ifcfg_names[] = {
+#define ETHT_NAME(eid, ename) [eid] = "" ename ""
+    /* indexed by NMEthtoolID */
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX, "adaptive-rx"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX, "adaptive-tx"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES, "rx-frames"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH, "rx-frames-high"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ, "rx-frames-irq"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW, "rx-frames-low"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH, "pkt-rate-high"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW, "pkt-rate-low"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS, "rx-usecs"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH, "rx-usecs-high"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ, "rx-usecs-irq"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW, "rx-usecs-low"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL, "sample-interval"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS, "stats-block-usecs"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES, "tx-frames"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH, "tx-frames-high"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ, "tx-frames-irq"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW, "tx-frames-low"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS, "tx-usecs"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH, "tx-usecs-high"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ, "tx-usecs-irq"),
+    ETHT_NAME(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW, "tx-usecs-low"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_GRO, "gro"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_GSO, "gso"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_LRO, "lro"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD, "macsec-hw-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_NTUPLE, "ntuple"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX, "rx"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RXHASH, "rxhash"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RXVLAN, "rxvlan"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST, "rx-gro-list"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING, "rx-udp-gro-forwarding"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_SG, "sg"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD, "tls-hw-rx-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TSO, "tso"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX, "tx"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TXVLAN, "txvlan"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST, "tx-gso-list"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION,
+              "tx-tunnel-remcsum-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"),
+    ETHT_NAME(NM_ETHTOOL_ID_RING_RX, "rx"),
+    ETHT_NAME(NM_ETHTOOL_ID_RING_RX_JUMBO, "rx-jumbo"),
+    ETHT_NAME(NM_ETHTOOL_ID_RING_RX_MINI, "rx-mini"),
+    ETHT_NAME(NM_ETHTOOL_ID_RING_TX, "tx"),
+};
+
+static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
+    _get_ethtoolid_coalesce_by_name,
+    NMEthtoolID,
+    { nm_assert(name); },
+    { return NM_ETHTOOL_ID_UNKNOWN; },
+    {"adaptive-rx", NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX},
+    {"adaptive-tx", NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX},
+    {"pkt-rate-high", NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH},
+    {"pkt-rate-low", NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW},
+    {"rx-frames", NM_ETHTOOL_ID_COALESCE_RX_FRAMES},
+    {"rx-frames-high", NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH},
+    {"rx-frames-irq", NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ},
+    {"rx-frames-low", NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW},
+    {"rx-usecs", NM_ETHTOOL_ID_COALESCE_RX_USECS},
+    {"rx-usecs-high", NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH},
+    {"rx-usecs-irq", NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ},
+    {"rx-usecs-low", NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW},
+    {"sample-interval", NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL},
+    {"stats-block-usecs", NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS},
+    {"tx-frames", NM_ETHTOOL_ID_COALESCE_TX_FRAMES},
+    {"tx-frames-high", NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH},
+    {"tx-frames-irq", NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ},
+    {"tx-frames-low", NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW},
+    {"tx-usecs", NM_ETHTOOL_ID_COALESCE_TX_USECS},
+    {"tx-usecs-high", NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH},
+    {"tx-usecs-irq", NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ},
+    {"tx-usecs-low", NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW}, );
+
+static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
+    _get_ethtoolid_feature_by_name,
+    NMEthtoolID,
+    { nm_assert(name); },
+    { return NM_ETHTOOL_ID_UNKNOWN; },
+
+    /* Map the names from kernel/ethtool/ifcfg to NMEthtoolID. Note that ethtool utility has built-in
+     * features and NetworkManager's API follows the naming of these built-in features, whenever
+     * they exist.
+     * For example, NM's "ethtool.feature-ntuple" corresponds to ethtool utility's "ntuple"
+     * feature. However the underlying kernel feature is called "rx-ntuple-filter" (as reported
+     * for ETH_SS_FEATURES).
+     *
+     * With ethtool utility, whose command line we attempt to parse here, the user can also
+     * specify the name of the underlying kernel feature directly. So, check whether that is
+     * the case and if yes, map them to the corresponding NetworkManager's features.
+     *
+     * That is why there are duplicate IDs in this list. */
+    {"esp-hw-offload", NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD},
+    {"esp-tx-csum-hw-offload", NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD},
+    {"fcoe-mtu", NM_ETHTOOL_ID_FEATURE_FCOE_MTU},
+    {"gro", NM_ETHTOOL_ID_FEATURE_GRO},
+    {"gso", NM_ETHTOOL_ID_FEATURE_GSO},
+    {"highdma", NM_ETHTOOL_ID_FEATURE_HIGHDMA},
+    {"hw-tc-offload", NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD},
+    {"l2-fwd-offload", NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD},
+    {"loopback", NM_ETHTOOL_ID_FEATURE_LOOPBACK},
+    {"lro", NM_ETHTOOL_ID_FEATURE_LRO},
+    {"macsec-hw-offload", NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD},
+    {"ntuple", NM_ETHTOOL_ID_FEATURE_NTUPLE},
+    {"rx", NM_ETHTOOL_ID_FEATURE_RX},
+    {"rx-all", NM_ETHTOOL_ID_FEATURE_RX_ALL},
+    {"rx-checksum", NM_ETHTOOL_ID_FEATURE_RX},  // kernel-only name
+    {"rx-fcs", NM_ETHTOOL_ID_FEATURE_RX_FCS},
+    {"rx-gro", NM_ETHTOOL_ID_FEATURE_GRO},  // kernel-only name
+    {"rx-gro-hw", NM_ETHTOOL_ID_FEATURE_RX_GRO_HW},
+    {"rx-gro-list", NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST},
+    {"rx-hashing", NM_ETHTOOL_ID_FEATURE_RXHASH},        // kernel-only name
+    {"rx-lro", NM_ETHTOOL_ID_FEATURE_LRO},               // kernel-only name
+    {"rx-ntuple-filter", NM_ETHTOOL_ID_FEATURE_NTUPLE},  // kernel-only name
+    {"rx-udp-gro-forwarding", NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING},
+    {"rx-udp_tunnel-port-offload", NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD},
+    {"rx-vlan-filter", NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER},
+    {"rx-vlan-hw-parse", NM_ETHTOOL_ID_FEATURE_RXVLAN},  // kernel-only name
+    {"rx-vlan-stag-filter", NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER},
+    {"rx-vlan-stag-hw-parse", NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE},
+    {"rxhash", NM_ETHTOOL_ID_FEATURE_RXHASH},
+    {"rxvlan", NM_ETHTOOL_ID_FEATURE_RXVLAN},
+    {"sg", NM_ETHTOOL_ID_FEATURE_SG},
+    {"tls-hw-record", NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD},
+    {"tls-hw-rx-offload", NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD},
+    {"tls-hw-tx-offload", NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD},
+    {"tso", NM_ETHTOOL_ID_FEATURE_TSO},
+    {"tx", NM_ETHTOOL_ID_FEATURE_TX},
+    {"tx-checksum-fcoe-crc", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC},
+    {"tx-checksum-ip-generic", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC},
+    {"tx-checksum-ipv4", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4},
+    {"tx-checksum-ipv6", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6},
+    {"tx-checksum-sctp", NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP},
+    {"tx-esp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION},
+    {"tx-fcoe-segmentation", NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION},
+    {"tx-generic-segmentation", NM_ETHTOOL_ID_FEATURE_GSO},  // kernel-only name
+    {"tx-gre-csum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION},
+    {"tx-gre-segmentation", NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION},
+    {"tx-gso-list", NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST},
+    {"tx-gso-partial", NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL},
+    {"tx-gso-robust", NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST},
+    {"tx-ipxip4-segmentation", NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION},
+    {"tx-ipxip6-segmentation", NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION},
+    {"tx-nocache-copy", NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY},
+    {"tx-scatter-gather", NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER},
+    {"tx-scatter-gather-fraglist", NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST},
+    {"tx-sctp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION},
+    {"tx-tcp-ecn-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION},
+    {"tx-tcp-mangleid-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION},
+    {"tx-tcp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION},
+    {"tx-tcp6-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION},
+    {"tx-tunnel-remcsum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION},
+    {"tx-udp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION},
+    {"tx-udp_tnl-csum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION},
+    {"tx-udp_tnl-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION},
+    {"tx-vlan-hw-insert", NM_ETHTOOL_ID_FEATURE_TXVLAN},  // kernel-only name
+    {"tx-vlan-stag-hw-insert", NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT},
+    {"txvlan", NM_ETHTOOL_ID_FEATURE_TXVLAN}, );
+
+static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
+    _get_ethtoolid_ring_by_name,
+    NMEthtoolID,
+    { nm_assert(name); },
+    { return NM_ETHTOOL_ID_UNKNOWN; },
+    {"rx", NM_ETHTOOL_ID_RING_RX},
+    {"rx-jumbo", NM_ETHTOOL_ID_RING_RX_JUMBO},
+    {"rx-mini", NM_ETHTOOL_ID_RING_RX_MINI},
+    {"tx", NM_ETHTOOL_ID_RING_TX}, );
+
+const NMEthtoolData *
+nms_ifcfg_rh_utils_get_ethtool_by_name(const char *name, NMEthtoolType ethtool_type)
+{
+    NMEthtoolID id;
+
+    switch (ethtool_type) {
+    case NM_ETHTOOL_TYPE_COALESCE:
+        id = _get_ethtoolid_coalesce_by_name(name);
+        break;
+    case NM_ETHTOOL_TYPE_FEATURE:
+        id = _get_ethtoolid_feature_by_name(name);
+        break;
+    case NM_ETHTOOL_TYPE_RING:
+        id = _get_ethtoolid_ring_by_name(name);
+        break;
+    default:
+        nm_assert_not_reached();
+        return NULL;
+    }
+
+    if (id == NM_ETHTOOL_ID_UNKNOWN)
+        return NULL;
+
+    nm_assert(_NM_INT_NOT_NEGATIVE(id));
+    nm_assert(id < G_N_ELEMENTS(nm_ethtool_data));
+    nm_assert(nm_ethtool_data[id]);
+    nm_assert(nm_ethtool_data[id]->id == id);
+    return nm_ethtool_data[id];
+}
+
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_utils_is_numbered_tag_impl(const char *key,
+                                        const char *tag,
+                                        gsize       tag_len,
+                                        gint64 *    out_idx)
+{
+    gint64 idx;
+
+    nm_assert(key);
+    nm_assert(tag);
+    nm_assert(tag_len == strlen(tag));
+    nm_assert(tag_len > 0);
+
+    if (strncmp(key, tag, tag_len) != 0)
+        return FALSE;
+
+    key += tag_len;
+
+    if (key[0] == '\0') {
+        /* The key has no number suffix. We treat this also as a numbered
+         * tag, and it is for certain tags like "IPADDR", but not so much
+         * for others like "ROUTING_RULE_". The caller may want to handle
+         * this case specially. */
+        NM_SET_OUT(out_idx, -1);
+        return TRUE;
+    }
+
+    if (!NM_STRCHAR_ALL(key, ch, g_ascii_isdigit(ch)))
+        return FALSE;
+
+    idx = _nm_utils_ascii_str_to_int64(key, 10, 0, G_MAXINT64, -1);
+    if (idx == -1)
+        return FALSE;
+
+    NM_SET_OUT(out_idx, idx);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+#define _KEY_TYPE(key, flags)                                                            \
+    {                                                                                    \
+        .key_name = "" key "", .key_flags = ((NMS_IFCFG_KEY_TYPE_WELL_KNOWN) | (flags)), \
+    }
+
+const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
+    _KEY_TYPE("ACD_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("ADDRESS", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("AP_ISOLATION", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("ARPING_WAIT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("AUTH_RETRIES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("AUTOCONNECT_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("AUTOCONNECT_RETRIES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("AUTOCONNECT_SLAVES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BAND", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BONDING_MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BONDING_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BOOTPROTO", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BRIDGE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BRIDGE_MACADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BRIDGE_PORT_VLANS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BRIDGE_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BRIDGE_VLANS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BRIDGING_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BROWSER_ONLY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BSSID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("CHANNEL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("CIPHER_GROUP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("CIPHER_PAIRWISE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("CONNECTED_MODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("CONNECTION_METERED", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("CTCPROT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DCB", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_FCOE_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_FCOE_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_FCOE_MODE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DCB_APP_FCOE_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_FCOE_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_FIP_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_FIP_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DCB_APP_FIP_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_FIP_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_ISCSI_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_ISCSI_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DCB_APP_ISCSI_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_APP_ISCSI_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PFC_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PFC_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PFC_UP, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PFC_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_ADVERTISE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_ENABLE, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_ID, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_PCT, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_STRICT, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_UP2TC, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_UPPCT, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE(KEY_DCB_PG_WILLING, NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DEFAULTKEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DEFROUTE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DELAY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DEVICE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DEVICETYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DEVTIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCPV6C", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCPV6_DUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _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_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),
+    _KEY_TYPE("DHCP_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCP_HOSTNAME_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCP_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCP_REJECT_SERVERS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCP_SEND_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCP_VENDOR_CLASS_IDENTIFIER", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCPv6_DUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCPv6_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DNS", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("DOMAIN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("ESSID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("ETHTOOL_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("ETHTOOL_WAKE_ON_LAN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("FILS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("FILTER", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("GATEWAY", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("GATEWAYDEV", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("GATEWAY_PING_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("GENERATE_MAC_ADDRESS_MASK", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("GVRP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_FROM_DHCP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_FROM_DNS_LOOKUP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_ONLY_FROM_DEFAULT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HWADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HWADDR_BLACKLIST", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_ALTSUBJECT_MATCHES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_ANON_IDENTITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_AUTH_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_CA_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_CA_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_CA_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_CA_PATH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_CLIENT_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_CLIENT_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_CLIENT_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_DOMAIN_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_DOMAIN_SUFFIX_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_EAP_METHODS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_FAST_PROVISIONING", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_IDENTITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_AUTH_METHODS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_CA_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_CA_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_CA_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_CLIENT_CERT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_CLIENT_CERT_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_CLIENT_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_PRIVATE_KEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_OPTIONAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PAC_FILE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PASSWORD_RAW", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PASSWORD_RAW_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PEAP_FORCE_NEW_LABEL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PEAP_VERSION", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PHASE1_AUTH_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PHASE2_CA_PATH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PHASE2_DOMAIN_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PHASE2_SUBJECT_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PIN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PIN_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PRIVATE_KEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PRIVATE_KEY_PASSWORD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_SUBJECT_MATCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IEEE_8021X_SYSTEM_CA_CERTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPADDR", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("IPV4_DHCP_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV4_DNS_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV4_FAILURE_FATAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV4_ROUTE_METRIC", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV4_ROUTE_TABLE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6ADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6ADDR_SECONDARIES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6FORWARDING", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6INIT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6TUNNELIPV4", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_ADDR_GEN_MODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_AUTOCONF", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_DEFAULTDEV", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_DEFAULTGW", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_DEFROUTE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_DHCP_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_DISABLED", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_DNS_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_DOMAIN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_FAILURE_FATAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_PEERDNS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_PEERROUTES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_PRIVACY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_PRIVACY_PREFER_PUBLIC_IP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_RA_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_RES_OPTIONS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_ROUTE_METRIC", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_ROUTE_TABLE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_TOKEN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("KEY", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("KEY_MGMT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("KEY_PASSPHRASE", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("KEY_TYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("LLDP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("LLMNR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MACADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MAC_ADDRESS_RANDOMIZATION", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MASTER_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MATCH_DRIVER", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MATCH_INTERFACE_NAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MATCH_KERNEL_COMMAND_LINE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MATCH_PATH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MDNS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("METRIC", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("MODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MTU", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MUD_URL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MULTI_CONNECT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("MVRP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("NAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("NETMASK", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("NETTYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("NM_CONTROLLED", NMS_IFCFG_KEY_TYPE_IS_PLAIN | NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY),
+    _KEY_TYPE("NM_USER_", NMS_IFCFG_KEY_TYPE_IS_PREFIX),
+    _KEY_TYPE("ONBOOT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("OPTIONS", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("OVS_PORT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("OVS_PORT_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PAC_SCRIPT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PAC_URL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PEERDNS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PEERROUTES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PHYSDEV", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PKEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PKEY_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PMF", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PORTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("POWERSAVE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("PREFIX", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("PROXY_METHOD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("QDISC", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("REORDER_HDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("RES_OPTIONS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("ROUTING_RULE6_", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("ROUTING_RULE_", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("SEARCH", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("SECONDARY_UUIDS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("SECURITYMODE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("SLAVE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("SRIOV_AUTOPROBE_DRIVERS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("SRIOV_TOTAL_VFS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("SRIOV_VF", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("SSID_HIDDEN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("STABLE_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("STP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("SUBCHANNELS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("TEAM_CONFIG", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("TEAM_MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("TEAM_MASTER_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("TEAM_PORT_CONFIG", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("TYPE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("USERS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("VLAN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("VLAN_EGRESS_PRIORITY_MAP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("VLAN_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("VLAN_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("VLAN_INGRESS_PRIORITY_MAP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("VRF", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("VRF_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("WEP_KEY_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("WPA_ALLOW_WPA", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("WPA_ALLOW_WPA2", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("WPA_PSK", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("WPA_PSK_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("WPS_METHOD", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("ZONE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+};
+
+const NMSIfcfgKeyTypeInfo *
+nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx)
+{
+    gssize idx;
+
+    G_STATIC_ASSERT(G_STRUCT_OFFSET(NMSIfcfgKeyTypeInfo, key_name) == 0);
+
+    idx = nm_utils_array_find_binary_search(nms_ifcfg_well_known_keys,
+                                            sizeof(nms_ifcfg_well_known_keys[0]),
+                                            G_N_ELEMENTS(nms_ifcfg_well_known_keys),
+                                            &key,
+                                            nm_strcmp_p_with_data,
+                                            NULL);
+    NM_SET_OUT(out_idx, idx);
+    if (idx < 0)
+        return NULL;
+    return &nms_ifcfg_well_known_keys[idx];
+}
+
+const NMSIfcfgKeyTypeInfo *
+nms_ifcfg_rh_utils_is_well_known_key(const char *key)
+{
+    const NMSIfcfgKeyTypeInfo *ti;
+    gssize                     idx;
+
+    nm_assert(key);
+
+    ti = nms_ifcfg_well_known_key_find_info(key, &idx);
+
+    if (ti) {
+        if (NM_FLAGS_ANY(ti->key_flags,
+                         NMS_IFCFG_KEY_TYPE_IS_PLAIN | NMS_IFCFG_KEY_TYPE_IS_NUMBERED)) {
+            /* These tags are valid on full match.
+             *
+             * Note that numbered tags we also treat as valid if they have no
+             * suffix. That is correct for "IPADDR", but less so for "ROUTING_RULE_". */
+            return ti;
+        }
+        nm_assert(NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_PREFIX));
+        /* a prefix tag needs some extra suffix afterwards to be valid. */
+        return NULL;
+    }
+
+    /* Not found. Maybe it's a numbered/prefixed key? With idx we got the index where
+     * we should insert the key. Since the numbered/prefixed keys share a prefix, we can
+     * find the possible prefix at the index before the insert position. */
+    idx = ~idx;
+    if (idx == 0)
+        return NULL;
+
+    ti = &nms_ifcfg_well_known_keys[idx - 1];
+
+    if (NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_NUMBERED)) {
+        if (nms_ifcfg_rh_utils_is_numbered_tag(key, ti->key_name, NULL))
+            return ti;
+        return NULL;
+    }
+
+    if (NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_PREFIX)) {
+        gsize l = strlen(ti->key_name);
+
+        if (strncmp(key, ti->key_name, l) == 0 && key[l] != '\0')
+            return ti;
+        return NULL;
+    }
+
+    return NULL;
+}
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
new file mode 100644
index 00000000..36ec9225
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
+ */
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#include "nm-connection.h"
+#include "nm-base/nm-ethtool-base.h"
+
+#include "shvar.h"
+
+/*****************************************************************************/
+
+typedef enum {
+    NMS_IFCFG_KEY_TYPE_UNKNOWN    = 0,
+    NMS_IFCFG_KEY_TYPE_WELL_KNOWN = (1u << 0),
+
+    NMS_IFCFG_KEY_TYPE_IS_PLAIN    = (1u << 1),
+    NMS_IFCFG_KEY_TYPE_IS_NUMBERED = (1u << 2),
+    NMS_IFCFG_KEY_TYPE_IS_PREFIX   = (1u << 3),
+
+    /* by default, well knowns keys that are not explicitly set
+     * by the writer (the unvisited, dirty ones) are removed.
+     * With this flag, such keys are kept if they are present. */
+    NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY = (1u << 4),
+
+} NMSIfcfgKeyTypeFlags;
+
+typedef struct {
+    const char *         key_name;
+    NMSIfcfgKeyTypeFlags key_flags;
+} NMSIfcfgKeyTypeInfo;
+
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[247];
+
+const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx);
+
+static inline NMSIfcfgKeyTypeFlags
+nms_ifcfg_well_known_key_find_info_flags(const char *key)
+{
+    const NMSIfcfgKeyTypeInfo *ti;
+
+    ti = nms_ifcfg_well_known_key_find_info(key, NULL);
+    if (!ti)
+        return NMS_IFCFG_KEY_TYPE_UNKNOWN;
+    return ti->key_flags;
+}
+
+/*****************************************************************************/
+
+gboolean nms_ifcfg_rh_utils_parse_unhandled_spec(const char * unhandled_spec,
+                                                 const char **out_unmanaged_spec,
+                                                 const char **out_unrecognized_spec);
+
+#define NM_IFCFG_CONNECTION_LOG_PATH(path) ((path) ?: "in-memory")
+#define NM_IFCFG_CONNECTION_LOG_FMT        "%s (%s,\"%s\")"
+#define NM_IFCFG_CONNECTION_LOG_ARG(con)                                      \
+    NM_IFCFG_CONNECTION_LOG_PATH(                                             \
+        nm_settings_connection_get_filename((NMSettingsConnection *) (con))), \
+        nm_settings_connection_get_uuid((NMSettingsConnection *) (con)),      \
+        nm_settings_connection_get_id((NMSettingsConnection *) (con))
+#define NM_IFCFG_CONNECTION_LOG_FMTD "%s (%s,\"%s\",%p)"
+#define NM_IFCFG_CONNECTION_LOG_ARGD(con)                                     \
+    NM_IFCFG_CONNECTION_LOG_PATH(                                             \
+        nm_settings_connection_get_filename((NMSettingsConnection *) (con))), \
+        nm_settings_connection_get_uuid((NMSettingsConnection *) (con)),      \
+        nm_settings_connection_get_id((NMSettingsConnection *) (con)), (con)
+
+char *utils_cert_path(const char *parent, const char *suffix, const char *extension);
+
+const char *utils_get_ifcfg_name(const char *file, gboolean only_ifcfg);
+
+gboolean utils_should_ignore_file(const char *filename, gboolean only_ifcfg);
+
+char *utils_get_ifcfg_path(const char *parent);
+char *utils_get_keys_path(const char *parent);
+char *utils_get_route_path(const char *parent);
+char *utils_get_route6_path(const char *parent);
+
+shvarFile *utils_get_extra_ifcfg(const char *parent, const char *tag, gboolean should_create);
+shvarFile *utils_get_keys_ifcfg(const char *parent, gboolean should_create);
+shvarFile *utils_get_route_ifcfg(const char *parent, gboolean should_create);
+
+gboolean utils_has_route_file_new_syntax(const char *filename);
+gboolean utils_has_route_file_new_syntax_content(const char *contents, gsize len);
+gboolean utils_has_complex_routes(const char *filename, int addr_family);
+
+gboolean utils_is_ifcfg_alias_file(const char *alias, const char *ifcfg);
+
+char *utils_detect_ifcfg_path(const char *path, gboolean only_ifcfg);
+
+void     nms_ifcfg_rh_utils_user_key_encode(const char *key, GString *str_buffer);
+gboolean nms_ifcfg_rh_utils_user_key_decode(const char *name, GString *str_buffer);
+
+static inline const char *
+_nms_ifcfg_rh_utils_numbered_tag(char *buf, gsize buf_len, const char *tag_name, int which)
+{
+    gsize l;
+
+#if NM_MORE_ASSERTS > 5
+    nm_assert(NM_FLAGS_ALL(nms_ifcfg_well_known_key_find_info_flags(tag_name),
+                           NMS_IFCFG_KEY_TYPE_WELL_KNOWN | NMS_IFCFG_KEY_TYPE_IS_NUMBERED));
+#endif
+
+    l = g_strlcpy(buf, tag_name, buf_len);
+    nm_assert(l < buf_len);
+    if (which != -1) {
+        buf_len -= l;
+        l = g_snprintf(&buf[l], buf_len, "%d", which);
+        nm_assert(l < buf_len);
+    }
+    return buf;
+}
+#define numbered_tag(buf, tag_name, which)                                                \
+    ({                                                                                    \
+        _nm_unused char *const _buf = (buf);                                              \
+                                                                                          \
+        /* some static assert trying to ensure that the buffer is statically allocated.
+         * It disallows a buffer size of sizeof(gpointer) to catch that. */   \
+        G_STATIC_ASSERT(G_N_ELEMENTS(buf) == sizeof(buf) && sizeof(buf) != sizeof(char *) \
+                        && sizeof(buf) < G_MAXINT);                                       \
+        _nms_ifcfg_rh_utils_numbered_tag(buf, sizeof(buf), "" tag_name "", (which));      \
+    })
+
+gboolean nms_ifcfg_rh_utils_is_numbered_tag_impl(const char *key,
+                                                 const char *tag,
+                                                 gsize       tag_len,
+                                                 gint64 *    out_idx);
+
+static inline gboolean
+nms_ifcfg_rh_utils_is_numbered_tag(const char *key, const char *tag, gint64 *out_idx)
+{
+    nm_assert(tag);
+
+    return nms_ifcfg_rh_utils_is_numbered_tag_impl(key, tag, strlen(tag), out_idx);
+}
+
+#define NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, tag, out_idx) \
+    nms_ifcfg_rh_utils_is_numbered_tag_impl(key, tag, NM_STRLEN(tag), out_idx)
+
+/*****************************************************************************/
+
+const NMSIfcfgKeyTypeInfo *nms_ifcfg_rh_utils_is_well_known_key(const char *key);
+
+/*****************************************************************************/
+
+extern const char *const _nm_ethtool_ifcfg_names[_NM_ETHTOOL_ID_NUM];
+
+static inline const char *
+nms_ifcfg_rh_utils_get_ethtool_name(NMEthtoolID ethtool_id)
+{
+    nm_assert(ethtool_id >= _NM_ETHTOOL_ID_FIRST && ethtool_id <= _NM_ETHTOOL_ID_LAST);
+    nm_assert(ethtool_id < G_N_ELEMENTS(_nm_ethtool_ifcfg_names));
+    nm_assert(_nm_ethtool_ifcfg_names[ethtool_id]);
+
+    return _nm_ethtool_ifcfg_names[ethtool_id];
+}
+
+const NMEthtoolData *nms_ifcfg_rh_utils_get_ethtool_by_name(const char *  name,
+                                                            NMEthtoolType ethtool_type);
+
+#endif /* _UTILS_H_ */
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
new file mode 100644
index 00000000..a968fce0
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -0,0 +1,3654 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2009 - 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifcfg-rh-writer.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "nm-glib-aux/nm-enum-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
+#include "nm-manager.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-ethtool.h"
+#include "nm-setting-8021x.h"
+#include "nm-setting-proxy.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-setting-pppoe.h"
+#include "nm-setting-vlan.h"
+#include "nm-setting-user.h"
+#include "nm-setting-team.h"
+#include "nm-setting-team-port.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "NetworkManagerUtils.h"
+#include "nm-base/nm-ethtool-base.h"
+
+#include "nms-ifcfg-rh-common.h"
+#include "nms-ifcfg-rh-reader.h"
+#include "nms-ifcfg-rh-utils.h"
+#include "shvar.h"
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG_PREFIX_NAME "ifcfg-rh"
+#define _NMLOG(level, ...)                                                 \
+    G_STMT_START                                                           \
+    {                                                                      \
+        nm_log((level),                                                    \
+               (_NMLOG_DOMAIN),                                            \
+               NULL,                                                       \
+               NULL,                                                       \
+               "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                    \
+               _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                      \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static void
+save_secret_flags(shvarFile *ifcfg, const char *key, NMSettingSecretFlags flags)
+{
+    GString *str;
+
+    g_return_if_fail(ifcfg != NULL);
+    g_return_if_fail(key != NULL);
+
+    if (flags == NM_SETTING_SECRET_FLAG_NONE)
+        return;
+
+    /* Convert flags bitfield into string representation */
+    str = g_string_sized_new(20);
+    if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED)
+        g_string_append(str, SECRET_FLAG_AGENT);
+
+    if (flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) {
+        if (str->len)
+            g_string_append_c(str, ' ');
+        g_string_append(str, SECRET_FLAG_NOT_SAVED);
+    }
+
+    if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) {
+        if (str->len)
+            g_string_append_c(str, ' ');
+        g_string_append(str, SECRET_FLAG_NOT_REQUIRED);
+    }
+
+    svSetValueStr(ifcfg, key, str->len ? str->str : NULL);
+    g_string_free(str, TRUE);
+}
+
+static void
+set_secret(shvarFile *          ifcfg,
+           GHashTable *         secrets,
+           const char *         key,
+           const char *         value,
+           const char *         flags_key,
+           NMSettingSecretFlags flags)
+{
+    /* Save secret flags */
+    save_secret_flags(ifcfg, flags_key, flags);
+
+    /* Only write the secret if it's system owned and supposed to be saved */
+    if (flags != NM_SETTING_SECRET_FLAG_NONE)
+        value = NULL;
+
+    g_hash_table_replace(secrets, g_strdup(key), g_strdup(value));
+}
+
+static gboolean
+write_secrets(shvarFile *ifcfg, GHashTable *secrets, GError **error)
+{
+    nm_auto_shvar_file_close shvarFile *keyfile = NULL;
+    gs_free NMUtilsNamedValue *secrets_arr_free = NULL;
+    NMUtilsNamedValue          secrets_arr_static[30];
+    const NMUtilsNamedValue *  secrets_arr;
+    guint                      secrets_len;
+    GError *                   local       = NULL;
+    gboolean                   any_secrets = FALSE;
+    guint                      i;
+
+    keyfile = utils_get_keys_ifcfg(svFileGetName(ifcfg), TRUE);
+    if (!keyfile) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Failure to create secrets file for '%s'",
+                    svFileGetName(ifcfg));
+        return FALSE;
+    }
+
+    secrets_arr = nm_utils_named_values_from_strdict(secrets,
+                                                     &secrets_len,
+                                                     secrets_arr_static,
+                                                     &secrets_arr_free);
+    for (i = 0; i < secrets_len; i++) {
+        const char *k = secrets_arr[i].name;
+        const char *v = secrets_arr[i].value_str;
+
+        if (v) {
+            svSetValueStr(keyfile, k, v);
+            any_secrets = TRUE;
+        }
+    }
+
+    if (!any_secrets)
+        (void) unlink(svFileGetName(keyfile));
+    else if (!svWriteFileWithoutDirtyWellknown(keyfile, 0600, &local)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Failure to write secrets to '%s': %s",
+                    svFileGetName(keyfile),
+                    local->message);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+typedef struct {
+    const NMSetting8021xSchemeVtable *vtable;
+    const char *                      ifcfg_rh_key;
+} Setting8021xSchemeVtable;
+
+static const Setting8021xSchemeVtable setting_8021x_scheme_vtable[] = {
+#define _D(_scheme_type, _ifcfg_rh_key)                                  \
+    [(_scheme_type)] = {                                                 \
+        .vtable       = &nm_setting_8021x_scheme_vtable[(_scheme_type)], \
+        .ifcfg_rh_key = ""_ifcfg_rh_key                                  \
+                        "",                                              \
+    }
+    _D(NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT, "IEEE_8021X_CA_CERT"),
+    _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT, "IEEE_8021X_INNER_CA_CERT"),
+    _D(NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT, "IEEE_8021X_CLIENT_CERT"),
+    _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, "IEEE_8021X_INNER_CLIENT_CERT"),
+    _D(NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY, "IEEE_8021X_PRIVATE_KEY"),
+    _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, "IEEE_8021X_INNER_PRIVATE_KEY"),
+#undef _D
+};
+
+static gboolean
+write_object(NMSetting8021x *                s_8021x,
+             shvarFile *                     ifcfg,
+             GHashTable *                    secrets,
+             GHashTable *                    blobs,
+             const Setting8021xSchemeVtable *objtype,
+             gboolean                        force_write,
+             GError **                       error)
+{
+    NMSetting8021xCKScheme scheme;
+    const char *           value    = NULL;
+    GBytes *               blob     = NULL;
+    const char *           password = NULL;
+    NMSettingSecretFlags   flags    = NM_SETTING_SECRET_FLAG_NONE;
+    char                   secret_name[100];
+    char                   secret_flags[sizeof(secret_name) + NM_STRLEN("_FLAGS")];
+    const char *           extension;
+    char *                 standard_file;
+
+    g_return_val_if_fail(ifcfg != NULL, FALSE);
+    g_return_val_if_fail(objtype != NULL, FALSE);
+
+    scheme = (*(objtype->vtable->scheme_func))(s_8021x);
+    switch (scheme) {
+    case NM_SETTING_802_1X_CK_SCHEME_UNKNOWN:
+        break;
+    case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+        blob = (*(objtype->vtable->blob_func))(s_8021x);
+        break;
+    case NM_SETTING_802_1X_CK_SCHEME_PATH:
+        value = (*(objtype->vtable->path_func))(s_8021x);
+        break;
+    case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
+        value = (*(objtype->vtable->uri_func))(s_8021x);
+        break;
+    default:
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Unhandled certificate object scheme");
+        return FALSE;
+    }
+
+    /* Set the password for certificate/private key. */
+    nm_sprintf_buf(secret_name, "%s_PASSWORD", objtype->ifcfg_rh_key);
+    nm_sprintf_buf(secret_flags, "%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key);
+    password = (*(objtype->vtable->passwd_func))(s_8021x);
+    flags    = (*(objtype->vtable->pwflag_func))(s_8021x);
+    set_secret(ifcfg, secrets, secret_name, password, secret_flags, flags);
+
+    if (!objtype->vtable->format_func)
+        extension = "der";
+    else if (objtype->vtable->format_func(s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
+        extension = "p12";
+    else
+        extension = "pem";
+
+    /* If the object path was specified, prefer that over any raw cert data that
+     * may have been sent.
+     */
+    if (value) {
+        svSetValueStr(ifcfg, objtype->ifcfg_rh_key, value);
+        return TRUE;
+    }
+
+    /* If it's raw certificate data, write the data out to the standard file */
+    if (blob) {
+        char *new_file;
+
+        new_file = utils_cert_path(svFileGetName(ifcfg), objtype->vtable->file_suffix, extension);
+        g_hash_table_replace(blobs, new_file, g_bytes_ref(blob));
+        svSetValueStr(ifcfg, objtype->ifcfg_rh_key, new_file);
+        return TRUE;
+    }
+
+    /* If certificate/private key wasn't sent, the connection may no longer be
+     * 802.1x and thus we clear out the paths and certs.
+     *
+     * Since no cert/private key is now being used, delete any standard file
+     * that was created for this connection, but leave other files alone.
+     * Thus, for example,
+     * /etc/sysconfig/network-scripts/ca-cert-Test_Write_Wifi_WPA_EAP-TLS.der
+     * will be deleted, but /etc/pki/tls/cert.pem will not.
+     */
+    standard_file = utils_cert_path(svFileGetName(ifcfg), objtype->vtable->file_suffix, extension);
+    g_hash_table_replace(blobs, standard_file, NULL);
+    svSetValue(ifcfg, objtype->ifcfg_rh_key, force_write ? "" : NULL);
+    return TRUE;
+}
+
+static gboolean
+write_blobs(GHashTable *blobs, GError **error)
+{
+    GHashTableIter iter;
+    const char *   filename;
+    GBytes *       blob;
+
+    if (!blobs)
+        return TRUE;
+
+    g_hash_table_iter_init(&iter, blobs);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &filename, (gpointer *) &blob)) {
+        GError *write_error = NULL;
+
+        if (!blob) {
+            (void) unlink(filename);
+            continue;
+        }
+
+        /* Write the raw certificate data out to the standard file so that we
+         * can use paths from now on instead of pushing around the certificate
+         * data itself.
+         */
+        if (!nm_utils_file_set_contents(filename,
+                                        (const char *) g_bytes_get_data(blob, NULL),
+                                        g_bytes_get_size(blob),
+                                        0600,
+                                        NULL,
+                                        &write_error)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "Could not write certificate to file \"%s\": %s",
+                        filename,
+                        write_error->message);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean
+write_8021x_certs(NMSetting8021x *s_8021x,
+                  GHashTable *    secrets,
+                  GHashTable *    blobs,
+                  gboolean        phase2,
+                  shvarFile *     ifcfg,
+                  GError **       error)
+{
+    const Setting8021xSchemeVtable *pk_otype      = NULL;
+    gs_free char *                  value_to_free = NULL;
+
+    /* CA certificate */
+    if (!write_object(
+            s_8021x,
+            ifcfg,
+            secrets,
+            blobs,
+            phase2 ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT]
+                   : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT],
+            FALSE,
+            error))
+        return FALSE;
+
+    /* Private key */
+    if (phase2)
+        pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY];
+    else
+        pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY];
+
+    /* Save the private key */
+    if (!write_object(s_8021x, ifcfg, secrets, blobs, pk_otype, FALSE, error))
+        return FALSE;
+
+    /* Save the client certificate.
+     * If there is a private key, always write a property for the
+     * client certificate even if it is empty, so that the reader
+     * doesn't have to read the private key file to determine if it
+     * is a PKCS #12 one which serves also as client certificate.
+     */
+    if (!write_object(
+            s_8021x,
+            ifcfg,
+            secrets,
+            blobs,
+            phase2 ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT]
+                   : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT],
+            !!svGetValue(ifcfg, pk_otype->ifcfg_rh_key, &value_to_free),
+            error))
+        return FALSE;
+
+    return TRUE;
+}
+
+static gboolean
+write_8021x_setting(NMConnection *connection,
+                    shvarFile *   ifcfg,
+                    GHashTable *  secrets,
+                    GHashTable *  blobs,
+                    gboolean      wired,
+                    GError **     error)
+{
+    NMSetting8021x *        s_8021x;
+    NMSetting8021xAuthFlags auth_flags;
+    const char *            value, *match;
+    gconstpointer           ptr;
+    GBytes *                bytes;
+    char *                  tmp = NULL;
+    GString *               phase2_auth;
+    GString *               str;
+    guint32                 i, num;
+    gsize                   size;
+    int                     vint;
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    if (!s_8021x)
+        return TRUE;
+
+    /* If wired, write KEY_MGMT */
+    if (wired)
+        svSetValueStr(ifcfg, "KEY_MGMT", "IEEE8021X");
+
+    /* EAP method */
+    if (nm_setting_802_1x_get_num_eap_methods(s_8021x)) {
+        value = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+        if (value)
+            tmp = g_ascii_strup(value, -1);
+    }
+    svSetValueStr(ifcfg, "IEEE_8021X_EAP_METHODS", tmp);
+    g_free(tmp);
+
+    svSetValueStr(ifcfg, "IEEE_8021X_IDENTITY", nm_setting_802_1x_get_identity(s_8021x));
+
+    svSetValueStr(ifcfg,
+                  "IEEE_8021X_ANON_IDENTITY",
+                  nm_setting_802_1x_get_anonymous_identity(s_8021x));
+
+    set_secret(ifcfg,
+               secrets,
+               "IEEE_8021X_PASSWORD",
+               nm_setting_802_1x_get_password(s_8021x),
+               "IEEE_8021X_PASSWORD_FLAGS",
+               nm_setting_802_1x_get_password_flags(s_8021x));
+
+    tmp   = NULL;
+    bytes = nm_setting_802_1x_get_password_raw(s_8021x);
+    if (bytes) {
+        ptr = g_bytes_get_data(bytes, &size);
+        tmp = nm_utils_bin2hexstr(ptr, size, -1);
+    }
+    set_secret(ifcfg,
+               secrets,
+               "IEEE_8021X_PASSWORD_RAW",
+               tmp,
+               "IEEE_8021X_PASSWORD_RAW_FLAGS",
+               nm_setting_802_1x_get_password_raw_flags(s_8021x));
+    g_free(tmp);
+
+    svSetValueBoolean_cond_true(ifcfg,
+                                "IEEE_8021X_SYSTEM_CA_CERTS",
+                                nm_setting_802_1x_get_system_ca_certs(s_8021x));
+
+    value = nm_setting_802_1x_get_phase1_peapver(s_8021x);
+    svSetValueStr(ifcfg, "IEEE_8021X_PEAP_VERSION", value);
+
+    svSetValueBoolean_cond_true(ifcfg,
+                                "IEEE_8021X_PEAP_FORCE_NEW_LABEL",
+                                nm_streq0(nm_setting_802_1x_get_phase1_peaplabel(s_8021x), "1"));
+
+    svSetValueStr(ifcfg, "IEEE_8021X_PAC_FILE", nm_setting_802_1x_get_pac_file(s_8021x));
+
+    /* FAST PAC provisioning */
+    value = nm_setting_802_1x_get_phase1_fast_provisioning(s_8021x);
+    if (value) {
+        if (strcmp(value, "1") == 0)
+            value = "allow-unauth";
+        else if (strcmp(value, "2") == 0)
+            value = "allow-auth";
+        else if (strcmp(value, "3") == 0)
+            value = "allow-unauth allow-auth";
+        else
+            value = NULL;
+    }
+    svSetValueStr(ifcfg, "IEEE_8021X_FAST_PROVISIONING", value);
+
+    /* Phase2 auth methods */
+    phase2_auth = g_string_new(NULL);
+
+    value = nm_setting_802_1x_get_phase2_auth(s_8021x);
+    if (value) {
+        tmp = g_ascii_strup(value, -1);
+        g_string_append(phase2_auth, tmp);
+        g_free(tmp);
+    }
+
+    value = nm_setting_802_1x_get_phase2_autheap(s_8021x);
+    if (value) {
+        if (phase2_auth->len)
+            g_string_append_c(phase2_auth, ' ');
+
+        tmp = g_ascii_strup(value, -1);
+        g_string_append_printf(phase2_auth, "EAP-%s", tmp);
+        g_free(tmp);
+    }
+
+    auth_flags = nm_setting_802_1x_get_phase1_auth_flags(s_8021x);
+    if (auth_flags != NM_SETTING_802_1X_AUTH_FLAGS_NONE) {
+        svSetValueEnum(ifcfg,
+                       "IEEE_8021X_PHASE1_AUTH_FLAGS",
+                       nm_setting_802_1x_auth_flags_get_type(),
+                       auth_flags);
+    }
+
+    svSetValueStr(ifcfg,
+                  "IEEE_8021X_INNER_AUTH_METHODS",
+                  phase2_auth->len ? phase2_auth->str : NULL);
+
+    g_string_free(phase2_auth, TRUE);
+
+    svSetValueStr(ifcfg, "IEEE_8021X_SUBJECT_MATCH", nm_setting_802_1x_get_subject_match(s_8021x));
+
+    svSetValueStr(ifcfg,
+                  "IEEE_8021X_PHASE2_SUBJECT_MATCH",
+                  nm_setting_802_1x_get_phase2_subject_match(s_8021x));
+
+    str = g_string_new(NULL);
+    num = nm_setting_802_1x_get_num_altsubject_matches(s_8021x);
+    for (i = 0; i < num; i++) {
+        if (i > 0)
+            g_string_append_c(str, ' ');
+        match = nm_setting_802_1x_get_altsubject_match(s_8021x, i);
+        g_string_append(str, match);
+    }
+    if (str->len > 0)
+        svSetValueStr(ifcfg, "IEEE_8021X_ALTSUBJECT_MATCHES", str->str);
+    g_string_free(str, TRUE);
+
+    str = g_string_new(NULL);
+    num = nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x);
+    for (i = 0; i < num; i++) {
+        if (i > 0)
+            g_string_append_c(str, ' ');
+        match = nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, i);
+        g_string_append(str, match);
+    }
+    if (str->len > 0)
+        svSetValueStr(ifcfg, "IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES", str->str);
+    g_string_free(str, TRUE);
+
+    svSetValueStr(ifcfg,
+                  "IEEE_8021X_DOMAIN_SUFFIX_MATCH",
+                  nm_setting_802_1x_get_domain_suffix_match(s_8021x));
+    svSetValueStr(ifcfg,
+                  "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH",
+                  nm_setting_802_1x_get_phase2_domain_suffix_match(s_8021x));
+
+    svSetValueStr(ifcfg, "IEEE_8021X_DOMAIN_MATCH", nm_setting_802_1x_get_domain_match(s_8021x));
+    svSetValueStr(ifcfg,
+                  "IEEE_8021X_PHASE2_DOMAIN_MATCH",
+                  nm_setting_802_1x_get_phase2_domain_match(s_8021x));
+
+    vint = nm_setting_802_1x_get_auth_timeout(s_8021x);
+    svSetValueInt64_cond(ifcfg, "IEEE_8021X_AUTH_TIMEOUT", vint > 0, vint);
+
+    svSetValueBoolean_cond_true(ifcfg,
+                                "IEEE_8021X_OPTIONAL",
+                                nm_setting_802_1x_get_optional(s_8021x));
+
+    svSetValue(ifcfg, "IEEE_8021X_CA_PATH", nm_setting_802_1x_get_ca_path(s_8021x));
+    svSetValue(ifcfg, "IEEE_8021X_PHASE2_CA_PATH", nm_setting_802_1x_get_phase2_ca_path(s_8021x));
+
+    set_secret(ifcfg,
+               secrets,
+               "IEEE_8021X_PIN",
+               nm_setting_802_1x_get_pin(s_8021x),
+               "IEEE_8021X_PIN_FLAGS",
+               nm_setting_802_1x_get_pin_flags(s_8021x));
+
+    if (!write_8021x_certs(s_8021x, secrets, blobs, FALSE, ifcfg, error))
+        return FALSE;
+
+    /* phase2/inner certs */
+    if (!write_8021x_certs(s_8021x, secrets, blobs, TRUE, ifcfg, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+static gboolean
+write_wireless_security_setting(NMConnection *connection,
+                                shvarFile *   ifcfg,
+                                GHashTable *  secrets,
+                                gboolean      adhoc,
+                                gboolean *    no_8021x,
+                                GError **     error)
+{
+    NMSettingWirelessSecurity *        s_wsec;
+    const char *                       key_mgmt, *auth_alg, *key, *proto, *cipher;
+    const char *                       psk = NULL;
+    gboolean                           wep = FALSE, wpa = FALSE, dynamic_wep = FALSE;
+    NMSettingWirelessSecurityWpsMethod wps_method;
+    char *                             tmp;
+    guint32                            i, num;
+    GString *                          str;
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    if (!s_wsec) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing '%s' setting",
+                    NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);
+        return FALSE;
+    }
+
+    key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec);
+    nm_assert(key_mgmt);
+
+    auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec);
+
+    if (!strcmp(key_mgmt, "none")) {
+        wep       = TRUE;
+        *no_8021x = TRUE;
+    } else if (!strcmp(key_mgmt, "wpa-psk")) {
+        svSetValueStr(ifcfg, "KEY_MGMT", "WPA-PSK");
+        wpa       = TRUE;
+        *no_8021x = TRUE;
+    } else if (!strcmp(key_mgmt, "sae")) {
+        svSetValueStr(ifcfg, "KEY_MGMT", "SAE");
+        wpa       = TRUE;
+        *no_8021x = TRUE;
+    } else if (!strcmp(key_mgmt, "owe")) {
+        svSetValueStr(ifcfg, "KEY_MGMT", "OWE");
+        wpa       = FALSE;
+        *no_8021x = TRUE;
+    } else if (!strcmp(key_mgmt, "ieee8021x")) {
+        svSetValueStr(ifcfg, "KEY_MGMT", "IEEE8021X");
+        dynamic_wep = TRUE;
+    } else if (!strcmp(key_mgmt, "wpa-eap")) {
+        svSetValueStr(ifcfg, "KEY_MGMT", "WPA-EAP");
+        wpa = TRUE;
+    } else if (!strcmp(key_mgmt, "wpa-eap-suite-b-192")) {
+        svSetValueStr(ifcfg, "KEY_MGMT", "WPA-EAP-SUITE-B-192");
+        wpa = TRUE;
+    }
+
+    if (auth_alg) {
+        if (!strcmp(auth_alg, "shared"))
+            svSetValueStr(ifcfg, "SECURITYMODE", "restricted");
+        else if (!strcmp(auth_alg, "open"))
+            svSetValueStr(ifcfg, "SECURITYMODE", "open");
+        else if (!strcmp(auth_alg, "leap")) {
+            svSetValueStr(ifcfg, "SECURITYMODE", "leap");
+            svSetValueStr(ifcfg,
+                          "IEEE_8021X_IDENTITY",
+                          nm_setting_wireless_security_get_leap_username(s_wsec));
+            set_secret(ifcfg,
+                       secrets,
+                       "IEEE_8021X_PASSWORD",
+                       nm_setting_wireless_security_get_leap_password(s_wsec),
+                       "IEEE_8021X_PASSWORD_FLAGS",
+                       nm_setting_wireless_security_get_leap_password_flags(s_wsec));
+            *no_8021x = TRUE;
+        }
+    }
+
+    wps_method = nm_setting_wireless_security_get_wps_method(s_wsec);
+    if (wps_method != NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
+        svSetValueEnum(ifcfg,
+                       "WPS_METHOD",
+                       nm_setting_wireless_security_wps_method_get_type(),
+                       wps_method);
+
+    /* WEP keys */
+
+    /* Clear any default key */
+    set_secret(ifcfg, secrets, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+
+    /* Clear existing keys */
+    for (i = 0; i < 4; i++) {
+        char tag[64];
+
+        numbered_tag(tag, "KEY_PASSPHRASE", i + 1);
+        set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+
+        numbered_tag(tag, "KEY", i + 1);
+        set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+    }
+
+    /* And write the new ones out */
+    if (wep) {
+        NMWepKeyType key_type;
+        const char * key_type_str = NULL;
+
+        /* Default WEP TX key index */
+        svSetValueInt64(ifcfg,
+                        "DEFAULTKEY",
+                        nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) + 1);
+
+        key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec);
+        switch (key_type) {
+        case NM_WEP_KEY_TYPE_KEY:
+            key_type_str = "key";
+            break;
+        case NM_WEP_KEY_TYPE_PASSPHRASE:
+            key_type_str = "passphrase";
+            break;
+        case NM_WEP_KEY_TYPE_UNKNOWN:
+            break;
+        }
+        svSetValue(ifcfg, "KEY_TYPE", key_type_str);
+
+        for (i = 0; i < 4; i++) {
+            key = nm_setting_wireless_security_get_wep_key(s_wsec, i);
+            if (key) {
+                gs_free char *ascii_key = NULL;
+                char          tag[64];
+                gboolean      key_valid = TRUE;
+
+                /* Passphrase needs a different ifcfg key since with WEP, there
+                 * are some passphrases that are indistinguishable from WEP hex
+                 * keys.
+                 */
+                if (key_type == NM_WEP_KEY_TYPE_UNKNOWN) {
+                    if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_KEY))
+                        key_type = NM_WEP_KEY_TYPE_KEY;
+                    else if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_PASSPHRASE))
+                        key_type = NM_WEP_KEY_TYPE_PASSPHRASE;
+                }
+
+                if (key_type == NM_WEP_KEY_TYPE_PASSPHRASE)
+                    numbered_tag(tag, "KEY_PASSPHRASE", i + 1);
+                else if (key_type == NM_WEP_KEY_TYPE_KEY) {
+                    numbered_tag(tag, "KEY", i + 1);
+
+                    /* Add 's:' prefix for ASCII keys */
+                    if (strlen(key) == 5 || strlen(key) == 13) {
+                        ascii_key = g_strdup_printf("s:%s", key);
+                        key       = ascii_key;
+                    }
+                } else {
+                    g_warn_if_reached();
+                    key_valid = FALSE;
+                }
+
+                if (key_valid) {
+                    set_secret(ifcfg,
+                               secrets,
+                               tag,
+                               key,
+                               "WEP_KEY_FLAGS",
+                               nm_setting_wireless_security_get_wep_key_flags(s_wsec));
+                }
+            }
+        }
+    }
+
+    /* WPA protos */
+    num = nm_setting_wireless_security_get_num_protos(s_wsec);
+    for (i = 0; i < num; i++) {
+        proto = nm_setting_wireless_security_get_proto(s_wsec, i);
+        if (proto && !strcmp(proto, "wpa"))
+            svSetValueStr(ifcfg, "WPA_ALLOW_WPA", "yes");
+        else if (proto && !strcmp(proto, "rsn"))
+            svSetValueStr(ifcfg, "WPA_ALLOW_WPA2", "yes");
+    }
+
+    /* WPA Pairwise ciphers */
+    str = g_string_new(NULL);
+    num = nm_setting_wireless_security_get_num_pairwise(s_wsec);
+    for (i = 0; i < num; i++) {
+        if (i > 0)
+            g_string_append_c(str, ' ');
+        cipher = nm_setting_wireless_security_get_pairwise(s_wsec, i);
+
+        /* Don't write out WEP40 or WEP104 if for some reason they are set; they
+         * are not valid pairwise ciphers.
+         */
+        if (strcmp(cipher, "wep40") && strcmp(cipher, "wep104")) {
+            tmp = g_ascii_strup(cipher, -1);
+            g_string_append(str, tmp);
+            g_free(tmp);
+        }
+    }
+    if (strlen(str->str) && (dynamic_wep == FALSE))
+        svSetValueStr(ifcfg, "CIPHER_PAIRWISE", str->str);
+    g_string_free(str, TRUE);
+
+    /* WPA Group ciphers */
+    str = g_string_new(NULL);
+    num = nm_setting_wireless_security_get_num_groups(s_wsec);
+    for (i = 0; i < num; i++) {
+        if (i > 0)
+            g_string_append_c(str, ' ');
+        cipher = nm_setting_wireless_security_get_group(s_wsec, i);
+        tmp    = g_ascii_strup(cipher, -1);
+        g_string_append(str, tmp);
+        g_free(tmp);
+    }
+    if (strlen(str->str) && (dynamic_wep == FALSE))
+        svSetValueStr(ifcfg, "CIPHER_GROUP", str->str);
+    g_string_free(str, TRUE);
+
+    if (wpa)
+        psk = nm_setting_wireless_security_get_psk(s_wsec);
+
+    set_secret(ifcfg,
+               secrets,
+               "WPA_PSK",
+               psk,
+               "WPA_PSK_FLAGS",
+               wpa ? nm_setting_wireless_security_get_psk_flags(s_wsec)
+                   : NM_SETTING_SECRET_FLAG_NONE);
+
+    if (nm_setting_wireless_security_get_pmf(s_wsec) != NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) {
+        svSetValueEnum(ifcfg,
+                       "PMF",
+                       nm_setting_wireless_security_pmf_get_type(),
+                       nm_setting_wireless_security_get_pmf(s_wsec));
+    }
+
+    if (nm_setting_wireless_security_get_fils(s_wsec)
+        != NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) {
+        svSetValueEnum(ifcfg,
+                       "FILS",
+                       nm_setting_wireless_security_fils_get_type(),
+                       nm_setting_wireless_security_get_fils(s_wsec));
+    }
+
+    return TRUE;
+}
+
+static gboolean
+write_wireless_setting(NMConnection *connection,
+                       shvarFile *   ifcfg,
+                       GHashTable *  secrets,
+                       gboolean *    no_8021x,
+                       GError **     error)
+{
+    NMSettingWireless *s_wireless;
+    GBytes *           ssid;
+    const guint8 *     ssid_data;
+    gsize              ssid_len;
+    const char *       mode, *bssid;
+    const char *       device_mac, *cloned_mac;
+    guint32            mtu, chan, i;
+    gboolean           adhoc = FALSE, hex_ssid = FALSE;
+    const char *const *macaddr_blacklist;
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    if (!s_wireless) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing '%s' setting",
+                    NM_SETTING_WIRELESS_SETTING_NAME);
+        return FALSE;
+    }
+
+    device_mac = nm_setting_wireless_get_mac_address(s_wireless);
+    svSetValueStr(ifcfg, "HWADDR", device_mac);
+
+    cloned_mac = nm_setting_wireless_get_cloned_mac_address(s_wireless);
+    svSetValueStr(ifcfg, "MACADDR", cloned_mac);
+
+    svSetValueStr(ifcfg,
+                  "GENERATE_MAC_ADDRESS_MASK",
+                  nm_setting_wireless_get_generate_mac_address_mask(s_wireless));
+
+    macaddr_blacklist = nm_setting_wireless_get_mac_address_blacklist(s_wireless);
+    if (macaddr_blacklist[0]) {
+        gs_free char *blacklist_str = NULL;
+
+        blacklist_str = g_strjoinv(" ", (char **) macaddr_blacklist);
+        svSetValueStr(ifcfg, "HWADDR_BLACKLIST", blacklist_str);
+    }
+
+    mtu = nm_setting_wireless_get_mtu(s_wireless);
+    svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    if (!ssid) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing SSID in '%s' setting",
+                    NM_SETTING_WIRELESS_SETTING_NAME);
+        return FALSE;
+    }
+    ssid_data = g_bytes_get_data(ssid, &ssid_len);
+    if (!ssid_len || ssid_len > 32) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Invalid SSID in '%s' setting",
+                    NM_SETTING_WIRELESS_SETTING_NAME);
+        return FALSE;
+    }
+
+    /* If the SSID contains any non-printable characters, we need to use the
+     * hex notation of the SSID instead.
+     */
+    if (ssid_len > 2 && ssid_data[0] == '0' && ssid_data[1] == 'x') {
+        hex_ssid = TRUE;
+        for (i = 2; i < ssid_len; i++) {
+            if (!g_ascii_isxdigit(ssid_data[i])) {
+                hex_ssid = FALSE;
+                break;
+            }
+        }
+    }
+    if (!hex_ssid) {
+        for (i = 0; i < ssid_len; i++) {
+            if (!g_ascii_isprint(ssid_data[i])) {
+                hex_ssid = TRUE;
+                break;
+            }
+        }
+    }
+
+    if (hex_ssid) {
+        GString *str;
+
+        /* Hex SSIDs don't get quoted */
+        str = g_string_sized_new(ssid_len * 2 + 3);
+        g_string_append(str, "0x");
+        for (i = 0; i < ssid_len; i++)
+            g_string_append_printf(str, "%02X", ssid_data[i]);
+        svSetValueStr(ifcfg, "ESSID", str->str);
+        g_string_free(str, TRUE);
+    } else {
+        char buf[33];
+
+        nm_assert(ssid_len <= 32);
+        memcpy(buf, ssid_data, ssid_len);
+        buf[ssid_len] = '\0';
+        svSetValueStr(ifcfg, "ESSID", buf);
+    }
+
+    mode = nm_setting_wireless_get_mode(s_wireless);
+    if (!mode) {
+        /* pass */
+    } else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_INFRA))
+        svSetValueStr(ifcfg, "MODE", "Managed");
+    else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+        svSetValueStr(ifcfg, "MODE", "Ad-Hoc");
+        adhoc = TRUE;
+    } else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_AP))
+        svSetValueStr(ifcfg, "MODE", "Ap");
+    else {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Invalid mode '%s' in '%s' setting",
+                    mode,
+                    NM_SETTING_WIRELESS_SETTING_NAME);
+        return FALSE;
+    }
+
+    chan = nm_setting_wireless_get_channel(s_wireless);
+    if (chan) {
+        svSetValueInt64(ifcfg, "CHANNEL", chan);
+    } else {
+        /* Band only set if channel is not, since channel implies band */
+        svSetValueStr(ifcfg, "BAND", nm_setting_wireless_get_band(s_wireless));
+    }
+
+    bssid = nm_setting_wireless_get_bssid(s_wireless);
+    svSetValueStr(ifcfg, "BSSID", bssid);
+
+    /* Ensure DEFAULTKEY and SECURITYMODE are cleared unless there's security;
+     * otherwise there's no way to detect WEP vs. open when WEP keys aren't
+     * saved.
+     */
+
+    if (nm_connection_get_setting_wireless_security(connection)) {
+        if (!write_wireless_security_setting(connection, ifcfg, secrets, adhoc, no_8021x, error))
+            return FALSE;
+    } else {
+        /* Clear out wifi security keys */
+        set_secret(ifcfg,
+                   secrets,
+                   "IEEE_8021X_PASSWORD",
+                   NULL,
+                   "IEEE_8021X_PASSWORD_FLAGS",
+                   NM_SETTING_SECRET_FLAG_NONE);
+
+        /* Clear existing keys */
+        set_secret(ifcfg, secrets, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+        for (i = 0; i < 4; i++) {
+            char tag[64];
+
+            numbered_tag(tag, "KEY_PASSPHRASE", i + 1);
+            set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+
+            numbered_tag(tag, "KEY", i + 1);
+            set_secret(ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+        }
+
+        set_secret(ifcfg, secrets, "WPA_PSK", NULL, "WPA_PSK_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+    }
+
+    svSetValueStr(ifcfg, "SSID_HIDDEN", nm_setting_wireless_get_hidden(s_wireless) ? "yes" : NULL);
+
+    switch (nm_setting_wireless_get_powersave(s_wireless)) {
+    case NM_SETTING_WIRELESS_POWERSAVE_IGNORE:
+        svSetValueStr(ifcfg, "POWERSAVE", "ignore");
+        break;
+    case NM_SETTING_WIRELESS_POWERSAVE_DISABLE:
+        svSetValueStr(ifcfg, "POWERSAVE", "disable");
+        break;
+    case NM_SETTING_WIRELESS_POWERSAVE_ENABLE:
+        svSetValueStr(ifcfg, "POWERSAVE", "enable");
+        break;
+    default:
+    case NM_SETTING_WIRELESS_POWERSAVE_DEFAULT:
+        break;
+    }
+
+    switch (nm_setting_wireless_get_mac_address_randomization(s_wireless)) {
+    case NM_SETTING_MAC_RANDOMIZATION_NEVER:
+        svSetValueStr(ifcfg, "MAC_ADDRESS_RANDOMIZATION", "never");
+        break;
+    case NM_SETTING_MAC_RANDOMIZATION_ALWAYS:
+        svSetValueStr(ifcfg, "MAC_ADDRESS_RANDOMIZATION", "always");
+        break;
+    case NM_SETTING_MAC_RANDOMIZATION_DEFAULT:
+    default:
+        svSetValueStr(ifcfg, "MAC_ADDRESS_RANDOMIZATION", "default");
+        break;
+    }
+
+    svSetValueTernary(ifcfg, "AP_ISOLATION", nm_setting_wireless_get_ap_isolation(s_wireless));
+
+    svSetValueStr(ifcfg, "TYPE", TYPE_WIRELESS);
+
+    return TRUE;
+}
+
+static gboolean
+write_infiniband_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingInfiniband *s_infiniband;
+    const char *         mac, *transport_mode, *parent;
+    guint32              mtu;
+    int                  p_key;
+
+    s_infiniband = nm_connection_get_setting_infiniband(connection);
+    if (!s_infiniband) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing '%s' setting",
+                    NM_SETTING_INFINIBAND_SETTING_NAME);
+        return FALSE;
+    }
+
+    mac = nm_setting_infiniband_get_mac_address(s_infiniband);
+    svSetValueStr(ifcfg, "HWADDR", mac);
+
+    mtu = nm_setting_infiniband_get_mtu(s_infiniband);
+    svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu);
+
+    transport_mode = nm_setting_infiniband_get_transport_mode(s_infiniband);
+    svSetValueBoolean(ifcfg, "CONNECTED_MODE", nm_streq(transport_mode, "connected"));
+
+    p_key = nm_setting_infiniband_get_p_key(s_infiniband);
+    if (p_key != -1) {
+        svSetValueStr(ifcfg, "PKEY", "yes");
+        svSetValueInt64(ifcfg, "PKEY_ID", p_key);
+
+        parent = nm_setting_infiniband_get_parent(s_infiniband);
+        if (parent)
+            svSetValueStr(ifcfg, "PHYSDEV", parent);
+    }
+
+    svSetValueStr(ifcfg, "TYPE", TYPE_INFINIBAND);
+
+    return TRUE;
+}
+
+static void
+write_hostname_setting(NMConnection *connection, shvarFile *ifcfg)
+{
+    NMSettingHostname *s_hostname;
+    NMTernary          t;
+
+    s_hostname = _nm_connection_get_setting(connection, NM_TYPE_SETTING_HOSTNAME);
+    if (!s_hostname)
+        return;
+
+    svSetValueInt64(ifcfg, "HOSTNAME_PRIORITY", nm_setting_hostname_get_priority(s_hostname));
+
+    t = nm_setting_hostname_get_from_dhcp(s_hostname);
+    svSetValueInt64_cond(ifcfg, "HOSTNAME_FROM_DHCP", t != NM_TERNARY_DEFAULT, t);
+
+    t = nm_setting_hostname_get_from_dns_lookup(s_hostname);
+    svSetValueInt64_cond(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP", t != NM_TERNARY_DEFAULT, t);
+
+    t = nm_setting_hostname_get_only_from_default(s_hostname);
+    svSetValueInt64_cond(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT", t != NM_TERNARY_DEFAULT, t);
+}
+
+static gboolean
+write_wired_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingWired *   s_wired;
+    const char *const *s390_subchannels;
+    guint32            mtu, num_opts, i;
+    const char *const *macaddr_blacklist;
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    if (!s_wired) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing '%s' setting",
+                    NM_SETTING_WIRED_SETTING_NAME);
+        return FALSE;
+    }
+
+    svSetValueStr(ifcfg, "HWADDR", nm_setting_wired_get_mac_address(s_wired));
+
+    svSetValueStr(ifcfg, "MACADDR", nm_setting_wired_get_cloned_mac_address(s_wired));
+
+    svSetValueStr(ifcfg,
+                  "GENERATE_MAC_ADDRESS_MASK",
+                  nm_setting_wired_get_generate_mac_address_mask(s_wired));
+
+    macaddr_blacklist = nm_setting_wired_get_mac_address_blacklist(s_wired);
+    if (macaddr_blacklist[0]) {
+        gs_free char *blacklist_str = NULL;
+
+        blacklist_str = g_strjoinv(" ", (char **) macaddr_blacklist);
+        svSetValueStr(ifcfg, "HWADDR_BLACKLIST", blacklist_str);
+    }
+
+    mtu = nm_setting_wired_get_mtu(s_wired);
+    svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu);
+
+    s390_subchannels = nm_setting_wired_get_s390_subchannels(s_wired);
+
+    {
+        gs_free char *tmp = NULL;
+        gsize         len = NM_PTRARRAY_LEN(s390_subchannels);
+
+        if (len == 2) {
+            tmp = g_strdup_printf("%s,%s", s390_subchannels[0], s390_subchannels[1]);
+        } else if (len == 3) {
+            tmp = g_strdup_printf("%s,%s,%s",
+                                  s390_subchannels[0],
+                                  s390_subchannels[1],
+                                  s390_subchannels[2]);
+        }
+
+        svSetValueStr(ifcfg, "SUBCHANNELS", tmp);
+    }
+
+    svSetValueStr(ifcfg, "NETTYPE", nm_setting_wired_get_s390_nettype(s_wired));
+
+    svSetValueStr(ifcfg, "PORTNAME", nm_setting_wired_get_s390_option_by_key(s_wired, "portname"));
+
+    svSetValueStr(ifcfg, "CTCPROT", nm_setting_wired_get_s390_option_by_key(s_wired, "ctcprot"));
+
+    num_opts = nm_setting_wired_get_num_s390_options(s_wired);
+    if (s390_subchannels && num_opts) {
+        nm_auto_free_gstring GString *tmp = NULL;
+
+        for (i = 0; i < num_opts; i++) {
+            const char *s390_key, *s390_val;
+
+            nm_setting_wired_get_s390_option(s_wired, i, &s390_key, &s390_val);
+
+            /* portname is handled separately */
+            if (NM_IN_STRSET(s390_key, "portname", "ctcprot"))
+                continue;
+
+            if (strchr(s390_key, '=')) {
+                /* this key cannot be expressed. But after all, it's not valid anyway
+                 * and the connection shouldn't even verify. */
+                continue;
+            }
+
+            if (!tmp)
+                tmp = g_string_sized_new(30);
+            else
+                g_string_append_c(tmp, ' ');
+            nm_utils_escaped_tokens_escape_gstr(s390_key, NM_ASCII_SPACES, tmp);
+            g_string_append_c(tmp, '=');
+            nm_utils_escaped_tokens_escape_gstr(s390_val, NM_ASCII_SPACES, tmp);
+        }
+        if (tmp)
+            svSetValueStr(ifcfg, "OPTIONS", tmp->str);
+    }
+
+    svSetValueStr(ifcfg, "TYPE", TYPE_ETHERNET);
+
+    return TRUE;
+}
+
+static void
+_ethtool_gstring_prepare(GString **str, gboolean *is_first, char cmdline_flag, const char *iface)
+{
+    if (!*is_first) {
+        nm_assert(*str && (*str)->len > 0);
+        return;
+    }
+
+    if (!*str)
+        *str = g_string_sized_new(30);
+    else {
+        nm_assert((*str)->len > 0);
+        g_string_append(*str, " ; ");
+    }
+    g_string_append_printf(*str, "-%c %s", cmdline_flag, iface);
+    *is_first = FALSE;
+}
+
+static gboolean
+write_ethtool_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingWired *        s_wired;
+    NMSettingEthtool *      s_ethtool;
+    const char *            duplex;
+    guint32                 speed;
+    GString *               str = NULL;
+    gboolean                auto_negotiate;
+    NMSettingWiredWakeOnLan wol;
+    const char *            wol_password;
+
+    s_wired   = nm_connection_get_setting_wired(connection);
+    s_ethtool = NM_SETTING_ETHTOOL(nm_connection_get_setting(connection, NM_TYPE_SETTING_ETHTOOL));
+
+    if (!s_wired && !s_ethtool)
+        return TRUE;
+
+    if (s_wired) {
+        auto_negotiate = nm_setting_wired_get_auto_negotiate(s_wired);
+        speed          = nm_setting_wired_get_speed(s_wired);
+        duplex         = nm_setting_wired_get_duplex(s_wired);
+
+        /* autoneg off + speed 0 + duplex NULL, means we want NM
+         * to skip link configuration which is default. So write
+         * down link config only if we have auto-negotiate true or
+         * a valid value for one among speed and duplex.
+         */
+        if (auto_negotiate) {
+            str = g_string_sized_new(64);
+            g_string_printf(str, "autoneg on");
+        } else if (speed || duplex) {
+            str = g_string_sized_new(64);
+            g_string_printf(str, "autoneg off");
+        }
+        if (speed)
+            g_string_append_printf(str, " speed %u", speed);
+        if (duplex)
+            g_string_append_printf(str, " duplex %s", duplex);
+
+        wol          = nm_setting_wired_get_wake_on_lan(s_wired);
+        wol_password = nm_setting_wired_get_wake_on_lan_password(s_wired);
+
+        svSetValue(ifcfg,
+                   "ETHTOOL_WAKE_ON_LAN",
+                   wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE ? "ignore" : NULL);
+        if (!NM_IN_SET(wol,
+                       NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE,
+                       NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT)) {
+            if (!str)
+                str = g_string_sized_new(30);
+            else
+                g_string_append(str, " ");
+
+            g_string_append(str, "wol ");
+
+            if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
+                g_string_append(str, "p");
+            if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
+                g_string_append(str, "u");
+            if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST))
+                g_string_append(str, "m");
+            if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST))
+                g_string_append(str, "b");
+            if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_ARP))
+                g_string_append(str, "a");
+            if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
+                g_string_append(str, "g");
+
+            if (!NM_FLAGS_ANY(wol, NM_SETTING_WIRED_WAKE_ON_LAN_ALL))
+                g_string_append(str, "d");
+
+            if (wol_password && NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
+                g_string_append_printf(str, "s sopass %s", wol_password);
+        }
+    }
+
+    if (s_ethtool) {
+        NMEthtoolID          ethtool_id;
+        NMSettingConnection *s_con;
+        const char *         iface;
+        gboolean             is_first;
+        guint32              u32;
+        gboolean             b;
+
+        s_con = nm_connection_get_setting_connection(connection);
+        if (s_con) {
+            iface = nm_setting_connection_get_interface_name(s_con);
+            if (iface
+                && (!iface[0]
+                    || !NM_STRCHAR_ALL(iface,
+                                       ch,
+                                       (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
+                                           || (ch >= '0' && ch <= '9') || NM_IN_SET(ch, '_'))))
+                iface = NULL;
+        } else
+            iface = NULL;
+        if (!iface)
+            iface = "net0";
+
+        is_first = TRUE;
+        for (ethtool_id = _NM_ETHTOOL_ID_FEATURE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST;
+             ethtool_id++) {
+            nm_assert(nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id));
+            if (!nm_setting_option_get_boolean(NM_SETTING(s_ethtool),
+                                               nm_ethtool_data[ethtool_id]->optname,
+                                               &b))
+                continue;
+
+            _ethtool_gstring_prepare(&str, &is_first, 'K', iface);
+            g_string_append_c(str, ' ');
+            g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id));
+            g_string_append(str, b ? " on" : " off");
+        }
+
+        is_first = TRUE;
+        for (ethtool_id = _NM_ETHTOOL_ID_COALESCE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_COALESCE_LAST;
+             ethtool_id++) {
+            nm_assert(nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id));
+            if (!nm_setting_option_get_uint32(NM_SETTING(s_ethtool),
+                                              nm_ethtool_data[ethtool_id]->optname,
+                                              &u32))
+                continue;
+
+            _ethtool_gstring_prepare(&str, &is_first, 'C', iface);
+            g_string_append_c(str, ' ');
+            g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id));
+            g_string_append_printf(str, " %" G_GUINT32_FORMAT, u32);
+        }
+
+        is_first = TRUE;
+        for (ethtool_id = _NM_ETHTOOL_ID_RING_FIRST; ethtool_id <= _NM_ETHTOOL_ID_RING_LAST;
+             ethtool_id++) {
+            nm_assert(nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id));
+            if (!nm_setting_option_get_uint32(NM_SETTING(s_ethtool),
+                                              nm_ethtool_data[ethtool_id]->optname,
+                                              &u32))
+                continue;
+
+            _ethtool_gstring_prepare(&str, &is_first, 'G', iface);
+            g_string_append_c(str, ' ');
+            g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id));
+            g_string_append_printf(str, " %" G_GUINT32_FORMAT, u32);
+        }
+    }
+
+    if (str) {
+        svSetValueStr(ifcfg, "ETHTOOL_OPTS", str->str);
+        g_string_free(str, TRUE);
+    }
+
+    return TRUE;
+}
+
+static char *
+vlan_priority_maplist_to_stringlist(NMSettingVlan *s_vlan, NMVlanPriorityMap map)
+{
+    char **strlist;
+    char * value;
+
+    if (map == NM_VLAN_INGRESS_MAP)
+        g_object_get(G_OBJECT(s_vlan), NM_SETTING_VLAN_INGRESS_PRIORITY_MAP, &strlist, NULL);
+    else if (map == NM_VLAN_EGRESS_MAP)
+        g_object_get(G_OBJECT(s_vlan), NM_SETTING_VLAN_EGRESS_PRIORITY_MAP, &strlist, NULL);
+    else
+        return NULL;
+
+    if (strlist[0])
+        value = g_strjoinv(",", strlist);
+    else
+        value = NULL;
+    g_strfreev(strlist);
+
+    return value;
+}
+
+static gboolean
+write_wired_for_virtual(NMConnection *connection, shvarFile *ifcfg)
+{
+    NMSettingWired *s_wired;
+    gboolean        has_wired = FALSE;
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    if (s_wired) {
+        const char *device_mac, *cloned_mac;
+        guint32     mtu;
+
+        has_wired = TRUE;
+
+        device_mac = nm_setting_wired_get_mac_address(s_wired);
+        svSetValue(ifcfg, "HWADDR", device_mac ?: "");
+
+        cloned_mac = nm_setting_wired_get_cloned_mac_address(s_wired);
+        svSetValueStr(ifcfg, "MACADDR", cloned_mac);
+
+        svSetValueStr(ifcfg,
+                      "GENERATE_MAC_ADDRESS_MASK",
+                      nm_setting_wired_get_generate_mac_address_mask(s_wired));
+
+        mtu = nm_setting_wired_get_mtu(s_wired);
+        svSetValueInt64_cond(ifcfg, "MTU", mtu != 0, mtu);
+    }
+    return has_wired;
+}
+
+static gboolean
+write_vlan_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
+{
+    NMSettingVlan *s_vlan;
+    char *         tmp;
+    guint32        vlan_flags = 0;
+    gsize          s_buf_len;
+    char           s_buf[50], *s_buf_ptr;
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    if (!s_vlan) {
+        g_set_error_literal(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_FAILED,
+                            "Missing VLAN setting");
+        return FALSE;
+    }
+
+    svSetValueStr(ifcfg, "VLAN", "yes");
+    svSetValueStr(ifcfg, "TYPE", TYPE_VLAN);
+    svSetValueStr(ifcfg, "PHYSDEV", nm_setting_vlan_get_parent(s_vlan));
+    svSetValueInt64(ifcfg, "VLAN_ID", nm_setting_vlan_get_id(s_vlan));
+
+    vlan_flags = nm_setting_vlan_get_flags(s_vlan);
+    svSetValueBoolean(ifcfg, "REORDER_HDR", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS));
+    svSetValueBoolean(ifcfg, "GVRP", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_GVRP));
+
+    nm_utils_strbuf_init(s_buf, &s_buf_ptr, &s_buf_len);
+
+    if (NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_LOOSE_BINDING))
+        nm_utils_strbuf_append_str(&s_buf_ptr, &s_buf_len, "LOOSE_BINDING");
+    if (!NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS))
+        nm_utils_strbuf_append(&s_buf_ptr, &s_buf_len, "%sNO_REORDER_HDR", s_buf[0] ? "," : "");
+
+    svSetValueStr(ifcfg, "VLAN_FLAGS", s_buf);
+
+    svSetValueBoolean(ifcfg, "MVRP", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_MVRP));
+
+    tmp = vlan_priority_maplist_to_stringlist(s_vlan, NM_VLAN_INGRESS_MAP);
+    svSetValueStr(ifcfg, "VLAN_INGRESS_PRIORITY_MAP", tmp);
+    g_free(tmp);
+
+    tmp = vlan_priority_maplist_to_stringlist(s_vlan, NM_VLAN_EGRESS_MAP);
+    svSetValueStr(ifcfg, "VLAN_EGRESS_PRIORITY_MAP", tmp);
+    g_free(tmp);
+
+    *wired = write_wired_for_virtual(connection, ifcfg);
+
+    return TRUE;
+}
+
+static gboolean
+write_bond_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
+{
+    NMSettingBond *s_bond;
+    guint32        i, num_opts;
+
+    s_bond = nm_connection_get_setting_bond(connection);
+    if (!s_bond) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing '%s' setting",
+                    NM_SETTING_BOND_SETTING_NAME);
+        return FALSE;
+    }
+
+    num_opts = nm_setting_bond_get_num_options(s_bond);
+    if (num_opts) {
+        nm_auto_free_gstring GString *str = NULL;
+        const char *                  name, *value;
+
+        str = g_string_sized_new(64);
+        for (i = 0; i < num_opts; i++) {
+            if (str->len)
+                g_string_append_c(str, ' ');
+            nm_setting_bond_get_option(s_bond, i, &name, &value);
+            g_string_append_printf(str, "%s=%s", name, value);
+        }
+
+        svSetValueStr(ifcfg, "BONDING_OPTS", str->str);
+    }
+
+    svSetValueStr(ifcfg, "TYPE", TYPE_BOND);
+    svSetValueStr(ifcfg, "BONDING_MASTER", "yes");
+
+    *wired = write_wired_for_virtual(connection, ifcfg);
+
+    return TRUE;
+}
+
+static gboolean
+write_team_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
+{
+    NMSettingTeam *s_team;
+    const char *   config;
+
+    s_team = nm_connection_get_setting_team(connection);
+    if (!s_team) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing '%s' setting",
+                    NM_SETTING_TEAM_SETTING_NAME);
+        return FALSE;
+    }
+
+    config = nm_setting_team_get_config(s_team);
+    svSetValueStr(ifcfg, "TEAM_CONFIG", config);
+
+    *wired = write_wired_for_virtual(connection, ifcfg);
+
+    return TRUE;
+}
+
+#define get_setting_default_checked_boolean(dflt, setting, prop)                       \
+    ({                                                                                 \
+        const gboolean _dflt = (dflt);                                                 \
+                                                                                       \
+        nm_assert(NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN(                                 \
+                      g_object_class_find_property(G_OBJECT_GET_CLASS(setting), prop)) \
+                  == _dflt);                                                           \
+        _dflt;                                                                         \
+    })
+
+#define get_setting_default_checked_uint(dflt, setting, prop)                          \
+    ({                                                                                 \
+        const guint _dflt = (dflt);                                                    \
+                                                                                       \
+        nm_assert(NM_G_PARAM_SPEC_GET_DEFAULT_UINT(                                    \
+                      g_object_class_find_property(G_OBJECT_GET_CLASS(setting), prop)) \
+                  == _dflt);                                                           \
+        _dflt;                                                                         \
+    })
+
+#define get_setting_default_checked_uint64(dflt, setting, prop)                        \
+    ({                                                                                 \
+        const guint64 _dflt = (dflt);                                                  \
+                                                                                       \
+        nm_assert(NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(                                  \
+                      g_object_class_find_property(G_OBJECT_GET_CLASS(setting), prop)) \
+                  == _dflt);                                                           \
+        _dflt;                                                                         \
+    })
+
+static gboolean
+write_bridge_vlans(NMSetting * setting,
+                   const char *property_name,
+                   shvarFile * ifcfg,
+                   const char *key,
+                   GError **   error)
+{
+    gs_unref_ptrarray GPtrArray *vlans = NULL;
+    NMBridgeVlan *               vlan;
+    GString *                    string;
+    guint                        i;
+
+    g_object_get(setting, property_name, &vlans, NULL);
+
+    if (!vlans || !vlans->len)
+        return TRUE;
+
+    string = g_string_new("");
+    for (i = 0; i < vlans->len; i++) {
+        gs_free char *vlan_str = NULL;
+
+        vlan     = vlans->pdata[i];
+        vlan_str = nm_bridge_vlan_to_str(vlan, error);
+        if (!vlan_str)
+            return FALSE;
+        if (string->len > 0)
+            g_string_append(string, ",");
+        nm_utils_escaped_tokens_escape_gstr_assert(vlan_str, ",", string);
+    }
+
+    svSetValueStr(ifcfg, key, string->str);
+    g_string_free(string, TRUE);
+    return TRUE;
+}
+
+static gboolean
+write_bridge_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
+{
+    NMSettingBridge *s_bridge;
+    guint32          u32;
+    guint64          u64;
+    guint            u;
+    gboolean         b;
+    const char *     s;
+    GString *        opts;
+
+    s_bridge = nm_connection_get_setting_bridge(connection);
+    if (!s_bridge) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Missing '%s' setting",
+                    NM_SETTING_BRIDGE_SETTING_NAME);
+        return FALSE;
+    }
+
+    svSetValueBoolean(ifcfg, "STP", FALSE);
+
+    s = nm_setting_bridge_get_mac_address(s_bridge);
+    svSetValueStr(ifcfg, "BRIDGE_MACADDR", s);
+
+    /* Bridge options */
+    opts = g_string_sized_new(32);
+
+    if (nm_setting_bridge_get_stp(s_bridge)) {
+        svSetValueStr(ifcfg, "STP", "yes");
+
+        u32 = nm_setting_bridge_get_forward_delay(s_bridge);
+        if (u32
+            != get_setting_default_checked_uint(NM_BRIDGE_FORWARD_DELAY_DEF,
+                                                s_bridge,
+                                                NM_SETTING_BRIDGE_FORWARD_DELAY))
+            svSetValueInt64(ifcfg, "DELAY", u32);
+
+        g_string_append_printf(opts, "priority=%u", nm_setting_bridge_get_priority(s_bridge));
+
+        u32 = nm_setting_bridge_get_hello_time(s_bridge);
+        if (u32
+            != get_setting_default_checked_uint(NM_BRIDGE_HELLO_TIME_DEF,
+                                                s_bridge,
+                                                NM_SETTING_BRIDGE_HELLO_TIME)) {
+            nm_gstring_add_space_delimiter(opts);
+            g_string_append_printf(opts, "hello_time=%u", u32);
+        }
+
+        u32 = nm_setting_bridge_get_max_age(s_bridge);
+        if (u32
+            != get_setting_default_checked_uint(NM_BRIDGE_MAX_AGE_DEF,
+                                                s_bridge,
+                                                NM_SETTING_BRIDGE_MAX_AGE)) {
+            nm_gstring_add_space_delimiter(opts);
+            g_string_append_printf(opts, "max_age=%u", u32);
+        }
+    }
+
+    u = nm_setting_bridge_get_ageing_time(s_bridge);
+    if (u
+        != get_setting_default_checked_uint(NM_BRIDGE_AGEING_TIME_DEF,
+                                            s_bridge,
+                                            NM_SETTING_BRIDGE_AGEING_TIME)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "ageing_time=%u", u);
+    }
+
+    s = nm_setting_bridge_get_group_address(s_bridge);
+    if (s) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "group_address=%s", s);
+    }
+
+    u32 = nm_setting_bridge_get_group_forward_mask(s_bridge);
+    if (u32
+        != get_setting_default_checked_uint(0, s_bridge, NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "group_fwd_mask=%u", u32);
+    }
+
+    u32 = nm_setting_bridge_get_multicast_hash_max(s_bridge);
+    if (u32
+        != get_setting_default_checked_uint(NM_BRIDGE_MULTICAST_HASH_MAX_DEF,
+                                            s_bridge,
+                                            NM_SETTING_BRIDGE_MULTICAST_HASH_MAX)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_hash_max=%u", u32);
+    }
+
+    u32 = nm_setting_bridge_get_multicast_last_member_count(s_bridge);
+    if (u32
+        != get_setting_default_checked_uint(NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF,
+                                            s_bridge,
+                                            NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_last_member_count=%u", u32);
+    }
+
+    u64 = nm_setting_bridge_get_multicast_last_member_interval(s_bridge);
+    if (u64
+        != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF,
+                                              s_bridge,
+                                              NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_last_member_interval=%" G_GUINT64_FORMAT, u64);
+    }
+
+    u64 = nm_setting_bridge_get_multicast_membership_interval(s_bridge);
+    if (u64
+        != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF,
+                                              s_bridge,
+                                              NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_membership_interval=%" G_GUINT64_FORMAT, u64);
+    }
+
+    b = nm_setting_bridge_get_multicast_querier(s_bridge);
+    if (b
+        != get_setting_default_checked_boolean(NM_BRIDGE_MULTICAST_QUERIER_DEF,
+                                               s_bridge,
+                                               NM_SETTING_BRIDGE_MULTICAST_QUERIER)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_querier=%u", (guint) b);
+    }
+
+    u64 = nm_setting_bridge_get_multicast_querier_interval(s_bridge);
+    if (u64
+        != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF,
+                                              s_bridge,
+                                              NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_querier_interval=%" G_GUINT64_FORMAT, u64);
+    }
+
+    u64 = nm_setting_bridge_get_multicast_query_interval(s_bridge);
+    if (u64
+        != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF,
+                                              s_bridge,
+                                              NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_query_interval=%" G_GUINT64_FORMAT, u64);
+    }
+
+    u64 = nm_setting_bridge_get_multicast_query_response_interval(s_bridge);
+    if (u64
+        != get_setting_default_checked_uint64(
+            NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF,
+            s_bridge,
+            NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_query_response_interval=%" G_GUINT64_FORMAT, u64);
+    }
+
+    b = nm_setting_bridge_get_multicast_query_use_ifaddr(s_bridge);
+    if (b
+        != get_setting_default_checked_boolean(NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF,
+                                               s_bridge,
+                                               NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_query_use_ifaddr=%u", (guint) b);
+    }
+
+    b = nm_setting_bridge_get_multicast_snooping(s_bridge);
+    if (b
+        != get_setting_default_checked_boolean(NM_BRIDGE_MULTICAST_SNOOPING_DEF,
+                                               s_bridge,
+                                               NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_snooping=%u", (guint32) b);
+    }
+
+    u32 = nm_setting_bridge_get_multicast_startup_query_count(s_bridge);
+    if (u32
+        != get_setting_default_checked_uint(NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF,
+                                            s_bridge,
+                                            NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_startup_query_count=%u", u32);
+    }
+
+    u64 = nm_setting_bridge_get_multicast_startup_query_interval(s_bridge);
+    if (u64
+        != get_setting_default_checked_uint64(NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF,
+                                              s_bridge,
+                                              NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_startup_query_interval=%" G_GUINT64_FORMAT, u64);
+    }
+
+    s = nm_setting_bridge_get_multicast_router(s_bridge);
+    if (s) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "multicast_router=%s", s);
+    }
+
+    b = nm_setting_bridge_get_vlan_filtering(s_bridge);
+    if (b
+        != get_setting_default_checked_boolean(FALSE, s_bridge, NM_SETTING_BRIDGE_VLAN_FILTERING)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "vlan_filtering=%u", (guint32) b);
+    }
+
+    u32 = nm_setting_bridge_get_vlan_default_pvid(s_bridge);
+    if (u32
+        != get_setting_default_checked_uint(NM_BRIDGE_VLAN_DEFAULT_PVID_DEF,
+                                            s_bridge,
+                                            NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "default_pvid=%u", u32);
+    }
+
+    s = nm_setting_bridge_get_vlan_protocol(s_bridge);
+    if (s) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "vlan_protocol=%s", s);
+    }
+
+    b = nm_setting_bridge_get_vlan_stats_enabled(s_bridge);
+    if (b
+        != get_setting_default_checked_boolean(NM_BRIDGE_VLAN_STATS_ENABLED_DEF,
+                                               s_bridge,
+                                               NM_SETTING_BRIDGE_VLAN_STATS_ENABLED)) {
+        nm_gstring_add_space_delimiter(opts);
+        g_string_append_printf(opts, "vlan_stats_enabled=%u", (guint) b);
+    }
+
+    if (opts->len)
+        svSetValueStr(ifcfg, "BRIDGING_OPTS", opts->str);
+    g_string_free(opts, TRUE);
+
+    if (!write_bridge_vlans((NMSetting *) s_bridge,
+                            NM_SETTING_BRIDGE_VLANS,
+                            ifcfg,
+                            "BRIDGE_VLANS",
+                            error))
+        return FALSE;
+
+    svSetValueStr(ifcfg, "TYPE", TYPE_BRIDGE);
+
+    *wired = write_wired_for_virtual(connection, ifcfg);
+
+    return TRUE;
+}
+
+static gboolean
+write_bridge_port_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingBridgePort *s_port;
+    guint32              u32;
+    GString *            string;
+
+    s_port = nm_connection_get_setting_bridge_port(connection);
+    if (!s_port)
+        return TRUE;
+
+    /* Bridge options */
+    string = g_string_sized_new(32);
+
+    u32 = nm_setting_bridge_port_get_priority(s_port);
+    if (u32
+        != get_setting_default_checked_uint(NM_BRIDGE_PORT_PRIORITY_DEF,
+                                            s_port,
+                                            NM_SETTING_BRIDGE_PORT_PRIORITY))
+        g_string_append_printf(string, "priority=%u", u32);
+
+    u32 = nm_setting_bridge_port_get_path_cost(s_port);
+    if (u32
+        != get_setting_default_checked_uint(NM_BRIDGE_PORT_PATH_COST_DEF,
+                                            s_port,
+                                            NM_SETTING_BRIDGE_PORT_PATH_COST)) {
+        if (string->len)
+            g_string_append_c(string, ' ');
+        g_string_append_printf(string, "path_cost=%u", u32);
+    }
+
+    if (nm_setting_bridge_port_get_hairpin_mode(s_port)) {
+        if (string->len)
+            g_string_append_c(string, ' ');
+        g_string_append_printf(string, "hairpin_mode=1");
+    }
+
+    if (string->len)
+        svSetValueStr(ifcfg, "BRIDGING_OPTS", string->str);
+    g_string_free(string, TRUE);
+
+    if (!write_bridge_vlans((NMSetting *) s_port,
+                            NM_SETTING_BRIDGE_PORT_VLANS,
+                            ifcfg,
+                            "BRIDGE_PORT_VLANS",
+                            error))
+        return FALSE;
+
+    return TRUE;
+}
+
+static gboolean
+write_team_port_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingTeamPort *s_port;
+    const char *       config;
+
+    s_port = nm_connection_get_setting_team_port(connection);
+    if (!s_port)
+        return TRUE;
+
+    config = nm_setting_team_port_get_config(s_port);
+    svSetValueStr(ifcfg, "TEAM_PORT_CONFIG", config);
+
+    return TRUE;
+}
+
+static void
+write_dcb_flags(shvarFile *ifcfg, const char *tag, NMSettingDcbFlags flags)
+{
+    char prop[NM_STRLEN("DCB_xxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyy")];
+
+    nm_sprintf_buf(prop, "DCB_%s_ENABLE", tag);
+    svSetValueStr(ifcfg, prop, (flags & NM_SETTING_DCB_FLAG_ENABLE) ? "yes" : NULL);
+
+    nm_sprintf_buf(prop, "DCB_%s_ADVERTISE", tag);
+    svSetValueStr(ifcfg, prop, (flags & NM_SETTING_DCB_FLAG_ADVERTISE) ? "yes" : NULL);
+
+    nm_sprintf_buf(prop, "DCB_%s_WILLING", tag);
+    svSetValueStr(ifcfg, prop, (flags & NM_SETTING_DCB_FLAG_WILLING) ? "yes" : NULL);
+}
+
+static void
+write_dcb_app(shvarFile *ifcfg, const char *tag, NMSettingDcbFlags flags, int priority)
+{
+    char prop[NM_STRLEN("DCB_xxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyy")];
+
+    write_dcb_flags(ifcfg, tag, flags);
+
+    if ((flags & NM_SETTING_DCB_FLAG_ENABLE) && (priority >= 0)) {
+        nm_sprintf_buf(prop, "DCB_%s_PRIORITY", tag);
+        svSetValueInt64(ifcfg, prop, priority);
+    }
+}
+
+typedef gboolean (*DcbGetBoolFunc)(NMSettingDcb *, guint);
+
+static void
+write_dcb_bool_array(shvarFile *       ifcfg,
+                     const char *      key,
+                     NMSettingDcb *    s_dcb,
+                     NMSettingDcbFlags flags,
+                     DcbGetBoolFunc    get_func)
+{
+    char  str[9];
+    guint i;
+
+    if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
+        return;
+
+    str[8] = 0;
+    for (i = 0; i < 8; i++)
+        str[i] = get_func(s_dcb, i) ? '1' : '0';
+    svSetValueStr(ifcfg, key, str);
+}
+
+typedef guint (*DcbGetUintFunc)(NMSettingDcb *, guint);
+
+static void
+write_dcb_uint_array(shvarFile *       ifcfg,
+                     const char *      key,
+                     NMSettingDcb *    s_dcb,
+                     NMSettingDcbFlags flags,
+                     DcbGetUintFunc    get_func)
+{
+    char  str[9];
+    guint i, num;
+
+    if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
+        return;
+
+    str[8] = 0;
+    for (i = 0; i < 8; i++) {
+        num = get_func(s_dcb, i);
+        if (num < 10)
+            str[i] = '0' + num;
+        else if (num == 15)
+            str[i] = 'f';
+        else
+            g_assert_not_reached();
+    }
+    svSetValueStr(ifcfg, key, str);
+}
+
+static void
+write_dcb_percent_array(shvarFile *       ifcfg,
+                        const char *      key,
+                        NMSettingDcb *    s_dcb,
+                        NMSettingDcbFlags flags,
+                        DcbGetUintFunc    get_func)
+{
+    GString *str;
+    guint    i;
+
+    if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
+        return;
+
+    str = g_string_sized_new(30);
+    for (i = 0; i < 8; i++) {
+        if (str->len)
+            g_string_append_c(str, ',');
+        g_string_append_printf(str, "%d", get_func(s_dcb, i));
+    }
+    svSetValueStr(ifcfg, key, str->str);
+    g_string_free(str, TRUE);
+}
+
+static gboolean
+write_dcb_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingDcb *    s_dcb;
+    NMSettingDcbFlags flags;
+
+    s_dcb = nm_connection_get_setting_dcb(connection);
+    if (!s_dcb)
+        return TRUE;
+
+    svSetValueStr(ifcfg, "DCB", "yes");
+
+    write_dcb_app(ifcfg,
+                  "APP_FCOE",
+                  nm_setting_dcb_get_app_fcoe_flags(s_dcb),
+                  nm_setting_dcb_get_app_fcoe_priority(s_dcb));
+    if (nm_setting_dcb_get_app_fcoe_flags(s_dcb) & NM_SETTING_DCB_FLAG_ENABLE)
+        svSetValueStr(ifcfg, KEY_DCB_APP_FCOE_MODE, nm_setting_dcb_get_app_fcoe_mode(s_dcb));
+
+    write_dcb_app(ifcfg,
+                  "APP_ISCSI",
+                  nm_setting_dcb_get_app_iscsi_flags(s_dcb),
+                  nm_setting_dcb_get_app_iscsi_priority(s_dcb));
+    write_dcb_app(ifcfg,
+                  "APP_FIP",
+                  nm_setting_dcb_get_app_fip_flags(s_dcb),
+                  nm_setting_dcb_get_app_fip_priority(s_dcb));
+
+    write_dcb_flags(ifcfg, "PFC", nm_setting_dcb_get_priority_flow_control_flags(s_dcb));
+    write_dcb_bool_array(ifcfg,
+                         KEY_DCB_PFC_UP,
+                         s_dcb,
+                         nm_setting_dcb_get_priority_flow_control_flags(s_dcb),
+                         nm_setting_dcb_get_priority_flow_control);
+
+    flags = nm_setting_dcb_get_priority_group_flags(s_dcb);
+    write_dcb_flags(ifcfg, "PG", flags);
+    write_dcb_uint_array(ifcfg, KEY_DCB_PG_ID, s_dcb, flags, nm_setting_dcb_get_priority_group_id);
+    write_dcb_percent_array(ifcfg,
+                            KEY_DCB_PG_PCT,
+                            s_dcb,
+                            flags,
+                            nm_setting_dcb_get_priority_group_bandwidth);
+    write_dcb_percent_array(ifcfg,
+                            KEY_DCB_PG_UPPCT,
+                            s_dcb,
+                            flags,
+                            nm_setting_dcb_get_priority_bandwidth);
+    write_dcb_bool_array(ifcfg,
+                         KEY_DCB_PG_STRICT,
+                         s_dcb,
+                         flags,
+                         nm_setting_dcb_get_priority_strict_bandwidth);
+    write_dcb_uint_array(ifcfg,
+                         KEY_DCB_PG_UP2TC,
+                         s_dcb,
+                         flags,
+                         nm_setting_dcb_get_priority_traffic_class);
+
+    return TRUE;
+}
+
+static void
+write_connection_setting(NMSettingConnection *s_con, shvarFile *ifcfg)
+{
+    guint32              n, i;
+    nm_auto_free_gstring GString *str = NULL;
+    const char *                  master, *master_iface = NULL, *type;
+    int                           vint;
+    gint32                        vint32;
+    NMSettingConnectionMdns       mdns;
+    NMSettingConnectionLlmnr      llmnr;
+    guint32                       vuint32;
+    const char *                  tmp, *mud_url;
+
+    svSetValueStr(ifcfg, "NAME", nm_setting_connection_get_id(s_con));
+    svSetValueStr(ifcfg, "UUID", nm_setting_connection_get_uuid(s_con));
+    svSetValueStr(ifcfg, "STABLE_ID", nm_setting_connection_get_stable_id(s_con));
+    svSetValueStr(ifcfg, "DEVICE", nm_setting_connection_get_interface_name(s_con));
+    svSetValueBoolean(ifcfg, "ONBOOT", nm_setting_connection_get_autoconnect(s_con));
+
+    vint = nm_setting_connection_get_autoconnect_priority(s_con);
+    svSetValueInt64_cond(ifcfg,
+                         "AUTOCONNECT_PRIORITY",
+                         vint != NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT,
+                         vint);
+
+    vint = nm_setting_connection_get_autoconnect_retries(s_con);
+    svSetValueInt64_cond(ifcfg, "AUTOCONNECT_RETRIES", vint != -1, vint);
+
+    vint = nm_setting_connection_get_multi_connect(s_con);
+    svSetValueInt64_cond(ifcfg, "MULTI_CONNECT", vint != NM_CONNECTION_MULTI_CONNECT_DEFAULT, vint);
+
+    /* Only save the value for master connections */
+    type = nm_setting_connection_get_connection_type(s_con);
+    if (_nm_connection_type_is_master(type)) {
+        NMSettingConnectionAutoconnectSlaves autoconnect_slaves;
+        autoconnect_slaves = nm_setting_connection_get_autoconnect_slaves(s_con);
+        svSetValueStr(ifcfg,
+                      "AUTOCONNECT_SLAVES",
+                      autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES  ? "yes"
+                      : autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO ? "no"
+                                                                                          : NULL);
+    }
+    switch (nm_setting_connection_get_lldp(s_con)) {
+    case NM_SETTING_CONNECTION_LLDP_ENABLE_RX:
+        tmp = "rx";
+        break;
+    case NM_SETTING_CONNECTION_LLDP_DISABLE:
+        tmp = "no";
+        break;
+    default:
+        tmp = NULL;
+    }
+    svSetValueStr(ifcfg, "LLDP", tmp);
+
+    /* Permissions */
+    n = nm_setting_connection_get_num_permissions(s_con);
+    if (n > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < n; i++) {
+            const char *ptype = NULL;
+            const char *puser = NULL;
+
+            if (!nm_setting_connection_get_permission(s_con, i, &ptype, &puser, NULL))
+                continue;
+            if (!nm_streq(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER))
+                continue;
+
+            /* Items separated by space for consistency with eg
+             * IPV6ADDR_SECONDARIES and DOMAIN.
+             */
+            if (str->len)
+                g_string_append_c(str, ' ');
+
+            g_string_append(str, puser);
+        }
+        svSetValueStr(ifcfg, "USERS", str->str);
+    }
+
+    svSetValueStr(ifcfg, "ZONE", nm_setting_connection_get_zone(s_con));
+
+    svSetValueStr(ifcfg, "MASTER_UUID", NULL);
+    svSetValueStr(ifcfg, "MASTER", NULL);
+    svSetValueStr(ifcfg, "SLAVE", NULL);
+    svSetValueStr(ifcfg, "BRIDGE_UUID", NULL);
+    svSetValueStr(ifcfg, "BRIDGE", NULL);
+    svSetValueStr(ifcfg, "TEAM_MASTER_UUID", NULL);
+    svSetValueStr(ifcfg, "TEAM_MASTER", NULL);
+
+    mud_url = nm_setting_connection_get_mud_url(s_con);
+    svSetValue(ifcfg, "MUD_URL", mud_url);
+
+    master = nm_setting_connection_get_master(s_con);
+    if (master) {
+        /* The reader prefers the *_UUID variants, however we still try to resolve
+         * it into an interface name, so that legacy tooling is not confused. */
+        if (!nm_utils_get_testing()) {
+            /* This is conditional for easier testing. */
+            master_iface = nm_manager_iface_for_uuid(NM_MANAGER_GET, master);
+        }
+        if (!master_iface) {
+            master_iface = master;
+            master       = NULL;
+        }
+
+        if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_BOND_SETTING_NAME)) {
+            svSetValueStr(ifcfg, "MASTER_UUID", master);
+            svSetValueStr(ifcfg, "MASTER", master_iface);
+            svSetValueStr(ifcfg, "SLAVE", "yes");
+        } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_BRIDGE_SETTING_NAME)) {
+            svSetValueStr(ifcfg, "BRIDGE_UUID", master);
+            svSetValueStr(ifcfg, "BRIDGE", master_iface);
+        } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_TEAM_SETTING_NAME)) {
+            svSetValueStr(ifcfg, "TEAM_MASTER_UUID", master);
+            svSetValueStr(ifcfg, "TEAM_MASTER", master_iface);
+            if (NM_IN_STRSET(type, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_VLAN_SETTING_NAME))
+                svUnsetValue(ifcfg, "TYPE");
+        } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_OVS_PORT_SETTING_NAME)) {
+            svSetValueStr(ifcfg, "OVS_PORT_UUID", master);
+            svSetValueStr(ifcfg, "OVS_PORT", master_iface);
+        } else if (nm_setting_connection_is_slave_type(s_con, NM_SETTING_VRF_SETTING_NAME)) {
+            svSetValueStr(ifcfg, "VRF_UUID", master);
+            svSetValueStr(ifcfg, "VRF", master_iface);
+        } else {
+            _LOGW("don't know how to set master for a %s slave",
+                  nm_setting_connection_get_slave_type(s_con));
+        }
+    }
+
+    if (nm_streq0(type, NM_SETTING_TEAM_SETTING_NAME))
+        svSetValueStr(ifcfg, "DEVICETYPE", TYPE_TEAM);
+    else if (master_iface
+             && nm_setting_connection_is_slave_type(s_con, NM_SETTING_TEAM_SETTING_NAME))
+        svSetValueStr(ifcfg, "DEVICETYPE", TYPE_TEAM_PORT);
+
+    /* secondary connection UUIDs */
+    n = nm_setting_connection_get_num_secondaries(s_con);
+    if (n > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < n; i++) {
+            const char *uuid;
+
+            /* Items separated by space for consistency with eg
+             * IPV6ADDR_SECONDARIES and DOMAIN.
+             */
+            if (!(uuid = nm_setting_connection_get_secondary(s_con, i)))
+                continue;
+
+            if (str->len)
+                g_string_append_c(str, ' ');
+            g_string_append(str, uuid);
+        }
+        svSetValueStr(ifcfg, "SECONDARY_UUIDS", str->str);
+    }
+
+    vuint32 = nm_setting_connection_get_gateway_ping_timeout(s_con);
+    svSetValueInt64_cond(ifcfg, "GATEWAY_PING_TIMEOUT", vuint32 != 0, vuint32);
+
+    switch (nm_setting_connection_get_metered(s_con)) {
+    case NM_METERED_YES:
+        svSetValueStr(ifcfg, "CONNECTION_METERED", "yes");
+        break;
+    case NM_METERED_NO:
+        svSetValueStr(ifcfg, "CONNECTION_METERED", "no");
+        break;
+    case NM_METERED_UNKNOWN:
+    case NM_METERED_GUESS_YES:
+    case NM_METERED_GUESS_NO:
+        break;
+    }
+
+    vint = nm_setting_connection_get_auth_retries(s_con);
+    svSetValueInt64_cond(ifcfg, "AUTH_RETRIES", vint >= 0, vint);
+
+    vint32 = nm_setting_connection_get_wait_device_timeout(s_con);
+    if (vint32 == -1) {
+        /* pass */
+    } else if ((vint32 % 1000) == 0)
+        svSetValueInt64(ifcfg, "DEVTIMEOUT", vint32 / 1000);
+    else {
+        char b[100];
+
+        svSetValueStr(ifcfg, "DEVTIMEOUT", nm_sprintf_buf(b, "%.3f", ((double) vint) / 1000.0));
+    }
+
+    mdns = nm_setting_connection_get_mdns(s_con);
+    if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
+        svSetValueEnum(ifcfg, "MDNS", nm_setting_connection_mdns_get_type(), mdns);
+    }
+
+    llmnr = nm_setting_connection_get_llmnr(s_con);
+    if (llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT) {
+        svSetValueEnum(ifcfg, "LLMNR", nm_setting_connection_llmnr_get_type(), llmnr);
+    }
+}
+
+static char *
+get_route_attributes_string(NMIPRoute *route, int family)
+{
+    gs_free const char **names = NULL;
+    GVariant *           attr, *lock;
+    GString *            str;
+    guint                i, len;
+
+    names = _nm_ip_route_get_attribute_names(route, TRUE, &len);
+    if (!len)
+        return NULL;
+
+    str = g_string_new("");
+
+    attr = nm_ip_route_get_attribute(route, NM_IP_ROUTE_ATTRIBUTE_TYPE);
+    if (attr
+        && nm_ip_route_attribute_validate(NM_IP_ROUTE_ATTRIBUTE_TYPE, attr, family, NULL, NULL))
+        g_string_append_printf(str, "%s ", g_variant_get_string(attr, NULL));
+
+    for (i = 0; i < len; i++) {
+        if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_TYPE))
+            continue;
+
+        attr = nm_ip_route_get_attribute(route, names[i]);
+
+        if (!nm_ip_route_attribute_validate(names[i], attr, family, NULL, NULL))
+            continue;
+
+        if (NM_IN_STRSET(names[i],
+                         NM_IP_ROUTE_ATTRIBUTE_WINDOW,
+                         NM_IP_ROUTE_ATTRIBUTE_CWND,
+                         NM_IP_ROUTE_ATTRIBUTE_INITCWND,
+                         NM_IP_ROUTE_ATTRIBUTE_INITRWND,
+                         NM_IP_ROUTE_ATTRIBUTE_MTU)) {
+            char lock_name[256];
+
+            nm_sprintf_buf(lock_name, "lock-%s", names[i]);
+            lock = nm_ip_route_get_attribute(route, lock_name);
+
+            g_string_append_printf(str,
+                                   "%s %s%u",
+                                   names[i],
+                                   (lock && g_variant_get_boolean(lock)) ? "lock " : "",
+                                   g_variant_get_uint32(attr));
+        } else if (strstr(names[i], "lock-")) {
+            const char *n = &(names[i])[NM_STRLEN("lock-")];
+
+            attr = nm_ip_route_get_attribute(route, n);
+            if (!attr) {
+                g_string_append_printf(str, "%s lock 0", n);
+            } else {
+                /* we also have a corresponding attribute with the numeric value. The
+                 * lock setting is handled above. */
+            }
+        } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_SCOPE)) {
+            g_string_append_printf(str, "%s %u", names[i], (unsigned) g_variant_get_byte(attr));
+        } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_TOS)) {
+            g_string_append_printf(str, "%s 0x%02x", names[i], (unsigned) g_variant_get_byte(attr));
+        } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_TABLE)) {
+            g_string_append_printf(str, "%s %u", names[i], (unsigned) g_variant_get_uint32(attr));
+        } else if (nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_ONLINK)) {
+            if (g_variant_get_boolean(attr))
+                g_string_append(str, "onlink");
+        } else if (NM_IN_STRSET(names[i], NM_IP_ROUTE_ATTRIBUTE_SRC, NM_IP_ROUTE_ATTRIBUTE_FROM)) {
+            char *arg = nm_streq(names[i], NM_IP_ROUTE_ATTRIBUTE_SRC) ? "src" : "from";
+
+            g_string_append_printf(str, "%s %s", arg, g_variant_get_string(attr, NULL));
+        } else {
+            g_warn_if_reached();
+            continue;
+        }
+        if (names[i + 1])
+            g_string_append_c(str, ' ');
+    }
+
+    return g_string_free(str, FALSE);
+}
+
+static shvarFile *
+write_route_file_svformat(const char *filename, NMSettingIPConfig *s_ip4)
+{
+    shvarFile *routefile;
+    guint      i, num;
+
+    routefile = utils_get_route_ifcfg(filename, TRUE);
+
+    num = nm_setting_ip_config_get_num_routes(s_ip4);
+    for (i = 0; i < num; i++) {
+        char          buf[INET_ADDRSTRLEN];
+        NMIPRoute *   route;
+        guint32       netmask;
+        gint64        metric;
+        char          addr_key[64];
+        char          gw_key[64];
+        char          netmask_key[64];
+        char          metric_key[64];
+        char          options_key[64];
+        gs_free char *options = NULL;
+
+        numbered_tag(addr_key, "ADDRESS", i);
+        numbered_tag(netmask_key, "NETMASK", i);
+        numbered_tag(gw_key, "GATEWAY", i);
+
+        route = nm_setting_ip_config_get_route(s_ip4, i);
+
+        svSetValueStr(routefile, addr_key, nm_ip_route_get_dest(route));
+
+        netmask = _nm_utils_ip4_prefix_to_netmask(nm_ip_route_get_prefix(route));
+        svSetValueStr(routefile, netmask_key, _nm_utils_inet4_ntop(netmask, buf));
+
+        svSetValueStr(routefile, gw_key, nm_ip_route_get_next_hop(route));
+
+        metric = nm_ip_route_get_metric(route);
+        if (metric != -1) {
+            svSetValueInt64(routefile, numbered_tag(metric_key, "METRIC", i), metric);
+        }
+
+        options = get_route_attributes_string(route, AF_INET);
+        if (options) {
+            svSetValueStr(routefile, numbered_tag(options_key, "OPTIONS", i), options);
+        }
+    }
+
+    return routefile;
+}
+
+static GString *
+write_route_file(NMSettingIPConfig *s_ip)
+{
+    GString *  contents;
+    NMIPRoute *route;
+    guint32    i, num;
+    int        addr_family;
+
+    addr_family = nm_setting_ip_config_get_addr_family(s_ip);
+
+    num = nm_setting_ip_config_get_num_routes(s_ip);
+    if (num == 0)
+        return NULL;
+
+    contents = g_string_new("");
+
+    for (i = 0; i < num; i++) {
+        gs_free char *options = NULL;
+        const char *  next_hop;
+        gint64        metric;
+
+        route    = nm_setting_ip_config_get_route(s_ip, i);
+        next_hop = nm_ip_route_get_next_hop(route);
+        metric   = nm_ip_route_get_metric(route);
+        options  = get_route_attributes_string(route, addr_family);
+
+        g_string_append_printf(contents,
+                               "%s/%u",
+                               nm_ip_route_get_dest(route),
+                               nm_ip_route_get_prefix(route));
+        if (next_hop)
+            g_string_append_printf(contents, " via %s", next_hop);
+        if (metric >= 0)
+            g_string_append_printf(contents, " metric %u", (guint) metric);
+        if (options) {
+            g_string_append_c(contents, ' ');
+            g_string_append(contents, options);
+        }
+
+        g_string_append_c(contents, '\n');
+    }
+
+    return contents;
+}
+
+static gboolean
+write_proxy_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingProxy *     s_proxy;
+    NMSettingProxyMethod method;
+    const char *         pac_url, *pac_script;
+
+    s_proxy = nm_connection_get_setting_proxy(connection);
+    if (!s_proxy)
+        return TRUE;
+
+    method = nm_setting_proxy_get_method(s_proxy);
+    switch (method) {
+    case NM_SETTING_PROXY_METHOD_AUTO:
+        svSetValueStr(ifcfg, "PROXY_METHOD", "auto");
+
+        pac_url = nm_setting_proxy_get_pac_url(s_proxy);
+        if (pac_url)
+            svSetValueStr(ifcfg, "PAC_URL", pac_url);
+
+        pac_script = nm_setting_proxy_get_pac_script(s_proxy);
+        if (pac_script)
+            svSetValueStr(ifcfg, "PAC_SCRIPT", pac_script);
+
+        break;
+    case NM_SETTING_PROXY_METHOD_NONE:
+        svSetValueStr(ifcfg, "PROXY_METHOD", "none");
+        break;
+    }
+
+    svSetValueBoolean(ifcfg, "BROWSER_ONLY", nm_setting_proxy_get_browser_only(s_proxy));
+
+    return TRUE;
+}
+
+static gboolean
+write_user_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingUser *    s_user;
+    guint              i, len;
+    const char *const *keys;
+
+    s_user = NM_SETTING_USER(nm_connection_get_setting(connection, NM_TYPE_SETTING_USER));
+
+    if (!s_user)
+        return TRUE;
+
+    keys = nm_setting_user_get_keys(s_user, &len);
+    if (len) {
+        nm_auto_free_gstring GString *str = g_string_sized_new(100);
+
+        for (i = 0; i < len; i++) {
+            const char *key = keys[i];
+
+            g_string_set_size(str, 0);
+            g_string_append(str, "NM_USER_");
+            nms_ifcfg_rh_utils_user_key_encode(key, str);
+            svSetValue(ifcfg, str->str, nm_setting_user_get_data(s_user, key));
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+write_sriov_setting(NMConnection *connection, shvarFile *ifcfg)
+{
+    NMSettingSriov *s_sriov;
+    guint           i, num = 0;
+    NMTernary       b;
+    NMSriovVF *     vf;
+    char            key[32];
+    char *          str;
+
+    s_sriov = NM_SETTING_SRIOV(nm_connection_get_setting(connection, NM_TYPE_SETTING_SRIOV));
+    if (!s_sriov) {
+        return;
+    }
+
+    svSetValueInt64(ifcfg, "SRIOV_TOTAL_VFS", nm_setting_sriov_get_total_vfs(s_sriov));
+
+    b = nm_setting_sriov_get_autoprobe_drivers(s_sriov);
+    if (b != NM_TERNARY_DEFAULT)
+        svSetValueInt64(ifcfg, "SRIOV_AUTOPROBE_DRIVERS", b);
+
+    num = nm_setting_sriov_get_num_vfs(s_sriov);
+    for (i = 0; i < num; i++) {
+        vf = nm_setting_sriov_get_vf(s_sriov, i);
+        nm_sprintf_buf(key, "SRIOV_VF%u", nm_sriov_vf_get_index(vf));
+        str = nm_utils_sriov_vf_to_str(vf, TRUE, NULL);
+        svSetValueStr(ifcfg, key, str);
+        g_free(str);
+    }
+}
+
+static gboolean
+write_tc_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+    NMSettingTCConfig *s_tc;
+    guint              i, num, n;
+    char               tag[64];
+
+    s_tc = nm_connection_get_setting_tc_config(connection);
+    if (!s_tc)
+        return TRUE;
+
+    num = nm_setting_tc_config_get_num_qdiscs(s_tc);
+    for (n = 1, i = 0; i < num; i++) {
+        NMTCQdisc *   qdisc;
+        gs_free char *str = NULL;
+
+        qdisc = nm_setting_tc_config_get_qdisc(s_tc, i);
+        str   = nm_utils_tc_qdisc_to_str(qdisc, error);
+        if (!str)
+            return FALSE;
+
+        svSetValueStr(ifcfg, numbered_tag(tag, "QDISC", n), str);
+        n++;
+    }
+
+    num = nm_setting_tc_config_get_num_tfilters(s_tc);
+    for (n = 1, i = 0; i < num; i++) {
+        NMTCTfilter * tfilter;
+        gs_free char *str = NULL;
+
+        tfilter = nm_setting_tc_config_get_tfilter(s_tc, i);
+        str     = nm_utils_tc_tfilter_to_str(tfilter, error);
+        if (!str)
+            return FALSE;
+
+        svSetValueStr(ifcfg, numbered_tag(tag, "FILTER", n), str);
+        n++;
+    }
+
+    return TRUE;
+}
+
+static void
+write_match_setting(NMConnection *connection, shvarFile *ifcfg)
+{
+    nm_auto_free_gstring GString *str = NULL;
+    NMSettingMatch *              s_match;
+    guint                         i, num;
+    const char *                  name;
+
+    s_match = (NMSettingMatch *) nm_connection_get_setting(connection, NM_TYPE_SETTING_MATCH);
+    if (!s_match)
+        return;
+
+    num = nm_setting_match_get_num_drivers(s_match);
+    if (num > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < num; i++) {
+            name = nm_setting_match_get_driver(s_match, i);
+            nm_gstring_add_space_delimiter(str);
+            nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str);
+        }
+        svSetValueStr(ifcfg, "MATCH_DRIVER", str->str);
+    }
+
+    num = nm_setting_match_get_num_interface_names(s_match);
+    if (num > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < num; i++) {
+            name = nm_setting_match_get_interface_name(s_match, i);
+            nm_gstring_add_space_delimiter(str);
+            nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str);
+        }
+        svSetValueStr(ifcfg, "MATCH_INTERFACE_NAME", str->str);
+    }
+
+    num = nm_setting_match_get_num_kernel_command_lines(s_match);
+    if (num > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < num; i++) {
+            name = nm_setting_match_get_kernel_command_line(s_match, i);
+            nm_gstring_add_space_delimiter(str);
+            nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str);
+        }
+        svSetValueStr(ifcfg, "MATCH_KERNEL_COMMAND_LINE", str->str);
+    }
+
+    num = nm_setting_match_get_num_paths(s_match);
+    if (num > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < num; i++) {
+            name = nm_setting_match_get_path(s_match, i);
+            nm_gstring_add_space_delimiter(str);
+            nm_utils_escaped_tokens_escape_gstr(name, NM_ASCII_SPACES, str);
+        }
+        svSetValueStr(ifcfg, "MATCH_PATH", str->str);
+    }
+}
+
+static void
+write_res_options(shvarFile *ifcfg, NMSettingIPConfig *s_ip, const char *var)
+{
+    nm_auto_free_gstring GString *value = NULL;
+    guint                         i, num_options;
+
+    if (!nm_setting_ip_config_has_dns_options(s_ip))
+        return;
+
+    value       = g_string_new(NULL);
+    num_options = nm_setting_ip_config_get_num_dns_options(s_ip);
+    for (i = 0; i < num_options; i++) {
+        if (i > 0)
+            g_string_append_c(value, ' ');
+        g_string_append(value, nm_setting_ip_config_get_dns_option(s_ip, i));
+    }
+
+    svSetValue(ifcfg, var, value->str);
+}
+
+static void
+write_dns_setting(shvarFile *ifcfg, NMConnection *connection, int addr_family)
+{
+    NMSettingIPConfig *s_ip;
+    NMSettingIPConfig *s_ip4;
+    NMSettingIPConfig *s_ip6 = NULL;
+    guint              num4;
+    guint              num6 = 0;
+    guint              num;
+    guint              i;
+    guint              offset;
+
+    if (addr_family == AF_INET6) {
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        num6  = s_ip6 ? nm_setting_ip_config_get_num_dns(s_ip6) : 0u;
+    }
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    num4  = s_ip4 ? nm_setting_ip_config_get_num_dns(s_ip4) : 0u;
+
+    if (addr_family == AF_INET6) {
+        num    = num6;
+        offset = num4;
+        s_ip   = s_ip6;
+    } else {
+        num    = num4;
+        offset = 0;
+        s_ip   = s_ip4;
+    }
+
+    for (i = 0; i < num; i++) {
+        char tag[64];
+
+        svSetValueStr(ifcfg,
+                      numbered_tag(tag, "DNS", offset + i + 1u),
+                      nm_setting_ip_config_get_dns(s_ip, i));
+    }
+}
+
+static gboolean
+write_ip4_setting(NMConnection *connection,
+                  shvarFile *   ifcfg,
+                  shvarFile **  out_route_content_svformat,
+                  GString **    out_route_content,
+                  GError **     error)
+{
+    NMSettingIPConfig *    s_ip4;
+    const char *           value;
+    char *                 tmp;
+    char                   tag[64];
+    int                    j;
+    guint                  i, num, n;
+    gint64                 route_metric;
+    NMIPRouteTableSyncMode route_table;
+    int                    priority;
+    int                    timeout;
+    nm_auto_free_gstring GString *str    = NULL;
+    const char *                  method = NULL;
+    gboolean                      has_netmask;
+    NMDhcpHostnameFlags           flags;
+    const char *const *           strv;
+
+    NM_SET_OUT(out_route_content_svformat, NULL);
+    NM_SET_OUT(out_route_content, NULL);
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    if (!s_ip4)
+        return TRUE;
+
+    method = nm_setting_ip_config_get_method(s_ip4);
+
+    /* Missing IP4 setting is assumed to be DHCP */
+    if (!method)
+        method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+
+    if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+        return TRUE;
+
+    num = nm_setting_ip_config_get_num_addresses(s_ip4);
+
+    if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+        svSetValueStr(ifcfg, "BOOTPROTO", "dhcp");
+    else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+        /* Preserve the archaic form of "static" if there actually
+         * is static configuration. */
+        if (g_strcmp0(svGetValue(ifcfg, "BOOTPROTO", &tmp), "static") || !num)
+            svSetValueStr(ifcfg, "BOOTPROTO", "none");
+        g_free(tmp);
+    } else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
+        svSetValueStr(ifcfg, "BOOTPROTO", "autoip");
+    else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+        svSetValueStr(ifcfg, "BOOTPROTO", "shared");
+
+    has_netmask = !!svFindFirstNumberedKey(ifcfg, "NETMASK");
+
+    /* Write out IPADDR<n>, PREFIX<n>, GATEWAY<n> for current IP addresses
+     * without labels. Unset obsolete NETMASK<n>.
+     */
+    for (i = n = 0; i < num; i++) {
+        NMIPAddress *addr;
+        guint        prefix;
+
+        addr = nm_setting_ip_config_get_address(s_ip4, i);
+
+        if (i > 0) {
+            GVariant *label;
+
+            label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
+            if (label)
+                continue;
+        }
+
+        if (n == 0) {
+            /* Instead of index 0 use un-numbered variables.
+             * It's needed for compatibility with ifup that only recognizes 'GATEAWAY'
+             * See https://bugzilla.redhat.com/show_bug.cgi?id=771673
+             * and https://bugzilla.redhat.com/show_bug.cgi?id=1105770
+             */
+            j = -1;
+        } else
+            j = n;
+
+        svSetValueStr(ifcfg, numbered_tag(tag, "IPADDR", j), nm_ip_address_get_address(addr));
+
+        prefix = nm_ip_address_get_prefix(addr);
+        svSetValueInt64(ifcfg, numbered_tag(tag, "PREFIX", j), prefix);
+
+        /* If the legacy "NETMASK" is present, keep it. */
+        numbered_tag(tag, "NETMASK", j);
+        if (has_netmask) {
+            char buf[INET_ADDRSTRLEN];
+
+            svSetValueStr(ifcfg,
+                          tag,
+                          _nm_utils_inet4_ntop(_nm_utils_ip4_prefix_to_netmask(prefix), buf));
+        }
+
+        n++;
+    }
+
+    svSetValueStr(ifcfg, "GATEWAY", nm_setting_ip_config_get_gateway(s_ip4));
+
+    write_dns_setting(ifcfg, connection, AF_INET);
+
+    num = nm_setting_ip_config_get_num_dns_searches(s_ip4);
+    if (num > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < num; i++) {
+            nm_gstring_add_space_delimiter(str);
+            g_string_append(str, nm_setting_ip_config_get_dns_search(s_ip4, i));
+        }
+        svSetValueStr(ifcfg, "DOMAIN", str->str);
+    }
+
+    /* DEFROUTE; remember that it has the opposite meaning from never-default */
+    svSetValueBoolean(ifcfg, "DEFROUTE", !nm_setting_ip_config_get_never_default(s_ip4));
+
+    /* Missing PEERDNS means TRUE, so write it only when is FALSE */
+    svSetValueStr(ifcfg, "PEERDNS", nm_setting_ip_config_get_ignore_auto_dns(s_ip4) ? "no" : NULL);
+    /* Missing PEERROUTES means TRUE, so write it only when is FALSE */
+    svSetValueStr(ifcfg,
+                  "PEERROUTES",
+                  nm_setting_ip_config_get_ignore_auto_routes(s_ip4) ? "no" : NULL);
+
+    value = nm_setting_ip_config_get_dhcp_hostname(s_ip4);
+    svSetValueStr(ifcfg, "DHCP_HOSTNAME", value);
+
+    value = nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4));
+    svSetValueStr(ifcfg, "DHCP_FQDN", value);
+
+    flags = nm_setting_ip_config_get_dhcp_hostname_flags(s_ip4);
+    svSetValueInt64_cond(ifcfg, "DHCP_HOSTNAME_FLAGS", flags != NM_DHCP_HOSTNAME_FLAG_NONE, flags);
+
+    /* Missing DHCP_SEND_HOSTNAME means TRUE, and we prefer not write it explicitly
+     * in that case, because it is NM-specific variable
+     */
+    svSetValueStr(ifcfg,
+                  "DHCP_SEND_HOSTNAME",
+                  nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) ? NULL : "no");
+
+    value = nm_setting_ip4_config_get_dhcp_client_id(NM_SETTING_IP4_CONFIG(s_ip4));
+    svSetValueStr(ifcfg, "DHCP_CLIENT_ID", value);
+
+    svSetValue(
+        ifcfg,
+        "DHCP_VENDOR_CLASS_IDENTIFIER",
+        nm_setting_ip4_config_get_dhcp_vendor_class_identifier(NM_SETTING_IP4_CONFIG(s_ip4)));
+
+    value = nm_setting_ip_config_get_dhcp_iaid(s_ip4);
+    svSetValueStr(ifcfg, "DHCP_IAID", value);
+
+    timeout = nm_setting_ip_config_get_dhcp_timeout(s_ip4);
+    svSetValueInt64_cond(ifcfg, "IPV4_DHCP_TIMEOUT", timeout != 0, timeout);
+
+    svSetValueBoolean(ifcfg, "IPV4_FAILURE_FATAL", !nm_setting_ip_config_get_may_fail(s_ip4));
+
+    route_metric = nm_setting_ip_config_get_route_metric(s_ip4);
+    svSetValueInt64_cond(ifcfg, "IPV4_ROUTE_METRIC", route_metric != -1, route_metric);
+
+    route_table = nm_setting_ip_config_get_route_table(s_ip4);
+    svSetValueInt64_cond(ifcfg, "IPV4_ROUTE_TABLE", route_table != 0, route_table);
+
+    NM_SET_OUT(out_route_content_svformat, write_route_file_svformat(svFileGetName(ifcfg), s_ip4));
+    NM_SET_OUT(out_route_content, write_route_file(s_ip4));
+
+    timeout = nm_setting_ip_config_get_dad_timeout(s_ip4);
+    if (timeout < 0) {
+        /* pass */
+    } else if (timeout == 0) {
+        svSetValueStr(ifcfg, "ACD_TIMEOUT", "0");
+        svSetValueStr(ifcfg, "ARPING_WAIT", "0");
+    } else {
+        svSetValueInt64(ifcfg, "ACD_TIMEOUT", timeout);
+        /* Round the value up to next integer for initscripts */
+        svSetValueInt64(ifcfg, "ARPING_WAIT", (timeout - 1) / 1000 + 1);
+    }
+
+    priority = nm_setting_ip_config_get_dns_priority(s_ip4);
+    if (priority)
+        svSetValueInt64(ifcfg, "IPV4_DNS_PRIORITY", priority);
+
+    write_res_options(ifcfg, s_ip4, "RES_OPTIONS");
+
+    strv = nm_setting_ip_config_get_dhcp_reject_servers(s_ip4, &num);
+    if (num > 0) {
+        nm_gstring_prepare(&str);
+        for (i = 0; i < num; i++) {
+            nm_gstring_add_space_delimiter(str);
+            nm_utils_escaped_tokens_escape_gstr(strv[i], NM_ASCII_SPACES, str);
+        }
+        svSetValueStr(ifcfg, "DHCP_REJECT_SERVERS", str->str);
+    }
+
+    return TRUE;
+}
+
+static void
+write_ip4_aliases(NMConnection *connection, const char *base_ifcfg_path)
+{
+    NMSettingIPConfig *s_ip4;
+    gs_free char *     base_ifcfg_dir = NULL, *base_ifcfg_name = NULL;
+    const char *       base_name;
+    int                i, num, base_ifcfg_name_len, base_name_len;
+    GDir *             dir;
+
+    base_ifcfg_dir      = g_path_get_dirname(base_ifcfg_path);
+    base_ifcfg_name     = g_path_get_basename(base_ifcfg_path);
+    base_ifcfg_name_len = strlen(base_ifcfg_name);
+    if (!g_str_has_prefix(base_ifcfg_name, IFCFG_TAG))
+        g_return_if_reached();
+    base_name     = base_ifcfg_name + strlen(IFCFG_TAG);
+    base_name_len = strlen(base_name);
+
+    /* Remove all existing aliases for this file first */
+    dir = g_dir_open(base_ifcfg_dir, 0, NULL);
+    if (dir) {
+        const char *item;
+
+        while ((item = g_dir_read_name(dir))) {
+            char *full_path;
+
+            if (strncmp(item, base_ifcfg_name, base_ifcfg_name_len) != 0
+                || item[base_ifcfg_name_len] != ':')
+                continue;
+
+            full_path = g_build_filename(base_ifcfg_dir, item, NULL);
+            unlink(full_path);
+            g_free(full_path);
+        }
+
+        g_dir_close(dir);
+    }
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    if (!s_ip4) {
+        /* slave-type: no alias files */
+        return;
+    }
+
+    num = nm_setting_ip_config_get_num_addresses(s_ip4);
+    for (i = 0; i < num; i++) {
+        GVariant *   label_var;
+        const char * label, *p;
+        char *       path;
+        NMIPAddress *addr;
+        shvarFile *  ifcfg;
+
+        addr = nm_setting_ip_config_get_address(s_ip4, i);
+
+        label_var = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
+        if (!label_var)
+            continue;
+        label = g_variant_get_string(label_var, NULL);
+        if (strncmp(label, base_name, base_name_len) != 0 || label[base_name_len] != ':')
+            continue;
+
+        for (p = label; *p; p++) {
+            if (!g_ascii_isalnum(*p) && *p != '_' && *p != ':')
+                break;
+        }
+        if (*p)
+            continue;
+
+        path  = g_strdup_printf("%s%s", base_ifcfg_path, label + base_name_len);
+        ifcfg = svCreateFile(path);
+        g_free(path);
+
+        svSetValueStr(ifcfg, "DEVICE", label);
+
+        addr = nm_setting_ip_config_get_address(s_ip4, i);
+        svSetValueStr(ifcfg, "IPADDR", nm_ip_address_get_address(addr));
+
+        svSetValueInt64(ifcfg, "PREFIX", nm_ip_address_get_prefix(addr));
+
+        svWriteFileWithoutDirtyWellknown(ifcfg, 0644, NULL);
+        svCloseFile(ifcfg);
+    }
+}
+
+static gboolean
+write_ip6_setting(NMConnection *connection,
+                  shvarFile *   ifcfg,
+                  GString **    out_route6_content,
+                  GError **     error)
+{
+    NMSettingIPConfig *           s_ip6;
+    const char *                  value;
+    guint                         i, num;
+    int                           priority;
+    NMIPAddress *                 addr;
+    gint64                        route_metric;
+    NMIPRouteTableSyncMode        route_table;
+    GString *                     ip_str1, *ip_str2, *ip_ptr;
+    NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+    NMDhcpHostnameFlags           flags;
+    const char *                  hostname;
+    int                           timeout;
+
+    NM_SET_OUT(out_route6_content, NULL);
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    if (!s_ip6)
+        return TRUE;
+
+    value = nm_setting_ip_config_get_method(s_ip6);
+    g_assert(value);
+    if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+        svSetValueStr(ifcfg, "IPV6INIT", "no");
+        return TRUE;
+    } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+        svSetValueStr(ifcfg, "IPV6_DISABLED", "yes");
+        svSetValueStr(ifcfg, "IPV6INIT", "no");
+        return TRUE;
+    } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
+        svSetValueStr(ifcfg, "IPV6INIT", "yes");
+        svSetValueStr(ifcfg, "IPV6_AUTOCONF", "yes");
+    } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+        svSetValueStr(ifcfg, "IPV6INIT", "yes");
+        svSetValueStr(ifcfg, "IPV6_AUTOCONF", "no");
+        svSetValueStr(ifcfg, "DHCPV6C", "yes");
+    } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+        svSetValueStr(ifcfg, "IPV6INIT", "yes");
+        svSetValueStr(ifcfg, "IPV6_AUTOCONF", "no");
+    } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
+        svSetValueStr(ifcfg, "IPV6INIT", "yes");
+        svSetValueStr(ifcfg, "IPV6_AUTOCONF", "no");
+    } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
+        svSetValueStr(ifcfg, "IPV6INIT", "yes");
+        svSetValueStr(ifcfg, "IPV6_AUTOCONF", "shared");
+    }
+
+    svSetValueStr(ifcfg,
+                  "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));
+
+    hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip6);
+    svSetValueStr(ifcfg, "DHCPV6_HOSTNAME", hostname);
+
+    /* Missing DHCPV6_SEND_HOSTNAME means TRUE, and we prefer not write it
+     * explicitly in that case, because it is NM-specific variable
+     */
+    if (!nm_setting_ip_config_get_dhcp_send_hostname(s_ip6))
+        svSetValueStr(ifcfg, "DHCPV6_SEND_HOSTNAME", "no");
+
+    timeout = nm_setting_ip6_config_get_ra_timeout(NM_SETTING_IP6_CONFIG(s_ip6));
+    svSetValueInt64_cond(ifcfg, "IPV6_RA_TIMEOUT", timeout != 0, timeout);
+
+    timeout = nm_setting_ip_config_get_dhcp_timeout(s_ip6);
+    svSetValueInt64_cond(ifcfg, "IPV6_DHCP_TIMEOUT", timeout != 0, timeout);
+
+    flags = nm_setting_ip_config_get_dhcp_hostname_flags(s_ip6);
+    svSetValueInt64_cond(ifcfg,
+                         "DHCPV6_HOSTNAME_FLAGS",
+                         flags != NM_DHCP_HOSTNAME_FLAG_NONE,
+                         flags);
+
+    /* Write out IP addresses */
+    num     = nm_setting_ip_config_get_num_addresses(s_ip6);
+    ip_str1 = g_string_new(NULL);
+    ip_str2 = g_string_new(NULL);
+    for (i = 0; i < num; i++) {
+        if (i == 0)
+            ip_ptr = ip_str1;
+        else
+            ip_ptr = ip_str2;
+
+        addr = nm_setting_ip_config_get_address(s_ip6, i);
+
+        if (i > 1)
+            g_string_append_c(ip_ptr, ' '); /* separate addresses in IPV6ADDR_SECONDARIES */
+        g_string_append_printf(ip_ptr,
+                               "%s/%u",
+                               nm_ip_address_get_address(addr),
+                               nm_ip_address_get_prefix(addr));
+    }
+    svSetValueStr(ifcfg, "IPV6ADDR", ip_str1->str);
+    svSetValueStr(ifcfg, "IPV6ADDR_SECONDARIES", ip_str2->str);
+    svSetValueStr(ifcfg, "IPV6_DEFAULTGW", nm_setting_ip_config_get_gateway(s_ip6));
+    g_string_free(ip_str1, TRUE);
+    g_string_free(ip_str2, TRUE);
+
+    write_dns_setting(ifcfg, connection, AF_INET6);
+
+    /* Write out DNS domains */
+    num = nm_setting_ip_config_get_num_dns_searches(s_ip6);
+    if (num > 0) {
+        nm_auto_free_gstring GString *searches = NULL;
+
+        searches = g_string_new(NULL);
+        for (i = 0; i < num; i++) {
+            if (searches->len > 0)
+                g_string_append_c(searches, ' ');
+            g_string_append(searches, nm_setting_ip_config_get_dns_search(s_ip6, i));
+        }
+        svSetValueStr(ifcfg, "IPV6_DOMAIN", searches->str);
+    }
+
+    /* handle IPV6_DEFROUTE */
+    /* IPV6_DEFROUTE has the opposite meaning from 'never-default' */
+    svSetValueBoolean(ifcfg, "IPV6_DEFROUTE", !nm_setting_ip_config_get_never_default(s_ip6));
+
+    svSetValueStr(ifcfg,
+                  "IPV6_PEERDNS",
+                  nm_setting_ip_config_get_ignore_auto_dns(s_ip6) ? "no" : NULL);
+
+    svSetValueStr(ifcfg,
+                  "IPV6_PEERROUTES",
+                  nm_setting_ip_config_get_ignore_auto_routes(s_ip6) ? "no" : NULL);
+
+    svSetValueStr(ifcfg,
+                  "IPV6_FAILURE_FATAL",
+                  nm_setting_ip_config_get_may_fail(s_ip6) ? "no" : "yes");
+
+    route_metric = nm_setting_ip_config_get_route_metric(s_ip6);
+    svSetValueInt64_cond(ifcfg, "IPV6_ROUTE_METRIC", route_metric != -1, route_metric);
+
+    route_table = nm_setting_ip_config_get_route_table(s_ip6);
+    svSetValueInt64_cond(ifcfg, "IPV6_ROUTE_TABLE", route_table != 0, route_table);
+
+    /* IPv6 Privacy Extensions */
+    switch (nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6))) {
+    case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
+        svSetValueStr(ifcfg, "IPV6_PRIVACY", "no");
+        break;
+    case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
+        svSetValueStr(ifcfg, "IPV6_PRIVACY", "rfc3041");
+        svSetValueStr(ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP", "yes");
+        break;
+    case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
+        svSetValueStr(ifcfg, "IPV6_PRIVACY", "rfc3041");
+        break;
+    default:
+        break;
+    }
+
+    /* IPv6 Address generation mode */
+    addr_gen_mode = nm_setting_ip6_config_get_addr_gen_mode(NM_SETTING_IP6_CONFIG(s_ip6));
+    if (addr_gen_mode != NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) {
+        svSetValueEnum(ifcfg,
+                       "IPV6_ADDR_GEN_MODE",
+                       nm_setting_ip6_config_addr_gen_mode_get_type(),
+                       addr_gen_mode);
+    }
+
+    /* IPv6 tokenized interface identifier */
+    value = nm_setting_ip6_config_get_token(NM_SETTING_IP6_CONFIG(s_ip6));
+    svSetValueStr(ifcfg, "IPV6_TOKEN", value);
+
+    priority = nm_setting_ip_config_get_dns_priority(s_ip6);
+    if (priority)
+        svSetValueInt64(ifcfg, "IPV6_DNS_PRIORITY", priority);
+
+    write_res_options(ifcfg, s_ip6, "IPV6_RES_OPTIONS");
+
+    NM_SET_OUT(out_route6_content, write_route_file(s_ip6));
+
+    return TRUE;
+}
+
+static void
+write_ip_routing_rules(NMConnection *connection, shvarFile *ifcfg, gboolean route_ignore)
+{
+    gsize idx;
+    int   is_ipv4;
+
+    if (route_ignore)
+        return;
+
+    idx = 0;
+
+    for (is_ipv4 = 1; is_ipv4 >= 0; is_ipv4--) {
+        const int          addr_family = is_ipv4 ? AF_INET : AF_INET6;
+        NMSettingIPConfig *s_ip;
+        guint              i, num;
+
+        s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
+        if (!s_ip)
+            continue;
+
+        num = nm_setting_ip_config_get_num_routing_rules(s_ip);
+        for (i = 0; i < num; i++) {
+            NMIPRoutingRule *   rule = nm_setting_ip_config_get_routing_rule(s_ip, i);
+            gs_free const char *s    = NULL;
+            char                key[64];
+
+            s = nm_ip_routing_rule_to_string(rule,
+                                             NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE,
+                                             NULL,
+                                             NULL);
+            if (!s)
+                continue;
+
+            if (is_ipv4)
+                numbered_tag(key, "ROUTING_RULE_", ++idx);
+            else
+                numbered_tag(key, "ROUTING_RULE6_", ++idx);
+            svSetValueStr(ifcfg, key, s);
+        }
+    }
+}
+
+static char *
+escape_id(const char *id)
+{
+    char *escaped = g_strdup(id);
+    char *p       = escaped;
+
+    /* Escape random stuff */
+    while (*p) {
+        if (*p == ' ')
+            *p = '_';
+        else if (strchr("\\][|/=()!:", *p))
+            *p = '-';
+        p++;
+    }
+
+    return escaped;
+}
+
+static gboolean
+do_write_construct(NMConnection *                  connection,
+                   const char *                    ifcfg_dir,
+                   const char *                    filename,
+                   NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                   gpointer                        allow_filename_user_data,
+                   shvarFile **                    out_ifcfg,
+                   GHashTable **                   out_blobs,
+                   GHashTable **                   out_secrets,
+                   gboolean *                      out_route_ignore,
+                   shvarFile **                    out_route_content_svformat,
+                   GString **                      out_route_content,
+                   GString **                      out_route6_content,
+                   GError **                       error)
+{
+    NMSettingConnection *    s_con;
+    nm_auto_shvar_file_close shvarFile *ifcfg       = NULL;
+    gs_free char *                      ifcfg_name  = NULL;
+    gs_free char *                      route_path  = NULL;
+    gs_free char *                      route6_path = NULL;
+    const char *                        type;
+    gs_unref_hashtable GHashTable *blobs   = NULL;
+    gs_unref_hashtable GHashTable *secrets = NULL;
+    gboolean                       wired;
+    gboolean                       no_8021x;
+    gboolean                       route_path_is_svformat;
+    gboolean                       has_complex_routes_v4;
+    gboolean                       has_complex_routes_v6;
+    gboolean                       route_ignore;
+    nm_auto_shvar_file_close shvarFile *route_content_svformat = NULL;
+    nm_auto_free_gstring GString *route_content                = NULL;
+    nm_auto_free_gstring GString *route6_content               = NULL;
+
+    nm_assert(NM_IS_CONNECTION(connection));
+    nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+
+    if (!nms_ifcfg_rh_writer_can_write_connection(connection, error))
+        return FALSE;
+
+    s_con = nm_connection_get_setting_connection(connection);
+
+    if (filename) {
+        /* For existing connections, 'filename' should be full path to ifcfg file */
+        ifcfg = svOpenFile(filename, error);
+        if (!ifcfg)
+            return FALSE;
+
+        ifcfg_name = g_strdup(filename);
+    } else if (ifcfg_dir) {
+        gs_free char *escaped = NULL;
+        int           i_path;
+
+        escaped = escape_id(nm_setting_connection_get_id(s_con));
+
+        for (i_path = 0; i_path < 10000; i_path++) {
+            gs_free char *path_candidate = NULL;
+
+            if (i_path == 0)
+                path_candidate = g_strdup_printf("%s/ifcfg-%s", ifcfg_dir, escaped);
+            else
+                path_candidate = g_strdup_printf("%s/ifcfg-%s-%d", ifcfg_dir, escaped, i_path);
+
+            if (allow_filename_cb && !allow_filename_cb(path_candidate, allow_filename_user_data))
+                continue;
+
+            if (g_file_test(path_candidate, G_FILE_TEST_EXISTS))
+                continue;
+
+            ifcfg_name = g_steal_pointer(&path_candidate);
+            break;
+        }
+
+        if (!ifcfg_name) {
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_FAILED,
+                                "Failed to find usable ifcfg file name");
+            return FALSE;
+        }
+
+        ifcfg = svCreateFile(ifcfg_name);
+    } else
+        ifcfg = svCreateFile("/tmp/ifcfg-dummy");
+
+    route_path = utils_get_route_path(svFileGetName(ifcfg));
+    if (!route_path) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Could not get route file path for '%s'",
+                    svFileGetName(ifcfg));
+        return FALSE;
+    }
+
+    route6_path = utils_get_route6_path(svFileGetName(ifcfg));
+    if (!route6_path) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Could not get route6 file path for '%s'",
+                    svFileGetName(ifcfg));
+        return FALSE;
+    }
+
+    type = nm_setting_connection_get_connection_type(s_con);
+    if (!type) {
+        g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, "Missing connection type!");
+        return FALSE;
+    }
+
+    secrets = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free);
+
+    wired    = FALSE;
+    no_8021x = FALSE;
+    if (!strcmp(type, NM_SETTING_WIRED_SETTING_NAME)) {
+        // FIXME: can't write PPPoE at this time
+        if (nm_connection_get_setting_pppoe(connection)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "Can't write connection type '%s'",
+                        NM_SETTING_PPPOE_SETTING_NAME);
+            return FALSE;
+        }
+
+        if (!write_wired_setting(connection, ifcfg, error))
+            return FALSE;
+        wired = TRUE;
+    } else if (!strcmp(type, NM_SETTING_VLAN_SETTING_NAME)) {
+        if (!write_vlan_setting(connection, ifcfg, &wired, error))
+            return FALSE;
+    } else if (!strcmp(type, NM_SETTING_WIRELESS_SETTING_NAME)) {
+        if (!write_wireless_setting(connection, ifcfg, secrets, &no_8021x, error))
+            return FALSE;
+    } else if (!strcmp(type, NM_SETTING_INFINIBAND_SETTING_NAME)) {
+        if (!write_infiniband_setting(connection, ifcfg, error))
+            return FALSE;
+    } else if (!strcmp(type, NM_SETTING_BOND_SETTING_NAME)) {
+        if (!write_bond_setting(connection, ifcfg, &wired, error))
+            return FALSE;
+    } else if (!strcmp(type, NM_SETTING_TEAM_SETTING_NAME)) {
+        if (!write_team_setting(connection, ifcfg, &wired, error))
+            return FALSE;
+    } else if (!strcmp(type, NM_SETTING_BRIDGE_SETTING_NAME)) {
+        if (!write_bridge_setting(connection, ifcfg, &wired, error))
+            return FALSE;
+    } else {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "Can't write connection type '%s'",
+                    type);
+        return FALSE;
+    }
+
+    if (!no_8021x) {
+        blobs =
+            g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref);
+        if (!write_8021x_setting(connection, ifcfg, secrets, blobs, wired, error))
+            return FALSE;
+    }
+
+    if (!write_bridge_port_setting(connection, ifcfg, error))
+        return FALSE;
+
+    if (!write_team_port_setting(connection, ifcfg, error))
+        return FALSE;
+
+    if (!write_dcb_setting(connection, ifcfg, error))
+        return FALSE;
+
+    if (!write_proxy_setting(connection, ifcfg, error))
+        return FALSE;
+
+    if (!write_ethtool_setting(connection, ifcfg, error))
+        return FALSE;
+
+    if (!write_user_setting(connection, ifcfg, error))
+        return FALSE;
+
+    write_match_setting(connection, ifcfg);
+    write_hostname_setting(connection, ifcfg);
+    write_sriov_setting(connection, ifcfg);
+
+    if (!write_tc_setting(connection, ifcfg, error))
+        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);
+    has_complex_routes_v6 = utils_has_complex_routes(ifcfg_name, AF_INET6);
+
+    if (has_complex_routes_v4 || has_complex_routes_v6) {
+        NMSettingIPConfig *s_ip4, *s_ip6;
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        if ((s_ip4 && nm_setting_ip_config_get_num_routes(s_ip4) > 0)
+            || (s_ip6 && nm_setting_ip_config_get_num_routes(s_ip6) > 0)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "Cannot configure static routes on a connection that has an associated "
+                        "'rule%s-' file",
+                        has_complex_routes_v4 ? "" : "6");
+            return FALSE;
+        }
+        if ((s_ip4 && nm_setting_ip_config_get_route_table(s_ip4) != 0)
+            || (s_ip6 && nm_setting_ip_config_get_route_table(s_ip6) != 0)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "Cannot configure a route table for policy routing on a connection that "
+                        "has an associated 'rule%s-' file",
+                        has_complex_routes_v4 ? "" : "6");
+            return FALSE;
+        }
+        if ((s_ip4 && nm_setting_ip_config_get_num_routing_rules(s_ip4) > 0)
+            || (s_ip6 && nm_setting_ip_config_get_num_routing_rules(s_ip6) > 0)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "Cannot configure routing rules on a connection that has an associated "
+                        "'rule%s-' file",
+                        has_complex_routes_v4 ? "" : "6");
+            return FALSE;
+        }
+        route_ignore = TRUE;
+    } else
+        route_ignore = FALSE;
+
+    if (!write_ip4_setting(connection,
+                           ifcfg,
+                           !route_ignore && route_path_is_svformat ? &route_content_svformat : NULL,
+                           !route_ignore && route_path_is_svformat ? NULL : &route_content,
+                           error))
+        return FALSE;
+
+    if (!write_ip6_setting(connection, ifcfg, !route_ignore ? &route6_content : NULL, error))
+        return FALSE;
+
+    write_ip_routing_rules(connection, ifcfg, route_ignore);
+
+    write_connection_setting(s_con, ifcfg);
+
+    NM_SET_OUT(out_ifcfg, g_steal_pointer(&ifcfg));
+    NM_SET_OUT(out_blobs, g_steal_pointer(&blobs));
+    NM_SET_OUT(out_secrets, g_steal_pointer(&secrets));
+    NM_SET_OUT(out_route_ignore, route_ignore);
+    NM_SET_OUT(out_route_content_svformat, g_steal_pointer(&route_content_svformat));
+    NM_SET_OUT(out_route_content, g_steal_pointer(&route_content));
+    NM_SET_OUT(out_route6_content, g_steal_pointer(&route6_content));
+    return TRUE;
+}
+
+static gboolean
+do_write_to_disk(NMConnection *connection,
+                 shvarFile *   ifcfg,
+                 GHashTable *  blobs,
+                 GHashTable *  secrets,
+                 gboolean      route_ignore,
+                 shvarFile *   route_content_svformat,
+                 GString *     route_content,
+                 GString *     route6_content,
+                 GError **     error)
+{
+    /* From here on, we persist data to disk. Before, it was all in-memory
+     * only. But we loaded the ifcfg files from disk, and managled our
+     * new settings (in-memory). */
+
+    if (!svWriteFileWithoutDirtyWellknown(ifcfg, 0644, error))
+        return FALSE;
+
+    write_ip4_aliases(connection, svFileGetName(ifcfg));
+
+    if (!write_blobs(blobs, error))
+        return FALSE;
+
+    if (!write_secrets(ifcfg, secrets, error))
+        return FALSE;
+
+    if (!route_ignore) {
+        gs_free char *route_path = utils_get_route_path(svFileGetName(ifcfg));
+
+        if (!route_content && !route_content_svformat)
+            (void) unlink(route_path);
+        else {
+            nm_assert(route_content_svformat || route_content);
+            if (route_content_svformat) {
+                if (!svWriteFileWithoutDirtyWellknown(route_content_svformat, 0644, error))
+                    return FALSE;
+            } else {
+                if (!g_file_set_contents(route_path,
+                                         route_content->str,
+                                         route_content->len,
+                                         NULL)) {
+                    g_set_error(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_FAILED,
+                                "Writing route file '%s' failed",
+                                route_path);
+                    return FALSE;
+                }
+            }
+        }
+    }
+
+    if (!route_ignore) {
+        gs_free char *route6_path = utils_get_route6_path(svFileGetName(ifcfg));
+
+        if (!route6_content)
+            (void) unlink(route6_path);
+        else {
+            if (!g_file_set_contents(route6_path, route6_content->str, route6_content->len, NULL)) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_FAILED,
+                            "Writing route6 file '%s' failed",
+                            route6_path);
+                return FALSE;
+            }
+        }
+    }
+
+    return TRUE;
+}
+
+gboolean
+nms_ifcfg_rh_writer_write_connection(NMConnection *                  connection,
+                                     const char *                    ifcfg_dir,
+                                     const char *                    filename,
+                                     NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                                     gpointer                        allow_filename_user_data,
+                                     char **                         out_filename,
+                                     NMConnection **                 out_reread,
+                                     gboolean *                      out_reread_same,
+                                     GError **                       error)
+{
+    nm_auto_shvar_file_close shvarFile *ifcfg                  = NULL;
+    nm_auto_free_gstring GString *route_content                = NULL;
+    gboolean                      route_ignore                 = FALSE;
+    nm_auto_shvar_file_close shvarFile *route_content_svformat = NULL;
+    nm_auto_free_gstring GString *route6_content               = NULL;
+    gs_unref_hashtable GHashTable *secrets                     = NULL;
+    gs_unref_hashtable GHashTable *blobs                       = NULL;
+
+    nm_assert(!out_reread || !*out_reread);
+
+    if (!do_write_construct(connection,
+                            ifcfg_dir,
+                            filename,
+                            allow_filename_cb,
+                            allow_filename_user_data,
+                            &ifcfg,
+                            &blobs,
+                            &secrets,
+                            &route_ignore,
+                            &route_content_svformat,
+                            &route_content,
+                            &route6_content,
+                            error))
+        return FALSE;
+
+    _LOGT("write: write connection %s (%s) to file \"%s\"",
+          nm_connection_get_id(connection),
+          nm_connection_get_uuid(connection),
+          svFileGetName(ifcfg));
+
+    if (!do_write_to_disk(connection,
+                          ifcfg,
+                          blobs,
+                          secrets,
+                          route_ignore,
+                          route_content_svformat,
+                          route_content,
+                          route6_content,
+                          error))
+        return FALSE;
+
+    /* Note that we just wrote the connection to disk, and re-read it from there.
+     * That is racy if somebody else modifies the connection.
+     * That race is why we must not tread a failure to re-read the profile
+     * as an error.
+     *
+     * FIXME: a much better solution might be, to re-read the connection only based
+     * on the in-memory representation of what we collected above. But the reader
+     * does not yet allow to inject the configuration. */
+    if (out_reread || out_reread_same) {
+        gs_unref_object NMConnection *reread      = NULL;
+        gboolean                      reread_same = FALSE;
+        gs_free_error GError *local               = NULL;
+        gs_free char *        unhandled           = NULL;
+
+        reread = connection_from_file(svFileGetName(ifcfg), &unhandled, &local, NULL);
+        nm_assert((NM_IS_CONNECTION(reread) && !local) || (!reread && local));
+
+        if (!reread) {
+            _LOGW("write: failure to re-read connection \"%s\": %s",
+                  svFileGetName(ifcfg),
+                  local->message);
+        } else if (unhandled) {
+            g_clear_object(&reread);
+            _LOGW("write: failure to re-read connection \"%s\": %s",
+                  svFileGetName(ifcfg),
+                  "connection is unhandled");
+        } else {
+            /* ifcfg-rh doesn't support the 'timestamp' property, let's add it here */
+            g_object_set(nm_connection_get_setting_connection(reread),
+                         NM_SETTING_CONNECTION_TIMESTAMP,
+                         nm_setting_connection_get_timestamp(
+                             nm_connection_get_setting_connection(connection)),
+                         NULL);
+            if (out_reread_same) {
+                reread_same =
+                    nm_connection_compare(reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
+                if (!reread_same) {
+                    _LOGD("write: connection %s (%s) was modified by persisting it to \"%s\" ",
+                          nm_connection_get_id(connection),
+                          nm_connection_get_uuid(connection),
+                          svFileGetName(ifcfg));
+                }
+            }
+        }
+
+        NM_SET_OUT(out_reread, g_steal_pointer(&reread));
+        NM_SET_OUT(out_reread_same, reread_same);
+    }
+
+    /* Only return the filename if this was a newly written ifcfg */
+    if (out_filename && !filename)
+        *out_filename = g_strdup(svFileGetName(ifcfg));
+
+    return TRUE;
+}
+
+gboolean
+nms_ifcfg_rh_writer_can_write_connection(NMConnection *connection, GError **error)
+{
+    const char *type, *id;
+
+    type = nm_connection_get_connection_type(connection);
+    if (NM_IN_STRSET(type,
+                     NM_SETTING_VLAN_SETTING_NAME,
+                     NM_SETTING_WIRELESS_SETTING_NAME,
+                     NM_SETTING_INFINIBAND_SETTING_NAME,
+                     NM_SETTING_BOND_SETTING_NAME,
+                     NM_SETTING_TEAM_SETTING_NAME,
+                     NM_SETTING_BRIDGE_SETTING_NAME))
+        return TRUE;
+    if (nm_streq0(type, NM_SETTING_WIRED_SETTING_NAME)
+        && !nm_connection_get_setting_pppoe(connection))
+        return TRUE;
+
+    id = nm_connection_get_id(connection);
+    g_set_error(error,
+                NM_SETTINGS_ERROR,
+                NM_SETTINGS_ERROR_FAILED,
+                "The ifcfg-rh plugin cannot write the connection %s%s%s (type %s%s%s)",
+                NM_PRINT_FMT_QUOTE_STRING(id),
+                NM_PRINT_FMT_QUOTE_STRING(type));
+    return FALSE;
+}
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
new file mode 100644
index 00000000..d0d8a4cd
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef __NMS_IFCFG_RH_WRITER_H__
+#define __NMS_IFCFG_RH_WRITER_H__
+
+#include "nm-connection.h"
+
+typedef gboolean (*NMSIfcfgRHWriterAllowFilenameCb)(const char *check_filename,
+                                                    gpointer    allow_filename_user_data);
+
+gboolean nms_ifcfg_rh_writer_can_write_connection(NMConnection *connection, GError **error);
+
+gboolean nms_ifcfg_rh_writer_write_connection(NMConnection *                  connection,
+                                              const char *                    ifcfg_dir,
+                                              const char *                    filename,
+                                              NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                                              gpointer       allow_filename_user_data,
+                                              char **        out_filename,
+                                              NMConnection **out_reread,
+                                              gboolean *     out_reread_same,
+                                              GError **      error);
+
+#endif /* __NMS_IFCFG_RH_WRITER_H__ */
diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.c b/src/core/settings/plugins/ifcfg-rh/shvar.c
new file mode 100644
index 00000000..386bcac7
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/shvar.c
@@ -0,0 +1,1585 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "shvar.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "nm-core-internal.h"
+#include "nm-core-utils.h"
+#include "nm-glib-aux/nm-enum-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
+#include "c-list/src/c-list.h"
+#include "nms-ifcfg-rh-utils.h"
+
+/*****************************************************************************/
+
+struct _shvarLine {
+    const char *key;
+
+    CList lst;
+
+    /* We index variables by their key in shvarFile.lst_idx. One shell variable might
+     * occur multiple times in a file (in which case the last occurrence wins).
+     * Hence, we need to keep a list of all the same keys.
+     *
+     * This is a pointer to the next shadowed line. */
+    struct _shvarLine *prev_shadowed;
+
+    /* There are three cases:
+     *
+     * 1) the line is not a valid variable assignment (that is, it doesn't
+     *   start with a "FOO=" with possible whitespace prefix).
+     *   In that case, @key and @key_with_prefix are %NULL, and the entire
+     *   original line is in @line. Such entries are ignored for the most part.
+     *
+     * 2) if the line can be parsed with a "FOO=" assignment, then @line contains
+     *   the part after '=', @key_with_prefix contains the key "FOO" with possible
+     *   whitespace prefix, and @key points into @key_with_prefix skipping over the
+     *   whitespace.
+     *
+     * 3) like 2, but if the value was deleted via svSetValue(), the entry is not removed,
+     *   but only marked for deletion. That is done by clearing @line but preserving
+     *   @key/@key_with_prefix.
+     * */
+    char *line;
+    char *key_with_prefix;
+
+    /* svSetValue() will clear the dirty flag. */
+    bool dirty : 1;
+};
+
+typedef struct _shvarLine shvarLine;
+
+struct _shvarFile {
+    char *      fileName;
+    CList       lst_head;
+    GHashTable *lst_idx;
+    int         fd;
+    bool        modified : 1;
+};
+
+/*****************************************************************************/
+
+static void _line_link_parse(shvarFile *s, const char *value, gsize len);
+
+/*****************************************************************************/
+
+#define ASSERT_key_is_well_known(key)                                  \
+    nm_assert(({                                                       \
+        const char *_key          = (key);                             \
+        gboolean    _is_wellknown = TRUE;                              \
+                                                                       \
+        if (!nms_ifcfg_rh_utils_is_well_known_key(_key)) {             \
+            _is_wellknown = FALSE;                                     \
+            g_critical("ifcfg-rh key \"%s\" is not well-known", _key); \
+        }                                                              \
+                                                                       \
+        _is_wellknown;                                                 \
+    }))
+
+/**
+ * svParseBoolean:
+ * @value: the input string
+ * @fallback: the fallback value
+ *
+ * Parses a string and returns the boolean value it contains or,
+ * in case no valid value is found, the fallback value. Valid values
+ * are: "yes", "true", "t", "y", "1" and "no", "false", "f", "n", "0".
+ *
+ * Returns: the parsed boolean value or @fallback.
+ */
+int
+svParseBoolean(const char *value, int fallback)
+{
+    if (!value)
+        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))
+        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))
+        return FALSE;
+
+    return fallback;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_shell_is_name(const char *key, gssize len)
+{
+    gssize i;
+
+    /* whether @key is a valid identifier (name). */
+    if (!key || len == 0)
+        return FALSE;
+    if (!g_ascii_isalpha(key[0]) && key[0] != '_')
+        return FALSE;
+    for (i = 1; TRUE; i++) {
+        if (len < 0) {
+            if (!key[i])
+                return TRUE;
+        } else {
+            if (i >= len)
+                return TRUE;
+        }
+        if (!g_ascii_isalnum(key[i]) && key[i] != '_')
+            return FALSE;
+    }
+}
+
+/*****************************************************************************/
+
+/* like g_strescape(), except that it also escapes '\''' *sigh*.
+ *
+ * While at it, add $''. */
+static char *
+_escape_ansic(const char *source)
+{
+    const char *p;
+    char *      dest;
+    char *      q;
+
+    nm_assert(source);
+
+    p = (const char *) source;
+    /* Each source byte needs maximally four destination chars (\777) */
+    q = dest = g_malloc(strlen(source) * 4 + 1 + 3);
+
+    *q++ = '$';
+    *q++ = '\'';
+
+    while (*p) {
+        switch (*p) {
+        case '\b':
+            *q++ = '\\';
+            *q++ = 'b';
+            break;
+        case '\f':
+            *q++ = '\\';
+            *q++ = 'f';
+            break;
+        case '\n':
+            *q++ = '\\';
+            *q++ = 'n';
+            break;
+        case '\r':
+            *q++ = '\\';
+            *q++ = 'r';
+            break;
+        case '\t':
+            *q++ = '\\';
+            *q++ = 't';
+            break;
+        case '\v':
+            *q++ = '\\';
+            *q++ = 'v';
+            break;
+        case '\\':
+        case '"':
+        case '\'':
+            *q++ = '\\';
+            *q++ = *p;
+            break;
+        default:
+            if ((*p < ' ') || (*p >= 0177)) {
+                *q++ = '\\';
+                *q++ = '0' + (((*p) >> 6) & 07);
+                *q++ = '0' + (((*p) >> 3) & 07);
+                *q++ = '0' + ((*p) & 07);
+            } else
+                *q++ = *p;
+            break;
+        }
+        p++;
+    }
+    *q++ = '\'';
+    *q++ = '\0';
+
+    nm_assert(q - dest <= strlen(source) * 4 + 1 + 3);
+
+    return dest;
+}
+
+/*****************************************************************************/
+
+#define _char_req_escape(ch)     NM_IN_SET(ch, '"', '\\', '$', '`')
+#define _char_req_escape_old(ch) NM_IN_SET(ch, '"', '\\', '\'', '$', '`', '~')
+#define _char_req_quotes(ch)     NM_IN_SET(ch, ' ', '\'', '~', '\t', '|', '&', ';', '(', ')', '<', '>')
+
+const char *
+svEscape(const char *s, char **to_free)
+{
+    char *new;
+    gsize    mangle          = 0;
+    gboolean requires_quotes = FALSE;
+    int      newlen;
+    size_t   i, j, slen;
+
+    for (slen = 0; s[slen]; slen++) {
+        if (_char_req_escape(s[slen]))
+            mangle++;
+        else if (_char_req_quotes(s[slen]))
+            requires_quotes = TRUE;
+        else if (s[slen] < ' ') {
+            /* if the string contains newline we can only express it using ANSI C quotation
+             * (as we don't support line continuation).
+             * Additionally, ANSI control characters look odd with regular quotation, so handle
+             * them too. */
+            return (*to_free = _escape_ansic(s));
+        }
+    }
+    if (!mangle && !requires_quotes) {
+        *to_free = NULL;
+        return s;
+    }
+
+    newlen = slen + mangle + 3; /* 3 is extra ""\0 */
+    new    = g_malloc(newlen);
+
+    j        = 0;
+    new[j++] = '"';
+    for (i = 0; i < slen; i++) {
+        if (_char_req_escape(s[i]))
+            new[j++] = '\\';
+        new[j++] = s[i];
+    }
+    new[j++] = '"';
+    new[j++] = '\0';
+
+    nm_assert(j == slen + mangle + 3);
+
+    *to_free = new;
+    return new;
+}
+
+static gboolean
+_looks_like_old_svescaped(const char *value)
+{
+    gsize k;
+
+    if (value[0] != '"')
+        return FALSE;
+
+    for (k = 1;; k++) {
+        if (value[k] == '\0')
+            return FALSE;
+        if (!_char_req_escape_old(value[k]))
+            continue;
+
+        if (value[k] == '"')
+            return (value[k + 1] == '\0');
+        else if (value[k] == '\\') {
+            k++;
+            if (!_char_req_escape_old(value[k]))
+                return FALSE;
+        } else
+            return FALSE;
+    }
+}
+
+static gboolean
+_ch_octal_is(char ch)
+{
+    return ch >= '0' && ch < '8';
+}
+
+static guint8
+_ch_octal_get(char ch)
+{
+    nm_assert(_ch_octal_is(ch));
+    return (ch - '0');
+}
+
+static gboolean
+_ch_hex_is(char ch)
+{
+    return g_ascii_isxdigit(ch);
+}
+
+static guint8
+_ch_hex_get(char ch)
+{
+    nm_assert(_ch_hex_is(ch));
+    return ch <= '9' ? ch - '0' : (ch & 0x4F) - 'A' + 10;
+}
+
+static void
+_gstr_init(GString **str, const char *value, gsize i)
+{
+    nm_assert(str);
+    nm_assert(value);
+
+    if (!(*str)) {
+        /* if @str is not yet initialized, it allocates
+         * a new GString and copies @i characters from
+         * @value over.
+         *
+         * Unescaping usually does not extend the length of a string,
+         * so we might be tempted to allocate a fixed buffer of length
+         * (strlen(value)+CONST).
+         * However, due to $'\Ux' escapes, the maximum length is some
+         * (FACTOR*strlen(value) + CONST), which is non trivial to get
+         * right in all cases. Also, we would have to provision for the
+         * very unlikely extreme case.
+         * Instead, use a GString buffer which can grow as needed. But for an
+         * initial guess, strlen(value) is a good start */
+        *str = g_string_new_len(NULL, strlen(value) + 3);
+        if (i)
+            g_string_append_len(*str, value, i);
+    }
+}
+
+const char *
+svUnescape(const char *value, char **to_free)
+{
+    gsize    i, j;
+    GString *str                      = NULL;
+    int      looks_like_old_svescaped = -1;
+
+    /* we handle bash syntax here (note that ifup has #!/bin/bash.
+     * Thus, see https://www.gnu.org/software/bash/manual/html_node/Quoting.html#Quoting */
+
+    /* @value shall start with the first character after "FOO=" */
+
+    nm_assert(value);
+    nm_assert(to_free);
+
+    /* we don't expect any newlines. They must be filtered out before-hand.
+     * We also don't support line continuation. */
+    nm_assert(!NM_STRCHAR_ANY(value, ch, ch == '\n'));
+
+    i = 0;
+    while (TRUE) {
+        if (value[i] == '\0')
+            goto out_value;
+
+        if (g_ascii_isspace(value[i]) || value[i] == ';') {
+            gboolean has_semicolon = (value[i] == ';');
+
+            /* starting with space is only allowed, if the entire
+             * string consists of spaces (possibly terminated by a comment).
+             * This disallows for example
+             *   LANG=C ls -1
+             *   LANG=  ls -1
+             * but allows
+             *   LANG= #comment
+             *
+             * As a special case, we also allow one trailing semicolon, as long
+             * it is only followed by whitespace or a #-comment.
+             *   FOO=;
+             *   FOO=a;
+             *   FOO=b ; #hallo
+             */
+            j = i + 1;
+            while (g_ascii_isspace(value[j])
+                   || (!has_semicolon && (has_semicolon = (value[j] == ';'))))
+                j++;
+            if (!NM_IN_SET(value[j], '\0', '#'))
+                goto out_error;
+            goto out_value;
+        }
+
+        if (value[i] == '\\') {
+            /* backslash escape */
+            _gstr_init(&str, value, i);
+            i++;
+            if (G_UNLIKELY(value[i] == '\0')) {
+                /* we don't support line continuation */
+                goto out_error;
+            }
+            g_string_append_c(str, value[i]);
+            i++;
+            goto loop1_next;
+        }
+
+        if (value[i] == '\'') {
+            /* single quotes */
+            _gstr_init(&str, value, i);
+            i++;
+            j = i;
+            while (TRUE) {
+                if (value[j] == '\0') {
+                    /* unterminated single quote. We don't support line continuation */
+                    goto out_error;
+                }
+                if (value[j] == '\'')
+                    break;
+                j++;
+            }
+            g_string_append_len(str, &value[i], j - i);
+            i = j + 1;
+            goto loop1_next;
+        }
+
+        if (value[i] == '"') {
+            /* double quotes */
+            _gstr_init(&str, value, i);
+            i++;
+            while (TRUE) {
+                if (value[i] == '"') {
+                    i++;
+                    break;
+                }
+                if (value[i] == '\0') {
+                    /* unterminated double quote. We don't support line continuation. */
+                    goto out_error;
+                }
+                if (NM_IN_SET(value[i], '`', '$')) {
+                    /* we don't support shell expansion. */
+                    goto out_error;
+                }
+                if (value[i] == '\\') {
+                    i++;
+                    if (value[i] == '\0') {
+                        /* we don't support line continuation */
+                        goto out_error;
+                    }
+                    if (NM_IN_SET(value[i], '$', '`', '"', '\\')) {
+                        /* Drop the backslash. */
+                    } else if (NM_IN_SET(value[i], '\'', '~')) {
+                        /* '\'' and '~' in double quotes are not handled special by shell.
+                         * However, old versions of svEscape() would wrongly use double-quoting
+                         * with backslash escaping for these characters (expecting svUnescape()
+                         * to remove the backslash).
+                         *
+                         * In order to preserve previous behavior, we continue to read such
+                         * strings different then shell does. */
+
+                        /* Actually, we can relax this. Old svEscape() escaped the entire value
+                         * in a particular way with double quotes.
+                         * If the value doesn't exactly look like something as created by svEscape(),
+                         * don't do the compat hack and preserve the backslash. */
+                        if (looks_like_old_svescaped < 0)
+                            looks_like_old_svescaped = _looks_like_old_svescaped(value);
+                        if (!looks_like_old_svescaped)
+                            g_string_append_c(str, '\\');
+                    } else
+                        g_string_append_c(str, '\\');
+                }
+                g_string_append_c(str, value[i]);
+                i++;
+            }
+            goto loop1_next;
+        }
+
+        if (value[i] == '$' && value[i + 1] == '\'') {
+            /* ANSI-C Quoting */
+            _gstr_init(&str, value, i);
+            i += 2;
+            while (TRUE) {
+                char ch;
+
+                if (value[i] == '\'') {
+                    i++;
+                    break;
+                }
+                if (value[i] == '\0') {
+                    /* unterminated double quote. We don't support line continuation. */
+                    goto out_error;
+                }
+                if (value[i] == '\\') {
+                    i++;
+                    if (value[i] == '\0') {
+                        /* we don't support line continuation */
+                        goto out_error;
+                    }
+                    switch (value[i]) {
+                    case 'a':
+                        ch = '\a';
+                        break;
+                    case 'b':
+                        ch = '\b';
+                        break;
+                    case 'e':
+                        ch = '\e';
+                        break;
+                    case 'E':
+                        ch = '\E';
+                        break;
+                    case 'f':
+                        ch = '\f';
+                        break;
+                    case 'n':
+                        ch = '\n';
+                        break;
+                    case 'r':
+                        ch = '\r';
+                        break;
+                    case 't':
+                        ch = '\t';
+                        break;
+                    case 'v':
+                        ch = '\v';
+                        break;
+                    case '?':
+                        ch = '\?';
+                        break;
+                    case '"':
+                        ch = '"';
+                        break;
+                    case '\\':
+                        ch = '\\';
+                        break;
+                    case '\'':
+                        ch = '\'';
+                        break;
+                    default:
+                        if (_ch_octal_is(value[i])) {
+                            guint v;
+
+                            v = _ch_octal_get(value[i]);
+                            i++;
+                            if (_ch_octal_is(value[i])) {
+                                v = (v * 8) + _ch_octal_get(value[i]);
+                                i++;
+                                if (_ch_octal_is(value[i])) {
+                                    v = (v * 8) + _ch_octal_get(value[i]);
+                                    i++;
+                                }
+                            }
+                            /* like bash, we cut too large numbers off. E.g. A=$'\772' becomes 0xfa  */
+                            g_string_append_c(str, (guint8) v);
+                        } else if (NM_IN_SET(value[i], 'x', 'u', 'U')) {
+                            const char escape_type = value[i];
+                            int max_digits = escape_type == 'x' ? 2 : escape_type == 'u' ? 4 : 8;
+                            guint64 v;
+
+                            i++;
+                            if (!_ch_hex_is(value[i])) {
+                                /* missing hex value after "\x" escape. This is treated like no escaping. */
+                                g_string_append_c(str, '\\');
+                                g_string_append_c(str, escape_type);
+                            } else {
+                                v = _ch_hex_get(value[i]);
+                                i++;
+
+                                while (--max_digits > 0) {
+                                    if (!_ch_hex_is(value[i]))
+                                        break;
+                                    v = v * 16 + _ch_hex_get(value[i]);
+                                    i++;
+                                }
+                                if (escape_type == 'x')
+                                    g_string_append_c(str, v);
+                                else {
+                                    /* we treat the unicode escapes as utf-8 encoded values. */
+                                    g_string_append_unichar(str, v);
+                                }
+                            }
+                        } else {
+                            g_string_append_c(str, '\\');
+                            g_string_append_c(str, value[i]);
+                            i++;
+                        }
+                        goto loop_ansic_next;
+                    }
+                } else
+                    ch = value[i];
+                g_string_append_c(str, ch);
+                i++;
+loop_ansic_next:;
+            }
+            goto loop1_next;
+        }
+
+        if (NM_IN_SET(value[i], '|', '&', '(', ')', '<', '>')) {
+            /* shell metacharacters are not supported without quoting.
+             * Note that ';' is already handled above. */
+            goto out_error;
+        }
+
+        /* an unquoted, regular character. Just consume it directly. */
+        if (str)
+            g_string_append_c(str, value[i]);
+        i++;
+
+loop1_next:;
+    }
+
+    nm_assert_not_reached();
+
+out_value:
+    if (i == 0) {
+        nm_assert(!str);
+        *to_free = NULL;
+        return "";
+    }
+
+    if (str) {
+        if (str->len == 0 || str->str[0] == '\0') {
+            g_string_free(str, TRUE);
+            *to_free = NULL;
+            return "";
+        } else {
+            *to_free = g_string_free(str, FALSE);
+            return *to_free;
+        }
+    }
+
+    if (value[i] != '\0') {
+        *to_free = g_strndup(value, i);
+        return *to_free;
+    }
+
+    *to_free = NULL;
+    return value;
+
+out_error:
+    if (str)
+        g_string_free(str, TRUE);
+    *to_free = NULL;
+    return NULL;
+}
+
+/*****************************************************************************/
+
+shvarFile *
+svFile_new(const char *name, int fd, const char *content)
+{
+    shvarFile * s;
+    const char *p;
+    const char *q;
+
+    nm_assert(name);
+    nm_assert(fd >= -1);
+
+    s  = g_slice_new(shvarFile);
+    *s = (shvarFile){
+        .fileName = g_strdup(name),
+        .fd       = fd,
+        .lst_head = C_LIST_INIT(s->lst_head),
+        .lst_idx  = g_hash_table_new(nm_pstr_hash, nm_pstr_equal),
+    };
+
+    if (content) {
+        for (p = content; (q = strchr(p, '\n')) != NULL; p = q + 1)
+            _line_link_parse(s, p, q - p);
+        if (p[0])
+            _line_link_parse(s, p, strlen(p));
+    }
+
+    return s;
+}
+
+const char *
+svFileGetName(const shvarFile *s)
+{
+    nm_assert(s);
+
+    return s->fileName;
+}
+
+void
+_nmtst_svFileSetName(shvarFile *s, const char *fileName)
+{
+    /* changing the file name is not supported for regular
+     * operation. Only allowed to use in tests, otherwise,
+     * the filename is immutable. */
+    g_free(s->fileName);
+    s->fileName = g_strdup(fileName);
+}
+
+void
+_nmtst_svFileSetModified(shvarFile *s)
+{
+    /* marking a file as modified is only for testing. */
+    s->modified = TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+ASSERT_shvarLine(const shvarLine *line)
+{
+#if NM_MORE_ASSERTS > 5
+    const char *s, *s2;
+
+    nm_assert(line);
+    if (!line->key) {
+        nm_assert(line->line);
+        nm_assert(!line->key_with_prefix);
+        s  = nm_str_skip_leading_spaces(line->line);
+        s2 = strchr(s, '=');
+        nm_assert(!s2 || !_shell_is_name(s, s2 - s));
+    } else {
+        nm_assert(line->key_with_prefix);
+        nm_assert(line->key == nm_str_skip_leading_spaces(line->key_with_prefix));
+        nm_assert(_shell_is_name(line->key, -1));
+    }
+#endif
+}
+
+static shvarLine *
+line_new_parse(const char *value, gsize len)
+{
+    shvarLine *line;
+    gsize      k, e;
+
+    nm_assert(value);
+
+    line  = g_slice_new(shvarLine);
+    *line = (shvarLine){
+        .lst   = C_LIST_INIT(line->lst),
+        .dirty = TRUE,
+    };
+
+    for (k = 0; k < len; k++) {
+        if (g_ascii_isspace(value[k]))
+            continue;
+
+        if (g_ascii_isalpha(value[k]) || value[k] == '_') {
+            for (e = k + 1; e < len; e++) {
+                if (value[e] == '=') {
+                    nm_assert(_shell_is_name(&value[k], e - k));
+                    line->line            = g_strndup(&value[e + 1], len - e - 1);
+                    line->key_with_prefix = g_strndup(value, e);
+                    line->key             = &line->key_with_prefix[k];
+                    ASSERT_shvarLine(line);
+                    return line;
+                }
+                if (!g_ascii_isalnum(value[e]) && value[e] != '_')
+                    break;
+            }
+        }
+        break;
+    }
+    line->line = g_strndup(value, len);
+    ASSERT_shvarLine(line);
+    return line;
+}
+
+static shvarLine *
+line_new_build(const char *key, const char *value)
+{
+    char *     value_escaped = NULL;
+    shvarLine *line;
+    char *     new_key;
+
+    value = svEscape(value, &value_escaped);
+
+    line    = g_slice_new(shvarLine);
+    new_key = g_strdup(key), *line = (shvarLine){
+                                 .lst             = C_LIST_INIT(line->lst),
+                                 .line            = value_escaped ?: g_strdup(value),
+                                 .key_with_prefix = new_key,
+                                 .key             = new_key,
+                                 .dirty           = FALSE,
+                             };
+    ASSERT_shvarLine(line);
+    return line;
+}
+
+static gboolean
+line_set(shvarLine *line, const char *value)
+{
+    char *   value_escaped = NULL;
+    gboolean changed       = FALSE;
+
+    ASSERT_shvarLine(line);
+    nm_assert(line->key);
+
+    line->dirty = FALSE;
+
+    if (line->key != line->key_with_prefix) {
+        memmove(line->key_with_prefix, line->key, strlen(line->key) + 1);
+        line->key = line->key_with_prefix;
+        changed   = TRUE;
+        ASSERT_shvarLine(line);
+    }
+
+    value = svEscape(value, &value_escaped);
+
+    if (line->line) {
+        if (nm_streq(value, line->line)) {
+            g_free(value_escaped);
+            return changed;
+        }
+        g_free(line->line);
+    }
+
+    line->line = value_escaped ?: g_strdup(value);
+    ASSERT_shvarLine(line);
+    return TRUE;
+}
+
+static void
+line_free(shvarLine *line)
+{
+    ASSERT_shvarLine(line);
+    c_list_unlink_stale(&line->lst);
+    g_free(line->line);
+    g_free(line->key_with_prefix);
+    g_slice_free(shvarLine, line);
+}
+
+/*****************************************************************************/
+
+static void
+_line_link_parse(shvarFile *s, const char *value, gsize len)
+{
+    shvarLine *line;
+
+    line = line_new_parse(value, len);
+    if (!line->key)
+        goto do_link;
+
+    if (G_UNLIKELY(!g_hash_table_insert(s->lst_idx, line, line))) {
+        shvarLine *existing_key;
+        shvarLine *existing_val;
+
+        /* Slow-path: we have duplicate keys. Fix the mess we created.
+         * Unfortunately, g_hash_table_insert() now had to allocate an extra
+         * array to track the keys/values differently. I wish there was an
+         * GHashTable API to add a key only if it does not exist yet. */
+
+        if (!g_hash_table_lookup_extended(s->lst_idx,
+                                          line,
+                                          (gpointer *) &existing_key,
+                                          (gpointer *) &existing_val))
+            nm_assert_not_reached();
+
+        nm_assert(existing_val == line);
+        nm_assert(existing_key != line);
+        line->prev_shadowed = existing_key;
+        g_hash_table_replace(s->lst_idx, line, line);
+    }
+
+do_link:
+    c_list_link_tail(&s->lst_head, &line->lst);
+}
+
+/*****************************************************************************/
+
+/* Open the file <name>, returning a shvarFile on success and NULL on failure.
+ * Add a wrinkle to let the caller specify whether or not to create the file
+ * (actually, return a structure anyway) if it doesn't exist.
+ */
+static shvarFile *
+svOpenFileInternal(const char *name, gboolean create, GError **error)
+{
+    gboolean      closefd       = FALSE;
+    int           errsv         = 0;
+    gs_free char *content       = NULL;
+    gs_free_error GError *local = NULL;
+    nm_auto_close int     fd    = -1;
+
+    if (create)
+        fd = open(name, O_RDWR | O_CLOEXEC); /* NOT O_CREAT */
+    if (fd < 0) {
+        /* try read-only */
+        fd = open(name, O_RDONLY | O_CLOEXEC); /* NOT O_CREAT */
+        if (fd < 0)
+            errsv = errno;
+        else
+            closefd = TRUE;
+    }
+
+    if (fd < 0) {
+        if (create)
+            return svFile_new(name, -1, NULL);
+
+        g_set_error(error,
+                    G_FILE_ERROR,
+                    g_file_error_from_errno(errsv),
+                    "Could not read file '%s': %s",
+                    name,
+                    nm_strerror_native(errsv));
+        return NULL;
+    }
+
+    if (!nm_utils_fd_get_contents(closefd ? nm_steal_fd(&fd) : fd,
+                                  closefd,
+                                  10 * 1024 * 1024,
+                                  NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+                                  &content,
+                                  NULL,
+                                  NULL,
+                                  &local)) {
+        if (create)
+            return svFile_new(name, -1, NULL);
+
+        g_set_error(error,
+                    G_FILE_ERROR,
+                    local->domain == G_FILE_ERROR ? local->code : G_FILE_ERROR_FAILED,
+                    "Could not read file '%s': %s",
+                    name,
+                    local->message);
+        return NULL;
+    }
+
+    /* closefd is set if we opened the file read-only, so go ahead and
+     * close it, because we can't write to it anyway */
+    nm_assert(closefd || fd >= 0);
+    return svFile_new(name, !closefd ? nm_steal_fd(&fd) : -1, content);
+}
+
+/* Open the file <name>, return shvarFile on success, NULL on failure */
+shvarFile *
+svOpenFile(const char *name, GError **error)
+{
+    return svOpenFileInternal(name, FALSE, error);
+}
+
+/* Create a new file structure, returning actual data if the file exists,
+ * and a suitable starting point if it doesn't.
+ */
+shvarFile *
+svCreateFile(const char *name)
+{
+    return svOpenFileInternal(name, TRUE, NULL);
+}
+
+/*****************************************************************************/
+
+static gboolean
+_svKeyMatchesType(const char *key, SvKeyType match_key_type)
+{
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ANY))
+        return TRUE;
+
+#define _IS_NUMBERED(key, tag)                             \
+    ({                                                     \
+        gint64 _idx;                                       \
+                                                           \
+        NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, tag, &_idx) \
+        &&_idx >= 0;                                       \
+    })
+
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTE_SVFORMAT)) {
+        if (_IS_NUMBERED(key, "ADDRESS") || _IS_NUMBERED(key, "NETMASK")
+            || _IS_NUMBERED(key, "GATEWAY") || _IS_NUMBERED(key, "METRIC")
+            || _IS_NUMBERED(key, "OPTIONS"))
+            return TRUE;
+    }
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_IP4_ADDRESS)) {
+        if (_IS_NUMBERED(key, "IPADDR") || _IS_NUMBERED(key, "PREFIX")
+            || _IS_NUMBERED(key, "NETMASK") || _IS_NUMBERED(key, "GATEWAY"))
+            return TRUE;
+    }
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_USER)) {
+        if (g_str_has_prefix(key, "NM_USER_"))
+            return TRUE;
+    }
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_TC)) {
+        if (_IS_NUMBERED(key, "QDISC") || _IS_NUMBERED(key, "FILTER"))
+            return TRUE;
+    }
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_SRIOV_VF)) {
+        if (_IS_NUMBERED(key, "SRIOV_VF"))
+            return TRUE;
+    }
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) {
+        if (_IS_NUMBERED(key, "ROUTING_RULE_"))
+            return TRUE;
+    }
+    if (NM_FLAGS_HAS(match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) {
+        if (_IS_NUMBERED(key, "ROUTING_RULE6_"))
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+gint64
+svNumberedParseKey(const char *key)
+{
+    gint64 idx;
+
+    if (NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, "ROUTING_RULE_", &idx)
+        || NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, "ROUTING_RULE6_", &idx))
+        return idx;
+    return -1;
+}
+
+GHashTable *
+svGetKeys(shvarFile *s, SvKeyType match_key_type)
+{
+    GHashTable *     keys = NULL;
+    CList *          current;
+    const shvarLine *line;
+
+    nm_assert(s);
+
+    c_list_for_each (current, &s->lst_head) {
+        line = c_list_entry(current, shvarLine, lst);
+        if (line->key && line->line && _svKeyMatchesType(line->key, match_key_type)) {
+            /* we don't clone the keys. The keys are only valid
+             * until @s gets modified. */
+            if (!keys)
+                keys = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, NULL);
+            g_hash_table_add(keys, (gpointer) line->key);
+        }
+    }
+    return keys;
+}
+
+static int
+_get_keys_sorted_cmp(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+    const char *k_a = *((const char *const *) a);
+    const char *k_b = *((const char *const *) b);
+    gint64      n_a;
+    gint64      n_b;
+
+    n_a = svNumberedParseKey(k_a);
+    n_b = svNumberedParseKey(k_b);
+    NM_CMP_DIRECT(n_a, n_b);
+    NM_CMP_RETURN(strcmp(k_a, k_b));
+    nm_assert_not_reached();
+    return 0;
+}
+
+const char **
+svGetKeysSorted(shvarFile *s, SvKeyType match_key_type, guint *out_len)
+{
+    gs_unref_hashtable GHashTable *keys_hash = NULL;
+
+    keys_hash = svGetKeys(s, match_key_type);
+    if (!keys_hash) {
+        NM_SET_OUT(out_len, 0);
+        return NULL;
+    }
+    return (
+        const char **) nm_utils_hash_keys_to_array(keys_hash, _get_keys_sorted_cmp, NULL, out_len);
+}
+
+/*****************************************************************************/
+
+const char *
+svFindFirstNumberedKey(shvarFile *s, const char *key_prefix)
+{
+    const shvarLine *line;
+
+    g_return_val_if_fail(s, NULL);
+    g_return_val_if_fail(key_prefix, NULL);
+
+    c_list_for_each_entry (line, &s->lst_head, lst) {
+        if (line->key && line->line
+            && nms_ifcfg_rh_utils_is_numbered_tag(line->key, key_prefix, NULL))
+            return line->key;
+    }
+
+    return NULL;
+}
+
+/*****************************************************************************/
+
+static const char *
+_svGetValue(shvarFile *s, const char *key, char **to_free)
+{
+    const shvarLine *line;
+    const char *     v;
+
+    nm_assert(s);
+    nm_assert(_shell_is_name(key, -1));
+    nm_assert(to_free);
+
+    ASSERT_key_is_well_known(key);
+
+    line = g_hash_table_lookup(s->lst_idx, &key);
+
+    if (line && line->line) {
+        v = svUnescape(line->line, to_free);
+        if (!v) {
+            /* a wrongly quoted value is treated like the empty string.
+             * See also svWriteFile(), which handles unparsable values
+             * that way. */
+            nm_assert(!*to_free);
+            return "";
+        }
+        return v;
+    }
+    *to_free = NULL;
+    return NULL;
+}
+
+/* Returns the value for key. The value is either owned by @s
+ * or returned as to_free. This aims to avoid cloning the string.
+ *
+ * - like svGetValue_cp(), but avoids cloning the value if possible.
+ * - like svGetValueStr(), but does not ignore empty string values.
+ */
+const char *
+svGetValue(shvarFile *s, const char *key, char **to_free)
+{
+    g_return_val_if_fail(s, NULL);
+    g_return_val_if_fail(key, NULL);
+    g_return_val_if_fail(to_free, NULL);
+
+    return _svGetValue(s, key, to_free);
+}
+
+/* Returns the value for key. The value is either owned by @s
+ * or returned as to_free. This aims to avoid cloning the string.
+ *
+ * - like svGetValue(), but does not return an empty string.
+ * - like svGetValueStr_cp(), but avoids cloning the value if possible.
+ */
+const char *
+svGetValueStr(shvarFile *s, const char *key, char **to_free)
+{
+    const char *value;
+
+    g_return_val_if_fail(s, NULL);
+    g_return_val_if_fail(key, NULL);
+    g_return_val_if_fail(to_free, NULL);
+
+    value = _svGetValue(s, key, to_free);
+    if (!value || !value[0]) {
+        nm_assert(!*to_free);
+        return NULL;
+    }
+    return value;
+}
+
+/* Returns the value for key. The returned value must be freed
+ * by the caller.
+ *
+ * - like svGetValue(), but always returns a copy of the value.
+ * - like svGetValueStr_cp(), but does not ignore an empty string.
+ */
+char *
+svGetValue_cp(shvarFile *s, const char *key)
+{
+    char *      to_free;
+    const char *value;
+
+    g_return_val_if_fail(s, NULL);
+    g_return_val_if_fail(key, NULL);
+
+    value = _svGetValue(s, key, &to_free);
+    if (!value) {
+        nm_assert(!to_free);
+        return NULL;
+    }
+    return to_free ?: g_strdup(value);
+}
+
+/* Returns the value for key. The returned value must be freed
+ * by the caller.
+ * If the key is unset or the value an empty string, NULL is returned.
+ *
+ * - like svGetValueStr(), but always returns a copy of the value.
+ * - like svGetValue_cp(), but returns NULL instead of an empty string.
+ */
+char *
+svGetValueStr_cp(shvarFile *s, const char *key)
+{
+    char *      to_free;
+    const char *value;
+
+    g_return_val_if_fail(s, NULL);
+    g_return_val_if_fail(key, NULL);
+
+    value = _svGetValue(s, key, &to_free);
+    if (!value || !value[0]) {
+        nm_assert(!to_free);
+        return NULL;
+    }
+    return to_free ?: g_strdup(value);
+}
+
+/* svGetValueBoolean:
+ * @s: fhe file
+ * @key: the name of the key to read
+ * @fallback: the fallback value in any error case
+ *
+ * Reads a value @key and converts it to a boolean using svParseBoolean().
+ *
+ * Returns: the parsed boolean value or @fallback.
+ */
+int
+svGetValueBoolean(shvarFile *s, const char *key, int fallback)
+{
+    gs_free char *to_free = NULL;
+    const char *  value;
+
+    value = _svGetValue(s, key, &to_free);
+    return svParseBoolean(value, fallback);
+}
+
+/* svGetValueTernary:
+ * @s: fhe file
+ * @key: the name of the key to read
+ *
+ * Reads a value @key and converts it to a NMTernary value.
+ *
+ * Returns: the parsed NMTernary
+ */
+NMTernary
+svGetValueTernary(shvarFile *s, const char *key)
+{
+    return svGetValueBoolean(s, key, NM_TERNARY_DEFAULT);
+}
+
+/* svGetValueInt64:
+ * @s: fhe file
+ * @key: the name of the key to read
+ * @base: the numeric base (usually 10). Setting to 0 means "auto". Usually you want 10.
+ * @min: the minimum for range-check
+ * @max: the maximum for range-check
+ * @fallback: the fallback value in any error case
+ *
+ * Reads a value @key and converts it to an integer using _nm_utils_ascii_str_to_int64().
+ * In case of error, @errno will be set and @fallback returned. */
+gint64
+svGetValueInt64(shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback)
+{
+    char *      to_free;
+    const char *value;
+    gint64      result;
+    int         errsv;
+
+    value = _svGetValue(s, key, &to_free);
+    if (!value) {
+        nm_assert(!to_free);
+        /* indicate that the key does not exist (or has a syntax error
+         * and svUnescape() failed). */
+        errno = ENOKEY;
+        return fallback;
+    }
+
+    result = _nm_utils_ascii_str_to_int64(value, base, min, max, fallback);
+    if (to_free) {
+        errsv = errno;
+        g_free(to_free);
+        errno = errsv;
+    }
+    return result;
+}
+
+gboolean
+svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GError **error)
+{
+    gs_free char *to_free = NULL;
+    const char *  svalue;
+    gs_free char *err_token = NULL;
+    int           value;
+
+    svalue = _svGetValue(s, key, &to_free);
+    if (!svalue) {
+        /* don't touch out_value. The caller is supposed
+         * to initialize it with the default value. */
+        return TRUE;
+    }
+
+    if (!nm_utils_enum_from_str(gtype, svalue, &value, &err_token)) {
+        g_set_error(error,
+                    NM_UTILS_ERROR,
+                    NM_UTILS_ERROR_UNKNOWN,
+                    "Invalid token \"%s\" in \"%s\" for %s",
+                    err_token,
+                    svalue,
+                    key);
+        return FALSE;
+    }
+
+    NM_SET_OUT(out_value, value);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
+svUnsetAll(shvarFile *s, SvKeyType match_key_type)
+{
+    shvarLine *line;
+    gboolean   changed = FALSE;
+
+    g_return_val_if_fail(s, FALSE);
+
+    c_list_for_each_entry (line, &s->lst_head, lst) {
+        ASSERT_shvarLine(line);
+        if (line->key && _svKeyMatchesType(line->key, match_key_type)) {
+            if (nm_clear_g_free(&line->line)) {
+                ASSERT_shvarLine(line);
+                changed = TRUE;
+            }
+        }
+    }
+
+    if (changed)
+        s->modified = TRUE;
+    return changed;
+}
+
+gboolean
+svUnsetDirtyWellknown(shvarFile *s, NMTernary new_dirty_value)
+{
+    shvarLine *line;
+    gboolean   changed = FALSE;
+
+    g_return_val_if_fail(s, FALSE);
+
+    c_list_for_each_entry (line, &s->lst_head, lst) {
+        const NMSIfcfgKeyTypeInfo *ti;
+
+        ASSERT_shvarLine(line);
+
+        if (line->dirty && line->key && line->line
+            && (ti = nms_ifcfg_rh_utils_is_well_known_key(line->key))
+            && !NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY)) {
+            if (nm_clear_g_free(&line->line)) {
+                ASSERT_shvarLine(line);
+                changed = TRUE;
+            }
+        }
+
+        if (new_dirty_value != NM_TERNARY_DEFAULT)
+            line->dirty = (new_dirty_value != NM_TERNARY_FALSE);
+    }
+
+    if (changed)
+        s->modified = TRUE;
+    return changed;
+}
+
+/* Same as svSetValueStr() but it preserves empty @value -- contrary to
+ * svSetValueStr() for which "" effectively means to remove the value. */
+gboolean
+svSetValue(shvarFile *s, const char *key, const char *value)
+{
+    shvarLine *line;
+    shvarLine *l_shadowed;
+    gboolean   changed = FALSE;
+
+    g_return_val_if_fail(s, FALSE);
+    g_return_val_if_fail(key, FALSE);
+
+    nm_assert(_shell_is_name(key, -1));
+
+    ASSERT_key_is_well_known(key);
+
+    line = g_hash_table_lookup(s->lst_idx, &key);
+    if (line && (l_shadowed = line->prev_shadowed)) {
+        /* if we find multiple entries for the same key, we can
+         * delete the shadowed ones. */
+        line->prev_shadowed = NULL;
+        changed             = TRUE;
+        do {
+            shvarLine *l = l_shadowed;
+
+            l_shadowed = l_shadowed->prev_shadowed;
+            line_free(l);
+        } while (l_shadowed);
+    }
+
+    if (!value) {
+        if (line) {
+            /* We only clear the value, but leave the line entry. This way, if we
+             * happen to re-add the value, we write it to the same line again. */
+            if (nm_clear_g_free(&line->line)) {
+                changed = TRUE;
+            }
+        }
+    } else {
+        if (!line) {
+            line = line_new_build(key, value);
+            if (!g_hash_table_add(s->lst_idx, line))
+                nm_assert_not_reached();
+            c_list_link_tail(&s->lst_head, &line->lst);
+            changed = TRUE;
+        } else {
+            if (line_set(line, value))
+                changed = TRUE;
+        }
+    }
+
+    if (changed)
+        s->modified = TRUE;
+    return changed;
+}
+
+/* Set the variable <key> equal to the value <value>.
+ * If <key> does not exist, and the <current> pointer is set, append
+ * the key=value pair after that line.  Otherwise, append the pair
+ * to the bottom of the file.
+ */
+gboolean
+svSetValueStr(shvarFile *s, const char *key, const char *value)
+{
+    return svSetValue(s, key, value && value[0] ? value : NULL);
+}
+
+gboolean
+svSetValueInt64(shvarFile *s, const char *key, gint64 value)
+{
+    char buf[NM_DECIMAL_STR_MAX(value)];
+
+    return svSetValue(s, key, nm_sprintf_buf(buf, "%" G_GINT64_FORMAT, value));
+}
+
+gboolean
+svSetValueInt64_cond(shvarFile *s, const char *key, gboolean do_set, gint64 value)
+{
+    if (do_set)
+        return svSetValueInt64(s, key, value);
+    else
+        return svUnsetValue(s, key);
+}
+
+gboolean
+svSetValueBoolean(shvarFile *s, const char *key, gboolean value)
+{
+    return svSetValue(s, key, value ? "yes" : "no");
+}
+
+gboolean
+svSetValueTernary(shvarFile *s, const char *key, NMTernary value)
+{
+    if (NM_IN_SET(value, NM_TERNARY_TRUE, NM_TERNARY_FALSE))
+        return svSetValueBoolean(s, key, (gboolean) value);
+    else
+        return svUnsetValue(s, key);
+}
+
+gboolean
+svSetValueBoolean_cond_true(shvarFile *s, const char *key, gboolean value)
+{
+    return svSetValue(s, key, value ? "yes" : NULL);
+}
+
+gboolean
+svSetValueEnum(shvarFile *s, const char *key, GType gtype, int value)
+{
+    gs_free char *v = NULL;
+
+    v = _nm_utils_enum_to_str_full(gtype, value, " ", NULL);
+    return svSetValueStr(s, key, v);
+}
+
+gboolean
+svUnsetValue(shvarFile *s, const char *key)
+{
+    return svSetValue(s, key, NULL);
+}
+
+/*****************************************************************************/
+
+/* Write the current contents iff modified.  Returns FALSE on error
+ * and TRUE on success.  Do not write if no values have been modified.
+ * The mode argument is only used if creating the file, not if
+ * re-writing an existing file, and is passed unchanged to the
+ * open() syscall.
+ */
+gboolean
+svWriteFile(shvarFile *s, int mode, GError **error)
+{
+    FILE * f;
+    int    tmpfd;
+    CList *current;
+    int    errsv;
+
+    if (s->modified) {
+        if (s->fd == -1)
+            s->fd = open(s->fileName, O_WRONLY | O_CREAT | O_CLOEXEC, mode);
+        if (s->fd == -1) {
+            errsv = errno;
+            g_set_error(error,
+                        G_FILE_ERROR,
+                        g_file_error_from_errno(errsv),
+                        "Could not open file '%s' for writing: %s",
+                        s->fileName,
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+        if (ftruncate(s->fd, 0) < 0) {
+            errsv = errno;
+            g_set_error(error,
+                        G_FILE_ERROR,
+                        g_file_error_from_errno(errsv),
+                        "Could not overwrite file '%s': %s",
+                        s->fileName,
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+
+        tmpfd = fcntl(s->fd, F_DUPFD_CLOEXEC, 0);
+        if (tmpfd == -1) {
+            errsv = errno;
+            g_set_error(error,
+                        G_FILE_ERROR,
+                        g_file_error_from_errno(errsv),
+                        "Internal error writing file '%s': %s",
+                        s->fileName,
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+        f = fdopen(tmpfd, "w");
+        if (!f) {
+            errsv = errno;
+            g_set_error(error,
+                        G_FILE_ERROR,
+                        g_file_error_from_errno(errsv),
+                        "Internal error writing file '%s': %s",
+                        s->fileName,
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+        fseek(f, 0, SEEK_SET);
+        c_list_for_each (current, &s->lst_head) {
+            const shvarLine *line = c_list_entry(current, shvarLine, lst);
+            const char *     str;
+            char *           s_tmp;
+            gboolean         valid_value;
+
+            ASSERT_shvarLine(line);
+
+            if (!line->key) {
+                str = nm_str_skip_leading_spaces(line->line);
+                if (NM_IN_SET(str[0], '\0', '#'))
+                    fprintf(f, "%s\n", line->line);
+                else
+                    fprintf(f, "#NM: %s\n", line->line);
+                continue;
+            }
+
+            if (!line->line)
+                continue;
+
+            /* we check that the assignment can be properly unescaped. */
+            valid_value = !!svUnescape(line->line, &s_tmp);
+            g_free(s_tmp);
+
+            if (valid_value)
+                fprintf(f, "%s=%s\n", line->key_with_prefix, line->line);
+            else {
+                fprintf(f, "%s=\n", line->key);
+                fprintf(f, "#NM: %s=%s\n", line->key_with_prefix, line->line);
+            }
+        }
+        fclose(f);
+    }
+
+    return TRUE;
+}
+
+void
+svCloseFile(shvarFile *s)
+{
+    shvarLine *line;
+
+    g_return_if_fail(s != NULL);
+
+    if (s->fd >= 0)
+        nm_close(s->fd);
+    g_free(s->fileName);
+    g_hash_table_destroy(s->lst_idx);
+    while ((line = c_list_first_entry(&s->lst_head, shvarLine, lst)))
+        line_free(line);
+    g_slice_free(shvarFile, s);
+}
diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.h b/src/core/settings/plugins/ifcfg-rh/shvar.h
new file mode 100644
index 00000000..6965d873
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/shvar.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 1999 Red Hat, Inc.
+ */
+
+#ifndef _SHVAR_H
+#define _SHVAR_H
+
+typedef struct _shvarFile shvarFile;
+
+typedef enum {
+    SV_KEY_TYPE_ANY            = (1LL << 0),
+    SV_KEY_TYPE_ROUTE_SVFORMAT = (1LL << 1),
+    SV_KEY_TYPE_IP4_ADDRESS    = (1LL << 2),
+    SV_KEY_TYPE_TC             = (1LL << 3),
+    SV_KEY_TYPE_USER           = (1LL << 4),
+    SV_KEY_TYPE_SRIOV_VF       = (1LL << 5),
+    SV_KEY_TYPE_ROUTING_RULE4  = (1LL << 6),
+    SV_KEY_TYPE_ROUTING_RULE6  = (1LL << 7),
+} SvKeyType;
+
+const char *svFileGetName(const shvarFile *s);
+
+void _nmtst_svFileSetName(shvarFile *s, const char *fileName);
+void _nmtst_svFileSetModified(shvarFile *s);
+
+/*****************************************************************************/
+
+shvarFile *svFile_new(const char *name, int fd, const char *content);
+
+/* Create the file <name>, return a shvarFile (never fails) */
+shvarFile *svCreateFile(const char *name);
+
+/* Open the file <name>, return shvarFile on success, NULL on failure */
+shvarFile *svOpenFile(const char *name, GError **error);
+
+/*****************************************************************************/
+
+const char *svFindFirstNumberedKey(shvarFile *s, const char *key_prefix);
+
+/* Get the value associated with the key, and leave the current pointer
+ * pointing at the line containing the value.  The char* returned MUST
+ * be freed by the caller.
+ */
+const char *svGetValue(shvarFile *s, const char *key, char **to_free);
+char *      svGetValue_cp(shvarFile *s, const char *key);
+
+const char *svGetValueStr(shvarFile *s, const char *key, char **to_free);
+char *      svGetValueStr_cp(shvarFile *s, const char *key);
+
+int svParseBoolean(const char *value, int def);
+
+gint64 svNumberedParseKey(const char *key);
+
+GHashTable *svGetKeys(shvarFile *s, SvKeyType match_key_type);
+
+const char **svGetKeysSorted(shvarFile *s, SvKeyType match_key_type, guint *out_len);
+
+/* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
+ * return FALSE if <key> resolves to any non-truth value (e.g. "no", "n", "false")
+ * return <def> otherwise
+ */
+int svGetValueBoolean(shvarFile *s, const char *key, int def);
+
+NMTernary svGetValueTernary(shvarFile *s, const char *key);
+
+gint64
+svGetValueInt64(shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback);
+
+gboolean svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GError **error);
+
+/* Set the variable <key> equal to the value <value>.
+ * If <key> does not exist, and the <current> pointer is set, append
+ * the key=value pair after that line.  Otherwise, prepend the pair
+ * to the top of the file.
+ */
+gboolean svSetValue(shvarFile *s, const char *key, const char *value);
+gboolean svSetValueStr(shvarFile *s, const char *key, const char *value);
+gboolean svSetValueBoolean(shvarFile *s, const char *key, gboolean value);
+gboolean svSetValueBoolean_cond_true(shvarFile *s, const char *key, gboolean value);
+gboolean svSetValueInt64(shvarFile *s, const char *key, gint64 value);
+gboolean svSetValueInt64_cond(shvarFile *s, const char *key, gboolean do_set, gint64 value);
+gboolean svSetValueEnum(shvarFile *s, const char *key, GType gtype, int value);
+gboolean svSetValueTernary(shvarFile *s, const char *key, NMTernary value);
+
+gboolean svUnsetValue(shvarFile *s, const char *key);
+gboolean svUnsetAll(shvarFile *s, SvKeyType match_key_type);
+gboolean svUnsetDirtyWellknown(shvarFile *s, NMTernary new_dirty_value);
+
+/* Write the current contents iff modified.  Returns FALSE on error
+ * and TRUE on success.  Do not write if no values have been modified.
+ * The mode argument is only used if creating the file, not if
+ * re-writing an existing file, and is passed unchanged to the
+ * open() syscall.
+ */
+gboolean svWriteFile(shvarFile *s, int mode, GError **error);
+
+static inline gboolean
+svWriteFileWithoutDirtyWellknown(shvarFile *s, int mode, GError **error)
+{
+    svUnsetDirtyWellknown(s, NM_TERNARY_FALSE);
+    return svWriteFile(s, mode, error);
+}
+
+/* Close the file descriptor (if open) and free the shvarFile. */
+void svCloseFile(shvarFile *s);
+
+const char *svEscape(const char *s, char **to_free);
+const char *svUnescape(const char *s, char **to_free);
+
+static inline void
+_nm_auto_shvar_file_close(shvarFile **p_s)
+{
+    if (*p_s) {
+        int errsv = errno;
+
+        svCloseFile(*p_s);
+        errno = errsv;
+    }
+}
+#define nm_auto_shvar_file_close nm_auto(_nm_auto_shvar_file_close)
+
+#endif /* _SHVAR_H */
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/meson.build b/src/core/settings/plugins/ifcfg-rh/tests/meson.build
new file mode 100644
index 00000000..077e4782
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/meson.build
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-ifcfg-rh',
+  'test-ifcfg-rh.c',
+  dependencies: libNetworkManagerTest_dep,
+  c_args: test_c_flags,
+  link_with: libnms_ifcfg_rh_core,
+)
+
+test(
+  'ifcfg-rh/test-ifcfg-rh',
+  test_script,
+  timeout: 90,
+  args: test_args + [exe.full_path()],
+)
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected
new file mode 100644
index 00000000..d81d9187
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected
@@ -0,0 +1,8 @@
+HWADDR=00:22:15:59:62:97
+TYPE=Ethernet
+BRIDGING_OPTS="priority=28 hairpin_mode=1"
+NAME="System test-bridge-component"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=no
+BRIDGE=br0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
new file mode 100644
index 00000000..2e69c5e7
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
@@ -0,0 +1,16 @@
+HWADDR=00:22:15:59:62:97
+TYPE=Ethernet
+NAME="System test-bridge-component"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=no
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
new file mode 100644
index 00000000..59a6f79b
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
@@ -0,0 +1,22 @@
+HWADDR=00:11:22:33:44:EE
+TYPE=Ethernet
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=PEAP
+IEEE_8021X_IDENTITY="Jara Cimrman"
+IEEE_8021X_PASSWORD_FLAGS="user ask"
+IEEE_8021X_PEAP_VERSION=1
+IEEE_8021X_INNER_AUTH_METHODS=GTC
+IEEE_8021X_SUBJECT_MATCH=server1.yourdomain.tld
+IEEE_8021X_PHASE2_SUBJECT_MATCH=server2.yourdomain.tld
+IEEE_8021X_ALTSUBJECT_MATCHES="a.yourdomain.tld b.yourdomain.tld c.yourdomain.tld"
+IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES="x.yourdomain.tld y.yourdomain.tld"
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="System test-wired-802-1X-subj-matches"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
new file mode 100644
index 00000000..a48be78a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
@@ -0,0 +1,34 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+NM_USER__M_Y___053=val=MY.+
+NM_USER__M_Y___055=val=MY.-
+NM_USER__M_Y___057=val=MY./
+NM_USER__M_Y__8_053_V=val=MY.8+V
+NM_USER__M_Y__8_055_V=val=MY.8-V
+NM_USER__M_Y__8_057_V=val=MY.8/V
+NM_USER__M_Y__8_075_V=val=MY.8=V
+NM_USER__M_Y__8_V=val=MY.8V
+NM_USER__M_Y__8_137_V=val=MY.8_V
+NM_USER__M_Y___075=val=MY.=
+NM_USER__M_Y___A_V=val=MY.AV
+NM_USER__M_Y___137=val=MY._
+NM_USER_MY___AV=val=my.Av
+NM_USER_MY___137V=val=my._v
+NM_USER_MY__KEYS__1=val=my.keys.1
+NM_USER_MY__OTHER___K_E_Y__42=val=my.other.KEY.42
+NM_USER_MY__V_053=val=my.v+
+NM_USER_MY__V_137_137AL3=val=my.v__al3
+NM_USER_MY__VAL1=
+NM_USER_MY__VAL2=val=my.val2
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test User 1"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
new file mode 100644
index 00000000..b288c04a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
@@ -0,0 +1,17 @@
+BONDING_OPTS="mode=balance-rr downdelay=5 miimon=100 updelay=10"
+TYPE=Bond
+BONDING_MASTER=yes
+HWADDR=
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=none
+IPADDR=1.1.1.3
+PREFIX=24
+GATEWAY=1.1.1.1
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write Bond Main"
+UUID=${UUID}
+DEVICE=bond0
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
new file mode 100644
index 00000000..a8ff8df3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
@@ -0,0 +1,9 @@
+HWADDR=31:33:33:37:BE:CD
+MTU=1492
+TYPE=Ethernet
+BRIDGING_OPTS="priority=50 path_cost=33"
+BRIDGE_PORT_VLANS="1 untagged,2 pvid,4-4094 untagged"
+NAME="Test Write Bridge Component"
+UUID=${UUID}
+ONBOOT=yes
+BRIDGE=br0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
new file mode 100644
index 00000000..80e96921
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
@@ -0,0 +1,11 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write Permissions"
+UUID=${UUID}
+ONBOOT=yes
+USERS="blahblah foobar asdfasdf"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected
new file mode 100644
index 00000000..716eedc2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected
@@ -0,0 +1,15 @@
+TYPE=Ethernet
+PROXY_METHOD=auto
+PAC_URL=https://wpad.neverland.org/wpad.dat
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Proxy Basic"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
new file mode 100644
index 00000000..0c2fa035
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
@@ -0,0 +1,19 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+ROUTING_RULE_1="priority 10 from 0.0.0.0/0 table 1"
+ROUTING_RULE_2="priority 10 to 192.167.8.0/24 table 2"
+ROUTING_RULE6_3="priority 10 from ::/0 table 10"
+ROUTING_RULE6_4="priority 10 to 1:2:3::5/24 table 22"
+ROUTING_RULE6_5="priority 10 to 1:3:3::5 table 55"
+NAME="Test Write Routing Rules"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected
new file mode 100644
index 00000000..2df1fbb3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected
@@ -0,0 +1,9 @@
+CONNECTED_MODE=no
+TYPE=InfiniBand
+TEAM_PORT_CONFIG="{\"inf1\": {\"prio\": -10, \"sticky\": true}}"
+NAME="Test Write Team Infiniband Port"
+UUID=${UUID}
+DEVICE=inf1
+ONBOOT=yes
+TEAM_MASTER=team0
+DEVICETYPE=TeamPort
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected
new file mode 100644
index 00000000..ff55cefe
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected
@@ -0,0 +1,6 @@
+TEAM_PORT_CONFIG="{\"p4p1\": {\"prio\": -10, \"sticky\": true}}"
+NAME="Test Write Team Port"
+UUID=${UUID}
+ONBOOT=yes
+TEAM_MASTER=team0
+DEVICETYPE=TeamPort
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
new file mode 100644
index 00000000..9c2a1ff0
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
@@ -0,0 +1,21 @@
+VLAN=yes
+TYPE=Vlan
+PHYSDEV=eth0
+VLAN_ID=444
+REORDER_HDR=yes
+GVRP=no
+MVRP=no
+HWADDR=
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write VLAN reorder_hdr"
+UUID=${UUID}
+ONBOOT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected
new file mode 100644
index 00000000..ccec2b81
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected
@@ -0,0 +1,19 @@
+ESSID=MySSID
+MODE=Ap
+CHANNEL=196
+MAC_ADDRESS_RANDOMIZATION=default
+AP_ISOLATION=yes
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wi-Fi AP Mode"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
new file mode 100644
index 00000000..ddbd986f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
@@ -0,0 +1,18 @@
+ESSID="Test SSID"
+MODE=Managed
+BAND=a
+MAC_ADDRESS_RANDOMIZATION=default
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wi-Fi Band A"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
new file mode 100644
index 00000000..495a24d3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
@@ -0,0 +1,17 @@
+ESSID="Test SSID"
+SSID_HIDDEN=yes
+MAC_ADDRESS_RANDOMIZATION=default
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wi-Fi Hidden"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
new file mode 100644
index 00000000..aec6918a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
@@ -0,0 +1,18 @@
+MACADDR=random
+ESSID="Test SSID"
+MODE=Managed
+MAC_ADDRESS_RANDOMIZATION=always
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wi-Fi MAC always"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
new file mode 100644
index 00000000..9d47163f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
@@ -0,0 +1,17 @@
+ESSID="Test SSID"
+MODE=Managed
+MAC_ADDRESS_RANDOMIZATION=default
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wi-Fi MAC default"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
new file mode 100644
index 00000000..43c07ddb
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
@@ -0,0 +1,18 @@
+MACADDR=permanent
+ESSID="Test SSID"
+MODE=Managed
+MAC_ADDRESS_RANDOMIZATION=never
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wi-Fi MAC missing"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
new file mode 100644
index 00000000..21f2e2de
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
@@ -0,0 +1,18 @@
+MACADDR=permanent
+ESSID="Test SSID"
+MODE=Managed
+MAC_ADDRESS_RANDOMIZATION=never
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wi-Fi MAC never"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
new file mode 100644
index 00000000..d3db19b2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
@@ -0,0 +1,16 @@
+ESSID=blahblah
+MODE=Managed
+KEY_MGMT=IEEE8021X
+SECURITYMODE=leap
+IEEE_8021X_IDENTITY="Bill Smith"
+MAC_ADDRESS_RANDOMIZATION=default
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write Wifi LEAP"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
new file mode 100644
index 00000000..32db7262
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
@@ -0,0 +1,15 @@
+ESSID=blahblah104
+MODE=Managed
+SECURITYMODE=open
+DEFAULTKEY=1
+MAC_ADDRESS_RANDOMIZATION=default
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write Wifi WEP 104 ASCII"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
new file mode 100644
index 00000000..214f5da5
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
@@ -0,0 +1,15 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+ETHTOOL_OPTS="autoneg off speed 10 duplex half"
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wired Auto-Negotiate"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
new file mode 100644
index 00000000..cd8fc96f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
@@ -0,0 +1,22 @@
+HWADDR=31:33:33:37:BE:CD
+MTU=1492
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=none
+IPADDR=1.1.1.3
+PREFIX=24
+IPADDR1=1.1.1.5
+PREFIX1=24
+GATEWAY=1.1.1.1
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+DOMAIN="foobar.com lab.foobar.com"
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+ACD_TIMEOUT=400
+ARPING_WAIT=1
+IPV6INIT=no
+NAME="Test Write Wired Static Routes"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
new file mode 100644
index 00000000..de66dcdd
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
@@ -0,0 +1,15 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+ETHTOOL_OPTS="wol umgs sopass 00:00:00:11:22:33"
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wired Wake-on-LAN"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
new file mode 100644
index 00000000..9499278e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
@@ -0,0 +1,13 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+MATCH_DRIVER="!virtio e1000e"
+MATCH_INTERFACE_NAME="ens* eth\\ 1? !veth*"
+MATCH_KERNEL_COMMAND_LINE=!ip=
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write Wired with Match setting"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
new file mode 100644
index 00000000..44eb777c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
@@ -0,0 +1,23 @@
+VLAN=yes
+TYPE=Vlan
+PHYSDEV=eth9
+VLAN_ID=43
+REORDER_HDR=yes
+GVRP=yes
+VLAN_FLAGS=LOOSE_BINDING
+MVRP=no
+VLAN_INGRESS_PRIORITY_MAP=0:1,2:5
+VLAN_EGRESS_PRIORITY_MAP=3:1,12:3,14:7
+HWADDR=
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=none
+IPADDR=192.168.43.149
+PREFIX=24
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Vlan test-vlan-interface"
+UUID=${UUID}
+DEVICE=vlan43
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0
new file mode 100644
index 00000000..4f9d645c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=aliasem0
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1
new file mode 100644
index 00000000..37c0df11
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1
@@ -0,0 +1,2 @@
+DEVICE=aliasem0:1
+IPADDR=192.168.1.6
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2
new file mode 100644
index 00000000..0c3b6a7e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2
@@ -0,0 +1,2 @@
+DEVICE=aliasem0:2
+IPADDR=192.168.1.9
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99
new file mode 100644
index 00000000..5df3f154
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99
@@ -0,0 +1,2 @@
+DEVICE=aliasem0:99
+IPADDR=192.168.1.99
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1
new file mode 100644
index 00000000..f6fefc0d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=aliasem1
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1
new file mode 100644
index 00000000..21971611
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1
@@ -0,0 +1,2 @@
+# bad, no DEVICE
+IPADDR=192.168.1.12
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2
new file mode 100644
index 00000000..92ed461e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=aliasem2
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1
new file mode 100644
index 00000000..ce0f23ba
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1
@@ -0,0 +1,3 @@
+# bad: wrong DEVICE
+DEVICE=aliasem0:1
+IPADDR=192.168.1.20
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3
new file mode 100644
index 00000000..b7bdd781
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3
@@ -0,0 +1,11 @@
+TYPE=Ethernet
+DEVICE=aliasem0
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1
new file mode 100644
index 00000000..5e151875
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1
@@ -0,0 +1,4 @@
+DEVICE=aliasem3:1
+IPADDR=192.168.1.6
+DEFROUTE=yes
+GATEWAY=192.168.1.1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
new file mode 100644
index 00000000..56e233cc
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
@@ -0,0 +1,41 @@
+TYPE=Ethernet
+DCB=yes
+DCB_APP_FCOE_ENABLE=yes
+DCB_APP_FCOE_ADVERTISE=yes
+DCB_APP_FCOE_WILLING=yes
+DCB_APP_FCOE_PRIORITY=5
+DCB_APP_FCOE_MODE=fabric
+DCB_APP_ISCSI_ENABLE=yes
+DCB_APP_ISCSI_ADVERTISE=yes
+DCB_APP_ISCSI_WILLING=yes
+DCB_APP_ISCSI_PRIORITY=1
+DCB_APP_FIP_ENABLE=yes
+DCB_APP_FIP_ADVERTISE=yes
+DCB_APP_FIP_WILLING=yes
+DCB_APP_FIP_PRIORITY=3
+DCB_PFC_ENABLE=yes
+DCB_PFC_ADVERTISE=yes
+DCB_PFC_WILLING=yes
+DCB_PFC_UP=11010110
+DCB_PG_ENABLE=yes
+DCB_PG_ADVERTISE=yes
+DCB_PG_WILLING=yes
+DCB_PG_ID=4f6f173f
+DCB_PG_PCT=10,20,15,10,2,3,35,5
+DCB_PG_UPPCT=10,20,30,40,50,10,0,25
+DCB_PG_STRICT=10110001
+DCB_PG_UP2TC=34721056
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME=dcb-test
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
new file mode 100644
index 00000000..ecb36c37
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
@@ -0,0 +1,14 @@
+DNS1="192.0.2.1"
+IPADDR="102.0.2.2"
+GATEWAY="192.0.2.1"
+NETMASK="255.254.0.0"
+BOOTPROTO="static"
+DEVICE="eth1"
+ONBOOT="yes"
+IPV6INIT="yes"
+
+#bogus
+PREFIX1=25
+NETMASK0=255.255.0.0
+
+#end
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
new file mode 100644
index 00000000..87493ac2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
@@ -0,0 +1,22 @@
+DNS1=192.0.2.1
+IPADDR=102.0.2.2
+GATEWAY=192.0.2.1
+NETMASK=255.254.0.0
+DEVICE=eth1
+ONBOOT=yes
+IPV6INIT=yes
+
+#bogus
+
+#end
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+PREFIX=15
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+NAME="System netmask-1"
+UUID=${UUID}
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
new file mode 100644
index 00000000..ead3a047
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
@@ -0,0 +1,13 @@
+ESSID=blahblah
+MODE=Managed
+MAC_ADDRESS_RANDOMIZATION=default
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="random wifi connection"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
new file mode 100644
index 00000000..7bc1ae6c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
@@ -0,0 +1,15 @@
+ESSID=SomeSSID
+MODE=Managed
+MAC_ADDRESS_RANDOMIZATION=default
+TYPE=Wireless
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="random wifi connection 2"
+UUID=${UUID}
+ONBOOT=yes
+USERS=superman
+DEFAULTKEY=1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
new file mode 100644
index 00000000..b01372af
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
@@ -0,0 +1,13 @@
+VLAN=yes
+PHYSDEV=enp31s0f1
+VLAN_ID=142
+REORDER_HDR=yes
+GVRP=no
+MVRP=no
+HWADDR=
+NAME=team-slave-enp31s0f1-142
+UUID=74f435bb-ede4-415a-9d48-f580b60eba04
+DEVICE=enp31s0f1-142
+ONBOOT=no
+TEAM_MASTER=team142
+DEVICETYPE=TeamPort
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type
new file mode 100644
index 00000000..8d295c76
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type
@@ -0,0 +1,7 @@
+DEVICE=bond0
+NM_CONTROLLED=yes
+TYPE=Ethernet
+BONDING_OPTS="miimon=213 mode=4 lacp_rate=1"
+BONDING_MASTER=yes
+ONBOOT=yes
+BOOTPROTO=none
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main
new file mode 100644
index 00000000..4c4834d5
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main
@@ -0,0 +1,5 @@
+DEVICE=bond0
+ONBOOT=no
+TYPE=Bond
+BOOTPROTO=dhcp
+BONDING_OPTS="miimon=100"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric
new file mode 100644
index 00000000..9989f58b
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric
@@ -0,0 +1,5 @@
+DEVICE=bond0
+ONBOOT=no
+TYPE=Bond
+BOOTPROTO=dhcp
+BONDING_OPTS="mode=4 miimon=100"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave
new file mode 100644
index 00000000..c0de645d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave
@@ -0,0 +1,6 @@
+DEVICE=eth0
+HWADDR=00:22:15:59:62:97
+ONBOOT=no
+MASTER=bond0
+# This should be ignored
+BOOTPROTO=dhcp
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib
new file mode 100644
index 00000000..5bd9a3a8
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib
@@ -0,0 +1,7 @@
+TYPE=InfiniBand
+DEVICE=ib0
+HWADDR=80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22
+ONBOOT=no
+MASTER=bond0
+# This should be ignored
+BOOTPROTO=dhcp
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component
new file mode 100644
index 00000000..24b51221
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component
@@ -0,0 +1,5 @@
+DEVICE=eth0
+HWADDR=00:22:15:59:62:97
+ONBOOT=no
+BRIDGE=br0
+BRIDGING_OPTS="priority=28 hairpin_mode=1 path_cost=100"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
new file mode 100644
index 00000000..1788efe2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
@@ -0,0 +1,8 @@
+DEVICE=br0
+ONBOOT=no
+TYPE=Bridge
+BOOTPROTO=dhcp
+STP=on
+DELAY=2
+BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0 group_fwd_mask=24 vlan_filtering=1 default_pvid=99"
+MACADDR=00:16:41:11:22:33
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp
new file mode 100644
index 00000000..7135cd42
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp
@@ -0,0 +1,5 @@
+DEVICE=br0
+ONBOOT=no
+TYPE=Bridge
+BOOTPROTO=dhcp
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb
new file mode 100644
index 00000000..6e6589d3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb
@@ -0,0 +1,43 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+# dcb global switch
+# use DCB=no to turn it off
+DCB=yes
+
+# application settings
+DCB_APP_FCOE_PRIORITY=7
+DCB_APP_FCOE_ENABLE=yes
+DCB_APP_FCOE_ADVERTISE=yes
+DCB_APP_FCOE_WILLING=yes
+
+DCB_APP_ISCSI_PRIORITY=6
+DCB_APP_ISCSI_ENABLE=yes
+DCB_APP_ISCSI_ADVERTISE=yes
+DCB_APP_ISCSI_WILLING=yes
+
+DCB_APP_FIP_PRIORITY=2
+DCB_APP_FIP_ENABLE=yes
+DCB_APP_FIP_ADVERTISE=yes
+DCB_APP_FIP_WILLING=yes
+
+# priority group settings
+DCB_PG_UP2TC=76543210
+DCB_PG_PCT=25,0,0,75,0,0,0,0
+DCB_PG_ID=0000111f
+DCB_PG_UPPCT=5,10,30,25,10,50,5,0
+DCB_PG_STRICT=00110101
+DCB_PG_ENABLE=yes
+DCB_PG_ADVERTISE=yes
+DCB_PG_WILLING=yes
+
+# priority flow control settings
+DCB_PFC_UP=10011010
+DCB_PFC_ENABLE=yes
+DCB_PFC_ADVERTISE=yes
+DCB_PFC_WILLING=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans
new file mode 100644
index 00000000..2f46a082
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+DCB=yes
+DCB_PG_STRICT=02030101
+DCB_PG_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent
new file mode 100644
index 00000000..0c5d7b88
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+DCB=yes
+DCB_PG_PCT=25,0,0,75,0,0,110,0
+DCB_PG_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints
new file mode 100644
index 00000000..8334999b
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+DCB=yes
+DCB_PG_UP2TC=96543210
+DCB_PG_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities
new file mode 100644
index 00000000..5bdcb63c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities
@@ -0,0 +1,7 @@
+TYPE=Ethernet
+DEVICE=eth0
+DCB=yes
+DCB_APP_FCOE_ENABLE=yes
+DCB_APP_ISCSI_ENABLE=yes
+DCB_APP_FIP_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100
new file mode 100644
index 00000000..95eb7c5b
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+DCB=yes
+DCB_PG_PCT=25,0,0,3,75,0,25,0
+DCB_PG_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans
new file mode 100644
index 00000000..fbe1cbbc
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+DCB=yes
+DCB_PG_STRICT=0111010
+DCB_PG_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent
new file mode 100644
index 00000000..07ba6b64
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+DCB=yes
+DCB_PG_PCT=25,0,0,75,0,0,0
+DCB_PG_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints
new file mode 100644
index 00000000..05ae39cb
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints
@@ -0,0 +1,12 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+GATEWAY=1.1.1.1
+
+DCB=yes
+DCB_PG_UP2TC=7654321
+DCB_PG_ENABLE=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options
new file mode 100644
index 00000000..cea2471a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options
@@ -0,0 +1,16 @@
+TYPE=Ethernet
+DEVICE=eth2
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=yes
+DNS1=10.2.0.4
+DOMAIN="lorem.com ipsum.org dolor.edu"
+RES_OPTIONS="ndots:3  single-request-reopen"
+IPV6_RES_OPTIONS="inet6"
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR="1001:abba::1234/56"
+IPV6ADDR_SECONDARIES="2001:abba::2234/64 3001:abba::3234/96"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric
new file mode 100644
index 00000000..cde1c28d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric
@@ -0,0 +1,6 @@
+TYPE=Ethernet
+DEVICE=eth0
+DCB=yes
+DCB_APP_FCOE_ENABLE=yes
+DCB_APP_FCOE_MODE=fabric
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn
new file mode 100644
index 00000000..20c08451
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn
@@ -0,0 +1,6 @@
+TYPE=Ethernet
+DEVICE=eth0
+DCB=yes
+DCB_APP_FCOE_ENABLE=yes
+DCB_APP_FCOE_MODE=vn2vn
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft
new file mode 100644
index 00000000..d3e4edca
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft
@@ -0,0 +1,10 @@
+# Generated by dracut initrd
+DEVICE="ibft0"
+ONBOOT=yes
+NETBOOT=yes
+UUID="2aa8c19d-9744-4ca4-b74f-c37e73f2918e"
+BOOTPROTO=ibft
+HWADDR="00:33:21:98:b9:f0"
+TYPE=Ethernet
+NAME="ibft0"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband
new file mode 100644
index 00000000..6c36f1de
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband
@@ -0,0 +1,8 @@
+TYPE=InfiniBand
+DEVICE=ib0
+HWADDR=80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22
+CONNECTED_MODE=yes
+MTU=65520
+IPADDR=192.168.2.2
+NETMASK=255.255.255.0
+GATEWAY=192.168.2.1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
new file mode 100644
index 00000000..cae51b89
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
@@ -0,0 +1,11 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6_DISABLED=yes
+IPV6INIT=no
+NAME="Test Write Wired Disabled IP6"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal
new file mode 100644
index 00000000..63bcc840
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal
@@ -0,0 +1,4 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+DEVICE=eth0
+HWADDR=00:16:41:11:22:33
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables
new file mode 100644
index 00000000..1b10cddb
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables
@@ -0,0 +1,4 @@
+DEVICE=eth0
+HWADDR_BLACKLIST="00:16:41:11:22:88 00:16:41:11:22:99 XX:aa:invalid 6A:5D:5A:FA:DD:F0"
+AUTOCONNECT_RETRIES=100
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled
new file mode 100644
index 00000000..a38f1f5f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled
@@ -0,0 +1,9 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:f8:9f
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=no
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized
new file mode 100644
index 00000000..58263ece
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized
@@ -0,0 +1,6 @@
+TYPE=IPoAC
+NAME=PigeonNet
+DEVICE=ipoac0
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip
new file mode 100644
index 00000000..b5028daa
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip
@@ -0,0 +1,3 @@
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no
new file mode 100644
index 00000000..b9491711
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no
@@ -0,0 +1,5 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+ONBOOT=no
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions
new file mode 100644
index 00000000..5b413aa9
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions
@@ -0,0 +1,8 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERS="dcbw ssmith johnny5"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic
new file mode 100644
index 00000000..e2cf6c21
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic
@@ -0,0 +1,15 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+PEERDNS=no
+
+# proxy configuration
+PROXY_METHOD=auto
+PAC_URL=http://wpad.mycompany.com/wpad.dat
+BROWSER_ONLY=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore
new file mode 100644
index 00000000..a2581db6
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore
@@ -0,0 +1,12 @@
+# this ifcfg-file represents a "sit" type without explicit TYPE.
+# Such connection types are not supported by NetworkManager and
+# the connection should be ignored based on the presence of
+# IPV6TUNNELIPV4.
+
+DEVICE=sit1
+BOOTPROTO=none
+ONBOOT=yes
+IPV6INIT=yes
+IPV6TUNNELIPV4=5.4.3.6
+IPV6TUNNELIPV4LOCAL=172.17.1.9
+IPV6ADDR=2001:470:2:3:4::2/64
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov
new file mode 100644
index 00000000..142f56e4
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov
@@ -0,0 +1,19 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=no
+NAME=ethernet-sriov
+UUID=acc703b8-e751-44ce-b456-1550bdf2057e
+SRIOV_TOTAL_VFS=16
+SRIOV_AUTOPROBE_DRIVERS=0
+SRIOV_VF15="max-tx-rate=200 mac=01:23:45:67:89:ab vlans=2"
+SRIOV_VF12="trust=false min-tx-rate=100  vlans=1.200.ad"
+SRIOV_VF3="mac=55:44:33:22:11:00 spoof-check=true"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
new file mode 100644
index 00000000..c882c479
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
@@ -0,0 +1,18 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+SRIOV_TOTAL_VFS=64
+SRIOV_AUTOPROBE_DRIVERS=1
+SRIOV_VF2="mac=55:55:55:55:55:55 vlans=3.10.ad;10"
+SRIOV_VF19=spoof-check=true
+BOOTPROTO=none
+IPADDR=1.1.1.3
+PREFIX=24
+GATEWAY=1.1.1.1
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write SR-IOV config"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy
new file mode 100644
index 00000000..2173729d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy
@@ -0,0 +1,12 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+DEVICE=eth0
+HWADDR=00:16:41:11:22:33
+NAME="test-static-routes-legacy"
+TYPE=Ethernet
+BOOTPROTO=dhcp
+DEFROUTE=yes
+UUID=ba60d05a-7898-820d-c2db-427a88f8f2a5
+ONBOOT=yes
+IPV6INIT=no
+PEERDNS=yes
+PEERROUTES=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
new file mode 100644
index 00000000..a28c5c1c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
@@ -0,0 +1,12 @@
+HWADDR=00:16:41:11:22:33
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME=test-static-routes-legacy
+UUID=ba60d05a-7898-820d-c2db-427a88f8f2a5
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc
new file mode 100644
index 00000000..d0a3c254
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc
@@ -0,0 +1,16 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=no
+QDISC1="root fq_codel"
+FILTER1="parent 1234: matchall action simple sdata Hello"
+NAME=ethernet-tc
+UUID=a42c8d4e-11a2-4144-92d2-5cbce8c6b2c4
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
new file mode 100644
index 00000000..a67ca598
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
@@ -0,0 +1,16 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+QDISC1="parent 2468:2 pfifo_fast"
+FILTER1="parent 1234: matchall action simple sdata Hello"
+BOOTPROTO=none
+IPADDR=1.1.1.3
+PREFIX=24
+GATEWAY=1.1.1.1
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write TC config"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1
new file mode 100644
index 00000000..209447b8
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1
@@ -0,0 +1,6 @@
+DEVICE=team0
+ONBOOT=no
+DEVICETYPE=Team
+BOOTPROTO=dhcp
+TEAM_CONFIG="{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2
new file mode 100644
index 00000000..26e448cc
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2
@@ -0,0 +1,5 @@
+DEVICE=team0
+ONBOOT=no
+BOOTPROTO=dhcp
+TEAM_CONFIG="{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid
new file mode 100644
index 00000000..41c0f25c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid
@@ -0,0 +1,4 @@
+DEVICE=team0
+ONBOOT=no
+BOOTPROTO=dhcp
+TEAM_CONFIG="{ foobar }x"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1
new file mode 100644
index 00000000..80355c26
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1
@@ -0,0 +1,5 @@
+TYPE=Ethernet
+TEAM_PORT_CONFIG="{\"p4p1\": {\"prio\": -10, \"sticky\": true}}"
+DEVICE=p4p1
+TEAM_MASTER=team0
+DEVICETYPE=TeamPort
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2
new file mode 100644
index 00000000..4284737a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2
@@ -0,0 +1,4 @@
+TYPE=Ethernet
+TEAM_PORT_CONFIG="{\"p4p1\": {\"prio\": -10, \"sticky\": true}}"
+DEVICE=p4p1
+TEAM_MASTER=team0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config
new file mode 100644
index 00000000..04df6938
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config
@@ -0,0 +1,4 @@
+TYPE=Ethernet
+DEVICE=p4p1
+TEAM_MASTER=team0
+DEVICETYPE=TeamPort
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized
new file mode 100644
index 00000000..18da66c1
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized
@@ -0,0 +1,7 @@
+TYPE=PPP-over-InfiniBand
+NAME=U Can't Touch This
+DEVICE=pppoipoib0
+HWADDR=00:11:22:33
+BOOTPROTO=dhcp
+ONBOOT=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1
new file mode 100644
index 00000000..af62dcfc
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1
@@ -0,0 +1,8 @@
+DEVICE=eth0
+HWADDR=00:16:41:11:22:33
+
+# Variable with only single character (double quote)
+NAME="
+
+# Variable with only single character (quote)
+ZONE='
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1
new file mode 100644
index 00000000..5ff87891
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1
@@ -0,0 +1,14 @@
+VLAN=yes
+TYPE=Vlan
+DEVICE=super-vlan
+VLAN_ID=44
+PHYSDEV=eth9
+REORDER_HDR=yes
+GVRP=no
+VLAN_FLAGS="GVRP LOOSE_BINDING"
+VLAN_INGRESS_PRIORITY_MAP=0:1,2:5
+VLAN_EGRESS_PRIORITY_MAP=12:3,14:7,3:1
+ONBOOT=yes
+BOOTPROTO=static
+IPADDR=192.168.43.149
+NETMASK=255.255.255.0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2
new file mode 100644
index 00000000..2c01be71
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2
@@ -0,0 +1,10 @@
+VLAN=yes
+TYPE=Vlan
+DEVICE=super-vlan
+VLAN_ID=44
+PHYSDEV=eth9
+VLAN_FLAGS="GVRP LOOSE_BINDING"
+ONBOOT=yes
+BOOTPROTO=static
+IPADDR=192.168.43.149
+NETMASK=255.255.255.0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface
new file mode 100644
index 00000000..d8d9193f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface
@@ -0,0 +1,11 @@
+VLAN=yes
+TYPE=Vlan
+DEVICE=vlan43
+PHYSDEV=eth9
+VLAN_FLAGS=GVRP,LOOSE_BINDING
+VLAN_INGRESS_PRIORITY_MAP=0:1,2:5
+VLAN_EGRESS_PRIORITY_MAP=12:3,14:7,3:1
+ONBOOT=yes
+BOOTPROTO=static
+IPADDR=192.168.43.149
+NETMASK=255.255.255.0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device
new file mode 100644
index 00000000..4ba06f28
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device
@@ -0,0 +1,4 @@
+VLAN=yes
+TYPE=Vlan
+DEVICE=eth0.9
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid
new file mode 100644
index 00000000..622d41ec
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid
@@ -0,0 +1,6 @@
+VLAN=yes
+TYPE=Vlan
+PHYSDEV=eth9
+VLAN_ID=43
+ONBOOT=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev
new file mode 100644
index 00000000..446c2a62
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev
@@ -0,0 +1,6 @@
+VLAN=yes
+TYPE=Vlan
+DEVICE=vlan0.3
+PHYSDEV=eth0
+VLAN_ID=3
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1
new file mode 100644
index 00000000..0dc53971
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1
@@ -0,0 +1,6 @@
+VLAN=yes
+TYPE=Vlan
+DEVICE=vlan0.3
+PHYSDEV=eth0
+VLAN_ID=3
+REORDER_HDR=0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2
new file mode 100644
index 00000000..d98a9d36
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2
@@ -0,0 +1,6 @@
+VLAN=yes
+TYPE=Vlan
+DEVICE=vlan0.3
+PHYSDEV=eth0
+VLAN_ID=3
+VLAN_FLAGS="LOOSE_BINDING,NO_REORDER_HDR"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces
new file mode 100644
index 00000000..4a31e475
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces
@@ -0,0 +1,11 @@
+DEVICE="vlan201"  
+ONBOOT=yes  
+NETBOOT=yes  
+BOOTPROTO=none   
+IPADDR="10.130.70.7" 
+NETMASK="255.255.0.0" 
+TYPE=Vlan   
+NAME="vlan201" 
+VLAN=yes 
+PHYSDEV="enccw0.0.fb00" 
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a
new file mode 100644
index 00000000..43286111
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a
@@ -0,0 +1,13 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+BAND=a
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch
new file mode 100644
index 00000000..8905ba0b
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch
@@ -0,0 +1,9 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=9
+BAND=a
+MODE=Managed
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch
new file mode 100644
index 00000000..e02e21ad
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch
@@ -0,0 +1,9 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=183
+BAND=bg
+MODE=Managed
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap
new file mode 100644
index 00000000..d9c95cc2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap
@@ -0,0 +1,17 @@
+ESSID="sdasdsdg"
+MODE=Managed
+KEY_MGMT=IEEE8021X
+CIPHER_GROUP="WEP40 WEP104"
+TYPE=Wireless
+IEEE_8021X_EAP_METHODS=LEAP
+IEEE_8021X_IDENTITY="bill smith"
+BOOTPROTO=dhcp
+DEFROUTE=yes
+PEERDNS=yes
+PEERROUTES=yes
+IPV4_FAILURE_FATAL=yes
+IPV6INIT=no
+NAME="Test Dynamic WEP LEAP"
+UUID=aca7a23c-d934-49a3-8bfb-ad66f846c57b
+ONBOOT=yes
+USERS=dcbw
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden
new file mode 100644
index 00000000..08b9a730
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden
@@ -0,0 +1,10 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ONBOOT=yes
+ESSID=blahblah
+MODE=Managed
+SSID_HIDDEN=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap
new file mode 100644
index 00000000..50f4fd31
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap
@@ -0,0 +1,17 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+KEY_MGMT=IEEE8021X
+SECURITYMODE=LEAP
+IEEE_8021X_IDENTITY="Bill Smith"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent
new file mode 100644
index 00000000..991ba67a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent
@@ -0,0 +1,17 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+KEY_MGMT=IEEE8021X
+SECURITYMODE=LEAP
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_PASSWORD_FLAGS=user
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask
new file mode 100644
index 00000000..5bb02b5c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask
@@ -0,0 +1,17 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+KEY_MGMT=IEEE8021X
+SECURITYMODE=LEAP
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_PASSWORD_FLAGS="user ask"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always
new file mode 100644
index 00000000..9dcb5bf8
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always
@@ -0,0 +1,7 @@
+TYPE=Wireless
+DEVICE=eth2
+BOOTPROTO=dhcp
+ESSID=blahblah
+ONBOOT=yes
+MAC_ADDRESS_RANDOMIZATION=always
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default
new file mode 100644
index 00000000..3709c493
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default
@@ -0,0 +1,6 @@
+TYPE=Wireless
+DEVICE=eth2
+BOOTPROTO=dhcp
+ESSID=blahblah
+ONBOOT=yes
+MAC_ADDRESS_RANDOMIZATION=default
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing
new file mode 100644
index 00000000..f896eb0f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing
@@ -0,0 +1,6 @@
+TYPE=Wireless
+DEVICE=eth2
+BOOTPROTO=dhcp
+ESSID=blahblah
+ONBOOT=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never
new file mode 100644
index 00000000..aa12d749
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never
@@ -0,0 +1,6 @@
+TYPE=Wireless
+DEVICE=eth2
+BOOTPROTO=dhcp
+ESSID=blahblah
+ONBOOT=yes
+MAC_ADDRESS_RANDOMIZATION=never
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open
new file mode 100644
index 00000000..b089103d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open
@@ -0,0 +1,16 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+AUTOCONNECT_PRIORITY=-1
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+IPV4_ROUTE_METRIC=104
+IPV6_ROUTE_METRIC=106
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto
new file mode 100644
index 00000000..42ee200a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto
@@ -0,0 +1,13 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Auto
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex
new file mode 100644
index 00000000..32d63342
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex
@@ -0,0 +1,13 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=0x626cxx
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex
new file mode 100644
index 00000000..dcb46da7
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex
@@ -0,0 +1,13 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=0x626c6168626c6168
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex
new file mode 100644
index 00000000..37bb085b
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex
@@ -0,0 +1,13 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=0x626c6168626c6168626c6168626c6168626c6168626c6168626c6168626c6168AA
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted
new file mode 100644
index 00000000..0b46acd6
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted
@@ -0,0 +1,13 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID="foo\"bar\\foo\"bar\\foo\"bar\\foo\"bar\\1"
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted
new file mode 100644
index 00000000..08496bb5
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted
@@ -0,0 +1,13 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID="foo\"bar\\"
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe
new file mode 100644
index 00000000..354046ad
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe
@@ -0,0 +1,5 @@
+TYPE=Wireless
+DEVICE=wlan1
+ESSID=blahblah_owe
+MODE=Managed
+KEY_MGMT=OWE
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
new file mode 100644
index 00000000..68afbe97
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
@@ -0,0 +1,5 @@
+TYPE=Wireless
+DEVICE=wlan1
+ESSID=blahblah
+MODE=Managed
+KEY_MGMT=SAE
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep
new file mode 100644
index 00000000..e1ce20d6
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep
@@ -0,0 +1,14 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+SECURITYMODE=restricted
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii
new file mode 100644
index 00000000..250efa13
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii
@@ -0,0 +1,14 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+SECURITYMODE=open
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii
new file mode 100644
index 00000000..250efa13
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii
@@ -0,0 +1,14 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+SECURITYMODE=open
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc
new file mode 100644
index 00000000..9d0bacfc
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc
@@ -0,0 +1,15 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=11
+MODE=Ad-Hoc
+RATE=auto
+ONBOOT=no
+USERCTL=yes
+PEERDNS=no
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPV6INIT=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys
new file mode 100644
index 00000000..2bc16b6e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys
@@ -0,0 +1,18 @@
+ESSID="foobar"
+MODE=Managed
+TYPE=Wireless
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=yes
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+UUID=9c4637bd-7600-40cc-9c24-13819c5bf5dd
+ONBOOT=yes
+HWADDR=00:16:BB:AA:CC:DD
+WEP_KEY_FLAGS=user
+PEERDNS=yes
+PEERROUTES=yes
+IPV6_PEERDNS=yes
+IPV6_PEERROUTES=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap
new file mode 100644
index 00000000..bdea6aa3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap
@@ -0,0 +1,20 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ONBOOT=yes
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+PEERDNS=yes
+ESSID=blahblah
+MODE=Managed
+RATE=auto
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TTLS
+IEEE_8021X_IDENTITY="David Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_INNER_AUTH_METHODS=CHAP
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys
new file mode 100644
index 00000000..cb4da43f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys
@@ -0,0 +1,18 @@
+ESSID="foobar"
+MODE=Managed
+TYPE=Wireless
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=yes
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+UUID=9c4637bd-7600-40cc-9c24-13819c5bf5dd
+ONBOOT=yes
+HWADDR=00:16:BB:AA:CC:DD
+DEFAULTKEY=1
+PEERDNS=yes
+PEERROUTES=yes
+IPV6_PEERDNS=yes
+IPV6_PEERROUTES=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase
new file mode 100644
index 00000000..250efa13
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase
@@ -0,0 +1,14 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+SECURITYMODE=open
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls
new file mode 100644
index 00000000..9a74bb4d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls
@@ -0,0 +1,22 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ONBOOT=yes
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+PEERDNS=yes
+ESSID=blahblah
+MODE=Managed
+RATE=auto
+KEY_MGMT=WPA-EAP-SUITE-B-192
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+IEEE_8021X_EAP_METHODS=TLS
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem
+IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls
new file mode 100644
index 00000000..92aaeeab
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls
@@ -0,0 +1,25 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ONBOOT=yes
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+PEERDNS=yes
+ESSID=blahblah
+MODE=Managed
+RATE=auto
+CIPHER_PAIRWISE="TKIP CCMP"
+CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
+KEY_MGMT=WPA-EAP
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+IEEE_8021X_EAP_METHODS=TLS
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem
+IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls
new file mode 100644
index 00000000..42ed1d68
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls
@@ -0,0 +1,28 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ONBOOT=yes
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+PEERDNS=yes
+ESSID=blahblah
+MODE=Managed
+RATE=auto
+CIPHER_PAIRWISE="TKIP CCMP"
+CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
+KEY_MGMT=WPA-EAP
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+IEEE_8021X_EAP_METHODS=TTLS
+IEEE_8021X_IDENTITY="Chuck Shumer"
+IEEE_8021X_ANON_IDENTITY="anonymous"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_INNER_AUTH_METHODS=EAP-TLS
+IEEE_8021X_INNER_CA_CERT=test_ca_cert.pem
+IEEE_8021X_INNER_CLIENT_CERT=test1_key_and_cert.pem
+IEEE_8021X_INNER_PRIVATE_KEY=test1_key_and_cert.pem
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
new file mode 100644
index 00000000..4ab45ae3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
@@ -0,0 +1,19 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+CIPHER_PAIRWISE="TKIP CCMP"
+CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
+KEY_MGMT=WPA-PSK
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+CTCPROT=no-newline
\ No newline at end of file
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2
new file mode 100644
index 00000000..038b656d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2
@@ -0,0 +1,19 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=ipsum
+CHANNEL=6
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+CIPHER_PAIRWISE="TKIP CCMP"
+CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
+KEY_MGMT=WPA-PSK
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc
new file mode 100644
index 00000000..c3cadbb8
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc
@@ -0,0 +1,17 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Ad-Hoc
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+CIPHER_GROUP=CCMP
+CIPHER_PAIRWISE=CCMP
+KEY_MGMT=WPA-PSK
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex
new file mode 100644
index 00000000..2119ba8e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex
@@ -0,0 +1,19 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+CIPHER_PAIRWISE="TKIP CCMP"
+CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
+KEY_MGMT=WPA-PSK
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted
new file mode 100644
index 00000000..2119ba8e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted
@@ -0,0 +1,19 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+CIPHER_PAIRWISE="TKIP CCMP"
+CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
+KEY_MGMT=WPA-PSK
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2
new file mode 100644
index 00000000..2119ba8e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2
@@ -0,0 +1,19 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+NM_CONTROLLED=yes
+BOOTPROTO=dhcp
+ESSID=blahblah
+CHANNEL=1
+MODE=Managed
+RATE=auto
+ONBOOT=yes
+USERCTL=yes
+PEERDNS=yes
+IPV6INIT=no
+CIPHER_PAIRWISE="TKIP CCMP"
+CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
+KEY_MGMT=WPA-PSK
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches
new file mode 100644
index 00000000..70d69bf5
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches
@@ -0,0 +1,17 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=PEAP
+IEEE_8021X_IDENTITY="Jara Cimrman"
+IEEE_8021X_PEAP_VERSION=1
+IEEE_8021X_INNER_AUTH_METHODS=GTC
+IEEE_8021X_PASSWORD_FLAGS="user ask"
+IEEE_8021X_SUBJECT_MATCH=server1.yourdomain.tld
+IEEE_8021X_ALTSUBJECT_MATCHES="a.yourdomain.tld b.yourdomain.tld c.yourdomain.tld"
+IEEE_8021X_PHASE2_SUBJECT_MATCH=server2.yourdomain.tld
+IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES="x.yourdomain.tld y.yourdomain.tld"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw
new file mode 100644
index 00000000..a5434434
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw
@@ -0,0 +1,15 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TTLS
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_INNER_AUTH_METHODS=EAP-GTC
+IEEE_8021X_PASSWORD_RAW=0408151623420001
+#IEEE_8021X_PIN=hallo1
+IEEE_8021X_PIN_FLAGS=0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc
new file mode 100644
index 00000000..fad92772
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc
@@ -0,0 +1,13 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TTLS
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_INNER_AUTH_METHODS=EAP-GTC
+IEEE_8021X_PASSWORD_FLAGS=ask
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2
new file mode 100644
index 00000000..27bcbbf9
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2
@@ -0,0 +1,15 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=PEAP
+IEEE_8021X_IDENTITY="David Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_PEAP_VERSION=1
+IEEE_8021X_PEAP_FORCE_NEW_LABEL=yes
+IEEE_8021X_INNER_AUTH_METHODS=MSCHAPV2
+IEEE_8021X_ANON_IDENTITY=somebody
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent
new file mode 100644
index 00000000..052ab425
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent
@@ -0,0 +1,14 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TLS
+IEEE_8021X_IDENTITY="David Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem
+IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem
+IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS=user
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always
new file mode 100644
index 00000000..5deee066
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always
@@ -0,0 +1,14 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TLS
+IEEE_8021X_IDENTITY="David Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem
+IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem
+IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS="user ask"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
new file mode 100644
index 00000000..24397473
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
@@ -0,0 +1,13 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TLS
+IEEE_8021X_IDENTITY="David Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_PRIVATE_KEY=test_client.p12
+IEEE_8021X_PRIVATE_KEY_PASSWORD="test1"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on
new file mode 100644
index 00000000..d0b50775
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on
@@ -0,0 +1,22 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR=dead:beaf::1
+IPV6ADDR_SECONDARIES="dead:beaf::2/56"
+DNS3=1:2:3:4::a
+DNS4=1:2:3:4::b
+RES_OPTIONS=
+ETHTOOL_OPTS="wol apgs sopass 00:11:22:33:44:55 autoneg on speed 100"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
new file mode 100644
index 00000000..e683db3c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
@@ -0,0 +1,6 @@
+TYPE=Ethernet
+DEVICE=eth0
+BOOTPROTO=autoip
+IPV4_FAILURE_FATAL=yes
+PEERDNS=no
+DEVTIMEOUT=2.6
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static
new file mode 100644
index 00000000..61f8f423
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static
@@ -0,0 +1,12 @@
+# IBM CTC
+DEVICE=ctc0
+TYPE=CTC
+BOOTPROTO=static
+IPADDR=192.168.70.87
+GATEWAY=192.168.70.136
+NETMASK=255.255.255.0
+ONBOOT=yes
+SUBCHANNELS=0.0.1b00,0.0.1b01
+NETTYPE=ctc
+CTCPROT=0
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no
new file mode 100644
index 00000000..fe8b15b2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no
@@ -0,0 +1,15 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth4
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=yes
+DEFROUTE=no
+
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=no
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes
new file mode 100644
index 00000000..3cf4323d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes
@@ -0,0 +1,15 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=yes
+DEFROUTE=no
+
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=no
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp
new file mode 100644
index 00000000..5d36675a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp
@@ -0,0 +1,19 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+PEERDNS=no
+DHCP_FQDN=foo.bar
+DHCP_HOSTNAME_FLAGS=6
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPV6_AUTOCONF=no
+IPV6INIT=yes
+DHCPV6C=yes
+DHCPV6_HOSTNAME_FLAGS=8
+DHCPV6_HOSTNAME=foo.bar
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip
new file mode 100644
index 00000000..af254f9c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip
@@ -0,0 +1,26 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=no
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+DNS3=1:2:3:4::a
+DNS4=1:2:3:4::b
+
+# additional IPs
+IPADDR=1.2.3.4
+PREFIX=24
+IPADDR1=9.8.7.6
+PREFIX1=16
+GATEWAY=1.1.1.1
+
+IPV6ADDR="1001:abba::1234/56"
+IPV6ADDR_SECONDARIES="2001:abba::2234/64 3001:abba::3234/96"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname
new file mode 100644
index 00000000..cba380db
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname
@@ -0,0 +1,12 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=no
+DHCP_HOSTNAME="svata-pulec"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only
new file mode 100644
index 00000000..de03e044
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only
@@ -0,0 +1,11 @@
+DEVICE="eth0"
+ONBOOT=no
+TYPE=Ethernet
+DEFROUTE=yes
+PEERDNS=yes
+PEERROUTES=yes
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+DHCPV6C=yes
+HWADDR=00:13:20:F5:F5:E4
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback
new file mode 100644
index 00000000..d38e762e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback
@@ -0,0 +1,12 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=no
+DHCP_HOSTNAME="fully.qualified.domain"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway
new file mode 100644
index 00000000..98d91053
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway
@@ -0,0 +1,14 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore
new file mode 100644
index 00000000..bb81399e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore
@@ -0,0 +1,8 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
new file mode 100644
index 00000000..db09afdb
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
@@ -0,0 +1,14 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR0=1.2.3.4
+PREFIX0=24
+IPADDR1=9.8.7.6
+PREFIX1=16
+IPADDR2=3.3.3.3
+PREFIX2=8
+GATEWAY=1.1.1.1
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
new file mode 100644
index 00000000..6972e279
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
@@ -0,0 +1,13 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+IPADDR2=9.8.7.6
+PREFIX2=16
+IPADDR3=3.3.3.3
+PREFIX3=8
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
new file mode 100644
index 00000000..d9065994
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
@@ -0,0 +1,13 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR2=1.2.3.4
+PREFIX2=24
+IPADDR3=9.8.7.6
+PREFIX3=16
+IPADDR4=3.3.3.3
+PREFIX4=8
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
new file mode 100644
index 00000000..935267f7
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
@@ -0,0 +1,13 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+IPADDR=1.2.3.4
+PREFIX=24
+IPADDR1=9.8.7.6
+PREFIX1=16
+IPADDR2=3.3.3.3
+PREFIX2=8
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual
new file mode 100644
index 00000000..45db0e4c
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual
@@ -0,0 +1,19 @@
+# Intel Corporation 82567LM Gigabit Network Connection
+TYPE=Ethernet
+DEVICE=eth2
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=yes
+DNS1=10.2.0.4
+DNS2=10.2.0.5
+DNS3=1:2:3:4::a
+DNS4=1:2:3:4::b
+DOMAIN="lorem.com ipsum.org dolor.edu"
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR="1001:abba::1234/56"
+IPV6ADDR_SECONDARIES="2001:abba::2234/64 3001:abba::3234/96"
+IPV6_FAILURE_FATAL=no
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only
new file mode 100644
index 00000000..94189064
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only
@@ -0,0 +1,14 @@
+# Intel Corporation 82567LM Gigabit Network Connection
+TYPE=Ethernet
+DEVICE=eth2
+HWADDR=00:11:22:33:44:ee
+ONBOOT=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=yes
+DNS1=1:2:3:4::a
+IPV6_DOMAIN="lorem.com ipsum.org dolor.edu"
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR="1001:abba::1234/56"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1
new file mode 100644
index 00000000..95f8a8ed
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1
@@ -0,0 +1,16 @@
+IPADDR=
+GATEWAY=
+NETMASK=
+BOOTPROTO=static
+TYPE=Ethernet
+DEVICE=eth2
+HWADDR=00:11:22:33:44:ee
+ONBOOT=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=yes
+DNS1=1:2:3:4::a
+DOMAIN="lorem.com ipsum.org dolor.edu"
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR="1001:abba::1234/56"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default
new file mode 100644
index 00000000..12d5b5e6
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default
@@ -0,0 +1,11 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth4
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+USERCTL=yes
+NM_CONTROLLED=yes
+PEERDNS=yes
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n
new file mode 100644
index 00000000..2190a995
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n
@@ -0,0 +1,13 @@
+# GATEWAY<N> is obsolete, but we read it for backwards compatibility
+
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+
+# manual IP
+IPADDR1=1.2.3.4
+PREFIX1=24
+GATEWAY1=1.1.1.1
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static
new file mode 100644
index 00000000..4719de21
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static
@@ -0,0 +1,13 @@
+# IBM QETH
+DEVICE=eth1
+BOOTPROTO=static
+IPADDR=192.168.70.87
+NETMASK=255.255.255.0
+ONBOOT=yes
+NETTYPE=qeth
+SUBCHANNELS=0.0.0600,0.0.0601,0.0.0602
+TYPE=Ethernet
+PORTNAME=OSAPORT
+OPTIONS='layer2=1 portno=0'
+MACADDR=02:00:00:23:65:1a
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip
new file mode 100644
index 00000000..5098aef7
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip
@@ -0,0 +1,19 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=shared
+ONBOOT=no
+IPV6INIT=yes
+
+# additional IPs
+IPADDR=10.20.30.5
+PREFIX=24
+GATEWAY=1.1.1.1
+
+# these are ignored for shared method
+IPADDR1=6.7.8.9
+PREFIX1=16
+IPADDR2=3.3.3.3
+PREFIX2=24
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static
new file mode 100644
index 00000000..34acf9fe
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static
@@ -0,0 +1,22 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR=dead:beaf::1
+IPV6ADDR_SECONDARIES="dead:beaf::2/56"
+DNS3=1:2:3:4::a
+DNS4=1:2:3:4::b
+RES_OPTIONS=
+IPV6_RES_OPTIONS=
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto
new file mode 100644
index 00000000..a01f6558
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto
@@ -0,0 +1,16 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=static
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+RES_OPTIONS=
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16
new file mode 100644
index 00000000..07999038
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16
@@ -0,0 +1,14 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=172.16.3.4
+GATEWAY=172.16.3.1
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24
new file mode 100644
index 00000000..688143cd
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24
@@ -0,0 +1,14 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+GATEWAY=192.168.1.1
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8
new file mode 100644
index 00000000..0433c62a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8
@@ -0,0 +1,14 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=10.11.12.13
+GATEWAY=10.0.0.1
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
new file mode 100644
index 00000000..7faf49bd
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
@@ -0,0 +1,15 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy
new file mode 100644
index 00000000..7faf49bd
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy
@@ -0,0 +1,15 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt
new file mode 100644
index 00000000..861dd41f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt
@@ -0,0 +1,22 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR=dead:beaf::1
+IPV6ADDR_SECONDARIES="dead:beaf::2/56"
+DNS3=1:2:3:4::a
+DNS4=1:2:3:4::b
+RES_OPTIONS=
+ETHTOOL_OPTS="unknown1 wol apgs sopass 00:11:22:33:44:55 unkwnown2 opt2 unknown3"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan
new file mode 100644
index 00000000..1dfc9a43
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan
@@ -0,0 +1,22 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=none
+ONBOOT=yes
+USERCTL=yes
+MTU=1492
+NM_CONTROLLED=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=yes
+IPV6_AUTOCONF=no
+IPV6ADDR=dead:beaf::1
+IPV6ADDR_SECONDARIES="dead:beaf::2/56"
+DNS3=1:2:3:4::a
+DNS4=1:2:3:4::b
+RES_OPTIONS=
+ETHTOOL_OPTS="speed 100 duplex full wol apgs sopass 00:11:22:33:44:55 autoneg off"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1
new file mode 100644
index 00000000..2af1daf3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1
@@ -0,0 +1,8 @@
+FOO='val
+  bar=3'
+wrong line
+  F2=b
+  F3='b
+XXA=adf'
+  XXA2=val2
+'
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected
new file mode 100644
index 00000000..7f154073
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected
@@ -0,0 +1,12 @@
+FOO=
+#NM: FOO='val
+bar=
+#NM:   bar=3'
+#NM: wrong line
+  F2=b
+F3=
+#NM:   F3='b
+XXA=
+#NM: XXA=adf'
+  XXA2=val2
+#NM: '
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2
new file mode 100644
index 00000000..850149b2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2
@@ -0,0 +1,3 @@
+FOO='
+BAR=a
+'
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected
new file mode 100644
index 00000000..6ec53c17
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected
@@ -0,0 +1,4 @@
+FOO=
+#NM: FOO='
+BAR=a
+#NM: '
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3
new file mode 100644
index 00000000..3d198e00
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3
@@ -0,0 +1,3 @@
+FOO='
+BAR="
+'
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected
new file mode 100644
index 00000000..d2b27eb3
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected
@@ -0,0 +1,5 @@
+FOO=
+#NM: FOO='
+BAR=
+#NM: BAR="
+#NM: '
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
new file mode 100644
index 00000000..f834464e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
@@ -0,0 +1,36 @@
+# test what happens with multiple defines of a name.
+#
+# Note that svGetValue() will return "l4x", which
+# isn't correct in terms of shell-parsing. But we
+# don't consider only line-by-line, thus this is
+# expected.
+#
+# Also note that setting IPADDR will replace the last
+# occurrence, and delete all previous once.
+
+#L1
+IPADDR=l2
+
+#L2
+IPADDR=l3
+
+METRIC1=''
+METRIC2=$'\U0x'
+METRIC3=$'x\U0'
+
+#L4
+IPADDR='
+IPADDR=l4x
+'
+
+#Lx-1
+IPADDR2=not-visible
+#Lx-2
+IPADDR2='invalid
+#Lx-3
+
+#Ly-1
+IPADDR3='invalid
+#Ly-2
+IPADDR3=name3-value
+#Ly-3
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
new file mode 100644
index 00000000..040ddc9d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
@@ -0,0 +1,31 @@
+# test what happens with multiple defines of a name.
+#
+# Note that svGetValue() will return "l4x", which
+# isn't correct in terms of shell-parsing. But we
+# don't consider only line-by-line, thus this is
+# expected.
+#
+# Also note that setting IPADDR will replace the last
+# occurrence, and delete all previous once.
+
+#L1
+
+#L2
+
+METRIC1=''
+METRIC2=$'\U0x'
+METRIC3=$'x\U0'
+
+#L4
+IPADDR=set-by-test1
+#NM: '
+
+#Lx-1
+#Lx-2
+IPADDR2=set-by-test2
+#Lx-3
+
+#Ly-1
+#Ly-2
+IPADDR3=set-by-test3
+#Ly-3
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected
new file mode 100644
index 00000000..42608576
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected
@@ -0,0 +1,15 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+ETHTOOL_OPTS="autoneg on ; -K net0 rxvlan off tx on"
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wired Auto-Negotiate"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap
new file mode 100644
index 00000000..6936f2e0
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap
@@ -0,0 +1,2 @@
+IEEE_8021X_PASSWORD="foobar baz"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap
new file mode 100644
index 00000000..fe78177e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap
@@ -0,0 +1 @@
+IEEE_8021X_PASSWORD="foobarblah"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
new file mode 100644
index 00000000..5a9569ed
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
@@ -0,0 +1 @@
+WPA_PSK="The king is dead."
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep
new file mode 100644
index 00000000..0bd766c2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep
@@ -0,0 +1 @@
+KEY=0123456789abcdef0123456789
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii
new file mode 100644
index 00000000..f5d532b2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii
@@ -0,0 +1 @@
+KEY1=s:LoremIpsumSit
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii
new file mode 100644
index 00000000..b0d14708
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii
@@ -0,0 +1 @@
+KEY1=s:Lorem
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc
new file mode 100644
index 00000000..0bd766c2
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc
@@ -0,0 +1 @@
+KEY=0123456789abcdef0123456789
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap
new file mode 100644
index 00000000..6936f2e0
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap
@@ -0,0 +1,2 @@
+IEEE_8021X_PASSWORD="foobar baz"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase
new file mode 100644
index 00000000..d45c0ea8
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase
@@ -0,0 +1 @@
+KEY_PASSPHRASE1="foobar222blahblah"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls
new file mode 100644
index 00000000..0c3bc1e1
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls
@@ -0,0 +1,2 @@
+IEEE_8021X_PRIVATE_KEY_PASSWORD="test1"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls
new file mode 100644
index 00000000..b6256b95
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls
@@ -0,0 +1,2 @@
+IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD="test1"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk
new file mode 100644
index 00000000..d7813b2f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk
@@ -0,0 +1,2 @@
+WPA_PSK="I wonder what the king is doing tonight?"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2
new file mode 100644
index 00000000..995721dd
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2
@@ -0,0 +1,2 @@
+WPA_PSK="They're really saying I love you. >>\`<< '"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc
new file mode 100644
index 00000000..d7813b2f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc
@@ -0,0 +1,2 @@
+WPA_PSK="I wonder what the king is doing tonight?"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex
new file mode 100644
index 00000000..d0576a8d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex
@@ -0,0 +1,2 @@
+WPA_PSK=1da190379817bc360dda52e85c388c439a21ea5c7bf819c64e9da051807deae6
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted
new file mode 100644
index 00000000..9a47196a
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted
@@ -0,0 +1,2 @@
+WPA_PSK=54336845e2f3f321c4c7
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2
new file mode 100644
index 00000000..347bb85e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2
@@ -0,0 +1,2 @@
+WPA_PSK="a5d4d45e78e1455d8e6124e81ea137f9a5d4d45e78e1455d8e6124e81ea137f9"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw
new file mode 100644
index 00000000..d29a4289
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw
@@ -0,0 +1 @@
+IEEE_8021X_PIN=hallo2
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2
new file mode 100644
index 00000000..6936f2e0
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2
@@ -0,0 +1,2 @@
+IEEE_8021X_PASSWORD="foobar baz"
+
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes
new file mode 100644
index 00000000..0d6a302f
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes
@@ -0,0 +1,2 @@
+GATEWAYDEV=eth0
+IPV6_DEFAULTDEV=eth0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway
new file mode 100644
index 00000000..7987d105
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway
@@ -0,0 +1 @@
+GATEWAY=192.168.1.2
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore
new file mode 100644
index 00000000..7987d105
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore
@@ -0,0 +1 @@
+GATEWAY=192.168.1.2
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
new file mode 100644
index 00000000..4347405e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
@@ -0,0 +1,4 @@
+GATEWAYDEV=eth0
+# when devices in IPV6_DEFAULTDEV and IPV6_DEFAULTGW don't match the one in IPV6_DEFAULTGW is preferred
+IPV6_DEFAULTDEV=eth4
+IPV6_DEFAULTGW=2001::1234%eth0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy
new file mode 100644
index 00000000..c9e422e7
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy
@@ -0,0 +1,4 @@
+1.2.3.0/24 via 222.173.190.239 metric 0
+3.2.1.0/24 via 202.254.171.190 metric 77
+7.7.7.7/32 via 10.0.2.2 metric 11
+8.8.8.8/32 metric 12
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
new file mode 100644
index 00000000..9c05417e
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
@@ -0,0 +1,19 @@
+ADDRESS0=11.22.33.0
+NETMASK0=255.255.255.0
+GATEWAY0=192.168.1.5
+
+ADDRESS1=44.55.66.77
+NETMASK1=255.255.255.255
+GATEWAY1=192.168.1.7
+METRIC1=3
+OPTIONS1="mtu lock 9000 cwnd 12 src 1.1.1.1 tos 0x28 window 30000 scope 10 initcwnd lock 13 initrwnd 14"
+
+ADDRESS2=44.55.66.78
+NETMASK2=255.255.255.255
+GATEWAY2=192.168.1.8
+METRIC2=3
+OPTIONS2="mtu lock 9000 cwnd 12 src 1.1.1.1 tos 0x28 onlink window 30000 initcwnd lock 13 initrwnd 14 scope link"
+
+ADDRESS3=1.2.3.4
+NETMASK3=255.255.255.255
+OPTIONS3="local scope host"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
new file mode 100644
index 00000000..1fef7e97
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
@@ -0,0 +1,9 @@
+# Test route file in legacy format; i.e. lines passed as argumet to "ip route add"
+
+
+21.31.41.0/24 via 9.9.9.9 metric 1
+	via 8.8.8.8 to 32.42.52.62
+ 43.53.0.0/16 metric 3 via 7.7.7.7 dev eth2 cwnd 14 mtu lock 9000  initrwnd 20 window lock 10000 initcwnd lock 42 src 1.2.3.4
+
+7.7.7.8/32 via (null) metric 18
+local 1.2.3.4
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual
new file mode 100644
index 00000000..8bdf0acf
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual
@@ -0,0 +1,9 @@
+9876::1234/96 via 9876::7777 metric 2
+
+# default route is ignored by ifcfg-rh reader, because NM handles it internally
+default via dead::beaf
+
+# routes without "via" are valid
+abbe::cafe/64 metric 777
+
+aaaa::cccc/64 from 1111::2222/48 via 3333::4444 src 5555::6666 mtu lock 1450 cwnd 13
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem
new file mode 100644
index 00000000..dec9aa1b
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem
@@ -0,0 +1,118 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,4DE0615F23D82107
+
+QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz
+L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7
+zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU
+h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW
+ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv
+QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na
+eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo
+cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m
+RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8
+xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr
+T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI
+ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo
+RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ
+EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt
+lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5
+r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4
+aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W
+GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e
+DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE
+lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO
+8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3
+VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt
+G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ
+tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW
+QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1
+-----END RSA PRIVATE KEY-----
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com
+        Validity
+            Not Before: Mar 10 15:13:16 2009 GMT
+            Not After : Mar  8 15:13:16 2019 GMT
+        Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a:
+                    82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48:
+                    ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49:
+                    c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8:
+                    03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14:
+                    ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9:
+                    c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c:
+                    60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d:
+                    44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b:
+                    f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d:
+                    c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75:
+                    d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2:
+                    06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38:
+                    ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba:
+                    ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36:
+                    2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11:
+                    a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f:
+                    a3:43
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C
+            X509v3 Authority Key Identifier: 
+                keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10
+                DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com
+                serial:EB:E7:64:FB:79:F7:22:19
+
+    Signature Algorithm: md5WithRSAEncryption
+        7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82:
+        f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89:
+        b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b:
+        12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76:
+        1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48:
+        32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02:
+        85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1:
+        fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c:
+        cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43:
+        8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71:
+        1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19:
+        07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69:
+        7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62:
+        8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7:
+        3b:65:fe:f9
+-----BEGIN CERTIFICATE-----
+MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx
+EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO
+TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx
+HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN
+MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl
+MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG
+A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR
+XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA
+vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9
+jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF
+TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX
+gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj
+ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy
+YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww
+gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx
+FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD
+VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3
+IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/
+DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2
+8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr
+vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD
+T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa
+PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk=
+-----END CERTIFICATE-----
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem
new file mode 100644
index 00000000..ef1be20d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
+VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw
+FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE
+AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx
+NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ
+QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55
+IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3
+DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU
+xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc
+Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3
+FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4
+HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm
+J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P
+T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF
+ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO
+BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL
+EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl
+c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC
+AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc
+JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT
+p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t
+qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn
+B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp
+3nniskIjbH0xjgZf/nVMyLnjxg==
+-----END CERTIFICATE-----
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
new file mode 100644
index 00000000..edc2af75
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
Binary files differdiff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
new file mode 100644
index 00000000..59127d01
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -0,0 +1,11858 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <linux/pkt_sched.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
+
+#include "nm-glib-aux/nm-json-aux.h"
+#include "nm-utils.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-user.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-setting-8021x.h"
+#include "nm-setting-pppoe.h"
+#include "nm-setting-ppp.h"
+#include "nm-setting-vpn.h"
+#include "nm-setting-ethtool.h"
+#include "nm-setting-gsm.h"
+#include "nm-setting-cdma.h"
+#include "nm-setting-serial.h"
+#include "nm-setting-vlan.h"
+#include "nm-setting-dcb.h"
+#include "nm-core-internal.h"
+#include "nm-base/nm-ethtool-base.h"
+#include "nm-base/nm-ethtool-utils-base.h"
+
+#include "NetworkManagerUtils.h"
+
+#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h"
+#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h"
+#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h"
+#include "settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h"
+
+#include "nm-test-utils-core.h"
+
+#define TEST_IFCFG_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/ifcfg-rh/tests/network-scripts"
+#define TEST_SCRATCH_DIR \
+    NM_BUILD_BUILDDIR "/src/core/settings/plugins/ifcfg-rh/tests/network-scripts"
+#define TEST_SCRATCH_DIR_TMP TEST_SCRATCH_DIR "/tmp"
+
+#define TEST_SCRATCH_ALIAS_BASE TEST_SCRATCH_DIR "/ifcfg-alias0"
+
+/*****************************************************************************/
+
+#define _svOpenFile(testfile)                \
+    ({                                       \
+        shvarFile * _f;                      \
+        GError *    _error    = NULL;        \
+        const char *_testfile = (testfile);  \
+                                             \
+        g_assert(_testfile);                 \
+        _f = svOpenFile(_testfile, &_error); \
+        nmtst_assert_success(_f, _error);    \
+        _f;                                  \
+    })
+
+#define _svGetValue_check(f, key, expected_value)                   \
+    G_STMT_START                                                    \
+    {                                                               \
+        const char *      _val;                                     \
+        gs_free char *    _to_free    = NULL;                       \
+        gs_free char *    _val_string = NULL;                       \
+        shvarFile *const  _f          = (f);                        \
+        const char *const _key        = (key);                      \
+                                                                    \
+        _val_string = svGetValueStr_cp(_f, _key);                   \
+        _val        = svGetValue(_f, _key, &_to_free);              \
+        g_assert_cmpstr(_val, ==, (expected_value));                \
+        g_assert((!_val_string && (!_val || !_val[0]))              \
+                 || (_val_string && nm_streq0(_val, _val_string))); \
+    }                                                               \
+    G_STMT_END
+
+static void
+_assert_reread_same(NMConnection *connection, NMConnection *reread)
+{
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+_assert_reread_same_FIXME(NMConnection *connection, NMConnection *reread)
+{
+    gs_unref_object NMConnection *connection_normalized = NULL;
+    gs_unref_hashtable GHashTable *settings             = NULL;
+
+    /* FIXME: these assertion failures should not happen as we expect
+     * that re-reading a connection after write yields the same result.
+     *
+     * Needs investigation and fixing. */
+    nmtst_assert_connection_verifies_without_normalization(reread);
+
+    connection_normalized = nmtst_connection_duplicate_and_normalize(connection);
+
+    g_assert(!nm_connection_compare(connection_normalized, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+    g_assert(!nm_connection_diff(connection_normalized,
+                                 reread,
+                                 NM_SETTING_COMPARE_FLAG_EXACT,
+                                 &settings));
+}
+
+/* dummy path for an "expected" file, meaning: don't check for expected
+ * written ifcfg file. */
+static const char NO_EXPECTED[1];
+
+static void
+_assert_expected_content(NMConnection *connection, const char *filename, const char *expected)
+{
+    gs_free char *content_expectd = NULL;
+    gs_free char *content_written = NULL;
+    GError *      error           = NULL;
+    gsize         len_expectd     = 0;
+    gsize         len_written     = 0;
+    gboolean      success;
+    const char *  uuid = NULL;
+
+    g_assert(NM_IS_CONNECTION(connection));
+    g_assert(filename);
+    g_assert(g_file_test(filename, G_FILE_TEST_EXISTS));
+
+    g_assert(expected);
+    if (expected == NO_EXPECTED)
+        return;
+
+    success = g_file_get_contents(filename, &content_written, &len_written, &error);
+    nmtst_assert_success(success, error);
+
+    success = g_file_get_contents(expected, &content_expectd, &len_expectd, &error);
+    nmtst_assert_success(success, error);
+
+    {
+        gsize i, j;
+
+        for (i = 0; i < len_expectd;) {
+            if (content_expectd[i] != '$') {
+                i++;
+                continue;
+            }
+            if (g_str_has_prefix(&content_expectd[i], "${UUID}")) {
+                GString *str;
+
+                if (!uuid) {
+                    uuid = nm_connection_get_uuid(connection);
+                    g_assert(uuid);
+                }
+
+                j = strlen(uuid);
+
+                str = g_string_new_len(content_expectd, len_expectd);
+                g_string_erase(str, i, NM_STRLEN("${UUID}"));
+                g_string_insert_len(str, i, uuid, j);
+
+                g_free(content_expectd);
+                len_expectd     = str->len;
+                content_expectd = g_string_free(str, FALSE);
+                i += j;
+                continue;
+            }
+
+            /* other '$' is not supported. If need be, support escaping of
+             * '$' via '$$'. */
+            g_assert_not_reached();
+        }
+    }
+
+    if (len_expectd != len_written || memcmp(content_expectd, content_written, len_expectd) != 0) {
+        if (g_getenv("NMTST_IFCFG_RH_UPDATE_EXPECTED")
+            || nm_streq0(g_getenv("NM_TEST_REGENERATE"), "1")) {
+            if (uuid) {
+                gs_free char *search = g_strdup_printf("UUID=%s\n", uuid);
+                const char *  s;
+                gsize         i;
+                GString *     str;
+
+                s = content_written;
+                while (TRUE) {
+                    s = strstr(s, search);
+                    g_assert(s);
+                    if (s == content_written || s[-1] == '\n')
+                        break;
+                    s += strlen(search);
+                }
+
+                i = s - content_written;
+
+                str = g_string_new_len(content_written, len_written);
+                g_string_erase(str, i, strlen(search));
+                g_string_insert(str, i, "UUID=${UUID}\n");
+
+                len_written     = str->len;
+                content_written = g_string_free(str, FALSE);
+            }
+            success = g_file_set_contents(expected, content_written, len_written, &error);
+            nmtst_assert_success(success, error);
+        } else {
+            g_error("The content of \"%s\" (%zu) differs from \"%s\" (%zu). Set "
+                    "NMTST_IFCFG_RH_UPDATE_EXPECTED=yes to update the files "
+                    "inplace\n\n>>>%s<<<\n\n>>>%s<<<\n",
+                    filename,
+                    len_written,
+                    expected,
+                    len_expectd,
+                    content_written,
+                    content_expectd);
+        }
+    }
+}
+
+#define _writer_update_connection_reread(connection,                      \
+                                         ifcfg_dir,                       \
+                                         filename,                        \
+                                         expected,                        \
+                                         out_reread,                      \
+                                         out_reread_same)                 \
+    G_STMT_START                                                          \
+    {                                                                     \
+        gs_unref_object NMConnection *_connection =                       \
+            nmtst_connection_duplicate_and_normalize(connection);         \
+        NMConnection **_out_reread      = (out_reread);                   \
+        gboolean *     _out_reread_same = (out_reread_same);              \
+        const char *   _ifcfg_dir       = (ifcfg_dir);                    \
+        const char *   _filename        = (filename);                     \
+        const char *   _expected        = (expected);                     \
+        GError *       _error           = NULL;                           \
+        gboolean       _success;                                          \
+                                                                          \
+        g_assert(_ifcfg_dir &&_ifcfg_dir[0]);                             \
+        g_assert(_filename &&_filename[0]);                               \
+                                                                          \
+        _success = nms_ifcfg_rh_writer_write_connection(_connection,      \
+                                                        _ifcfg_dir,       \
+                                                        _filename,        \
+                                                        NULL,             \
+                                                        NULL,             \
+                                                        NULL,             \
+                                                        _out_reread,      \
+                                                        _out_reread_same, \
+                                                        &_error);         \
+        nmtst_assert_success(_success, _error);                           \
+        _assert_expected_content(_connection, _filename, _expected);      \
+    }                                                                     \
+    G_STMT_END
+
+#define _writer_update_connection(connection, ifcfg_dir, filename, expected) \
+    G_STMT_START                                                             \
+    {                                                                        \
+        gs_unref_object NMConnection *_reread      = NULL;                   \
+        NMConnection *                _c           = (connection);           \
+        gboolean                      _reread_same = FALSE;                  \
+                                                                             \
+        _writer_update_connection_reread(_c,                                 \
+                                         ifcfg_dir,                          \
+                                         filename,                           \
+                                         expected,                           \
+                                         &_reread,                           \
+                                         &_reread_same);                     \
+        _assert_reread_same(_c, _reread);                                    \
+        g_assert(_reread_same);                                              \
+    }                                                                        \
+    G_STMT_END
+
+static NMConnection *
+_connection_from_file(const char *filename,
+                      const char *network_file,
+                      const char *test_type,
+                      char **     out_unhandled)
+{
+    NMConnection *connection;
+    GError *      error              = NULL;
+    char *        unhandled_fallback = NULL;
+
+    g_assert(!out_unhandled || !*out_unhandled);
+
+    connection = nmtst_connection_from_file(filename,
+                                            network_file,
+                                            test_type,
+                                            out_unhandled ?: &unhandled_fallback,
+                                            &error);
+    g_assert_no_error(error);
+    g_assert(!unhandled_fallback);
+
+    if (out_unhandled && *out_unhandled)
+        nmtst_assert_connection_verifies(connection);
+    else
+        nmtst_assert_connection_verifies_without_normalization(connection);
+    return connection;
+}
+
+static void
+_connection_from_file_fail(const char *filename,
+                           const char *network_file,
+                           const char *test_type,
+                           GError **   error)
+{
+    NMConnection *connection;
+    GError *      local     = NULL;
+    char *        unhandled = NULL;
+
+    connection = nmtst_connection_from_file(filename, network_file, test_type, &unhandled, &local);
+
+    g_assert(!connection);
+    g_assert(local);
+    g_assert(!unhandled);
+    g_propagate_error(error, local);
+}
+
+static void
+_writer_new_connection_reread(NMConnection * connection,
+                              const char *   ifcfg_dir,
+                              char **        out_filename,
+                              const char *   expected,
+                              NMConnection **out_reread,
+                              gboolean *     out_reread_same)
+{
+    gboolean        success;
+    GError *        error                      = NULL;
+    char *          filename                   = NULL;
+    gs_unref_object NMConnection *con_verified = NULL;
+    gs_unref_object NMConnection *reread_copy  = NULL;
+    NMConnection **reread = out_reread ?: ((nmtst_get_rand_uint32() % 2) ? &reread_copy : NULL);
+
+    g_assert(NM_IS_CONNECTION(connection));
+    g_assert(ifcfg_dir);
+
+    con_verified = nmtst_connection_duplicate_and_normalize(connection);
+
+    success = nms_ifcfg_rh_writer_write_connection(con_verified,
+                                                   ifcfg_dir,
+                                                   NULL,
+                                                   NULL,
+                                                   NULL,
+                                                   &filename,
+                                                   reread,
+                                                   out_reread_same,
+                                                   &error);
+    nmtst_assert_success(success, error);
+    g_assert(filename && filename[0]);
+
+    if (reread)
+        nmtst_assert_connection_verifies_without_normalization(*reread);
+
+    _assert_expected_content(con_verified, filename, expected);
+
+    if (out_filename)
+        *out_filename = filename;
+    else
+        g_free(filename);
+}
+
+static void
+_writer_new_connec_exp(NMConnection *connection,
+                       const char *  ifcfg_dir,
+                       const char *  expected,
+                       char **       out_filename)
+{
+    gs_unref_object NMConnection *reread      = NULL;
+    gboolean                      reread_same = FALSE;
+
+    _writer_new_connection_reread(connection,
+                                  ifcfg_dir,
+                                  out_filename,
+                                  expected,
+                                  &reread,
+                                  &reread_same);
+    _assert_reread_same(connection, reread);
+    g_assert(reread_same);
+}
+
+static void
+_writer_new_connection(NMConnection *connection, const char *ifcfg_dir, char **out_filename)
+{
+    _writer_new_connec_exp(connection, ifcfg_dir, NO_EXPECTED, out_filename);
+}
+
+static void
+_writer_new_connection_FIXME(NMConnection *connection, const char *ifcfg_dir, char **out_filename)
+{
+    gs_unref_object NMConnection *reread      = NULL;
+    gboolean                      reread_same = FALSE;
+
+    /* FIXME: this should not happen. Fix it to use _writer_new_connection() instead. */
+
+    _writer_new_connection_reread(connection,
+                                  ifcfg_dir,
+                                  out_filename,
+                                  NO_EXPECTED,
+                                  &reread,
+                                  &reread_same);
+    _assert_reread_same_FIXME(connection, reread);
+    g_assert(!reread_same);
+}
+
+static void
+_writer_new_connection_fail(NMConnection *connection, const char *ifcfg_dir, GError **error)
+{
+    gs_unref_object NMConnection *connection_normalized = NULL;
+    gs_unref_object NMConnection *reread                = NULL;
+    gboolean                      success;
+    GError *                      local    = NULL;
+    char *                        filename = NULL;
+
+    g_assert(NM_IS_CONNECTION(connection));
+    g_assert(ifcfg_dir);
+
+    connection_normalized = nmtst_connection_duplicate_and_normalize(connection);
+
+    success = nms_ifcfg_rh_writer_write_connection(connection_normalized,
+                                                   ifcfg_dir,
+                                                   NULL,
+                                                   NULL,
+                                                   NULL,
+                                                   &filename,
+                                                   &reread,
+                                                   NULL,
+                                                   &local);
+    nmtst_assert_no_success(success, local);
+    g_assert(!filename);
+    g_assert(!reread);
+
+    g_propagate_error(error, local);
+}
+
+/*****************************************************************************/
+
+static void
+test_read_netmask_1(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_free char *                content    = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMIPAddress *                 ip4_addr;
+    const char *                  FILENAME = TEST_IFCFG_DIR "/ifcfg-netmask-1";
+
+    connection = _connection_from_file(FILENAME, NULL, TYPE_ETHERNET, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System netmask-1");
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "102.0.2.2");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 15);
+
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    content = nmtst_file_get_contents(FILENAME);
+
+    testfile = g_strdup(TEST_SCRATCH_DIR "/ifcfg-netmask-1.copy");
+
+    nmtst_file_set_contents(testfile, content);
+
+    _writer_update_connection(connection,
+                              TEST_SCRATCH_DIR,
+                              testfile,
+                              TEST_IFCFG_DIR "/ifcfg-netmask-1.cexpected");
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify_cert_or_key(NMSetting8021x *s_compare,
+                   const char *    file,
+                   const char *    privkey_password,
+                   const char *    property)
+{
+    NMSetting8021x *       s_8021x;
+    GError *               error    = NULL;
+    gboolean               success  = FALSE;
+    const char *           expected = NULL, *setting = NULL;
+    gboolean               phase2 = FALSE;
+    NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
+
+    if (strstr(property, "phase2"))
+        phase2 = TRUE;
+
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+
+    /* Load the certificate into an empty setting */
+    if (strstr(property, "ca-cert")) {
+        if (phase2)
+            success = nm_setting_802_1x_set_phase2_ca_cert(s_8021x,
+                                                           file,
+                                                           NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                           NULL,
+                                                           &error);
+        else
+            success = nm_setting_802_1x_set_ca_cert(s_8021x,
+                                                    file,
+                                                    NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                    NULL,
+                                                    &error);
+    } else if (strstr(property, "client-cert")) {
+        if (phase2)
+            success = nm_setting_802_1x_set_phase2_client_cert(s_8021x,
+                                                               file,
+                                                               NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                               NULL,
+                                                               &error);
+        else
+            success = nm_setting_802_1x_set_client_cert(s_8021x,
+                                                        file,
+                                                        NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                        NULL,
+                                                        &error);
+    } else if (strstr(property, "private-key")) {
+        if (phase2)
+            success = nm_setting_802_1x_set_phase2_private_key(s_8021x,
+                                                               file,
+                                                               privkey_password,
+                                                               NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                               NULL,
+                                                               &error);
+        else
+            success = nm_setting_802_1x_set_private_key(s_8021x,
+                                                        file,
+                                                        privkey_password,
+                                                        NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                        NULL,
+                                                        &error);
+    }
+    g_assert_no_error(error);
+    g_assert_cmpint(success, ==, TRUE);
+
+    /* Ensure it was loaded using the PATH scheme */
+    if (strstr(property, "ca-cert")) {
+        if (phase2)
+            scheme = nm_setting_802_1x_get_phase2_ca_cert_scheme(s_8021x);
+        else
+            scheme = nm_setting_802_1x_get_ca_cert_scheme(s_8021x);
+    } else if (strstr(property, "client-cert")) {
+        if (phase2)
+            scheme = nm_setting_802_1x_get_phase2_client_cert_scheme(s_8021x);
+        else
+            scheme = nm_setting_802_1x_get_client_cert_scheme(s_8021x);
+    } else if (strstr(property, "private-key")) {
+        if (phase2)
+            scheme = nm_setting_802_1x_get_phase2_private_key_scheme(s_8021x);
+        else
+            scheme = nm_setting_802_1x_get_private_key_scheme(s_8021x);
+    }
+    g_assert_cmpint(scheme, ==, NM_SETTING_802_1X_CK_SCHEME_PATH);
+
+    /* Grab the path back out */
+    if (strstr(property, "ca-cert")) {
+        if (phase2)
+            expected = nm_setting_802_1x_get_phase2_ca_cert_path(s_8021x);
+        else
+            expected = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    } else if (strstr(property, "client-cert")) {
+        if (phase2)
+            expected = nm_setting_802_1x_get_phase2_client_cert_path(s_8021x);
+        else
+            expected = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    } else if (strstr(property, "private-key")) {
+        if (phase2)
+            expected = nm_setting_802_1x_get_phase2_private_key_path(s_8021x);
+        else
+            expected = nm_setting_802_1x_get_private_key_path(s_8021x);
+    }
+    g_assert_cmpstr(expected, ==, file);
+
+    /* Compare the path with the expected path from the real setting */
+    if (strstr(property, "ca-cert")) {
+        if (phase2)
+            setting = nm_setting_802_1x_get_phase2_ca_cert_path(s_compare);
+        else
+            setting = nm_setting_802_1x_get_ca_cert_path(s_compare);
+    } else if (strstr(property, "client-cert")) {
+        if (phase2)
+            setting = nm_setting_802_1x_get_phase2_client_cert_path(s_compare);
+        else
+            setting = nm_setting_802_1x_get_client_cert_path(s_compare);
+    } else if (strstr(property, "private-key")) {
+        if (phase2)
+            setting = nm_setting_802_1x_get_phase2_private_key_path(s_compare);
+        else
+            setting = nm_setting_802_1x_get_private_key_path(s_compare);
+    }
+    g_assert_cmpstr(setting, ==, expected);
+
+    g_object_unref(s_8021x);
+    return TRUE;
+}
+
+static void
+test_read_basic(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    const char *         mac;
+    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-minimal", NULL, TYPE_ETHERNET, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-minimal");
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+    g_assert_cmpint(nm_setting_connection_get_autoconnect_retries(s_con), ==, -1);
+
+    /* UUID can't be tested if the ifcfg does not contain the UUID key, because
+     * the UUID is generated on the full path of the ifcfg file, which can change
+     * depending on where the tests are run.
+     */
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0);
+
+    /* MAC address */
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
+
+    /* ===== IPv4 SETTING ===== */
+    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_DISABLED);
+    g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
+
+    /* ===== IPv6 SETTING ===== */
+    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_IGNORE);
+    g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_miscellaneous_variables(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    char *               expected_mac_blacklist[3] = {"00:16:41:11:22:88",
+                                       "00:16:41:11:22:99",
+                                       "6a:5d:5a:fa:dd:f0"};
+    int                  mac_blacklist_num, i;
+    guint64              expected_timestamp = 0;
+
+    NMTST_EXPECT_NM_WARN("*invalid MAC in HWADDR_BLACKLIST 'XX:aa:invalid'*");
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-misc-variables",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+    g_test_assert_expected_messages();
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+    g_assert_cmpint(nm_setting_connection_get_autoconnect_retries(s_con), ==, 100);
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0);
+
+    /* MAC blacklist */
+    mac_blacklist_num = nm_setting_wired_get_num_mac_blacklist_items(s_wired);
+    g_assert_cmpint(mac_blacklist_num, ==, 3);
+    for (i = 0; i < mac_blacklist_num; i++)
+        g_assert(nm_utils_hwaddr_matches(nm_setting_wired_get_mac_blacklist_item(s_wired, i),
+                                         -1,
+                                         expected_mac_blacklist[i],
+                                         -1));
+
+    /* ===== IPv4 SETTING ===== */
+    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_DISABLED);
+    g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_variables_corner_cases(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    const char *         mac;
+    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-variables-corner-cases-1",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System test-variables-corner-cases-1");
+    g_assert_cmpstr(nm_setting_connection_get_zone(s_con), ==, NULL);
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0);
+
+    /* MAC address */
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
+
+    /* ===== IPv4 SETTING ===== */
+    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_DISABLED);
+    g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_unmanaged(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    char *               unhandled_spec     = NULL;
+    guint64              expected_timestamp = 0;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-nm-controlled",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unhandled_spec);
+    g_assert_cmpstr(unhandled_spec, ==, "unmanaged:mac:00:11:22:33:f8:9f");
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-nm-controlled");
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    g_free(unhandled_spec);
+    g_object_unref(connection);
+}
+
+static void
+test_read_unmanaged_unrecognized(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    gs_free char *       unhandled_spec     = NULL;
+    guint64              expected_timestamp = 0;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-nm-controlled-unrecognized",
+                                       NULL,
+                                       NULL,
+                                       &unhandled_spec);
+    g_assert_cmpstr(unhandled_spec, ==, "unmanaged:interface-name:=ipoac0");
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "PigeonNet");
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_unrecognized(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    gs_free char *       unhandled_spec     = NULL;
+    guint64              expected_timestamp = 0;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-unrecognized",
+                                       NULL,
+                                       NULL,
+                                       &unhandled_spec);
+    g_assert_cmpstr(unhandled_spec, ==, "unrecognized:mac:00:11:22:33");
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-unrecognized");
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_static(gconstpointer test_data)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    char *               unmanaged = NULL;
+    const char *         mac;
+    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee};
+    NMIPAddress *        ip4_addr;
+    NMIPAddress *        ip6_addr;
+    const char *         file, *expected_id;
+    gpointer             expect_ip6_p;
+
+    nmtst_test_data_unpack(test_data, &file, &expected_id, &expect_ip6_p);
+
+    g_assert(expected_id);
+
+    connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged);
+    g_assert_cmpstr(unmanaged, ==, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1492);
+
+    /* MAC address */
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
+
+    /* ===== IPv4 SETTING ===== */
+    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_MANUAL);
+    g_assert(nm_setting_ip_config_get_may_fail(s_ip4));
+
+    g_assert(nm_setting_ip_config_has_dns_options(s_ip4));
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip4), ==, 0);
+
+    /* DNS Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.1.5");
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1");
+
+    /* ===== IPv6 SETTING ===== */
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    if (GPOINTER_TO_INT(expect_ip6_p)) {
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_MANUAL);
+        g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
+
+        g_assert(nm_setting_ip_config_has_dns_options(s_ip6));
+        g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip6), ==, 0);
+
+        /* DNS Addresses */
+        g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
+        g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a");
+        g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1:2:3:4::b");
+
+        /* IP addresses */
+        g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 2);
+
+        ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0);
+        g_assert(ip6_addr);
+        g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64);
+        g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "dead:beaf::1");
+
+        ip6_addr = nm_setting_ip_config_get_address(s_ip6, 1);
+        g_assert(ip6_addr);
+        g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56);
+        g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "dead:beaf::2");
+    } else {
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+        g_assert(!nm_setting_ip_config_has_dns_options(s_ip6));
+    }
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_static_no_prefix(gconstpointer user_data)
+{
+    guint32              expected_prefix = GPOINTER_TO_UINT(user_data);
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMIPAddress *        ip4_addr;
+    char *               file, *expected_id;
+
+    file = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-wired-static-no-prefix-%u", expected_prefix);
+    expected_id = g_strdup_printf("System test-wired-static-no-prefix-%u", expected_prefix);
+
+    NMTST_EXPECT_NM_WARN("*missing PREFIX, assuming*");
+    connection = _connection_from_file(file, NULL, TYPE_ETHERNET, NULL);
+    g_test_assert_expected_messages();
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+
+    /* ===== IPv4 SETTING ===== */
+    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_MANUAL);
+
+    g_assert(!nm_setting_ip_config_has_dns_options(s_ip4));
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip4), ==, 0);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, expected_prefix);
+
+    g_free(file);
+    g_free(expected_id);
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_dhcp(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    char *               unmanaged                      = NULL;
+    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee};
+    const char *         mac;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(unmanaged == NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-dhcp");
+    g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* MAC address */
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+
+    /* ===== IPv4 SETTING ===== */
+    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_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)),
+                    ==,
+                    "foo.bar");
+    g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
+    g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
+    g_assert_cmpuint(nm_setting_ip_config_get_dhcp_hostname_flags(s_ip4),
+                     ==,
+                     NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE);
+
+    /* ===== IPv6 SETTING ===== */
+    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_DHCP);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "foo.bar");
+    g_assert_cmpuint(nm_setting_ip_config_get_dhcp_hostname_flags(s_ip6),
+                     ==,
+                     NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_dhcp_plus_ip(void)
+{
+    NMConnection *     connection;
+    NMSettingIPConfig *s_ip4;
+    NMSettingIPConfig *s_ip6;
+    NMIPAddress *      ip4_addr;
+    NMIPAddress *      ip6_addr;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp-plus-ip",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== IPv4 SETTING ===== */
+    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_may_fail(s_ip4));
+
+    /* DNS Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 2);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "1.2.3.4");
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1");
+
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 1);
+    g_assert(ip4_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 16);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "9.8.7.6");
+
+    /* ===== IPv6 SETTING ===== */
+    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));
+
+    /* DNS Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1:2:3:4::b");
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 3);
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0);
+    g_assert(ip6_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "1001:abba::1234");
+
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 1);
+    g_assert(ip6_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "2001:abba::2234");
+
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 2);
+    g_assert(ip6_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 96);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "3001:abba::3234");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_shared_plus_ip(void)
+{
+    NMConnection *     connection;
+    NMSettingIPConfig *s_ip4;
+    NMIPAddress *      ip4_addr;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-shared-plus-ip",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== IPv4 SETTING ===== */
+    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_SHARED);
+    g_assert(nm_setting_ip_config_get_may_fail(s_ip4));
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.20.30.5");
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_global_gateway(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMIPAddress *        ip4_addr;
+    char *               unmanaged = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-global-gateway",
+                                       TEST_IFCFG_DIR "/network-test-wired-global-gateway",
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(unmanaged == NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-global-gateway");
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+    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_MANUAL);
+
+    /* Address #1 */
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.1.5");
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.2");
+
+    g_object_unref(connection);
+}
+
+/* Ignore GATEWAY from /etc/sysconfig/network for automatic connections */
+static void
+test_read_wired_global_gateway_ignore(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    char *               unmanaged = NULL;
+
+    NMTST_EXPECT_NM_WARN("*ignoring GATEWAY (/etc/sysconfig/network) for * because the connection "
+                         "has no static addresses");
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-global-gateway-ignore",
+                                       TEST_IFCFG_DIR "/network-test-wired-global-gateway-ignore",
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_test_assert_expected_messages();
+    g_assert(unmanaged == NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System test-wired-global-gateway-ignore");
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+    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);
+
+    /* Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0);
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, NULL);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_obsolete_gateway_n(void)
+{
+    NMConnection *     connection;
+    NMSettingIPConfig *s_ip4;
+    NMIPAddress *      ip4_addr;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-obsolete-gateway-n",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== IPv4 SETTING ===== */
+    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_MANUAL);
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "1.2.3.4");
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1");
+
+    g_object_unref(connection);
+}
+
+static void
+test_user_1(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingUser *               s_user;
+
+    connection =
+        nmtst_create_minimal_connection("Test User 1", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+    s_user = NM_SETTING_USER(nm_setting_user_new());
+
+#define _USER_SET_DATA(s_user, key, val)                                      \
+    G_STMT_START                                                              \
+    {                                                                         \
+        GError * _error = NULL;                                               \
+        gboolean _success;                                                    \
+                                                                              \
+        _success = nm_setting_user_set_data((s_user), (key), (val), &_error); \
+        nmtst_assert_success(_success, _error);                               \
+    }                                                                         \
+    G_STMT_END
+
+#define _USER_SET_DATA_X(s_user, key) _USER_SET_DATA(s_user, key, "val=" key "")
+
+    _USER_SET_DATA(s_user, "my.val1", "");
+    _USER_SET_DATA_X(s_user, "my.val2");
+    _USER_SET_DATA_X(s_user, "my.v__al3");
+    _USER_SET_DATA_X(s_user, "my._v");
+    _USER_SET_DATA_X(s_user, "my.v+");
+    _USER_SET_DATA_X(s_user, "my.Av");
+    _USER_SET_DATA_X(s_user, "MY.AV");
+    _USER_SET_DATA_X(s_user, "MY.8V");
+    _USER_SET_DATA_X(s_user, "MY.8-V");
+    _USER_SET_DATA_X(s_user, "MY.8_V");
+    _USER_SET_DATA_X(s_user, "MY.8+V");
+    _USER_SET_DATA_X(s_user, "MY.8/V");
+    _USER_SET_DATA_X(s_user, "MY.8=V");
+    _USER_SET_DATA_X(s_user, "MY.-");
+    _USER_SET_DATA_X(s_user, "MY._");
+    _USER_SET_DATA_X(s_user, "MY.+");
+    _USER_SET_DATA_X(s_user, "MY./");
+    _USER_SET_DATA_X(s_user, "MY.=");
+    _USER_SET_DATA_X(s_user, "my.keys.1");
+    _USER_SET_DATA_X(s_user, "my.other.KEY.42");
+
+    nm_connection_add_setting(connection, NM_SETTING(s_user));
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_User_1.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_wired_never_default(void)
+{
+    NMConnection *     connection;
+    NMSettingIPConfig *s_ip4;
+    NMSettingIPConfig *s_ip6;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-never-default",
+                                       TEST_IFCFG_DIR "/network-test-wired-never-default",
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== WIRED SETTING ===== */
+    g_assert(nm_connection_get_setting_wired(connection));
+
+    /* ===== IPv4 SETTING ===== */
+    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_never_default(s_ip4));
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0);
+
+    /* ===== IPv6 SETTING ===== */
+    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_never_default(s_ip6));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_defroute_no(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    char *               unmanaged = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-defroute-no",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(unmanaged == NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-defroute-no");
+
+    g_assert(nm_connection_get_setting_wired(connection));
+
+    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_never_default(s_ip4));
+
+    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_never_default(s_ip6));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_defroute_no_gatewaydev_yes(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    char *               unmanaged = NULL;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-defroute-no-gatewaydev-yes",
+                              TEST_IFCFG_DIR "/network-test-wired-defroute-no-gatewaydev-yes",
+                              TYPE_ETHERNET,
+                              &unmanaged);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System test-wired-defroute-no-gatewaydev-yes");
+
+    g_assert(nm_connection_get_setting_wired(connection));
+
+    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_never_default(s_ip4) == FALSE);
+
+    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_never_default(s_ip6) == FALSE);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_static_routes(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMIPRoute *          ip4_route;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-static-routes",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-static-routes");
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+    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_MANUAL);
+
+    /* Routes */
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 4);
+
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 0);
+    g_assert(ip4_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "11.22.33.0");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 24);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "192.168.1.5");
+    g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, -1);
+
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 1);
+    g_assert(ip4_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "44.55.66.77");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "192.168.1.7");
+    g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 3);
+    nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TOS, 0x28);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 30000);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 12);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 13);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 14);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000);
+    nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
+    nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, TRUE);
+    nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1.1.1.1");
+    nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 10);
+
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 2);
+    g_assert(ip4_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "44.55.66.78");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "192.168.1.8");
+    g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 3);
+    nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TOS, 0x28);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 30000);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 12);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 13);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 14);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000);
+    nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
+    nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, TRUE);
+    nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1.1.1.1");
+    nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, TRUE);
+    nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 253);
+
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 3);
+    g_assert(ip4_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "1.2.3.4");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32);
+    nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
+    nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 254);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_static_routes_legacy(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    char *               unmanaged = NULL;
+    NMIPRoute *          ip4_route;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-static-routes-legacy",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System test-wired-static-routes-legacy");
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_MANUAL);
+
+    /* Routes */
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 5);
+
+    /* Route #1 */
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 0);
+    g_assert(ip4_route != NULL);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "21.31.41.0");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 24);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "9.9.9.9");
+    g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 1);
+
+    /* Route #2 */
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 1);
+    g_assert(ip4_route != NULL);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "32.42.52.62");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "8.8.8.8");
+    g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, -1);
+
+    /* Route #3 */
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 2);
+    g_assert(ip4_route != NULL);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "43.53.0.0");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 16);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, "7.7.7.7");
+    g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 3);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 10000);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 14);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 42);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 20);
+    nmtst_assert_route_attribute_uint32(ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000);
+    nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW, TRUE);
+    nmtst_assert_route_attribute_boolean(ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
+    nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1.2.3.4");
+
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 3);
+    g_assert(ip4_route != NULL);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "7.7.7.8");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip4_route), ==, NULL);
+    g_assert_cmpint(nm_ip_route_get_metric(ip4_route), ==, 18);
+
+    /* Route #5 */
+    ip4_route = nm_setting_ip_config_get_route(s_ip4, 4);
+    g_assert(ip4_route != NULL);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "1.2.3.4");
+    nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_ipv4_manual(gconstpointer data)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    char *               unmanaged = NULL;
+    NMIPAddress *        ip4_addr;
+    const char *         file, *expected_id;
+
+    nmtst_test_data_unpack(data, &file, &expected_id);
+
+    g_assert(expected_id);
+
+    connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_MANUAL);
+    g_assert_cmpint(nm_setting_ip_config_get_dad_timeout(s_ip4), ==, 2000);
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 3);
+
+    /* Address #1 */
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "1.2.3.4");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+
+    /* Address #2 */
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 1);
+    g_assert(ip4_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "9.8.7.6");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 16);
+
+    /* Address #3 */
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 2);
+    g_assert(ip4_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "3.3.3.3");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_ipv6_manual(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    char *               unmanaged = NULL;
+    NMIPAddress *        ip6_addr;
+    NMIPRoute *          ip6_route;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-ipv6-manual",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-ipv6-manual");
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+
+    /* DNS Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+
+    /* DNS search domains */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip4), ==, 3);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 0), ==, "lorem.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 1), ==, "ipsum.org");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 2), ==, "dolor.edu");
+
+    /* ===== IPv6 SETTING ===== */
+
+    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_MANUAL);
+    g_assert(!nm_setting_ip_config_get_never_default(s_ip6));
+    g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 3);
+
+    /* Address #1 */
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0);
+    g_assert(ip6_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "1001:abba::1234");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56);
+
+    /* Address #2 */
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 1);
+    g_assert(ip6_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "2001:abba::2234");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64);
+
+    /* Address #3 */
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 2);
+    g_assert(ip6_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "3001:abba::3234");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 96);
+
+    /* Routes */
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 4);
+    /* Route #1 */
+    ip6_route = nm_setting_ip_config_get_route(s_ip6, 0);
+    g_assert(ip6_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "9876::1234");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 96);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, "9876::7777");
+    g_assert_cmpint(nm_ip_route_get_metric(ip6_route), ==, 2);
+    /* Route #2 */
+    ip6_route = nm_setting_ip_config_get_route(s_ip6, 1);
+    g_assert(ip6_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "::");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 0);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, "dead::beaf");
+    g_assert_cmpint(nm_ip_route_get_metric(ip6_route), ==, -1);
+    /* Route #3 */
+    ip6_route = nm_setting_ip_config_get_route(s_ip6, 2);
+    g_assert(ip6_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "abbe::cafe");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 64);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, NULL);
+    g_assert_cmpint(nm_ip_route_get_metric(ip6_route), ==, 777);
+    /* Route #4 */
+    ip6_route = nm_setting_ip_config_get_route(s_ip6, 3);
+    g_assert(ip6_route);
+    g_assert_cmpstr(nm_ip_route_get_dest(ip6_route), ==, "aaaa::cccc");
+    g_assert_cmpint(nm_ip_route_get_prefix(ip6_route), ==, 64);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(ip6_route), ==, "3333::4444");
+    nmtst_assert_route_attribute_uint32(ip6_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 13);
+    nmtst_assert_route_attribute_uint32(ip6_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 1450);
+    nmtst_assert_route_attribute_boolean(ip6_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
+    nmtst_assert_route_attribute_string(ip6_route, NM_IP_ROUTE_ATTRIBUTE_FROM, "1111::2222/48");
+    nmtst_assert_route_attribute_string(ip6_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "5555::6666");
+
+    /* DNS Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1:2:3:4::b");
+
+    /* DNS domains - none as domains are stuffed to 'ipv4' setting */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 0);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_ipv6_only(gconstpointer test_data)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    char *               unmanaged = NULL;
+    NMIPAddress *        ip6_addr;
+    const char *         method;
+    const char *         file, *expected_id;
+
+    nmtst_test_data_unpack(test_data, &file, &expected_id);
+
+    g_assert(expected_id);
+
+    connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+
+    method = nm_setting_ip_config_get_method(s_ip4);
+    g_assert_cmpstr(method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+
+    /* ===== IPv6 SETTING ===== */
+
+    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_MANUAL);
+
+    /* IP addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1);
+
+    /* Address #1 */
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0);
+    g_assert(ip6_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "1001:abba::1234");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 56);
+
+    /* DNS Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 1);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1:2:3:4::a");
+
+    /* DNS domains should be in IPv6, because IPv4 is disabled */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 3);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "lorem.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "ipsum.org");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 2), ==, "dolor.edu");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_dhcp6_only(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    char *               unmanaged = NULL;
+    const char *         method;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp6-only",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-dhcp6-only");
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+
+    method = nm_setting_ip_config_get_method(s_ip4);
+    g_assert_cmpstr(method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+
+    /* ===== IPv6 SETTING ===== */
+
+    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_DHCP);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_autoip(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    char *                        unmanaged = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-autoip",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(unmanaged == NULL);
+
+    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_LINK_LOCAL);
+    g_assert(!nm_setting_ip_config_get_may_fail(s_ip4));
+    g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, 2600);
+}
+
+static void
+test_read_onboot_no(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    char *               unmanaged = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-onboot-no",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    g_assert(!nm_setting_connection_get_autoconnect(s_con));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_noip(void)
+{
+    NMConnection *     connection;
+    NMSettingIPConfig *s_ip4;
+    NMSettingIPConfig *s_ip6;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-noip", NULL, TYPE_ETHERNET, NULL);
+
+    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_DISABLED);
+    g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
+
+    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_IGNORE);
+    g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE);
+
+    g_object_unref(connection);
+}
+
+#define TEST_IFCFG_WIRED_8021x_PEAP_MSCHAPV2_CA_CERT TEST_IFCFG_DIR "/test_ca_cert.pem"
+
+static void
+test_read_wired_8021x_peap_mschapv2(void)
+{
+    NMConnection *     connection;
+    NMSettingWired *   s_wired;
+    NMSettingIPConfig *s_ip4;
+    NMSetting8021x *   s_8021x;
+    NMSetting8021x *   tmp_8021x;
+    char *             unmanaged = NULL;
+    GError *           error     = NULL;
+    gboolean           success   = FALSE;
+    const char *       expected_ca_cert_path;
+    const char *       read_ca_cert_path;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-peap-mschapv2",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap");
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith");
+    g_assert_cmpstr(nm_setting_802_1x_get_anonymous_identity(s_8021x), ==, "somebody");
+    g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz");
+    g_assert_cmpstr(nm_setting_802_1x_get_phase1_peapver(s_8021x), ==, "1");
+    g_assert_cmpstr(nm_setting_802_1x_get_phase1_peaplabel(s_8021x), ==, "1");
+
+    /* CA Cert */
+    tmp_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+
+    success = nm_setting_802_1x_set_ca_cert(tmp_8021x,
+                                            TEST_IFCFG_WIRED_8021x_PEAP_MSCHAPV2_CA_CERT,
+                                            NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                            NULL,
+                                            &error);
+    g_assert_no_error(error);
+    g_assert(success == TRUE);
+
+    expected_ca_cert_path = nm_setting_802_1x_get_ca_cert_path(tmp_8021x);
+    g_assert(expected_ca_cert_path);
+
+    read_ca_cert_path = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_assert(read_ca_cert_path);
+
+    g_assert_cmpstr(read_ca_cert_path, ==, expected_ca_cert_path);
+
+    g_object_unref(tmp_8021x);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_8021x_tls_secret_flags(gconstpointer test_data)
+{
+    NMConnection *  connection;
+    NMSettingWired *s_wired;
+    NMSetting8021x *s_8021x;
+    char *          dirname, *tmp;
+    const char *    ifcfg;
+    gpointer        expected_flags_p;
+
+    nmtst_test_data_unpack(test_data, &ifcfg, &expected_flags_p);
+
+    connection = _connection_from_file(ifcfg, NULL, TYPE_ETHERNET, NULL);
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "tls");
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith");
+    g_assert_cmpint(nm_setting_802_1x_get_private_key_password_flags(s_8021x),
+                    ==,
+                    GPOINTER_TO_INT(expected_flags_p));
+
+    dirname = g_path_get_dirname(ifcfg);
+    tmp     = g_build_path("/", dirname, "test_ca_cert.pem", NULL);
+    g_assert_cmpstr(nm_setting_802_1x_get_ca_cert_path(s_8021x), ==, tmp);
+    g_free(tmp);
+
+    tmp = g_build_path("/", dirname, "test1_key_and_cert.pem", NULL);
+    g_assert_cmpstr(nm_setting_802_1x_get_client_cert_path(s_8021x), ==, tmp);
+    g_assert_cmpstr(nm_setting_802_1x_get_private_key_path(s_8021x), ==, tmp);
+    g_free(tmp);
+
+    g_free(dirname);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_write_802_1X_subj_matches(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSetting8021x *              s_8021x;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-802-1X-subj-matches",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap");
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Jara Cimrman");
+    g_assert_cmpstr(nm_setting_802_1x_get_subject_match(s_8021x), ==, "server1.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_subject_match(s_8021x),
+                    ==,
+                    "server2.yourdomain.tld");
+    g_assert_cmpint(nm_setting_802_1x_get_num_altsubject_matches(s_8021x), ==, 3);
+    g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 0), ==, "a.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 1), ==, "b.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 2), ==, "c.yourdomain.tld");
+    g_assert_cmpint(nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x), ==, 2);
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 0),
+                    ==,
+                    "x.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 1),
+                    ==,
+                    "y.yourdomain.tld");
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-System_test-wired-802-1X-subj-matches.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    /* Check 802.1X stuff of the re-read connection. */
+    s_8021x = nm_connection_get_setting_802_1x(reread);
+    g_assert(s_8021x);
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap");
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Jara Cimrman");
+    g_assert_cmpstr(nm_setting_802_1x_get_subject_match(s_8021x), ==, "server1.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_subject_match(s_8021x),
+                    ==,
+                    "server2.yourdomain.tld");
+    g_assert_cmpint(nm_setting_802_1x_get_num_altsubject_matches(s_8021x), ==, 3);
+    g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 0), ==, "a.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 1), ==, "b.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_altsubject_match(s_8021x, 2), ==, "c.yourdomain.tld");
+    g_assert_cmpint(nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x), ==, 2);
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 0),
+                    ==,
+                    "x.yourdomain.tld");
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_altsubject_match(s_8021x, 1),
+                    ==,
+                    "y.yourdomain.tld");
+}
+
+static void
+test_read_802_1x_ttls_eapgtc(void)
+{
+    NMConnection *  connection;
+    NMSetting8021x *s_8021x;
+
+    /* Test that EAP-* inner methods are correctly read into the
+     * NMSetting8021x::autheap property.
+     */
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-802-1x-ttls-eapgtc",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    /* EAP methods */
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "ttls");
+
+    /* Auth methods */
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_auth(s_8021x), ==, NULL);
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_autheap(s_8021x), ==, "gtc");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_802_1x_tls_p12_no_client_cert(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSetting8021x *              s_8021x;
+    const char *                  path;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-tls-p12-no-client-cert",
+                              NULL,
+                              TYPE_ETHERNET,
+                              NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    g_assert_cmpint(nm_setting_802_1x_get_private_key_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_PATH);
+    path = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert(path);
+
+    g_assert_cmpint(nm_setting_802_1x_get_client_cert_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert_cmpstr(path, ==, nm_setting_802_1x_get_client_cert_path(s_8021x));
+}
+
+static void
+test_read_write_802_1x_password_raw(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSetting8021x *              s_8021x;
+    GBytes *                      bytes;
+    gconstpointer                 data;
+    gsize                         size;
+
+    /* Test that the 802-1x.password-raw is correctly read and written. */
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-802-1x-password-raw",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    bytes = nm_setting_802_1x_get_password_raw(s_8021x);
+    g_assert(bytes);
+    data = g_bytes_get_data(bytes, &size);
+    g_assert_cmpmem(data, size, "\x04\x08\x15\x16\x23\x42\x00\x01", 8);
+
+    g_assert_cmpint(nm_setting_802_1x_get_password_raw_flags(s_8021x),
+                    ==,
+                    NM_SETTING_SECRET_FLAG_NONE);
+
+    g_assert_cmpstr(nm_setting_802_1x_get_pin(s_8021x), ==, "hallo2");
+    g_assert_cmpint(nm_setting_802_1x_get_pin_flags(s_8021x), ==, NM_SETTING_SECRET_FLAG_NONE);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+    reread  = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+    keyfile = utils_get_keys_path(testfile);
+    g_assert(g_file_test(keyfile, G_FILE_TEST_EXISTS));
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_wired_aliases_good(gconstpointer test_data)
+{
+    const int            N = GPOINTER_TO_INT(test_data);
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    int                  expected_num_addresses;
+    const char *         expected_address_0[] = {"192.168.1.5",
+                                        "192.168.1.6",
+                                        "192.168.1.9",
+                                        "192.168.1.99",
+                                        NULL};
+    const char *         expected_address_3[] = {"192.168.1.5", "192.168.1.6", NULL};
+    const char *         expected_label_0[]   = {
+        NULL,
+        "aliasem0:1",
+        "aliasem0:2",
+        "aliasem0:99",
+        NULL,
+    };
+    const char *expected_label_3[] = {
+        NULL,
+        "aliasem3:1",
+        NULL,
+    };
+    const char **expected_address;
+    const char **expected_label;
+    int          i, j;
+    char         path[256];
+
+    expected_address       = N == 0 ? expected_address_0 : expected_address_3;
+    expected_label         = N == 0 ? expected_label_0 : expected_label_3;
+    expected_num_addresses = g_strv_length((char **) expected_address);
+
+    nm_sprintf_buf(path, TEST_IFCFG_DIR "/ifcfg-aliasem%d", N);
+
+    connection = _connection_from_file(path, NULL, TYPE_ETHERNET, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    if (N == 0)
+        g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System aliasem0");
+    else
+        g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System aliasem3");
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_MANUAL);
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, expected_num_addresses);
+
+    /* Addresses */
+    for (i = 0; i < expected_num_addresses; i++) {
+        NMIPAddress *ip4_addr;
+        const char * addr;
+        GVariant *   label;
+
+        ip4_addr = nm_setting_ip_config_get_address(s_ip4, i);
+        g_assert(ip4_addr != NULL);
+
+        addr = nm_ip_address_get_address(ip4_addr);
+        g_assert(nm_utils_ipaddr_is_valid(AF_INET, addr));
+
+        for (j = 0; j < expected_num_addresses; j++) {
+            if (!g_strcmp0(addr, expected_address[j]))
+                break;
+        }
+        g_assert(j < expected_num_addresses);
+
+        g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+        label = nm_ip_address_get_attribute(ip4_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
+        if (expected_label[j])
+            g_assert_cmpstr(g_variant_get_string(label, NULL), ==, expected_label[j]);
+        else
+            g_assert(label == NULL);
+
+        expected_address[j] = NULL;
+        expected_label[j]   = NULL;
+    }
+
+    /* Gateway */
+    g_assert(!nm_setting_ip_config_get_never_default(s_ip4));
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1");
+
+    for (i = 0; i < expected_num_addresses; i++)
+        g_assert(!expected_address[i]);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_aliases_bad(const char *base, const char *expected_id)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMIPAddress *        ip4_addr;
+
+    g_assert(expected_id);
+
+    connection = _connection_from_file(base, NULL, TYPE_ETHERNET, NULL);
+    g_test_assert_expected_messages();
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_MANUAL);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+
+    /* Addresses */
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.1.5");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
+    g_assert(nm_ip_address_get_attribute(ip4_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL) == NULL);
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_aliases_bad_1(void)
+{
+    NMTST_EXPECT_NM_WARN("*aliasem1:1*has no DEVICE*");
+    test_read_wired_aliases_bad(TEST_IFCFG_DIR "/ifcfg-aliasem1", "System aliasem1");
+}
+
+static void
+test_read_wired_aliases_bad_2(void)
+{
+    NMTST_EXPECT_NM_WARN("*aliasem2:1*has invalid DEVICE*");
+    test_read_wired_aliases_bad(TEST_IFCFG_DIR "/ifcfg-aliasem2", "System aliasem2");
+}
+
+static void
+test_read_dns_options(void)
+{
+    NMConnection *     connection;
+    NMSettingIPConfig *s_ip4, *s_ip6;
+    char *             unmanaged = NULL;
+    const char *       option;
+    const char *       options4[] = {"ndots:3", "single-request-reopen"};
+    const char *       options6[] = {"inet6"};
+    guint32            i, num;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dns-options",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert_cmpstr(unmanaged, ==, NULL);
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+
+    num = nm_setting_ip_config_get_num_dns_options(s_ip4);
+    g_assert_cmpint(num, ==, G_N_ELEMENTS(options4));
+
+    for (i = 0; i < num; i++) {
+        option = nm_setting_ip_config_get_dns_option(s_ip4, i);
+        g_assert_cmpstr(options4[i], ==, option);
+    }
+
+    num = nm_setting_ip_config_get_num_dns_options(s_ip6);
+    g_assert_cmpint(num, ==, G_N_ELEMENTS(options6));
+
+    for (i = 0; i < num; i++) {
+        option = nm_setting_ip_config_get_dns_option(s_ip6, i);
+        g_assert_cmpstr(options6[i], ==, option);
+    }
+
+    g_object_unref(connection);
+}
+
+static void
+test_clear_master(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_free char *              keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    char *                        unmanaged = NULL;
+    shvarFile *                   f;
+
+    /* 1. load the bridge slave connection from disk */
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-component",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert_cmpstr(unmanaged, ==, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0");
+    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, "bridge");
+
+    /* 2. write the connection to a new file */
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-System_test-bridge-component-a.cexpected",
+                           &testfile);
+
+    /* 3. clear master and slave-type */
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_MASTER,
+                 NULL,
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NULL,
+                 NULL);
+
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, NULL);
+    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NULL);
+
+    nmtst_assert_connection_verifies_after_normalization(connection, 0, 0);
+
+    /* 4. update the connection on disk */
+    _writer_update_connection(connection,
+                              TEST_SCRATCH_DIR,
+                              testfile,
+                              TEST_IFCFG_DIR "/ifcfg-System_test-bridge-component-b.cexpected");
+    keyfile = utils_get_keys_path(testfile);
+    g_assert(!g_file_test(keyfile, G_FILE_TEST_EXISTS));
+
+    /* 5. check that BRIDGE variable has been removed */
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "BRIDGE", NULL);
+    svCloseFile(f);
+}
+
+static void
+test_write_dns_options(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    static const char *           mac = "31:33:33:37:be:cd";
+    guint32                       mtu = 1492;
+    NMIPAddress *                 addr;
+    NMIPAddress *                 addr6;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test DNS options",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 (gint64) 204,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    nm_setting_ip_config_add_dns_option(s_ip4, "debug");
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 (gint64) 206,
+                 NULL);
+
+    /* Add addresses */
+    addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    nm_setting_ip_config_add_dns_option(s_ip6, "timeout:3");
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_wifi_open(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSettingIPConfig *        s_ip4, *s_ip6;
+    GBytes *                   ssid;
+    const char *               mac;
+    char        expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+    const char *expected_ssid                  = "blahblah";
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open", NULL, TYPE_WIRELESS, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-open)");
+
+    /* UUID can't be tested if the ifcfg does not contain the UUID key, because
+     * the UUID is generated on the full path of the ifcfg file, which can change
+     * depending on where the tests are run.
+     */
+
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+    g_assert_cmpint(nm_setting_connection_get_autoconnect_priority(s_con), ==, -1);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* MAC address */
+    mac = nm_setting_wireless_get_mac_address(s_wireless);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1);
+
+    /* ===== Wi-Fi SECURITY SETTING ===== */
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec == NULL);
+
+    /* ===== IPv4 SETTING ===== */
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpint(nm_setting_ip_config_get_route_metric(s_ip4), ==, 104);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpint(nm_setting_ip_config_get_route_metric(s_ip6), ==, 106);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_open_auto(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWireless *  s_wireless;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-auto",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System blahblah (test-wifi-open-auto)");
+
+    /* ===== WIRELESS SETTING ===== */
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_open_ssid_hex(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWireless *  s_wireless;
+    GBytes *             ssid;
+    const char *         expected_ssid = "blahblah";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-hex",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System blahblah (test-wifi-open-ssid-hex)");
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_open_ssid_hex_bad(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "0x626cxx";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-bad-hex",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System 0x626cxx (test-wifi-open-ssid-bad-hex)");
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+}
+
+static void
+test_read_wifi_open_ssid_bad(gconstpointer data)
+{
+    _connection_from_file_fail((const char *) data, NULL, TYPE_WIRELESS, NULL);
+}
+
+static void
+test_read_wifi_open_ssid_quoted(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWireless *  s_wireless;
+    GBytes *             ssid;
+    const char *         expected_ssid = "foo\"bar\\";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-quoted",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System foo\"bar\\ (test-wifi-open-ssid-quoted)");
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSettingIPConfig *        s_ip4;
+    GBytes *                   ssid;
+    const char *               mac;
+    char         expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+    const char * expected_ssid                  = "blahblah";
+    NMWepKeyType key_type;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep", NULL, TYPE_WIRELESS, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-wep)");
+
+    /* UUID can't be tested if the ifcfg does not contain the UUID key, because
+     * the UUID is generated on the full path of the ifcfg file, which can change
+     * depending on where the tests are run.
+     */
+
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* MAC address */
+    mac = nm_setting_wireless_get_mac_address(s_wireless);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+
+    /* MTU */
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
+    g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, "shared");
+    g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
+
+    key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec);
+    g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY);
+
+    g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0),
+                    ==,
+                    "0123456789abcdef0123456789");
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep_adhoc(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSettingIPConfig *        s_ip4;
+    GBytes *                   ssid;
+    const char *               expected_ssid = "blahblah";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-adhoc",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System blahblah (test-wifi-wep-adhoc)");
+
+    /* UUID can't be tested if the ifcfg does not contain the UUID key, because
+     * the UUID is generated on the full path of the ifcfg file, which can change
+     * depending on where the tests are run.
+     */
+
+    g_assert(!nm_setting_connection_get_autoconnect(s_con));
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "adhoc");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 11);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
+    g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec));
+    g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
+
+    g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0),
+                    ==,
+                    "0123456789abcdef0123456789");
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
+
+    /* ===== IPv4 SETTING ===== */
+
+    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);
+
+    /* Ignore auto DNS */
+    g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
+
+    /* DNS Addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep_passphrase(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-passphrase",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
+    g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
+    g_assert_cmpint(nm_setting_wireless_security_get_wep_key_type(s_wsec),
+                    ==,
+                    NM_WEP_KEY_TYPE_UNKNOWN);
+    g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), ==, "foobar222blahblah");
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep_40_ascii(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMWepKeyType               key_type;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-40-ascii",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
+    g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
+
+    key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec);
+    g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY);
+
+    g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), ==, "Lorem");
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep_104_ascii(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMWepKeyType               key_type;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-104-ascii",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
+    g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
+
+    key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec);
+    g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY);
+
+    g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), ==, "LoremIpsumSit");
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_leap(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-leap", NULL, TYPE_WIRELESS, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-leap)");
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x");
+    g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, "leap");
+    g_assert_cmpstr(nm_setting_wireless_security_get_leap_username(s_wsec), ==, "Bill Smith");
+    g_assert_cmpstr(nm_setting_wireless_security_get_leap_password(s_wsec), ==, "foobarblah");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_leap_secret_flags(gconstpointer test_data)
+{
+    NMConnection *             connection;
+    NMSettingWireless *        s_wifi;
+    NMSettingWirelessSecurity *s_wsec;
+    const char *               file;
+    gpointer                   expected_flags_p;
+
+    nmtst_test_data_unpack(test_data, &file, &expected_flags_p);
+
+    connection = _connection_from_file(file, NULL, TYPE_WIRELESS, NULL);
+
+    /* ===== WIRELESS SETTING ===== */
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+
+    g_assert(g_strcmp0(nm_setting_wireless_security_get_key_mgmt(s_wsec), "ieee8021x") == 0);
+    g_assert(g_strcmp0(nm_setting_wireless_security_get_auth_alg(s_wsec), "leap") == 0);
+    g_assert(g_strcmp0(nm_setting_wireless_security_get_leap_username(s_wsec), "Bill Smith") == 0);
+    /* password blank as it's not system-owned */
+    g_assert(nm_setting_wireless_security_get_leap_password_flags(s_wsec)
+             == GPOINTER_TO_INT(expected_flags_p));
+    g_assert(nm_setting_wireless_security_get_leap_password(s_wsec) == NULL);
+
+    g_object_unref(connection);
+}
+
+static void
+test_ifcfg_no_trailing_newline(void)
+{
+    shvarFile *sv;
+
+    sv = _svOpenFile(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk");
+    _svGetValue_check(sv, "CTCPROT", "no-newline");
+    svCloseFile(sv);
+}
+
+static void
+test_read_wifi_wpa_psk(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSettingIPConfig *        s_ip4;
+    GBytes *                   ssid;
+    const char *               mac;
+    char        expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+    const char *expected_ssid                  = "blahblah";
+    guint32     n, i;
+    gboolean    found_pair_tkip    = FALSE;
+    gboolean    found_pair_ccmp    = FALSE;
+    gboolean    found_group_tkip   = FALSE;
+    gboolean    found_group_ccmp   = FALSE;
+    gboolean    found_group_wep40  = FALSE;
+    gboolean    found_group_wep104 = FALSE;
+    gboolean    found_proto_wpa    = FALSE;
+    gboolean    found_proto_rsn    = FALSE;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk", NULL, TYPE_WIRELESS, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-wpa-psk)");
+
+    /* UUID can't be tested if the ifcfg does not contain the UUID key, because
+     * the UUID is generated on the full path of the ifcfg file, which can change
+     * depending on where the tests are run.
+     */
+
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    mac = nm_setting_wireless_get_mac_address(s_wireless);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
+                    ==,
+                    "I wonder what the king is doing tonight?");
+    g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec));
+
+    /* Pairwise ciphers */
+    n = nm_setting_wireless_security_get_num_pairwise(s_wsec);
+    g_assert_cmpint(n, ==, 2);
+    for (i = 0; i < n; i++) {
+        const char *tmp = nm_setting_wireless_security_get_pairwise(s_wsec, i);
+        g_assert(tmp);
+        if (strcmp(tmp, "tkip") == 0)
+            found_pair_tkip = TRUE;
+        else if (strcmp(tmp, "ccmp") == 0)
+            found_pair_ccmp = TRUE;
+    }
+    g_assert(found_pair_tkip);
+    g_assert(found_pair_ccmp);
+
+    /* Group ciphers */
+    n = nm_setting_wireless_security_get_num_groups(s_wsec);
+    g_assert_cmpint(n, ==, 4);
+    for (i = 0; i < n; i++) {
+        const char *tmp = nm_setting_wireless_security_get_group(s_wsec, i);
+        g_assert(tmp);
+        if (strcmp(tmp, "tkip") == 0)
+            found_group_tkip = TRUE;
+        else if (strcmp(tmp, "ccmp") == 0)
+            found_group_ccmp = TRUE;
+        else if (strcmp(tmp, "wep40") == 0)
+            found_group_wep40 = TRUE;
+        else if (strcmp(tmp, "wep104") == 0)
+            found_group_wep104 = TRUE;
+    }
+    g_assert(found_group_tkip);
+    g_assert(found_group_ccmp);
+    g_assert(found_group_wep40);
+    g_assert(found_group_wep104);
+
+    /* Protocols */
+    n = nm_setting_wireless_security_get_num_protos(s_wsec);
+    g_assert_cmpint(n, ==, 2);
+    for (i = 0; i < n; i++) {
+        const char *tmp = nm_setting_wireless_security_get_proto(s_wsec, i);
+        g_assert(tmp);
+        if (strcmp(tmp, "wpa") == 0)
+            found_proto_wpa = TRUE;
+        else if (strcmp(tmp, "rsn") == 0)
+            found_proto_rsn = TRUE;
+    }
+    g_assert(found_proto_wpa);
+    g_assert(found_proto_rsn);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_object_unref(connection);
+}
+
+static void
+test_read_wifi_sae(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingWirelessSecurity *   s_wsec;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "blahblah";
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-sae", NULL, TYPE_WIRELESS, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-sae)");
+
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "sae");
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "The king is dead.");
+    g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec));
+}
+
+static void
+test_read_wifi_owe(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingWirelessSecurity *   s_wsec;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "blahblah_owe";
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-owe", NULL, TYPE_WIRELESS, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah_owe (test-wifi-owe)");
+
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
+                    g_bytes_get_size(ssid),
+                    expected_ssid,
+                    strlen(expected_ssid));
+
+    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "owe");
+    g_assert(!nm_setting_wireless_security_get_psk(s_wsec));
+    g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec));
+}
+
+static void
+test_read_wifi_wpa_psk_2(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-2",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System ipsum (test-wifi-wpa-psk-2)");
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
+                    ==,
+                    "They're really saying I love you. >>`<< '");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wpa_psk_unquoted(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-unquoted",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System blahblah (test-wifi-wpa-psk-unquoted)");
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "54336845e2f3f321c4c7");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wpa_psk_unquoted2(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-unquoted2",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+}
+
+static void
+test_read_wifi_wpa_psk_adhoc(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSettingIPConfig *        s_ip4;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-adhoc",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System blahblah (test-wifi-wpa-psk-adhoc)");
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "adhoc");
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
+                    ==,
+                    "I wonder what the king is doing tonight?");
+
+    g_assert_cmpint(nm_setting_wireless_security_get_num_pairwise(s_wsec), ==, 1);
+    g_assert_cmpstr(nm_setting_wireless_security_get_pairwise(s_wsec, 0), ==, "ccmp");
+
+    g_assert_cmpint(nm_setting_wireless_security_get_num_groups(s_wsec), ==, 1);
+    g_assert_cmpstr(nm_setting_wireless_security_get_group(s_wsec, 0), ==, "ccmp");
+
+    g_assert_cmpint(nm_setting_wireless_security_get_num_protos(s_wsec), ==, 1);
+    g_assert_cmpstr(nm_setting_wireless_security_get_proto(s_wsec, 0), ==, "rsn");
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_object_unref(connection);
+}
+
+static void
+test_read_wifi_wpa_psk_hex(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSettingIPConfig *        s_ip4;
+    GBytes *                   ssid;
+    const char *               expected_ssid = "blahblah";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-hex",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System blahblah (test-wifi-wpa-psk-hex)");
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
+                    ==,
+                    "1da190379817bc360dda52e85c388c439a21ea5c7bf819c64e9da051807deae6");
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_object_unref(connection);
+}
+
+#define TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT     TEST_IFCFG_DIR "/test_ca_cert.pem"
+#define TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT TEST_IFCFG_DIR "/test1_key_and_cert.pem"
+#define TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY TEST_IFCFG_DIR "/test1_key_and_cert.pem"
+
+static void
+test_read_wifi_wpa_eap_tls(void)
+{
+    NMConnection *     connection;
+    NMSettingWireless *s_wireless;
+    NMSettingIPConfig *s_ip4;
+    NMSetting8021x *   s_8021x;
+    char *             unmanaged                 = NULL;
+    const char *       expected_privkey_password = "test1";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-tls",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "tls");
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Bill Smith");
+
+    /* CA Cert */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT,
+                       NULL,
+                       NM_SETTING_802_1X_CA_CERT);
+
+    /* Client Cert */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT,
+                       NULL,
+                       NM_SETTING_802_1X_CLIENT_CERT);
+
+    /* Private Key Password */
+    g_assert_cmpstr(nm_setting_802_1x_get_private_key_password(s_8021x),
+                    ==,
+                    expected_privkey_password);
+
+    /* Private key */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY,
+                       expected_privkey_password,
+                       NM_SETTING_802_1X_PRIVATE_KEY);
+
+    g_object_unref(connection);
+}
+
+/* Also use TLS defines from the previous test */
+
+static void
+test_read_wifi_wpa_eap_ttls_tls(void)
+{
+    NMConnection *     connection;
+    NMSettingWireless *s_wireless;
+    NMSettingIPConfig *s_ip4;
+    NMSetting8021x *   s_8021x;
+    char *             unmanaged                 = NULL;
+    const char *       expected_privkey_password = "test1";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-ttls-tls",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "ttls");
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Chuck Shumer");
+
+    /* CA Cert */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_DIR "/test_ca_cert.pem",
+                       NULL,
+                       NM_SETTING_802_1X_CA_CERT);
+
+    /* Inner auth method */
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_autheap(s_8021x), ==, "tls");
+
+    /* Inner CA Cert */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT,
+                       NULL,
+                       NM_SETTING_802_1X_PHASE2_CA_CERT);
+
+    /* Inner Client Cert */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT,
+                       NULL,
+                       NM_SETTING_802_1X_PHASE2_CLIENT_CERT);
+
+    /* Inner Private Key Password */
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_private_key_password(s_8021x),
+                    ==,
+                    expected_privkey_password);
+
+    /* Inner private key */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY,
+                       expected_privkey_password,
+                       NM_SETTING_802_1X_PHASE2_PRIVATE_KEY);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wpa_eap_suite_b_192_tls(void)
+{
+    NMConnection *             connection;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wireless_sec;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-suite-b-192-tls",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wireless_sec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wireless_sec),
+                    ==,
+                    "wpa-eap-suite-b-192");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_dynamic_wep_leap(void)
+{
+    NMConnection *             connection;
+    NMSettingWireless *        s_wifi;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSetting8021x *           s_8021x;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-dynamic-wep-leap",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    /* ===== Wi-Fi SECURITY SETTING ===== */
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+
+    /* Key management */
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x");
+
+    /* Auth alg should be NULL (open) for dynamic WEP with LEAP as the EAP method;
+     * only "old-school" LEAP uses 'leap' for the auth alg.
+     */
+    g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, NULL);
+
+    /* Expect no old-school LEAP username/password, that'll be in the 802.1x setting */
+    g_assert_cmpstr(nm_setting_wireless_security_get_leap_username(s_wsec), ==, NULL);
+    g_assert_cmpstr(nm_setting_wireless_security_get_leap_password(s_wsec), ==, NULL);
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    /* EAP method should be "leap" */
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "leap");
+
+    /* username & password */
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "bill smith");
+    g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep_eap_ttls_chap(void)
+{
+    NMConnection *             connection;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSettingIPConfig *        s_ip4;
+    NMSetting8021x *           s_8021x;
+    char *                     unmanaged = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-eap-ttls-chap",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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);
+
+    /* ===== 802.1x SETTING ===== */
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x");
+
+    /* ===== 802.1x SETTING ===== */
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    /* EAP methods */
+    g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
+    g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "ttls");
+
+    /* CA Cert */
+    verify_cert_or_key(s_8021x,
+                       TEST_IFCFG_DIR "/test_ca_cert.pem",
+                       NULL,
+                       NM_SETTING_802_1X_CA_CERT);
+
+    g_assert_cmpstr(nm_setting_802_1x_get_phase2_auth(s_8021x), ==, "chap");
+    g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith");
+    g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_wake_on_lan(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-wake-on-lan",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_wake_on_lan(s_wired),
+                    ==,
+                    NM_SETTING_WIRED_WAKE_ON_LAN_ARP | NM_SETTING_WIRED_WAKE_ON_LAN_PHY
+                        | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC);
+
+    g_assert_cmpstr(nm_setting_wired_get_wake_on_lan_password(s_wired), ==, "00:11:22:33:44:55");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_auto_negotiate_off(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-wake-on-lan",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    g_assert(!nm_setting_wired_get_auto_negotiate(s_wired));
+    g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 100);
+    g_assert_cmpstr(nm_setting_wired_get_duplex(s_wired), ==, "full");
+}
+
+static void
+test_read_wired_auto_negotiate_on(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-auto-negotiate-on",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    g_assert(nm_setting_wired_get_auto_negotiate(s_wired));
+    g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 0);
+    g_assert_cmpstr(nm_setting_wired_get_duplex(s_wired), ==, NULL);
+}
+
+static void
+test_read_wired_unknown_ethtool_opt(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-unknown-ethtool-opt",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    g_assert(!nm_setting_wired_get_auto_negotiate(s_wired));
+    g_assert(!nm_setting_wired_get_speed(s_wired));
+    g_assert(!nm_setting_wired_get_duplex(s_wired));
+
+    g_assert_cmpint(nm_setting_wired_get_wake_on_lan(s_wired),
+                    ==,
+                    NM_SETTING_WIRED_WAKE_ON_LAN_ARP | NM_SETTING_WIRED_WAKE_ON_LAN_PHY
+                        | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC);
+    g_assert_cmpstr(nm_setting_wired_get_wake_on_lan_password(s_wired), ==, "00:11:22:33:44:55");
+}
+
+static void
+test_read_wifi_hidden(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWireless *  s_wifi;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-hidden", NULL, TYPE_WIRELESS, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRELESS_SETTING_NAME);
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+    g_assert(nm_setting_wireless_get_hidden(s_wifi) == TRUE);
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_wifi_hidden(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    shvarFile *                   f;
+    GBytes *                      ssid;
+    const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wi-Fi Hidden",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
+
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
+
+    g_bytes_unref(ssid);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_Hidden.cexpected",
+                           &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "SSID_HIDDEN", "yes");
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_wifi_mac_random(gconstpointer user_data)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    gs_free char *                path       = NULL;
+    NMSettingWireless *           s_wifi;
+    const char *                  name;
+    gpointer                      value_p;
+    NMSettingMacRandomization     value;
+
+    nmtst_test_data_unpack(user_data, &name, &value_p);
+    value = GPOINTER_TO_INT(value_p);
+
+    path       = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-wifi-mac-random-%s", name);
+    connection = _connection_from_file(path, NULL, TYPE_WIRELESS, NULL);
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+    g_assert_cmpint(nm_setting_wireless_get_mac_address_randomization(s_wifi), ==, value);
+}
+
+static void
+test_write_wifi_mac_random(gconstpointer user_data)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    char *                        val;
+    shvarFile *                   f;
+    GBytes *                      ssid;
+    const unsigned char       ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+    const char *              name, *write_expected;
+    gpointer                  value_p;
+    NMSettingMacRandomization value;
+    char                      cexpected[NM_STRLEN(TEST_IFCFG_DIR) + 100];
+
+    nmtst_test_data_unpack(user_data, &name, &value_p, &write_expected);
+    value = GPOINTER_TO_INT(value_p);
+
+    g_assert(write_expected);
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    val = g_strdup_printf("Test Write Wi-Fi MAC %s", name);
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 val,
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+    g_free(val);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
+                 value,
+                 NULL);
+    g_bytes_unref(ssid);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(
+        connection,
+        TEST_SCRATCH_DIR,
+        nm_sprintf_buf(cexpected, TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_MAC_%s.cexpected", name),
+        &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "MAC_ADDRESS_RANDOMIZATION", write_expected);
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_wake_on_lan(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingWiredWakeOnLan       wol;
+    char *                        val;
+    shvarFile *                   f;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired Wake-on-LAN",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    wol = NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST | NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST
+          | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC;
+
+    g_object_set(s_wired,
+                 NM_SETTING_WIRED_WAKE_ON_LAN,
+                 wol,
+                 NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD,
+                 "00:00:00:11:22:33",
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected",
+                           &testfile);
+
+    f   = _svOpenFile(testfile);
+    val = svGetValueStr_cp(f, "ETHTOOL_OPTS");
+    g_assert(val);
+    g_assert(strstr(val, "wol"));
+    g_assert(strstr(val, "sopass 00:00:00:11:22:33"));
+    g_free(val);
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_auto_negotiate_off(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingWired *              s_wired;
+    char *                        val;
+    shvarFile *                   f;
+
+    connection = nmtst_create_minimal_connection("Test Write Wired Auto-Negotiate",
+                                                 NULL,
+                                                 NM_SETTING_WIRED_SETTING_NAME,
+                                                 NULL);
+    s_wired    = nm_connection_get_setting_wired(connection);
+    g_object_set(s_wired,
+                 NM_SETTING_WIRED_AUTO_NEGOTIATE,
+                 FALSE,
+                 NM_SETTING_WIRED_DUPLEX,
+                 "half",
+                 NM_SETTING_WIRED_SPEED,
+                 10,
+                 NULL);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected",
+                           &testfile);
+
+    f   = _svOpenFile(testfile);
+    val = svGetValueStr_cp(f, "ETHTOOL_OPTS");
+    g_assert(val);
+    g_assert(strstr(val, "autoneg off"));
+    g_assert(strstr(val, "speed 10"));
+    g_assert(strstr(val, "duplex half"));
+    g_free(val);
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_auto_negotiate_on(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingWired *              s_wired;
+    NMSettingEthtool *            s_ethtool;
+    char *                        val;
+    shvarFile *                   f;
+
+    connection = nmtst_create_minimal_connection("Test Write Wired Auto-Negotiate",
+                                                 NULL,
+                                                 NM_SETTING_WIRED_SETTING_NAME,
+                                                 NULL);
+    s_wired    = nm_connection_get_setting_wired(connection);
+    g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL);
+
+    s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new());
+    nm_setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX, NM_TERNARY_TRUE);
+    nm_setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN, NM_TERNARY_FALSE);
+    nm_connection_add_setting(connection, NM_SETTING(s_ethtool));
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-test_write_wired_auto_negotiate_on.cexpected",
+                           &testfile);
+
+    f   = _svOpenFile(testfile);
+    val = svGetValueStr_cp(f, "ETHTOOL_OPTS");
+    g_assert(val);
+    g_assert(strstr(val, "autoneg on"));
+    g_assert(!strstr(val, "speed"));
+    g_assert(!strstr(val, "duplex"));
+    g_free(val);
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_verifies_without_normalization(reread);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    s_ethtool = NM_SETTING_ETHTOOL(nm_connection_get_setting(reread, NM_TYPE_SETTING_ETHTOOL));
+    g_assert(s_ethtool);
+    g_assert_cmpint(nm_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX),
+                    ==,
+                    NM_TERNARY_TRUE);
+    g_assert_cmpint(nm_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN),
+                    ==,
+                    NM_TERNARY_FALSE);
+    g_assert_cmpint(nm_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TXVLAN),
+                    ==,
+                    NM_TERNARY_DEFAULT);
+}
+
+static void
+test_read_wifi_band_a(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWireless *  s_wifi;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-a", NULL, TYPE_WIRELESS, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRELESS_SETTING_NAME);
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+    g_assert_cmpstr(nm_setting_wireless_get_band(s_wifi), ==, "a");
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_wifi_band_a(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    shvarFile *                   f;
+    GBytes *                      ssid;
+    const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wi-Fi Band A",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NM_SETTING_WIRELESS_BAND,
+                 "a",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_Band_A.cexpected",
+                           &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "BAND", "a");
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_ap_mode(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    gs_unref_bytes GBytes *ssid = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wi-Fi AP Mode",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new("MySSID", NM_STRLEN("MySSID"));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "ap",
+                 NM_SETTING_WIRELESS_BAND,
+                 "a",
+                 NM_SETTING_WIRELESS_CHANNEL,
+                 (guint) 196,
+                 NM_SETTING_WIRELESS_AP_ISOLATION,
+                 NM_TERNARY_TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_AP_Mode.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_wifi_band_a_channel_mismatch(void)
+{
+    gs_free_error GError *error = NULL;
+
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-a-channel-mismatch",
+                               NULL,
+                               TYPE_WIRELESS,
+                               &error);
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+}
+
+static void
+test_read_wifi_band_bg_channel_mismatch(void)
+{
+    gs_free_error GError *error = NULL;
+
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-bg-channel-mismatch",
+                               NULL,
+                               TYPE_WIRELESS,
+                               &error);
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+}
+
+static void
+test_read_wired_qeth_static(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    char *               unmanaged = NULL;
+    const char *const *  subchannels;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-qeth-static",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-qeth-static");
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    g_assert(!nm_setting_wired_get_mac_address(s_wired));
+
+    /* Subchannels */
+    subchannels = nm_setting_wired_get_s390_subchannels(s_wired);
+    g_assert(subchannels);
+    g_assert(subchannels[0] && subchannels[1] && subchannels[2] && !subchannels[3]);
+
+    g_assert_cmpstr(subchannels[0], ==, "0.0.0600");
+    g_assert_cmpstr(subchannels[1], ==, "0.0.0601");
+    g_assert_cmpstr(subchannels[2], ==, "0.0.0602");
+
+    g_assert_cmpstr(nm_setting_wired_get_s390_nettype(s_wired), ==, "qeth");
+    g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "portname"), ==, "OSAPORT");
+    g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "portno"), ==, "0");
+    g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "layer2"), ==, "1");
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_MANUAL);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wired_ctc_static(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    char *               unmanaged = NULL;
+    const char *const *  subchannels;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-ctc-static",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       &unmanaged);
+    g_assert(unmanaged == NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con != NULL);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-ctc-static");
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    g_assert(nm_setting_wired_get_mac_address(s_wired) == NULL);
+
+    /* Subchannels */
+    subchannels = nm_setting_wired_get_s390_subchannels(s_wired);
+    g_assert(subchannels != NULL);
+    g_assert(subchannels[0] && subchannels[1] && !subchannels[2]);
+
+    g_assert_cmpstr(subchannels[0], ==, "0.0.1b00");
+    g_assert_cmpstr(subchannels[1], ==, "0.0.1b01");
+
+    g_assert_cmpstr(nm_setting_wired_get_s390_nettype(s_wired), ==, "ctc");
+    g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "ctcprot"), ==, "0");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep_no_keys(void)
+{
+    NMConnection *             connection;
+    NMSettingConnection *      s_con;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NMWepKeyType               key_type;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-no-keys",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con),
+                    ==,
+                    "System foobar (test-wifi-wep-no-keys)");
+
+    /* UUID can't be tested if the ifcfg does not contain the UUID key, because
+     * the UUID is generated on the full path of the ifcfg file, which can change
+     * depending on where the tests are run.
+     */
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+
+    /* Key management */
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
+
+    /* WEP key index */
+    g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
+
+    /* WEP key type */
+    key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec);
+    g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY);
+
+    /* WEP key index 0; we don't expect it to be filled */
+    g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 0));
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_permissions(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    gboolean             success;
+    guint32              num;
+    const char *         tmp;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-permissions", NULL, TYPE_ETHERNET, NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    num = nm_setting_connection_get_num_permissions(s_con);
+    g_assert_cmpint(num, ==, 3);
+
+    /* verify each permission */
+    tmp     = NULL;
+    success = nm_setting_connection_get_permission(s_con, 0, NULL, &tmp, NULL);
+    g_assert(success);
+    g_assert_cmpstr(tmp, ==, "dcbw");
+
+    tmp     = NULL;
+    success = nm_setting_connection_get_permission(s_con, 1, NULL, &tmp, NULL);
+    g_assert(success);
+    g_assert_cmpstr(tmp, ==, "ssmith");
+
+    tmp     = NULL;
+    success = nm_setting_connection_get_permission(s_con, 2, NULL, &tmp, NULL);
+    g_assert(success);
+    g_assert_cmpstr(tmp, ==, "johnny5");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_wifi_wep_agent_keys(void)
+{
+    NMConnection *             connection;
+    NMSettingWireless *        s_wifi;
+    NMSettingWirelessSecurity *s_wsec;
+    NMWepKeyType               key_type;
+    NMSettingSecretFlags       flags;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-agent-keys",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* Ensure the connection is still marked for wifi security even though
+     * we don't have any WEP keys because they are agent owned.
+     */
+
+    /* ===== WIRELESS SETTING ===== */
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+
+    g_assert(strcmp(nm_setting_wireless_security_get_key_mgmt(s_wsec), "none") == 0);
+    g_assert(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) == 0);
+
+    key_type = nm_setting_wireless_security_get_wep_key_type(s_wsec);
+    g_assert(key_type == NM_WEP_KEY_TYPE_UNKNOWN || key_type == NM_WEP_KEY_TYPE_KEY);
+
+    /* We don't expect WEP key0 to be filled */
+    g_assert(nm_setting_wireless_security_get_wep_key(s_wsec, 0) == NULL);
+
+    flags = nm_setting_wireless_security_get_wep_key_flags(s_wsec);
+    g_assert(flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_wired_static(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *route6file   = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4, *reread_s_ip4;
+    NMSettingIPConfig *           s_ip6, *reread_s_ip6;
+    NMIPAddress *                 addr;
+    NMIPAddress *                 addr6;
+    NMIPRoute *                   route6;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired Static",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES,
+                 1,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired,
+                 NM_SETTING_WIRED_MAC_ADDRESS,
+                 "31:33:33:37:be:cd",
+                 NM_SETTING_WIRED_MTU,
+                 (guint32) 1492,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 (gint64) 204,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.5", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1");
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.2");
+
+    nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com");
+    nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com");
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 (gint64) 206,
+                 NULL);
+
+    /* Add addresses */
+    addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    addr6 = nm_ip_address_new(AF_INET6, "2003:1234:abcd::2", 22, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    addr6 = nm_ip_address_new(AF_INET6, "3003:1234:abcd::3", 33, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    /* Add routes */
+    route6 = nm_ip_route_new(AF_INET6,
+                             "2222:aaaa:bbbb:cccc::",
+                             64,
+                             "2222:aaaa:bbbb:cccc:dddd:eeee:5555:6666",
+                             99,
+                             &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_route(s_ip6, route6);
+    nm_ip_route_unref(route6);
+
+    route6 = nm_ip_route_new(AF_INET6, "::", 128, "2222:aaaa::9999", 1, &error);
+    g_assert_no_error(error);
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32(100));
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(1280));
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, g_variant_new_boolean(TRUE));
+    nm_ip_route_set_attribute(route6,
+                              NM_IP_ROUTE_ATTRIBUTE_FROM,
+                              g_variant_new_string("2222::bbbb/32"));
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("::42"));
+    nm_setting_ip_config_add_route(s_ip6, route6);
+    nm_ip_route_unref(route6);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face");
+    nm_setting_ip_config_add_dns(s_ip6, "cafe:ffff:eeee:dddd:cccc:bbbb:aaaa:feed");
+
+    /* DNS domains */
+    nm_setting_ip_config_add_dns_search(s_ip6, "foobar6.com");
+    nm_setting_ip_config_add_dns_search(s_ip6, "lab6.foobar.com");
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+    route6file = utils_get_route6_path(testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    reread_s_ip4 = nm_connection_get_setting_ip4_config(reread);
+    reread_s_ip6 = nm_connection_get_setting_ip6_config(reread);
+
+    g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip4), ==, 204);
+    g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip6), ==, 206);
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    nmtst_assert_connection_equals(connection, FALSE, reread, FALSE);
+}
+
+static void
+test_write_wired_static_with_generic(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *route6file   = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4, *reread_s_ip4;
+    NMSettingIPConfig *           s_ip6, *reread_s_ip6;
+    NMIPAddress *                 addr;
+    NMIPAddress *                 addr6;
+    NMIPRoute *                   route6;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired Static",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES,
+                 1,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired,
+                 NM_SETTING_WIRED_MAC_ADDRESS,
+                 "31:33:33:37:be:cd",
+                 NM_SETTING_WIRED_MTU,
+                 (guint32) 1492,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 (gint64) 204,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.5", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1");
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.2");
+
+    nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com");
+    nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com");
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_ROUTE_METRIC,
+                 (gint64) 206,
+                 NULL);
+
+    /* Add addresses */
+    addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    addr6 = nm_ip_address_new(AF_INET6, "2003:1234:abcd::2", 22, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    addr6 = nm_ip_address_new(AF_INET6, "3003:1234:abcd::3", 33, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    /* Add routes */
+    route6 = nm_ip_route_new(AF_INET6,
+                             "2222:aaaa:bbbb:cccc::",
+                             64,
+                             "2222:aaaa:bbbb:cccc:dddd:eeee:5555:6666",
+                             99,
+                             &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_route(s_ip6, route6);
+    nm_ip_route_unref(route6);
+
+    route6 = nm_ip_route_new(AF_INET6, "::", 128, "2222:aaaa::9999", 1, &error);
+    g_assert_no_error(error);
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32(100));
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(1280));
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, g_variant_new_boolean(TRUE));
+    nm_ip_route_set_attribute(route6,
+                              NM_IP_ROUTE_ATTRIBUTE_FROM,
+                              g_variant_new_string("2222::bbbb/32"));
+    nm_ip_route_set_attribute(route6, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("::42"));
+    nm_setting_ip_config_add_route(s_ip6, route6);
+    nm_ip_route_unref(route6);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face");
+    nm_setting_ip_config_add_dns(s_ip6, "cafe:ffff:eeee:dddd:cccc:bbbb:aaaa:feed");
+
+    /* DNS domains */
+    nm_setting_ip_config_add_dns_search(s_ip6, "foobar6.com");
+    nm_setting_ip_config_add_dns_search(s_ip6, "lab6.foobar.com");
+
+    nm_connection_add_setting(connection, nm_setting_generic_new());
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile);
+    route6file = utils_get_route6_path(testfile);
+
+    reread       = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+    reread_s_ip4 = nm_connection_get_setting_ip4_config(reread);
+    reread_s_ip6 = nm_connection_get_setting_ip6_config(reread);
+
+    g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip4), ==, 204);
+    g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip6), ==, 206);
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    {
+        gs_unref_hashtable GHashTable *diffs = NULL;
+
+        g_assert(!nm_connection_diff(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT, &diffs));
+        g_assert(diffs);
+        g_assert(g_hash_table_size(diffs) == 1);
+        g_assert(g_hash_table_lookup(diffs, "generic"));
+        g_assert(!nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+    }
+    g_assert(!nm_connection_get_setting(reread, NM_TYPE_SETTING_GENERIC));
+    nm_connection_add_setting(reread, nm_setting_generic_new());
+    {
+        gs_unref_hashtable GHashTable *diffs = NULL;
+
+        g_assert(nm_connection_diff(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT, &diffs));
+        g_assert(!diffs);
+        g_assert(nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+    }
+}
+
+static void
+test_write_wired_dhcp(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired DHCP",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+                 NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID,
+                 "random-client-id-00:22:33",
+                 NM_SETTING_IP4_CONFIG_DHCP_FQDN,
+                 "awesome.hostname",
+                 NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS,
+                 (guint) NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED,
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_DHCP_IAID,
+                 "2864434397",
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_DHCP,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_DHCP_HOSTNAME,
+                 "awesome.hostname",
+                 NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS,
+                 (guint) NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE,
+                 NULL);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static NMIPRoutingRule *
+_ip_routing_rule_new(int addr_family, const char *str)
+{
+    NMIPRoutingRuleAsStringFlags flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE;
+    gs_free_error GError *local        = NULL;
+    NMIPRoutingRule *     rule;
+
+    if (addr_family != AF_UNSPEC) {
+        if (addr_family == AF_INET)
+            flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET;
+        else {
+            g_assert(addr_family == AF_INET6);
+            flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6;
+        }
+    }
+
+    rule = nm_ip_routing_rule_from_string(str,
+                                          NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE | flags,
+                                          NULL,
+                                          nmtst_get_rand_bool() ? &local : NULL);
+    nmtst_assert_success(rule, local);
+
+    if (addr_family != AF_UNSPEC)
+        g_assert_cmpint(nm_ip_routing_rule_get_addr_family(rule), ==, addr_family);
+    return rule;
+}
+
+static void
+_ip_routing_rule_add_to_setting(NMSettingIPConfig *s_ip, const char *str)
+{
+    nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+
+    rule = _ip_routing_rule_new(nm_setting_ip_config_get_addr_family(s_ip), str);
+    nm_setting_ip_config_add_routing_rule(s_ip, rule);
+}
+
+static void
+test_write_routing_rules(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Routing Rules",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    _ip_routing_rule_add_to_setting(s_ip4, "pref 10 from 0.0.0.0/0 table 1");
+    _ip_routing_rule_add_to_setting(s_ip4, "priority 10 to 192.167.8.0/24 table 2");
+    _ip_routing_rule_add_to_setting(s_ip6, "pref 10 from ::/0 table 10");
+    _ip_routing_rule_add_to_setting(s_ip6, "pref 10 from ::/0 to 1:2:3::5/24 table 22");
+    _ip_routing_rule_add_to_setting(s_ip6, "pref 10 from ::/0 to 1:3:3::5/128 table 55");
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Routing_Rules.cexpected",
+                           &testfile);
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_match(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingMatch *              s_match;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired with Match setting",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+
+    /* Match setting */
+    s_match = (NMSettingMatch *) nm_setting_match_new();
+    nm_setting_match_add_interface_name(s_match, "ens*");
+    nm_setting_match_add_interface_name(s_match, "eth 1?");
+    nm_setting_match_add_interface_name(s_match, "!veth*");
+    nm_setting_match_add_driver(s_match, "!virtio");
+    nm_setting_match_add_driver(s_match, "e1000e");
+    nm_setting_match_add_kernel_command_line(s_match, "!ip=");
+    nm_connection_add_setting(connection, NM_SETTING(s_match));
+
+    nmtst_assert_connection_verifies(connection);
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_match.cexpected",
+                           &testfile);
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_dhcp_plus_ip(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp-plus-ip",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_write_wired_dhcp_send_hostname(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  dhcp_hostname = "kamil-patka";
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp-send-hostname",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* Check dhcp-hostname and dhcp-send-hostname */
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip4);
+    g_assert(s_ip6);
+    g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) == TRUE);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "svata-pulec");
+    g_assert(!nm_setting_ip_config_get_dhcp_hostname(s_ip6));
+
+    /* Set dhcp-send-hostname=false dhcp-hostname="kamil-patka" and write the connection. */
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, FALSE, NULL);
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, dhcp_hostname, NULL);
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, dhcp_hostname, NULL);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    /* Check dhcp-hostname and dhcp-send-hostname from the re-read connection. */
+    s_ip4 = nm_connection_get_setting_ip4_config(reread);
+    s_ip6 = nm_connection_get_setting_ip6_config(reread);
+    g_assert(s_ip4);
+    g_assert(s_ip6);
+    g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) == FALSE);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, dhcp_hostname);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, dhcp_hostname);
+}
+
+static void
+test_read_wired_dhcpv6_hostname_fallback(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcpv6-hostname-fallback",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip6) == TRUE);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "fully.qualified.domain");
+}
+
+static void
+test_write_wired_static_ip6_only(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPAddress *                 addr6;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired Static IP6 Only",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, "31:33:33:37:be:cd", NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL);
+
+    /* Add addresses */
+    addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    /* DNS server */
+    nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face");
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_ip6_disabled(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nmtst_create_minimal_connection("Test Write Wired Disabled IP6",
+                                                 NULL,
+                                                 NM_SETTING_WIRED_SETTING_NAME,
+                                                 &s_con);
+
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED, NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR_TMP,
+                           TEST_IFCFG_DIR "/ifcfg-test-ip6-disabled.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+/* Test writing an IPv6 config with varying gateway address.
+ * For missing gateway (::), we expect no IPV6_DEFAULTGW to be written
+ * to ifcfg-rh.
+ *
+ * As user_data pass the IPv6 address of the gateway as string. NULL means
+ * not to explicitly set the gateway in the configuration before writing it.
+ * That way, the gateway actually defaults to "::".
+ */
+static void
+test_write_wired_static_ip6_only_gw(gconstpointer user_data)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPAddress *                 addr6;
+    GError *                      error                 = NULL;
+    char *                        id                    = NULL;
+    gs_free char *                written_ifcfg_gateway = NULL;
+    const char *                  gateway6              = user_data;
+    shvarFile *                   ifcfg;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    id = g_strdup_printf("Test Write Wired Static IP6 Only With Gateway %s", gateway6 ?: "NULL");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 id,
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+    g_free(id);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, "31:33:33:37:be:cd", NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 gateway6,
+                 NULL);
+
+    /* Add addresses */
+    addr6 = nm_ip_address_new(AF_INET6, "1003:1234:abcd::1", 11, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip6, addr6);
+    nm_ip_address_unref(addr6);
+
+    /* DNS server */
+    nm_setting_ip_config_add_dns(s_ip6, "fade:0102:0103::face");
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    ifcfg                 = _svOpenFile(testfile);
+    written_ifcfg_gateway = svGetValueStr_cp(ifcfg, "IPV6_DEFAULTGW");
+    svCloseFile(ifcfg);
+
+    /* access the gateway from the loaded connection. */
+    s_ip6 = nm_connection_get_setting_ip6_config(reread);
+    g_assert(s_ip6 && nm_setting_ip_config_get_num_addresses(s_ip6) == 1);
+    addr6 = nm_setting_ip_config_get_address(s_ip6, 0);
+    g_assert(addr6);
+
+    /* assert that the gateway was written and reloaded as expected */
+    if (!gateway6 || !strcmp(gateway6, "::")) {
+        g_assert(nm_setting_ip_config_get_gateway(s_ip6) == NULL);
+        g_assert(written_ifcfg_gateway == NULL);
+    } else {
+        g_assert(nm_setting_ip_config_get_gateway(s_ip6) != NULL);
+        g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, gateway6);
+        g_assert_cmpstr(written_ifcfg_gateway, ==, gateway6);
+    }
+}
+
+static void
+test_read_write_static_routes_legacy(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *routefile    = NULL;
+    gs_free char *              route6file   = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    const char *                  tmp;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-static-routes-legacy",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== CONNECTION SETTING ===== */
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    /* ID */
+    tmp = nm_setting_connection_get_id(s_con);
+    g_assert(tmp);
+
+    /* Autoconnect */
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    /* ===== WIRED SETTING ===== */
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+
+    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_never_default(s_ip4));
+
+    /* Save the ifcfg; use a special different scratch dir to ensure that
+     * we can clean up after the written connection in both the original
+     * source tree and for 'make distcheck'.
+     */
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR_TMP,
+                           TEST_IFCFG_DIR "/ifcfg-test-static-routes-legacy.cexpected",
+                           &testfile);
+
+    reread     = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+    routefile  = utils_get_route_path(testfile);
+    route6file = utils_get_route6_path(testfile);
+    g_assert(!g_file_test(route6file, G_FILE_TEST_EXISTS));
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_static_routes(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *routefile    = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPAddress *                 addr;
+    NMIPRoute *                   route;
+    GError *                      error       = NULL;
+    gboolean                      reread_same = FALSE;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired Static Routes",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired,
+                 NM_SETTING_WIRED_MAC_ADDRESS,
+                 "31:33:33:37:be:cd",
+                 NM_SETTING_WIRED_MTU,
+                 (guint32) 1492,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_DAD_TIMEOUT,
+                 400,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.5", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    /* Write out routes */
+    route = nm_ip_route_new(AF_INET, "1.2.3.0", 24, "222.173.190.239", 0, &error);
+    nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, g_variant_new_uint32(3455));
+    nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(TRUE));
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_route(s_ip4, route);
+    nm_ip_route_unref(route);
+
+    route = nm_ip_route_new(AF_INET, "3.2.1.0", 24, "202.254.186.190", 77, &error);
+    nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, g_variant_new_uint32(30000));
+    nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(FALSE));
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_route(s_ip4, route);
+    nm_ip_route_unref(route);
+
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1");
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.2");
+
+    nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com");
+    nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com");
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_reread(connection,
+                                  TEST_SCRATCH_DIR,
+                                  &testfile,
+                                  TEST_IFCFG_DIR "/ifcfg-Test_Write_Wired_Static_Routes.cexpected",
+                                  &reread,
+                                  &reread_same);
+    /* ifcfg does not support setting onlink=0. It gets lost during write+re-read.
+     * Assert that it's missing, and patch it to check whether the rest of the
+     * connection equals. */
+    g_assert(!reread_same);
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    s_ip4 = nm_connection_get_setting_ip4_config(reread);
+    g_assert(s_ip4);
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 2);
+    route = nm_setting_ip_config_get_route(s_ip4, 1);
+    g_assert(route);
+    g_assert(!nm_ip_route_get_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK));
+    nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(FALSE));
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    routefile = utils_get_route_path(testfile);
+}
+
+static void
+test_write_wired_dhcp_8021x_peap_mschapv2(void)
+{
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMSetting8021x *              s_8021x;
+    gboolean                      success;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired DHCP 802.1x PEAP MSCHAPv2",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    /* 802.1x setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_IDENTITY,
+                 "Bob Saget",
+                 NM_SETTING_802_1X_ANONYMOUS_IDENTITY,
+                 "barney",
+                 NM_SETTING_802_1X_PASSWORD,
+                 "Kids, it was back in October 2008...",
+                 NM_SETTING_802_1X_PHASE1_PEAPVER,
+                 "1",
+                 NM_SETTING_802_1X_PHASE1_PEAPLABEL,
+                 "1",
+                 NM_SETTING_802_1X_PHASE2_AUTH,
+                 "mschapv2",
+                 NULL);
+
+    nm_setting_802_1x_add_eap_method(s_8021x, "peap");
+
+    success = nm_setting_802_1x_set_ca_cert(s_8021x,
+                                            TEST_IFCFG_WIRED_8021x_PEAP_MSCHAPV2_CA_CERT,
+                                            NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                            NULL,
+                                            &error);
+    nmtst_assert_success(success, error);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    keyfile = utils_get_keys_path(testfile);
+}
+
+static void
+test_write_wired_8021x_tls(gconstpointer test_data)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_free char *              keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMSetting8021x *              s_8021x;
+    gboolean                      success;
+    GError *                      error  = NULL;
+    NMSetting8021xCKFormat        format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+    const char *                  pw;
+    char *                        tmp;
+    gpointer                      scheme_p, flags_p;
+    NMSetting8021xCKScheme        scheme;
+    NMSettingSecretFlags          flags;
+
+    nmtst_test_data_unpack(test_data, &scheme_p, &flags_p);
+    scheme = GPOINTER_TO_INT(scheme_p);
+    flags  = GPOINTER_TO_INT(flags_p);
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired 802.1x TLS Blobs",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    /* 802.1x setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL);
+    nm_setting_802_1x_add_eap_method(s_8021x, "tls");
+
+    /* CA cert */
+    success = nm_setting_802_1x_set_ca_cert(s_8021x,
+                                            TEST_IFCFG_DIR "/test_ca_cert.pem",
+                                            scheme,
+                                            &format,
+                                            &error);
+    g_assert_no_error(error);
+    g_assert(success);
+    g_assert(format == NM_SETTING_802_1X_CK_FORMAT_X509);
+
+    /* Client cert */
+    format  = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+    success = nm_setting_802_1x_set_client_cert(s_8021x,
+                                                TEST_IFCFG_DIR "/test1_key_and_cert.pem",
+                                                scheme,
+                                                &format,
+                                                &error);
+    g_assert_no_error(error);
+    g_assert(success);
+    g_assert(format == NM_SETTING_802_1X_CK_FORMAT_X509);
+
+    /* Private key */
+    format  = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+    success = nm_setting_802_1x_set_private_key(s_8021x,
+                                                TEST_IFCFG_DIR "/test1_key_and_cert.pem",
+                                                "test1",
+                                                scheme,
+                                                &format,
+                                                &error);
+    g_assert_no_error(error);
+    g_assert(success);
+    g_assert(format == NM_SETTING_802_1X_CK_FORMAT_RAW_KEY);
+
+    /* Set secret flags */
+    g_object_set(s_8021x, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS, flags, NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+    nmtst_file_unlink_if_exists(keyfile);
+
+    /* Ensure the reread connection's certificates and private key are paths; no
+     * matter what scheme was used in the original connection they will be read
+     * back in as paths.
+     */
+    s_8021x = nm_connection_get_setting_802_1x(reread);
+    g_assert(s_8021x);
+    g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert_cmpint(nm_setting_802_1x_get_client_cert_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert_cmpint(nm_setting_802_1x_get_private_key_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_PATH);
+
+    g_assert_cmpint(nm_setting_802_1x_get_private_key_password_flags(s_8021x), ==, flags);
+    pw = nm_setting_802_1x_get_private_key_password(s_8021x);
+    if (flags == NM_SETTING_SECRET_FLAG_NONE) {
+        /* Ensure the private key password is still set */
+        g_assert(pw != NULL);
+        g_assert_cmpstr(pw, ==, "test1");
+    } else {
+        /* If the secret isn't owned by system settings, make sure its no longer there */
+        g_assert(pw == NULL);
+    }
+
+    if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+        /* Do a direct compare if using the path scheme since then the
+         * certificate and key properties should be the same.  If using blob
+         * scheme the original connection cert/key properties will be blobs
+         * but the re-read connection is always path scheme, so we wouldn't
+         * expect it to compare successfully.
+         */
+        if (flags != NM_SETTING_SECRET_FLAG_NONE) {
+            /* Clear original connection's private key password because flags
+             * say it's not system-owned, and therefore it should not show up
+             * in the re-read connection.
+             */
+            s_8021x = nm_connection_get_setting_802_1x(connection);
+            g_object_set(s_8021x, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, NULL, NULL);
+        }
+
+        nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+    }
+
+    /* Clean up created certs and keys */
+    tmp = utils_cert_path(testfile, "ca-cert", "der");
+    nmtst_file_unlink_if_exists(tmp);
+    g_free(tmp);
+
+    tmp = utils_cert_path(testfile, "client-cert", "der");
+    nmtst_file_unlink_if_exists(tmp);
+    g_free(tmp);
+
+    tmp = utils_cert_path(testfile, "private-key", "pem");
+    nmtst_file_unlink_if_exists(tmp);
+    g_free(tmp);
+}
+
+static void
+test_write_wired_aliases(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    int                           num_addresses = 4;
+    const char *                  ip[]          = {"1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4"};
+    const char *                  label[]       = {NULL, "alias0:2", NULL, "alias0:3"};
+    NMIPAddress *                 addr;
+    GError *                      error = NULL;
+    shvarFile *                   ifcfg;
+    int                           i, j;
+
+    nmtst_file_unlink_if_exists(TEST_SCRATCH_ALIAS_BASE ":2");
+    nmtst_file_unlink_if_exists(TEST_SCRATCH_ALIAS_BASE ":3");
+    nmtst_file_unlink_if_exists(TEST_SCRATCH_ALIAS_BASE ":5");
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "alias0",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    for (i = 0; i < num_addresses; i++) {
+        addr = nm_ip_address_new(AF_INET, ip[i], 24, &error);
+        g_assert_no_error(error);
+        if (label[i])
+            nm_ip_address_set_attribute(addr,
+                                        NM_IP_ADDRESS_ATTRIBUTE_LABEL,
+                                        g_variant_new_string(label[i]));
+        nm_setting_ip_config_add_address(s_ip4, addr);
+        nm_ip_address_unref(addr);
+    }
+
+    nmtst_assert_connection_verifies(connection);
+
+    /* Create some pre-existing alias files, to make sure they get overwritten / deleted. */
+    ifcfg = svCreateFile(TEST_SCRATCH_ALIAS_BASE ":2");
+    svSetValueStr(ifcfg, "DEVICE", "alias0:2");
+    svSetValueStr(ifcfg, "IPADDR", "192.168.1.2");
+    svWriteFile(ifcfg, 0644, NULL);
+    svCloseFile(ifcfg);
+    g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":2", G_FILE_TEST_EXISTS));
+
+    ifcfg = svCreateFile(TEST_SCRATCH_ALIAS_BASE ":5");
+    svSetValueStr(ifcfg, "DEVICE", "alias0:5");
+    svSetValueStr(ifcfg, "IPADDR", "192.168.1.5");
+    svWriteFile(ifcfg, 0644, NULL);
+    svCloseFile(ifcfg);
+    g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":5", G_FILE_TEST_EXISTS));
+
+    _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile);
+
+    /* Re-check the alias files */
+    g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":2", G_FILE_TEST_EXISTS));
+    g_assert(g_file_test(TEST_SCRATCH_ALIAS_BASE ":3", G_FILE_TEST_EXISTS));
+    g_assert(!g_file_test(TEST_SCRATCH_ALIAS_BASE ":5", G_FILE_TEST_EXISTS));
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+    nmtst_file_unlink(TEST_SCRATCH_ALIAS_BASE ":2");
+    nmtst_file_unlink(TEST_SCRATCH_ALIAS_BASE ":3");
+
+    /* nm_connection_compare() is not guaranteed to succeed, because the
+     * aliases get read back in essentially random order. So just
+     * verify the aliases manually.
+     */
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(nm_setting_ip_config_get_num_addresses(s_ip4) == num_addresses);
+
+    /* Addresses */
+    for (i = 0; i < num_addresses; i++) {
+        const char *addrstr;
+
+        addr = nm_setting_ip_config_get_address(s_ip4, i);
+        g_assert(addr != NULL);
+
+        addrstr = nm_ip_address_get_address(addr);
+        for (j = 0; j < num_addresses; j++) {
+            if (!g_strcmp0(addrstr, ip[j]))
+                break;
+        }
+        if (j >= num_addresses)
+            g_assert_not_reached();
+        else {
+            g_assert_cmpint(nm_ip_address_get_prefix(addr), ==, 24);
+            if (label[j])
+                g_assert_cmpstr(
+                    g_variant_get_string(
+                        nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL),
+                        NULL),
+                    ==,
+                    label[j]);
+            else
+                g_assert(nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL) == NULL);
+            ip[j] = NULL;
+        }
+    }
+
+    for (i = 0; i < num_addresses; i++)
+        g_assert(!ip[i]);
+
+    /* Gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1");
+}
+
+static void
+test_write_gateway(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    GError *                      error = NULL;
+    shvarFile *                   f;
+    NMIPAddress *                 addr;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Static Addresses Gateway",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.254",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    addr = nm_ip_address_new(AF_INET, "2.2.2.5", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "IPADDR", "1.1.1.3");
+    _svGetValue_check(f, "IPADDR1", "2.2.2.5");
+    _svGetValue_check(f, "IPADDR0", NULL);
+    _svGetValue_check(f, "PREFIX", "24");
+    _svGetValue_check(f, "PREFIX1", "24");
+    _svGetValue_check(f, "PREFIX0", NULL);
+    _svGetValue_check(f, "GATEWAY", "1.1.1.254");
+    _svGetValue_check(f, "GATEWAY0", NULL);
+    _svGetValue_check(f, "GATEWAY1", NULL);
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_open(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+    shvarFile *         ifcfg;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi Open",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_BSSID,
+                 "11:22:33:44:55:66",
+                 NM_SETTING_WIRELESS_MAC_ADDRESS,
+                 "aa:bb:cc:dd:ee:ff",
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NM_SETTING_WIRELESS_BAND,
+                 "bg",
+                 NM_SETTING_WIRELESS_CHANNEL,
+                 (guint32) 9,
+                 NM_SETTING_WIRELESS_MTU,
+                 (guint32) 1345,
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    ifcfg = _svOpenFile(testfile);
+    _svGetValue_check(ifcfg, "ESSID", "Test SSID");
+    svCloseFile(ifcfg);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_open_hex_ssid(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const unsigned char           ssid_data[] =
+        {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd};
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi Open Hex SSID",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wep(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    struct stat                   statbuf;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WEP",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "none",
+                 NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
+                 2,
+                 NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                 "shared",
+                 NULL);
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, "0123456789abcdef0123456789");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 1, "11111111111111111111111111");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 2, "aaaaaaaaaaaaaaaaaaaaaaaaaa");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 3, "BBBBBBBBBBBBBBBBBBBBBBBBBB");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+    g_assert_cmpint(stat(keyfile, &statbuf), ==, 0);
+    g_assert(S_ISREG(statbuf.st_mode));
+    g_assert_cmpint((statbuf.st_mode & 0077), ==, 0);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wep_adhoc(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GError *                      error = NULL;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    struct stat                   statbuf;
+    NMIPAddress *                 addr;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WEP AdHoc",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NM_SETTING_WIRELESS_MODE, "adhoc", NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL);
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, "0123456789abcdef0123456789");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NULL);
+
+    /* IP Address */
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1");
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+    g_assert_cmpint(stat(keyfile, &statbuf), ==, 0);
+    g_assert(S_ISREG(statbuf.st_mode));
+    g_assert_cmpint((statbuf.st_mode & 0077), ==, 0);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wep_passphrase(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    struct stat                   statbuf;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WEP Passphrase",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "none",
+                 NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
+                 0,
+                 NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                 "shared",
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                 NM_WEP_KEY_TYPE_PASSPHRASE,
+                 NULL);
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, "asdfdjaslfjasd;flasjdfl;aksdf");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+    g_assert_cmpint(stat(keyfile, &statbuf), ==, 0);
+    g_assert(S_ISREG(statbuf.st_mode));
+    g_assert_cmpint((statbuf.st_mode & 0077), ==, 0);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wep_40_ascii(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah40";
+    struct stat                   statbuf;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WEP 40 ASCII",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "none",
+                 NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
+                 2,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                 NM_WEP_KEY_TYPE_KEY,
+                 NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                 "shared",
+                 NULL);
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, "lorem");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 1, "ipsum");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 2, "dolor");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 3, "donec");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+    g_assert_cmpint(stat(keyfile, &statbuf), ==, 0);
+    g_assert(S_ISREG(statbuf.st_mode));
+    g_assert_cmpint((statbuf.st_mode & 0077), ==, 0);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wep_104_ascii(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah104";
+    struct stat                   statbuf;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WEP 104 ASCII",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "none",
+                 NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
+                 0,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                 NM_WEP_KEY_TYPE_UNKNOWN,
+                 NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                 "open",
+                 NULL);
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, "LoremIpsumSit");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 1, "AlfaBetaGamma");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 2, "WEP-104 ASCII");
+    nm_setting_wireless_security_set_wep_key(s_wsec, 3, "thisismyascii");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+    g_assert_cmpint(stat(keyfile, &statbuf), ==, 0);
+    g_assert(S_ISREG(statbuf.st_mode));
+    g_assert_cmpint((statbuf.st_mode & 0077), ==, 0);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_leap(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    struct stat                   statbuf;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi LEAP",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "ieee8021x",
+                 NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                 "leap",
+                 NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME,
+                 "Bill Smith",
+                 NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
+                 "foobar22",
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Wifi_LEAP.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+    g_assert_cmpint(stat(keyfile, &statbuf), ==, 0);
+    g_assert(S_ISREG(statbuf.st_mode));
+    g_assert_cmpint((statbuf.st_mode & 0077), ==, 0);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_leap_secret_flags(gconstpointer data)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_free char *              keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    NMSettingSecretFlags          flags     = GPOINTER_TO_UINT(data);
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi LEAP Secret Flags",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "ieee8021x",
+                 NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                 "leap",
+                 NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME,
+                 "Bill Smith",
+                 NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
+                 "foobar22",
+                 NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS,
+                 flags,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    /* No key should be written out since the secret is not system owned */
+    keyfile = utils_get_keys_path(testfile);
+    g_assert(g_file_test(keyfile, G_FILE_TEST_EXISTS) == FALSE);
+
+    /* Remove the LEAP password from the original connection since it wont' be
+     * in the reread connection, as the password is not system owned.
+     */
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, NULL, NULL);
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wpa_psk(gconstpointer test_data)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    struct {
+        const char *name, *psk;
+        gpointer    wep_group_p, wpa_p, wpa2_p;
+    } args;
+
+    nmtst_test_data_unpack(test_data,
+                           &args.name,
+                           &args.wep_group_p,
+                           &args.wpa_p,
+                           &args.wpa2_p,
+                           &args.psk);
+
+    g_assert(args.psk);
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 args.name,
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "wpa-psk",
+                 NM_SETTING_WIRELESS_SECURITY_PSK,
+                 args.psk,
+                 NM_SETTING_WIRELESS_SECURITY_PMF,
+                 (int) NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED,
+                 NULL);
+
+    if (GPOINTER_TO_INT(args.wep_group_p)) {
+        nm_setting_wireless_security_add_group(s_wsec, "wep40");
+        nm_setting_wireless_security_add_group(s_wsec, "wep104");
+    }
+    if (GPOINTER_TO_INT(args.wpa_p)) {
+        nm_setting_wireless_security_add_proto(s_wsec, "wpa");
+        nm_setting_wireless_security_add_pairwise(s_wsec, "tkip");
+        nm_setting_wireless_security_add_group(s_wsec, "tkip");
+    }
+    if (GPOINTER_TO_INT(args.wpa2_p)) {
+        nm_setting_wireless_security_add_proto(s_wsec, "rsn");
+        nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
+        nm_setting_wireless_security_add_group(s_wsec, "ccmp");
+    }
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wpa_psk_adhoc(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GError *                      error = NULL;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    NMIPAddress *                 addr;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WPA PSK",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "adhoc",
+                 NM_SETTING_WIRELESS_CHANNEL,
+                 11,
+                 NM_SETTING_WIRELESS_BAND,
+                 "bg",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "wpa-psk",
+                 NM_SETTING_WIRELESS_SECURITY_PSK,
+                 "7d308b11df1b4243b0f78e5f3fc68cdbb9a264ed0edf4c188edf329ff5b467f0",
+                 NULL);
+
+    nm_setting_wireless_security_add_proto(s_wsec, "rsn");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
+    nm_setting_wireless_security_add_group(s_wsec, "ccmp");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NULL);
+
+    /* IP Address */
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 25, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1");
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wpa_eap_tls(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSetting8021x *              s_8021x;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    gboolean                      success;
+    GError *                      error = NULL;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WPA EAP-TLS",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "wpa-eap",
+                 NM_SETTING_WIRELESS_SECURITY_FILS,
+                 (int) NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED,
+                 NULL);
+    nm_setting_wireless_security_add_proto(s_wsec, "wpa");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "tkip");
+    nm_setting_wireless_security_add_group(s_wsec, "tkip");
+
+    /* Wireless security setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL);
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_PHASE1_AUTH_FLAGS,
+                 (guint)(NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE
+                         | NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE),
+                 NULL);
+
+    nm_setting_802_1x_add_eap_method(s_8021x, "tls");
+
+    success = nm_setting_802_1x_set_ca_cert(s_8021x,
+                                            TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT,
+                                            NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                            NULL,
+                                            &error);
+    nmtst_assert_success(success, error);
+
+    success = nm_setting_802_1x_set_client_cert(s_8021x,
+                                                TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT,
+                                                NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                NULL,
+                                                &error);
+    nmtst_assert_success(success, error);
+
+    success = nm_setting_802_1x_set_private_key(s_8021x,
+                                                TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY,
+                                                "test1",
+                                                NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                NULL,
+                                                &error);
+    nmtst_assert_success(success, error);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wpa_eap_ttls_tls(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSetting8021x *              s_8021x;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    gboolean                      success;
+    GError *                      error = NULL;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WPA EAP-TTLS (TLS)",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL);
+    nm_setting_wireless_security_add_proto(s_wsec, "rsn");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
+    nm_setting_wireless_security_add_group(s_wsec, "ccmp");
+
+    /* Wireless security setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    nm_setting_802_1x_add_eap_method(s_8021x, "ttls");
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_IDENTITY,
+                 "Bill Smith",
+                 NM_SETTING_802_1X_ANONYMOUS_IDENTITY,
+                 "foobar22",
+                 NM_SETTING_802_1X_PHASE2_AUTHEAP,
+                 "tls",
+                 NULL);
+
+    success = nm_setting_802_1x_set_ca_cert(s_8021x,
+                                            TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT,
+                                            NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                            NULL,
+                                            &error);
+    nmtst_assert_success(success, error);
+
+    /* Phase 2 TLS stuff */
+
+    /* phase2 CA cert */
+    success = nm_setting_802_1x_set_phase2_ca_cert(s_8021x,
+                                                   TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT,
+                                                   NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                   NULL,
+                                                   &error);
+    nmtst_assert_success(success, error);
+
+    /* phase2 client cert */
+    success = nm_setting_802_1x_set_phase2_client_cert(s_8021x,
+                                                       TEST_IFCFG_WIFI_WPA_EAP_TLS_CLIENT_CERT,
+                                                       NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                       NULL,
+                                                       &error);
+    nmtst_assert_success(success, error);
+
+    /* phase2 private key */
+    success = nm_setting_802_1x_set_phase2_private_key(s_8021x,
+                                                       TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY,
+                                                       "test1",
+                                                       NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                                       NULL,
+                                                       &error);
+    nmtst_assert_success(success, error);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wpa_eap_ttls_mschapv2(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSetting8021x *              s_8021x;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    gboolean                      success;
+    GError *                      error = NULL;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WPA EAP-TTLS (MSCHAPv2)",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL);
+    nm_setting_wireless_security_add_proto(s_wsec, "wpa");
+    nm_setting_wireless_security_add_proto(s_wsec, "rsn");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "tkip");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
+    nm_setting_wireless_security_add_group(s_wsec, "tkip");
+    nm_setting_wireless_security_add_group(s_wsec, "ccmp");
+
+    /* Wireless security setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    nm_setting_802_1x_add_eap_method(s_8021x, "ttls");
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_IDENTITY,
+                 "Bill Smith",
+                 NM_SETTING_802_1X_PASSWORD,
+                 ";alkdfja;dslkfjsad;lkfjsadf",
+                 NM_SETTING_802_1X_ANONYMOUS_IDENTITY,
+                 "foobar22",
+                 NM_SETTING_802_1X_PHASE2_AUTHEAP,
+                 "mschapv2",
+                 NULL);
+
+    success = nm_setting_802_1x_set_ca_cert(s_8021x,
+                                            TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT,
+                                            NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                            NULL,
+                                            &error);
+    nmtst_assert_success(success, error);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    keyfile = utils_get_keys_path(testfile);
+}
+
+static void
+test_write_wifi_wpa_then_open(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_free char *              keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+
+    /* Test that writing out a WPA config then changing that to an open
+     * config doesn't leave various WPA-related keys lying around in the ifcfg.
+     */
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "random wifi connection",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "wpa-psk",
+                 NM_SETTING_WIRELESS_SECURITY_PSK,
+                 "some cool PSK",
+                 NULL);
+
+    nm_setting_wireless_security_add_proto(s_wsec, "wpa");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "tkip");
+    nm_setting_wireless_security_add_group(s_wsec, "tkip");
+
+    nm_setting_wireless_security_add_proto(s_wsec, "rsn");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
+    nm_setting_wireless_security_add_group(s_wsec, "ccmp");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    g_object_unref(reread);
+
+    nmtst_connection_normalize(connection);
+
+    /* Now change the connection to open and recheck */
+    nm_connection_remove_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
+
+    /* Write it back out */
+    _writer_update_connection(connection,
+                              TEST_SCRATCH_DIR,
+                              testfile,
+                              TEST_IFCFG_DIR "/ifcfg-random_wifi_connection.cexpected");
+    keyfile = utils_get_keys_path(testfile);
+    g_assert(!g_file_test(keyfile, G_FILE_TEST_EXISTS));
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wpa_then_wep_with_perms(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    gboolean                      success;
+    GBytes *                      ssid;
+    char **                       perms;
+    const char *                  ssid_data = "SomeSSID";
+
+    /* Test that writing out a WPA config then changing that to a WEP
+     * config works and doesn't cause infinite loop or other issues.
+     */
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    perms = g_strsplit("user:superman:", ",", -1);
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "random wifi connection 2",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_PERMISSIONS,
+                 perms,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+    g_strfreev(perms);
+    g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "wpa-psk",
+                 NM_SETTING_WIRELESS_SECURITY_PSK,
+                 "My cool PSK",
+                 NULL);
+
+    nm_setting_wireless_security_add_proto(s_wsec, "wpa");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "tkip");
+    nm_setting_wireless_security_add_group(s_wsec, "tkip");
+
+    nm_setting_wireless_security_add_proto(s_wsec, "rsn");
+    nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
+    nm_setting_wireless_security_add_group(s_wsec, "ccmp");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    g_object_unref(reread);
+
+    nmtst_connection_normalize(connection);
+
+    /* Now change the connection to WEP and recheck */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL);
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, "abraka  dabra");
+
+    /* Write it back out */
+    _writer_update_connection(connection,
+                              TEST_SCRATCH_DIR,
+                              testfile,
+                              TEST_IFCFG_DIR "/ifcfg-random_wifi_connection_2.cexpected");
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    nmtst_connection_normalize(connection);
+    success = nm_connection_compare(connection,
+                                    reread,
+                                    NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS
+                                        | NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS);
+    g_assert(success);
+
+    keyfile = utils_get_keys_path(testfile);
+}
+
+static void
+test_write_wifi_dynamic_wep_leap(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    nmtst_auto_unlinkfile char *keyfile      = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSetting8021x *              s_8021x;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  ssid_data = "blahblah";
+    shvarFile *                   ifcfg;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi Dynamic WEP LEAP",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* Wireless security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", NULL);
+
+    /* Wireless security setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    nm_setting_802_1x_add_eap_method(s_8021x, "leap");
+
+    g_object_set(s_8021x,
+                 NM_SETTING_802_1X_IDENTITY,
+                 "Bill Smith",
+                 NM_SETTING_802_1X_PASSWORD,
+                 ";alkdfja;dslkfjsad;lkfjsadf",
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    keyfile = utils_get_keys_path(testfile);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    /* Check and make sure that an "old-school" LEAP (Network EAP) connection
+     * did not get written.  Check first that the auth alg is not set to "LEAP"
+     * and next that the only IEEE 802.1x EAP method is "LEAP".
+     */
+    ifcfg = _svOpenFile(testfile);
+    _svGetValue_check(ifcfg, "SECURITYMODE", NULL);
+    _svGetValue_check(ifcfg, "IEEE_8021X_EAP_METHODS", "LEAP");
+    svCloseFile(ifcfg);
+}
+
+static void
+test_write_wired_qeth_dhcp(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char **                       subchans;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired qeth Static",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    subchans = g_strsplit("0.0.600,0.0.601,0.0.602", ",", -1);
+    g_object_set(s_wired,
+                 NM_SETTING_WIRED_S390_SUBCHANNELS,
+                 subchans,
+                 NM_SETTING_WIRED_S390_NETTYPE,
+                 "qeth",
+                 NULL);
+    g_strfreev(subchans);
+
+    nm_setting_wired_add_s390_option(s_wired, "portname", "FOOBAR");
+    nm_setting_wired_add_s390_option(s_wired, "portno", "1");
+    nm_setting_wired_add_s390_option(s_wired, "layer2", "0");
+    nm_setting_wired_add_s390_option(s_wired, "protocol", "blahbalh");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_ctc_dhcp(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char **                       subchans;
+    shvarFile *                   ifcfg;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired ctc Static",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    subchans = g_strsplit("0.0.600,0.0.601", ",", -1);
+    g_object_set(s_wired,
+                 NM_SETTING_WIRED_S390_SUBCHANNELS,
+                 subchans,
+                 NM_SETTING_WIRED_S390_NETTYPE,
+                 "ctc",
+                 NULL);
+    g_strfreev(subchans);
+    nm_setting_wired_add_s390_option(s_wired, "ctcprot", "0");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    /* Ensure the CTCPROT item gets written out as its own option */
+    ifcfg = _svOpenFile(testfile);
+
+    _svGetValue_check(ifcfg, "CTCPROT", "0");
+
+    /* And that it's not in the generic OPTIONS string */
+    _svGetValue_check(ifcfg, "OPTIONS", NULL);
+
+    svCloseFile(ifcfg);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_permissions(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Permissions",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    nm_setting_connection_add_permission(s_con, "user", "blahblah", NULL);
+    nm_setting_connection_add_permission(s_con, "user", "foobar", NULL);
+    nm_setting_connection_add_permission(s_con, "user", "asdfasdf", NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Permissions.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wifi_wep_agent_keys(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  str_ssid = "foobarbaz";
+    GBytes *                      ssid;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection != NULL);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wifi WEP Agent Owned",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    /* Wifi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(str_ssid, strlen(str_ssid));
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 "infrastructure",
+                 NULL);
+    g_bytes_unref(ssid);
+
+    /* Wifi security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "none",
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                 NM_WEP_KEY_TYPE_PASSPHRASE,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS,
+                 NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+                 NULL);
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, "asdfdjaslfjasd;flasjdfl;aksdf");
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+
+    /* Remove the WEP key from the original, because it should not have been
+     * written out to disk as it was agent-owned.  The new connection should
+     * not have any WEP keys set.
+     * Also the new connection should not have WEP key type set.
+     */
+    nm_setting_wireless_security_set_wep_key(s_wsec, 0, NULL);
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_UNKNOWN, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_wired_pppoe(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingWired *     s_wired;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingPppoe *     s_pppoe;
+    NMSettingPpp *       s_ppp;
+    GError *             error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired PPPoE",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* PPPoE setting */
+    s_pppoe = (NMSettingPppoe *) nm_setting_pppoe_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_pppoe));
+
+    g_object_set(G_OBJECT(s_pppoe),
+                 NM_SETTING_PPPOE_SERVICE,
+                 "stupid-service",
+                 NM_SETTING_PPPOE_USERNAME,
+                 "Bill Smith",
+                 NM_SETTING_PPPOE_PASSWORD,
+                 "test1",
+                 NULL);
+
+    /* PPP setting */
+    s_ppp = (NMSettingPpp *) nm_setting_ppp_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ppp));
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL);
+
+    g_object_unref(connection);
+    g_clear_error(&error);
+}
+
+static void
+test_write_vpn(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingVpn *       s_vpn;
+    GError *             error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write VPN",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_VPN_SETTING_NAME,
+                 NULL);
+
+    /* VPN setting */
+    s_vpn = (NMSettingVpn *) nm_setting_vpn_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_vpn));
+
+    g_object_set(s_vpn,
+                 NM_SETTING_VPN_SERVICE_TYPE,
+                 "awesomevpn",
+                 NM_SETTING_VPN_USER_NAME,
+                 "Bill Smith",
+                 NULL);
+
+    nm_setting_vpn_add_data_item(s_vpn, "server", "vpn.somewhere.com");
+    nm_setting_vpn_add_secret(s_vpn, "password", "sup3rs3cr3t");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL);
+
+    g_object_unref(connection);
+    g_clear_error(&error);
+}
+
+static void
+test_write_mobile_broadband(gconstpointer data)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingGsm *       s_gsm;
+    NMSettingCdma *      s_cdma;
+    NMSettingPpp *       s_ppp;
+    NMSettingSerial *    s_serial;
+    GError *             error = NULL;
+    gboolean             gsm   = GPOINTER_TO_UINT(data);
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 gsm ? "Test Write GSM" : "Test Write CDMA",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 gsm ? NM_SETTING_GSM_SETTING_NAME : NM_SETTING_CDMA_SETTING_NAME,
+                 NULL);
+
+    if (gsm) {
+        /* GSM setting */
+        s_gsm = (NMSettingGsm *) nm_setting_gsm_new();
+        nm_connection_add_setting(connection, NM_SETTING(s_gsm));
+    } else {
+        /* CDMA setting */
+        s_cdma = (NMSettingCdma *) nm_setting_cdma_new();
+        nm_connection_add_setting(connection, NM_SETTING(s_cdma));
+
+        g_object_set(s_cdma, NM_SETTING_CDMA_NUMBER, "#777", NULL);
+    }
+
+    /* Serial setting */
+    s_serial = (NMSettingSerial *) nm_setting_serial_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_serial));
+
+    g_object_set(s_serial,
+                 NM_SETTING_SERIAL_BAUD,
+                 115200,
+                 NM_SETTING_SERIAL_BITS,
+                 8,
+                 NM_SETTING_SERIAL_PARITY,
+                 NM_SETTING_SERIAL_PARITY_NONE,
+                 NM_SETTING_SERIAL_STOPBITS,
+                 1,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* PPP setting */
+    s_ppp = (NMSettingPpp *) nm_setting_ppp_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ppp));
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL);
+
+    g_object_unref(connection);
+    g_clear_error(&error);
+}
+
+static void
+test_read_bridge_main(void)
+{
+    NMConnection *   connection;
+    NMSettingBridge *s_bridge;
+    NMSettingWired * s_wired;
+    const char *     mac;
+    char             expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-main", NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "br0");
+
+    /* ===== Bridging SETTING ===== */
+
+    s_bridge = nm_connection_get_setting_bridge(connection);
+    g_assert(s_bridge);
+    g_assert_cmpuint(nm_setting_bridge_get_forward_delay(s_bridge), ==, 2);
+    g_assert(nm_setting_bridge_get_stp(s_bridge));
+    g_assert_cmpuint(nm_setting_bridge_get_priority(s_bridge), ==, 32744);
+    g_assert_cmpuint(nm_setting_bridge_get_hello_time(s_bridge), ==, 7);
+    g_assert_cmpuint(nm_setting_bridge_get_max_age(s_bridge), ==, 39);
+    g_assert_cmpuint(nm_setting_bridge_get_ageing_time(s_bridge), ==, 235352);
+    g_assert_cmpuint(nm_setting_bridge_get_group_forward_mask(s_bridge), ==, 24);
+    g_assert(!nm_setting_bridge_get_multicast_snooping(s_bridge));
+    g_assert_cmpint(nm_setting_bridge_get_vlan_filtering(s_bridge), ==, TRUE);
+    g_assert_cmpint(nm_setting_bridge_get_vlan_default_pvid(s_bridge), ==, 99);
+
+    /* MAC address */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    mac = nm_setting_wired_get_cloned_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_bridge_main(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridge *             s_bridge;
+    NMSettingIPConfig *           s_ip4, *s_ip6;
+    NMSettingWired *              s_wired;
+    NMIPAddress *                 addr;
+    static const char *           mac   = "31:33:33:37:be:cd";
+    GError *                      error = NULL;
+    gs_unref_ptrarray GPtrArray *vlans  = NULL;
+    NMBridgeVlan *               vlan;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bridge Main",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "br0",
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_BRIDGE_SETTING_NAME,
+                 NULL);
+
+    /* bridge setting */
+    s_bridge = (NMSettingBridge *) nm_setting_bridge_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_bridge));
+
+    vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref);
+    vlan  = nm_bridge_vlan_new(10, 16);
+    nm_bridge_vlan_set_untagged(vlan, TRUE);
+    g_ptr_array_add(vlans, vlan);
+    vlan = nm_bridge_vlan_new(22, 22);
+    nm_bridge_vlan_set_pvid(vlan, TRUE);
+    nm_bridge_vlan_set_untagged(vlan, TRUE);
+    g_ptr_array_add(vlans, vlan);
+    vlan = nm_bridge_vlan_new(44, 0);
+    g_ptr_array_add(vlans, vlan);
+
+    g_object_set(s_bridge,
+                 NM_SETTING_BRIDGE_MAC_ADDRESS,
+                 mac,
+                 NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,
+                 19008,
+                 NM_SETTING_BRIDGE_VLAN_FILTERING,
+                 TRUE,
+                 NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID,
+                 4000,
+                 NM_SETTING_BRIDGE_VLANS,
+                 vlans,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_BRIDGE, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_bridge_component(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingBridgePort *s_port;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-component",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0");
+    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
+                    ==,
+                    NM_SETTING_BRIDGE_SETTING_NAME);
+
+    s_port = nm_connection_get_setting_bridge_port(connection);
+    g_assert(s_port);
+    g_assert(nm_setting_bridge_port_get_hairpin_mode(s_port));
+    g_assert_cmpuint(nm_setting_bridge_port_get_priority(s_port), ==, 28);
+    g_assert_cmpuint(nm_setting_bridge_port_get_path_cost(s_port), ==, 100);
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_bridge_component(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSetting *                   s_port;
+    static const char *           mac  = "31:33:33:37:be:cd";
+    guint32                       mtu  = 1492;
+    gs_unref_ptrarray GPtrArray *vlans = NULL;
+    NMBridgeVlan *               vlan;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bridge Component",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "br0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_BRIDGE_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL);
+
+    /* Bridge port */
+    vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref);
+    vlan  = nm_bridge_vlan_new(1, 0);
+    nm_bridge_vlan_set_untagged(vlan, TRUE);
+    g_ptr_array_add(vlans, vlan);
+    vlan = nm_bridge_vlan_new(4, 4094);
+    nm_bridge_vlan_set_untagged(vlan, TRUE);
+    g_ptr_array_add(vlans, vlan);
+    vlan = nm_bridge_vlan_new(2, 2);
+    nm_bridge_vlan_set_pvid(vlan, TRUE);
+    g_ptr_array_add(vlans, vlan);
+
+    s_port = nm_setting_bridge_port_new();
+    nm_connection_add_setting(connection, s_port);
+    g_object_set(s_port,
+                 NM_SETTING_BRIDGE_PORT_PRIORITY,
+                 50,
+                 NM_SETTING_BRIDGE_PORT_PATH_COST,
+                 33,
+                 NM_SETTING_BRIDGE_PORT_VLANS,
+                 vlans,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Bridge_Component.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_bridge_missing_stp(void)
+{
+    NMConnection *   connection;
+    NMSettingBridge *s_bridge;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-missing-stp",
+                                       NULL,
+                                       TYPE_BRIDGE,
+                                       NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "br0");
+
+    /* ===== Bridging SETTING ===== */
+
+    s_bridge = nm_connection_get_setting_bridge(connection);
+    g_assert(s_bridge);
+    g_assert(nm_setting_bridge_get_stp(s_bridge) == FALSE);
+
+    g_object_unref(connection);
+}
+
+#define TEST_IFCFG_VLAN_INTERFACE TEST_IFCFG_DIR "/ifcfg-test-vlan-interface"
+
+static void
+test_read_vlan_interface(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+    guint32        from = 0, to = 0;
+
+    connection = _connection_from_file(TEST_IFCFG_VLAN_INTERFACE, NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan43");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 43);
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan),
+                    ==,
+                    NM_VLAN_FLAG_GVRP | NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS);
+
+    /* Ingress map */
+    g_assert_cmpint(nm_setting_vlan_get_num_priorities(s_vlan, NM_VLAN_INGRESS_MAP), ==, 2);
+
+    g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_INGRESS_MAP, 0, &from, &to));
+    g_assert_cmpint(from, ==, 0);
+    g_assert_cmpint(to, ==, 1);
+
+    g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_INGRESS_MAP, 1, &from, &to));
+    g_assert_cmpint(from, ==, 2);
+    g_assert_cmpint(to, ==, 5);
+
+    /* Egress map */
+    g_assert_cmpint(nm_setting_vlan_get_num_priorities(s_vlan, NM_VLAN_EGRESS_MAP), ==, 3);
+
+    g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_EGRESS_MAP, 0, &from, &to));
+    g_assert_cmpint(from, ==, 3);
+    g_assert_cmpint(to, ==, 1);
+
+    g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_EGRESS_MAP, 1, &from, &to));
+    g_assert_cmpint(from, ==, 12);
+    g_assert_cmpint(to, ==, 3);
+
+    g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_EGRESS_MAP, 2, &from, &to));
+    g_assert_cmpint(from, ==, 14);
+    g_assert_cmpint(to, ==, 7);
+
+    g_object_unref(connection);
+}
+
+#define TEST_IFCFG_VLAN_ONLY_VLANID TEST_IFCFG_DIR "/ifcfg-test-vlan-only-vlanid"
+
+static void
+test_read_vlan_only_vlan_id(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+
+    connection = _connection_from_file(TEST_IFCFG_VLAN_ONLY_VLANID, NULL, TYPE_ETHERNET, NULL);
+
+    g_assert(nm_connection_get_interface_name(connection) == NULL);
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 43);
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_vlan_only_device(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-only-device",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0.9");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 9);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_vlan_physdev(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-physdev", NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_vlan_reorder_hdr_1(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+
+    NMTST_EXPECT_NM_WARN("*REORDER_HDR key is deprecated, use VLAN_FLAGS*");
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-reorder-hdr-1",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+    g_test_assert_expected_messages();
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3);
+    /* Check that REORDER_HDR=0 is ignored */
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_vlan_reorder_hdr_2(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-reorder-hdr-2",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3);
+    /* Check that VLAN_FLAGS=NO_REORDER_HDR works */
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_LOOSE_BINDING);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_vlan_flags_1(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-1", NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "super-vlan");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 44);
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan),
+                    ==,
+                    NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_vlan_flags_2(void)
+{
+    NMConnection * connection;
+    NMSettingVlan *s_vlan;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-2", NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "super-vlan");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 44);
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan),
+                    ==,
+                    NM_VLAN_FLAG_GVRP | NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS);
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_vlan(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_VLAN_INTERFACE, NULL, TYPE_VLAN, NULL);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Vlan_test-vlan-interface.cexpected",
+                           &testfile);
+}
+
+static void
+test_write_vlan_flags(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-2", NULL, TYPE_VLAN, NULL);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_vlan_only_vlanid(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+
+    connection = _connection_from_file(TEST_IFCFG_VLAN_ONLY_VLANID, NULL, TYPE_VLAN, NULL);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_vlan_reorder_hdr(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingVlan *               s_vlan;
+    NMSettingWired *              s_wired;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write VLAN reorder_hdr",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_VLAN_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* VLAN setting */
+    s_vlan = (NMSettingVlan *) nm_setting_vlan_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_vlan));
+
+    g_object_set(s_vlan,
+                 NM_SETTING_VLAN_PARENT,
+                 "eth0",
+                 NM_SETTING_VLAN_ID,
+                 444,
+                 NM_SETTING_VLAN_FLAGS,
+                 1,
+                 NULL);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_ethernet_missing_ipv6(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Ethernet Without IPv6 Setting",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+                 NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID,
+                 "random-client-id-00:22:33",
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
+                 TRUE,
+                 NULL);
+
+    /* IP6 setting */
+    /*
+     * We intentionally don't add IPv6 setting here. ifcfg-rh plugin should regard
+     * missing IPv6 as IPv6 with NM_SETTING_IP6_CONFIG_METHOD_AUTO method.
+     */
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_ibft_ignored(void)
+{
+    gs_free_error GError *error = NULL;
+
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-ibft", NULL, TYPE_ETHERNET, &error);
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+}
+
+static void
+test_read_bond_main(void)
+{
+    NMConnection * connection;
+    NMSettingBond *s_bond;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-main", NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0");
+
+    /* ===== Bonding SETTING ===== */
+
+    s_bond = nm_connection_get_setting_bond(connection);
+    g_assert(s_bond);
+
+    g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
+                    ==,
+                    "100");
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_bond_eth_type(void)
+{
+    NMConnection * connection;
+    NMSettingBond *s_bond;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-eth-type",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0");
+
+    /* ===== Bonding SETTING ===== */
+
+    s_bond = nm_connection_get_setting_bond(connection);
+    g_assert(s_bond);
+
+    g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
+                    ==,
+                    "213");
+    g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_LACP_RATE),
+                    ==,
+                    "1");
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_bond_main(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBond *               s_bond;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMSettingWired *              s_wired;
+    NMIPAddress *                 addr;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bond Main",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "bond0",
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_BOND_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* bond setting */
+    s_bond = (NMSettingBond *) nm_setting_bond_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_bond));
+
+    nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY, "5");
+    nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_UPDELAY, "10");
+    nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_MIIMON, "100");
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Bond_Main.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_BOND, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_bond_slave(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-slave", NULL, TYPE_ETHERNET, NULL);
+    g_test_assert_expected_messages();
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "bond0");
+
+    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME);
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_bond_slave(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    static const char *           mac = "31:33:33:37:be:cd";
+    guint32                       mtu = 1492;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bond Slave",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "bond0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_BOND_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_infiniband(void)
+{
+    NMConnection *       connection;
+    NMSettingInfiniband *s_infiniband;
+    char *               unmanaged = NULL;
+    const char *         mac;
+    char        expected_mac_address[INFINIBAND_ALEN] = {0x80, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+                                                  0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc,
+                                                  0xdd, 0xee, 0xff, 0x00, 0x11, 0x22};
+    const char *transport_mode;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-infiniband",
+                                       NULL,
+                                       TYPE_INFINIBAND,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    /* ===== INFINIBAND SETTING ===== */
+
+    s_infiniband = nm_connection_get_setting_infiniband(connection);
+    g_assert(s_infiniband);
+
+    /* MAC address */
+    mac = nm_setting_infiniband_get_mac_address(s_infiniband);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+
+    /* Transport mode */
+    transport_mode = nm_setting_infiniband_get_transport_mode(s_infiniband);
+    g_assert(transport_mode);
+    g_assert_cmpstr(transport_mode, ==, "connected");
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_infiniband(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingInfiniband *         s_infiniband;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char * mac = "80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22";
+    guint32      mtu = 65520;
+    NMIPAddress *addr;
+    GError *     error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write InfiniBand",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_INFINIBAND_SETTING_NAME,
+                 NULL);
+
+    /* InfiniBand setting */
+    s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_infiniband));
+
+    g_object_set(s_infiniband,
+                 NM_SETTING_INFINIBAND_MAC_ADDRESS,
+                 mac,
+                 NM_SETTING_INFINIBAND_MTU,
+                 mtu,
+                 NM_SETTING_INFINIBAND_TRANSPORT_MODE,
+                 "connected",
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_INFINIBAND, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_bond_slave_ib(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-slave-ib", NULL, NULL, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "bond0");
+    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME);
+}
+
+static void
+test_write_bond_slave_ib(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingInfiniband *         s_infiniband;
+    static const char *mac = "80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bond Slave InfiniBand",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_INFINIBAND_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "bond0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_BOND_SETTING_NAME,
+                 NULL);
+
+    /* InfiniBand setting */
+    s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_infiniband));
+
+    g_object_set(s_infiniband,
+                 NM_SETTING_INFINIBAND_MAC_ADDRESS,
+                 mac,
+                 NM_SETTING_INFINIBAND_MTU,
+                 2044,
+                 NM_SETTING_INFINIBAND_TRANSPORT_MODE,
+                 "datagram",
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, NULL, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_bond_opts_mode_numeric(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingBond *      s_bond;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-mode-numeric",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_BOND_SETTING_NAME);
+
+    s_bond = nm_connection_get_setting_bond(connection);
+    g_assert(s_bond);
+    g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MODE),
+                    ==,
+                    "802.3ad");
+
+    g_object_unref(connection);
+}
+
+#define DCB_ALL_FLAGS \
+    (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING)
+
+static void
+test_read_dcb_basic(void)
+{
+    NMConnection *connection;
+    NMSettingDcb *s_dcb;
+    guint         i;
+    guint         expected_group_ids[8]        = {0, 0, 0, 0, 1, 1, 1, 0xF};
+    guint         expected_group_bandwidths[8] = {25, 0, 0, 75, 0, 0, 0, 0};
+    guint         expected_bandwidths[8]       = {5, 10, 30, 25, 10, 50, 5, 0};
+    gboolean      expected_strict[8] = {FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE};
+    guint         expected_traffic_classes[8] = {7, 6, 5, 4, 3, 2, 1, 0};
+    gboolean      expected_pfcs[8] = {TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE};
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dcb", NULL, TYPE_ETHERNET, NULL);
+
+    s_dcb = nm_connection_get_setting_dcb(connection);
+    g_assert(s_dcb);
+
+    g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, DCB_ALL_FLAGS);
+    g_assert_cmpint(nm_setting_dcb_get_app_fcoe_priority(s_dcb), ==, 7);
+
+    g_assert_cmpint(nm_setting_dcb_get_app_iscsi_flags(s_dcb), ==, DCB_ALL_FLAGS);
+    g_assert_cmpint(nm_setting_dcb_get_app_iscsi_priority(s_dcb), ==, 6);
+
+    g_assert_cmpint(nm_setting_dcb_get_app_fip_flags(s_dcb), ==, DCB_ALL_FLAGS);
+    g_assert_cmpint(nm_setting_dcb_get_app_fip_priority(s_dcb), ==, 2);
+
+    g_assert_cmpint(nm_setting_dcb_get_priority_flow_control_flags(s_dcb),
+                    ==,
+                    (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE));
+    for (i = 0; i < 8; i++)
+        g_assert_cmpint(nm_setting_dcb_get_priority_flow_control(s_dcb, i), ==, expected_pfcs[i]);
+
+    g_assert_cmpint(nm_setting_dcb_get_priority_group_flags(s_dcb), ==, DCB_ALL_FLAGS);
+
+    /* Group IDs */
+    for (i = 0; i < 8; i++)
+        g_assert_cmpint(nm_setting_dcb_get_priority_group_id(s_dcb, i), ==, expected_group_ids[i]);
+
+    /* Group bandwidth */
+    for (i = 0; i < 8; i++)
+        g_assert_cmpint(nm_setting_dcb_get_priority_group_bandwidth(s_dcb, i),
+                        ==,
+                        expected_group_bandwidths[i]);
+
+    /* User priority bandwidth */
+    for (i = 0; i < 8; i++)
+        g_assert_cmpint(nm_setting_dcb_get_priority_bandwidth(s_dcb, i),
+                        ==,
+                        expected_bandwidths[i]);
+
+    /* Strict bandwidth */
+    for (i = 0; i < 8; i++)
+        g_assert_cmpint(nm_setting_dcb_get_priority_strict_bandwidth(s_dcb, i),
+                        ==,
+                        expected_strict[i]);
+
+    /* Traffic class */
+    for (i = 0; i < 8; i++)
+        g_assert_cmpint(nm_setting_dcb_get_priority_traffic_class(s_dcb, i),
+                        ==,
+                        expected_traffic_classes[i]);
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_dcb_basic(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingDcb *                s_dcb;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    guint                         i;
+    const guint                   group_ids[8]        = {4, 0xF, 6, 0xF, 1, 7, 3, 0xF};
+    const guint                   group_bandwidths[8] = {10, 20, 15, 10, 2, 3, 35, 5};
+    const guint                   bandwidths[8]       = {10, 20, 30, 40, 50, 10, 0, 25};
+    const gboolean                strict[8] = {TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE};
+    const guint                   traffic_classes[8] = {3, 4, 7, 2, 1, 0, 5, 6};
+    const gboolean                pfcs[8] = {TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, TRUE, FALSE};
+
+    connection = nm_simple_connection_new();
+
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+    g_object_set(G_OBJECT(s_con),
+                 NM_SETTING_CONNECTION_ID,
+                 "dcb-test",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "eth0",
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP stuff */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_object_set(G_OBJECT(s_ip4),
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+                 NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_object_set(G_OBJECT(s_ip6),
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                 NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    /* DCB */
+    s_dcb = (NMSettingDcb *) nm_setting_dcb_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_dcb));
+
+    g_object_set(G_OBJECT(s_dcb),
+                 NM_SETTING_DCB_APP_FCOE_FLAGS,
+                 DCB_ALL_FLAGS,
+                 NM_SETTING_DCB_APP_FCOE_PRIORITY,
+                 5,
+                 NM_SETTING_DCB_APP_ISCSI_FLAGS,
+                 DCB_ALL_FLAGS,
+                 NM_SETTING_DCB_APP_ISCSI_PRIORITY,
+                 1,
+                 NM_SETTING_DCB_APP_FIP_FLAGS,
+                 DCB_ALL_FLAGS,
+                 NM_SETTING_DCB_APP_FIP_PRIORITY,
+                 3,
+                 NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS,
+                 DCB_ALL_FLAGS,
+                 NM_SETTING_DCB_PRIORITY_GROUP_FLAGS,
+                 DCB_ALL_FLAGS,
+                 NULL);
+
+    for (i = 0; i < 8; i++) {
+        nm_setting_dcb_set_priority_flow_control(s_dcb, i, pfcs[i]);
+        nm_setting_dcb_set_priority_group_id(s_dcb, i, group_ids[i]);
+        nm_setting_dcb_set_priority_group_bandwidth(s_dcb, i, group_bandwidths[i]);
+        nm_setting_dcb_set_priority_bandwidth(s_dcb, i, bandwidths[i]);
+        nm_setting_dcb_set_priority_strict_bandwidth(s_dcb, i, strict[i]);
+        nm_setting_dcb_set_priority_traffic_class(s_dcb, i, traffic_classes[i]);
+    }
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "//ifcfg-dcb-test.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_dcb_default_app_priorities(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingDcb *                s_dcb;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dcb-default-app-priorities",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    s_dcb = nm_connection_get_setting_dcb(connection);
+    g_assert(s_dcb);
+
+    g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE);
+    g_assert_cmpint(nm_setting_dcb_get_app_fcoe_priority(s_dcb), ==, -1);
+
+    g_assert_cmpint(nm_setting_dcb_get_app_iscsi_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE);
+    g_assert_cmpint(nm_setting_dcb_get_app_iscsi_priority(s_dcb), ==, -1);
+
+    g_assert_cmpint(nm_setting_dcb_get_app_fip_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE);
+    g_assert_cmpint(nm_setting_dcb_get_app_fip_priority(s_dcb), ==, -1);
+}
+
+static void
+test_read_dcb_bad_booleans(void)
+{
+    gs_free_error GError *error = NULL;
+
+    NMTST_EXPECT_NM_WARN("*invalid DCB_PG_STRICT value*not all 0s and 1s*");
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-bad-booleans",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    g_test_assert_expected_messages();
+
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+    g_assert(strstr(error->message, "invalid boolean digit"));
+}
+
+static void
+test_read_dcb_short_booleans(void)
+{
+    gs_free_error GError *error = NULL;
+
+    NMTST_EXPECT_NM_WARN("*DCB_PG_STRICT value*8 characters*");
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-short-booleans",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    g_test_assert_expected_messages();
+
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+    g_assert(strstr(error->message, "boolean array must be 8 characters"));
+}
+
+static void
+test_read_dcb_bad_uints(void)
+{
+    gs_free_error GError *error = NULL;
+
+    NMTST_EXPECT_NM_WARN("*invalid DCB_PG_UP2TC value*not 0 - 7*");
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-bad-uints",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    g_test_assert_expected_messages();
+
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+    g_assert(strstr(error->message, "invalid uint digit"));
+}
+
+static void
+test_read_dcb_short_uints(void)
+{
+    gs_free_error GError *error = NULL;
+
+    NMTST_EXPECT_NM_WARN("*DCB_PG_UP2TC value*8 characters*");
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-short-uints",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    g_test_assert_expected_messages();
+
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+    g_assert(strstr(error->message, "uint array must be 8 characters"));
+}
+
+static void
+test_read_dcb_bad_percent(void)
+{
+    gs_free_error GError *error = NULL;
+
+    NMTST_EXPECT_NM_WARN("*invalid DCB_PG_PCT percentage value*");
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-bad-percent",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    g_test_assert_expected_messages();
+
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+    g_assert(strstr(error->message, "invalid percent element"));
+}
+
+static void
+test_read_dcb_short_percent(void)
+{
+    gs_free_error GError *error = NULL;
+
+    NMTST_EXPECT_NM_WARN("*invalid DCB_PG_PCT percentage list value*");
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-short-percent",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    g_test_assert_expected_messages();
+
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+    g_assert(strstr(error->message, "percent array must be 8 elements"));
+}
+
+static void
+test_read_dcb_pgpct_not_100(void)
+{
+    gs_free_error GError *error = NULL;
+
+    NMTST_EXPECT_NM_WARN("*DCB_PG_PCT percentages do not equal 100*");
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-dcb-pgpct-not-100",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    g_test_assert_expected_messages();
+
+    g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
+    g_assert(strstr(error->message, "invalid percentage sum"));
+}
+
+static void
+test_read_fcoe_mode(gconstpointer user_data)
+{
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_free char *                file          = NULL;
+    const char *                  expected_mode = user_data;
+    NMSettingDcb *                s_dcb;
+
+    file       = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-fcoe-%s", expected_mode);
+    connection = _connection_from_file(file, NULL, TYPE_ETHERNET, NULL);
+
+    s_dcb = nm_connection_get_setting_dcb(connection);
+    g_assert(s_dcb);
+
+    g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE);
+    g_assert_cmpstr(nm_setting_dcb_get_app_fcoe_mode(s_dcb), ==, expected_mode);
+}
+
+static void
+test_write_fcoe_mode(gconstpointer user_data)
+{
+    nmtst_auto_unlinkfile char *testfile        = NULL;
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_unref_object NMConnection *reread        = NULL;
+    const char *                  expected_mode = user_data;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingDcb *                s_dcb;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    shvarFile *                   ifcfg;
+
+    connection = nm_simple_connection_new();
+
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+    g_object_set(G_OBJECT(s_con),
+                 NM_SETTING_CONNECTION_ID,
+                 "fcoe-test",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "eth0",
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP stuff */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_object_set(G_OBJECT(s_ip4),
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+                 NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_object_set(G_OBJECT(s_ip6),
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                 NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    /* DCB */
+    s_dcb = (NMSettingDcb *) nm_setting_dcb_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_dcb));
+
+    g_object_set(G_OBJECT(s_dcb),
+                 NM_SETTING_DCB_APP_FCOE_FLAGS,
+                 NM_SETTING_DCB_FLAG_ENABLE,
+                 NM_SETTING_DCB_APP_FCOE_MODE,
+                 expected_mode,
+                 NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    ifcfg = _svOpenFile(testfile);
+    _svGetValue_check(ifcfg, "DCB_APP_FCOE_MODE", expected_mode);
+    svCloseFile(ifcfg);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_team_master(gconstpointer user_data)
+{
+    const char *const    PATH_NAME = user_data;
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingTeam *      s_team;
+    const char *         expected_config =
+        "{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}";
+
+    connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "team0");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_TEAM_SETTING_NAME);
+
+    s_team = nm_connection_get_setting_team(connection);
+    g_assert(s_team);
+    g_assert_cmpstr(nm_setting_team_get_config(s_team), ==, expected_config);
+
+    g_object_unref(connection);
+}
+
+static void
+test_read_team_master_invalid(gconstpointer user_data)
+{
+    const char *const PATH_NAME              = user_data;
+    gs_free_error GError *error              = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+
+    if (nm_json_vt()) {
+        _connection_from_file_fail(PATH_NAME, NULL, TYPE_ETHERNET, &error);
+
+        g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY);
+        g_assert(strstr(error->message, _("invalid json")));
+    } else
+        connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL);
+}
+
+static void
+test_write_team_master(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingTeam *               s_team;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  expected_config =
+        "{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}";
+    shvarFile *f;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Team Master",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "team0",
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_TEAM_SETTING_NAME,
+                 NULL);
+
+    /* Team setting */
+    s_team = (NMSettingTeam *) nm_setting_team_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_team));
+
+    g_object_set(s_team, NM_SETTING_TEAM_CONFIG, expected_config, NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "DEVICETYPE", "Team");
+    _svGetValue_check(f, "TEAM_CONFIG", expected_config);
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_team_port(gconstpointer user_data)
+{
+    const char *const    PATH_NAME = user_data;
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingTeamPort *  s_team_port;
+    const char *         expected_config = "{\"p4p1\": {\"prio\": -10, \"sticky\": true}}";
+
+    connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "team0");
+
+    s_team_port = nm_connection_get_setting_team_port(connection);
+    g_assert(s_team_port);
+    g_assert_cmpstr(nm_setting_team_port_get_config(s_team_port), ==, expected_config);
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_team_port(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingTeamPort *           s_team_port;
+    NMSettingWired *              s_wired;
+    const char *                  expected_config = "{\"p4p1\": {\"prio\": -10, \"sticky\": true}}";
+    shvarFile *                   f;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Team Port",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "team0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_TEAM_SETTING_NAME,
+                 NULL);
+
+    /* Team setting */
+    s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_team_port));
+    g_object_set(s_team_port, NM_SETTING_TEAM_PORT_CONFIG, expected_config, NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Team_Port.cexpected",
+                           &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "TYPE", NULL);
+    _svGetValue_check(f, "DEVICETYPE", "TeamPort");
+    _svGetValue_check(f, "TEAM_PORT_CONFIG", expected_config);
+    _svGetValue_check(f, "TEAM_MASTER", "team0");
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_write_team_infiniband_port(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingTeamPort *           s_team_port;
+    NMSettingInfiniband *         s_inf;
+    const char *                  expected_config = "{\"inf1\": {\"prio\": -10, \"sticky\": true}}";
+    shvarFile *                   f;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Team Infiniband Port",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_INFINIBAND_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "team0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_TEAM_SETTING_NAME,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "inf1",
+                 NULL);
+
+    /* Team setting */
+    s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_team_port));
+    g_object_set(s_team_port, NM_SETTING_TEAM_PORT_CONFIG, expected_config, NULL);
+
+    /* Infiniband setting */
+    s_inf = (NMSettingInfiniband *) nm_setting_infiniband_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_inf));
+    g_object_set(s_inf, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Team_Infiniband_Port.cexpected",
+                           &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "TYPE", "InfiniBand");
+    _svGetValue_check(f, "DEVICETYPE", "TeamPort");
+    _svGetValue_check(f, "TEAM_PORT_CONFIG", expected_config);
+    _svGetValue_check(f, "TEAM_MASTER", "team0");
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+test_read_team_port_empty_config(void)
+{
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-team-port-empty-config",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "team0");
+
+    /* Normalization adds a team-port setting */
+    g_assert(nm_connection_get_setting_team_port(connection));
+
+    /* empty/missing config */
+    g_assert(!nm_setting_team_port_get_config(nm_connection_get_setting_team_port(connection)));
+
+    g_object_unref(connection);
+}
+
+static void
+test_team_reread_slave(void)
+{
+    nmtst_auto_unlinkfile char *testfile       = NULL;
+    gs_unref_object NMConnection *connection_1 = NULL;
+    gs_unref_object NMConnection *connection_2 = NULL;
+    gs_unref_object NMConnection *reread       = NULL;
+    gboolean                      reread_same  = FALSE;
+    NMSettingConnection *         s_con;
+
+    connection_1 =
+        nmtst_create_connection_from_keyfile("[connection]\n"
+                                             "id=team-slave-enp31s0f1-142\n"
+                                             "uuid=74f435bb-ede4-415a-9d48-f580b60eba04\n"
+                                             "type=vlan\n"
+                                             "autoconnect=false\n"
+                                             "interface-name=enp31s0f1-142\n"
+                                             "master=team142\n"
+                                             "permissions=\n"
+                                             "slave-type=team\n"
+                                             "\n"
+                                             "[vlan]\n"
+                                             "egress-priority-map=\n"
+                                             "flags=1\n"
+                                             "id=142\n"
+                                             "ingress-priority-map=\n"
+                                             "parent=enp31s0f1\n",
+                                             "/test_team_reread_slave");
+
+    /* to double-check keyfile syntax, re-create the connection by hand. */
+    connection_2 = nmtst_create_minimal_connection("team-slave-enp31s0f1-142",
+                                                   "74f435bb-ede4-415a-9d48-f580b60eba04",
+                                                   NM_SETTING_VLAN_SETTING_NAME,
+                                                   &s_con);
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "enp31s0f1-142",
+                 NM_SETTING_CONNECTION_MASTER,
+                 "team142",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 "team",
+                 NULL);
+    g_object_set(nm_connection_get_setting_vlan(connection_2),
+                 NM_SETTING_VLAN_FLAGS,
+                 1,
+                 NM_SETTING_VLAN_ID,
+                 142,
+                 NM_SETTING_VLAN_PARENT,
+                 "enp31s0f1",
+                 NULL);
+    nm_connection_add_setting(connection_2, nm_setting_team_port_new());
+    nmtst_connection_normalize(connection_2);
+
+    nmtst_assert_connection_equals(connection_1, FALSE, connection_2, FALSE);
+
+    _writer_new_connection_reread((nmtst_get_rand_uint32() % 2) ? connection_1 : connection_2,
+                                  TEST_SCRATCH_DIR,
+                                  &testfile,
+                                  TEST_IFCFG_DIR "/ifcfg-team-slave-enp31s0f1-142.cexpected",
+                                  &reread,
+                                  &reread_same);
+    _assert_reread_same((nmtst_get_rand_uint32() % 2) ? connection_1 : connection_2, reread);
+    g_assert(reread_same);
+    g_clear_object(&reread);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_VLAN, NULL);
+    nmtst_assert_connection_equals((nmtst_get_rand_uint32() % 2) ? connection_1 : connection_2,
+                                   FALSE,
+                                   reread,
+                                   FALSE);
+}
+
+static void
+test_read_proxy_basic(void)
+{
+    NMConnection *  connection;
+    NMSettingProxy *s_proxy;
+
+    /* Test basic proxy configuration */
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-read-proxy-basic",
+                                       NULL,
+                                       TYPE_ETHERNET,
+                                       NULL);
+
+    /* ===== Proxy setting ===== */
+    s_proxy = nm_connection_get_setting_proxy(connection);
+    g_assert(s_proxy);
+
+    /* Proxy method */
+    g_assert_cmpint(nm_setting_proxy_get_method(s_proxy), ==, NM_SETTING_PROXY_METHOD_AUTO);
+    g_assert(nm_setting_proxy_get_browser_only(s_proxy));
+    g_assert_cmpstr(nm_setting_proxy_get_pac_url(s_proxy),
+                    ==,
+                    "http://wpad.mycompany.com/wpad.dat");
+
+    g_object_unref(connection);
+}
+
+static void
+test_write_proxy_basic(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingProxy *              s_proxy;
+    const char *                  expected_url = "https://wpad.neverland.org/wpad.dat";
+    shvarFile *                   f;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Proxy Basic",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Proxy setting */
+    s_proxy = (NMSettingProxy *) nm_setting_proxy_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_proxy));
+    g_object_set(s_proxy, NM_SETTING_PROXY_METHOD, NM_SETTING_PROXY_METHOD_AUTO, NULL);
+    g_object_set(s_proxy, NM_SETTING_PROXY_PAC_URL, expected_url, NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-Test_Write_Proxy_Basic.cexpected",
+                           &testfile);
+
+    f = _svOpenFile(testfile);
+    _svGetValue_check(f, "TYPE", "Ethernet");
+    _svGetValue_check(f, "PROXY_METHOD", "auto");
+    _svGetValue_check(f, "PAC_URL", expected_url);
+    svCloseFile(f);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+/*****************************************************************************/
+
+static const char *
+_svUnescape(const char *str, char **to_free)
+{
+    const char *  s;
+    gs_free char *str_free = NULL;
+
+    g_assert(str);
+    g_assert(to_free);
+
+    if (str[0] == '\0') {
+        /* avoid static string "" */
+        str = (str_free = g_strdup(str));
+    }
+
+    s = svUnescape(str, to_free);
+    if (*to_free) {
+        g_assert(s == *to_free);
+        g_assert(s[0]);
+    } else {
+        g_assert(s == NULL || (!s[0] && (s < str || s > strchr(str, '\0')))
+                 || (s[0] && s >= str && s <= strchr(str, '\0')));
+    }
+    return s;
+}
+
+typedef struct {
+    const char *val;
+    const char *exp;
+    bool        can_concat : 1;
+    bool        needs_ascii_separator : 1;
+} UnescapeTestData;
+
+static void
+do_svUnescape_assert(const char *str, const char *expected)
+{
+    gs_free char *to_free = NULL;
+    const char *  s;
+
+    s = _svUnescape(str, &to_free);
+    g_assert_cmpstr(s, ==, expected);
+
+    /* check we can make a round-trip */
+    if (expected) {
+        gs_free char *s1_free = NULL;
+        gs_free char *s2_free = NULL;
+        const char *  s1, *s2;
+
+        s1 = svEscape(expected, &s1_free);
+        g_assert(s1);
+
+        s2 = _svUnescape(s1, &s2_free);
+        g_assert(s2);
+
+        g_assert_cmpstr(s2, ==, expected);
+    }
+}
+
+static void
+do_svUnescape_combine_ansi_append(GString *               str_val,
+                                  GString *               str_exp,
+                                  const UnescapeTestData *data,
+                                  gboolean                honor_needs_ascii_separator)
+{
+    g_string_append(str_val, data->val);
+    g_string_append(str_exp, data->exp);
+    if (honor_needs_ascii_separator && data->needs_ascii_separator) {
+        /* the string has an open escape sequence. We must ensure that when
+         * combining it with another sequence, that they don't merge into
+         * something different. for example "\xa" + "a" must not result in
+         * "\xaa". Instead, we add a space in between to get "\xa a". */
+        g_string_append(str_val, " ");
+        g_string_append(str_exp, " ");
+    }
+}
+
+static void
+do_svUnescape_combine_ansi(GString *               str_val,
+                           GString *               str_exp,
+                           const UnescapeTestData *data_ansi,
+                           gsize                   data_len,
+                           gssize                  idx)
+{
+    gsize i, j;
+
+    g_string_set_size(str_val, 0);
+    g_string_set_size(str_exp, 0);
+    g_string_append(str_val, "$'");
+    if (idx < 0) {
+        for (i = -idx; i > 0; i--) {
+            j = nmtst_get_rand_uint32() % data_len;
+            if (!data_ansi[j].can_concat) {
+                i++;
+                continue;
+            }
+            do_svUnescape_combine_ansi_append(str_val, str_exp, &data_ansi[j], i > 1);
+        }
+    } else {
+        g_assert_cmpint(idx, <, data_len);
+        do_svUnescape_combine_ansi_append(str_val, str_exp, &data_ansi[idx], FALSE);
+    }
+    g_string_append(str_val, "'");
+}
+
+static void
+test_svUnescape(void)
+{
+#define V0(v_value, v_expected)                                       \
+    {                                                                 \
+        .val = "" v_value "", .exp = v_expected, .can_concat = FALSE, \
+    }
+#define V1(v_value, v_expected)                                              \
+    {                                                                        \
+        .val = "" v_value "", .exp = v_expected, .can_concat = !!v_expected, \
+    }
+#define V2(v_value, v_expected)                                      \
+    {                                                                \
+        .val = "" v_value "", .exp = v_expected, .can_concat = TRUE, \
+        .needs_ascii_separator = TRUE,                               \
+    }
+    const UnescapeTestData data_full[] = {
+        V1("", ""),
+        V0("'", NULL),
+        V1("'x'", "x"),
+        V1("'  '", "  "),
+        V1("'x'", "x"),
+        V0("\"", NULL),
+        V0("\\", NULL),
+        V0(" ", ""),
+        V0("   ", ""),
+        V0("a;   #", "a"),
+        V0(" ;   #", ""),
+        V0(";   ", ""),
+        V0("; ;", NULL),
+        V0(" ; a #", NULL),
+        V0(" ; a;;  #", NULL),
+        V0("a; ; #", NULL),
+        V0("\t  # ", ""),
+        V0("\t  #a", ""),
+        V0("\t  #a\r", ""),
+        V0("\r", ""),
+        V1("\\\"", "\""),
+        V1("\\`", "`"),
+        V1("\\$", "$"),
+        V1("\\\\", "\\"),
+        V1("\\a", "a"),
+        V1("\\b", "b"),
+        V1("\\'", "'"),
+        V1("\\~", "~"),
+        V1("\\\t", "\t"),
+        V1("\"\\\"\"", "\""),
+        V1("\"\\`\"", "`"),
+        V1("\"\\$\"", "$"),
+        V1("\"\\\\\"", "\\"),
+        V1("\"\\a\"", "\\a"),
+        V1("\"\\b\"", "\\b"),
+        V1("\"\\\t\"", "\\\t"),
+        V0("ab\r", "ab"),
+        V0("a'b'\r ", "ab"),
+        V0("a'b' \r", "ab"),
+        V0("a#b", "a#b"),
+        V0("#b", "#b"),
+        V1("\'some string\'", "some string"),
+        V0("Bob outside LAN", NULL),
+        V1("x", "x"),
+        V1("'{ \"device\": \"team0\", \"link_watch\": { \"name\": \"ethtool\" } }'",
+           "{ \"device\": \"team0\", \"link_watch\": { \"name\": \"ethtool\" } }"),
+        V1("'{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}'",
+           "{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}"),
+        V1("x\"\"b", "xb"),
+        V1("x\"c\"b", "xcb"),
+        V1("\"c\"b", "cb"),
+        V1("\"c\"\\'b", "c'b"),
+        V1("$''", ""),
+        V1("$'\\n'", "\n"),
+        V0("$'\\'", NULL),
+        V1("$'\\x'", "\\x"),
+        V1("$'\\xa'", "\xa"),
+        V0("$'\\x0'", ""),
+        V1("$'\\x12'", "\x12"),
+        V1("$'\\x12A'",
+           "\x12"
+           "A"),
+        V1("$'\\x12t'", "\x12t"),
+        V1("\"aa\\\"\"", "aa\""),
+        V1("\"aa\\\"b\"c", "aa\"bc"),
+        V1("\"aa\\\"\"b", "aa\"b"),
+
+        /* the following is not shell behavior, but kept for backward compatibility
+         * with old svEscape(). */
+        V0("\"\\'\"", "'"),
+        V0("\"\\~\"", "~"),
+        V0("\"b\\~b\"", "b~b"),
+        V0("\"\\~\\~\"", "~~"),
+        V0("\"\\~\\'\"", "~'"),
+
+        /* the following is shell-behavior, because it doesn't look like written
+         * by old svEscape(). */
+        V1("\"\\~~\"", "\\~~"),
+        V1("\"\\a\\'\"", "\\a\\'"),
+        V1("x\"\\~\"", "x\\~"),
+        V1("\"\\'\"''", "\\'"),
+        V0("\"b\\~b\" ", "b\\~b"),
+        V1("\"b\\~b\"x", "b\\~bx"),
+    };
+    const UnescapeTestData data_ansi[] = {
+        /* strings inside $''. They cannot be compared directly, but must
+         * be wrapped by do_svUnescape_combine_ansi(). */
+        V1("", ""),
+        V1("a", "a"),
+        V1("b", "b"),
+        V1("x", "x"),
+        V1(" ", " "),
+        V1("\\a", "\a"),
+        V1("\\b", "\b"),
+        V1("\\e", "\e"),
+        V1("\\E", "\E"),
+        V1("\\f", "\f"),
+        V1("\\n", "\n"),
+        V1("\\r", "\r"),
+        V1("\\t", "\t"),
+        V1("\\v", "\v"),
+        V1("\\\\", "\\"),
+        V1("\\'", "'"),
+        V1("\\\"", "\""),
+        V1("\\?", "\?"),
+        V1("\\?", "?"),
+        V2("\\8", "\\8"),
+        V2("\\1", "\1"),
+        V1("\\1A", "\1A"),
+        V1("\\18", "\18"),
+        V2("\\01", "\1"),
+        V1("\\001", "\1"),
+        V0("\\008", ""),
+        V1("\\018", "\0018"),
+        V0("\\08", ""),
+        V1("\\18", "\0018"),
+        V1("\\x", "\\x"),
+        V2("\\xa", "\xa"),
+        V1("\\x12", "\x12"),
+        V1("\\x12A",
+           "\x12"
+           "A"),
+        V1("\\x12a",
+           "\x12"
+           "a"),
+        V1("\\x12t", "\x12t"),
+        V1("\\x1a", "\x1a"),
+        V1("\\x1A", "\x1A"),
+        V1("\\ut", "\\ut"),
+        V2("\\ua", "\xa"),
+        V1("\\uat", "\xat"),
+        V2("\\uab", "\xc2\xab"),
+        V1("\\uabt", "\xc2\xabt"),
+        V2("\\uabc", "\xe0\xaa\xbc"),
+        V1("\\uabct", "\xe0\xaa\xbct"),
+        V2("\\uabcd", "\xea\xaf\x8d"),
+        V1("\\uabcdt", "\xea\xaf\x8dt"),
+        V2("\\uabcde",
+           "\xea\xaf\x8d"
+           "e"),
+        V1("\\uabcdet",
+           "\xea\xaf\x8d"
+           "et"),
+        V1("\\Ut", "\\Ut"),
+        V2("\\Ua", "\xa"),
+        V1("\\Uat", "\xat"),
+        V2("\\Uab", "\xc2\xab"),
+        V1("\\Uabt", "\xc2\xabt"),
+        V2("\\Uabc", "\xe0\xaa\xbc"),
+        V1("\\Uabct", "\xe0\xaa\xbct"),
+        V2("\\Uabcd", "\xea\xaf\x8d"),
+        V1("\\Uabcdt", "\xea\xaf\x8dt"),
+        V2("\\Uabcde", "\xf2\xab\xb3\x9e"),
+        V1("\\Uabcdet", "\xf2\xab\xb3\x9et"),
+        V2("\\Uabcde0", "\xf8\xaa\xbc\xb7\xa0"),
+        V1("\\Uabcde0t", "\xf8\xaa\xbc\xb7\xa0t"),
+        V2("\\Uabcde01", "\xfc\x8a\xaf\x8d\xb8\x81"),
+        V1("\\Uabcde01t", "\xfc\x8a\xaf\x8d\xb8\x81t"),
+        V2("\\U0abcde01", "\xfc\x8a\xaf\x8d\xb8\x81"),
+        V1("\\U0abcde01t", "\xfc\x8a\xaf\x8d\xb8\x81t"),
+        V1("\\U00abcde01",
+           "\xf8\xaa\xbc\xb7\xa0"
+           "1"),
+        V1("\\U00abcde01t",
+           "\xf8\xaa\xbc\xb7\xa0"
+           "1t"),
+
+        /* control-x sequence is not supported */
+        V1("\\c", "\\c"),
+        V1("\\c1", "\\c1"),
+    };
+#undef V0
+#undef V1
+#undef V2
+    gsize                i;
+    nm_auto_free_gstring GString *str_val  = g_string_new(NULL);
+    nm_auto_free_gstring GString *str_val2 = g_string_new(NULL);
+    nm_auto_free_gstring GString *str_exp  = g_string_new(NULL);
+    nm_auto_free_gstring GString *str_exp2 = g_string_new(NULL);
+
+    do_svUnescape_assert("'  ''  '", "    ");
+
+    for (i = 0; i < G_N_ELEMENTS(data_full); i++)
+        do_svUnescape_assert(data_full[i].val, data_full[i].exp);
+
+    for (i = 0; i < G_N_ELEMENTS(data_ansi); i++) {
+        do_svUnescape_combine_ansi(str_val, str_exp, data_ansi, G_N_ELEMENTS(data_ansi), i);
+        do_svUnescape_assert(str_val->str, str_exp->str);
+    }
+
+    /* different values can be just concatenated... */
+    for (i = 0; i < 200; i++) {
+        gsize num_concat = (nmtst_get_rand_uint32() % 5) + 2;
+
+        g_string_set_size(str_val, 0);
+        g_string_set_size(str_exp, 0);
+
+        while (num_concat > 0) {
+            gsize idx;
+
+            if ((nmtst_get_rand_uint32() % 3 == 0)) {
+                do_svUnescape_combine_ansi(str_val2,
+                                           str_exp2,
+                                           data_ansi,
+                                           G_N_ELEMENTS(data_ansi),
+                                           -((int) ((nmtst_get_rand_uint32() % 5) + 1)));
+                continue;
+            }
+
+            idx = nmtst_get_rand_uint32() % G_N_ELEMENTS(data_full);
+            if (!data_full[idx].can_concat)
+                continue;
+            g_string_append(str_val, data_full[idx].val);
+            g_string_append(str_exp, data_full[idx].exp);
+            num_concat--;
+        }
+
+        switch (nmtst_get_rand_uint32() % 3) {
+        case 0:
+            g_string_append(str_val, " ");
+            break;
+        case 1:
+            g_string_append(str_val, "    ");
+            break;
+        }
+        switch (nmtst_get_rand_uint32() % 3) {
+        case 0:
+            g_string_append(str_val, " #");
+            break;
+        case 1:
+            g_string_append(str_val, " #foo");
+            break;
+        }
+        do_svUnescape_assert(str_val->str, str_exp->str);
+    }
+}
+
+/*****************************************************************************/
+
+static void
+test_write_unknown(gconstpointer test_data)
+{
+    nmtst_auto_unlinkfile char *filename_tmp_1    = g_strdup(TEST_SCRATCH_DIR_TMP "/tmp-1");
+    const char *                testfile          = test_data;
+    gs_free char *              testfile_expected = g_strconcat(testfile, ".expected", NULL);
+    shvarFile *                 sv;
+    gs_free_error GError *error = NULL;
+    gboolean              success;
+    gs_free char *        file_contents_out = NULL;
+    gs_free char *        file_contents_exp = NULL;
+
+    sv = _svOpenFile(testfile);
+
+    _nmtst_svFileSetName(sv, filename_tmp_1);
+    _nmtst_svFileSetModified(sv);
+
+    if (g_str_has_suffix(testfile, "ifcfg-test-write-unknown-4")) {
+        _svGetValue_check(sv, "IPADDR", "l4x");
+        _svGetValue_check(sv, "IPADDR2", "");
+        _svGetValue_check(sv, "IPADDR3", "name3-value");
+
+        svSetValue(sv, "IPADDR", "set-by-test1");
+        svSetValue(sv, "IPADDR2", NULL);
+        svSetValue(sv, "IPADDR2", "set-by-test2");
+        svSetValue(sv, "IPADDR3", "set-by-test3");
+
+        _svGetValue_check(sv, "METRIC", NULL);
+        _svGetValue_check(sv, "METRIC1", "");
+        _svGetValue_check(sv, "METRIC2", "");
+        _svGetValue_check(sv, "METRIC3", "x");
+
+        _svGetValue_check(sv, "IPADDR", "set-by-test1");
+        _svGetValue_check(sv, "IPADDR2", "set-by-test2");
+        _svGetValue_check(sv, "IPADDR3", "set-by-test3");
+    }
+
+    success = svWriteFile(sv, 0644, &error);
+    nmtst_assert_success(success, error);
+
+    file_contents_out = nmtst_file_get_contents(filename_tmp_1);
+    file_contents_exp = nmtst_file_get_contents(testfile_expected);
+
+    g_assert_cmpstr(file_contents_out, ==, file_contents_exp);
+
+    svCloseFile(sv);
+}
+
+/*****************************************************************************/
+
+static void
+test_read_vlan_trailing_spaces(void)
+{
+    const char *   testfile = TEST_IFCFG_DIR "/ifcfg-test-vlan-trailing-spaces";
+    NMConnection * connection;
+    gboolean       success;
+    GError *       error = NULL;
+    NMSettingVlan *s_vlan;
+    char *         contents = NULL;
+
+    /* Ensure there is whitespace at the end of the VLAN interface name,
+     * to prevent the whitespace getting stripped off and committed mistakenly
+     * by something in the future.
+     */
+    success = g_file_get_contents(testfile, &contents, NULL, &error);
+    g_assert_no_error(error);
+    g_assert(success);
+    g_assert(contents && contents[0]);
+    g_assert(strstr(contents, "DEVICE=\"vlan201\"  \n"));
+    g_free(contents);
+
+    connection = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan201");
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "enccw0.0.fb00");
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 201);
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
+
+    g_object_unref(connection);
+}
+
+/*****************************************************************************/
+
+static void
+test_sit_read_ignore(void)
+{
+    gs_free_error GError *error = NULL;
+
+    _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-sit-ignore",
+                               NULL,
+                               TYPE_ETHERNET,
+                               &error);
+    nmtst_assert_error(error, 0, 0, "*Ignoring unsupported connection due to IPV6TUNNELIPV4*");
+}
+
+/*****************************************************************************/
+
+static void
+do_test_utils_name(const char *desc, const char *path, gboolean only_ifcfg, const char *expected)
+{
+    const char *result;
+
+    result = utils_get_ifcfg_name(path, only_ifcfg);
+    g_assert_cmpstr(result, ==, expected);
+}
+
+static void
+test_utils_name(void)
+{
+    do_test_utils_name("get-ifcfg-name-bad", "/foo/bar/adfasdfadf", FALSE, NULL);
+    do_test_utils_name("get-ifcfg-name-good", "/foo/bar/ifcfg-FooBar", FALSE, "FooBar");
+    do_test_utils_name("get-ifcfg-name-keys", "/foo/bar/keys-BlahLbah", FALSE, "BlahLbah");
+    do_test_utils_name("get-ifcfg-name-route", "/foo/bar/route-Lalalala", FALSE, "Lalalala");
+    do_test_utils_name("get-ifcfg-name-only-ifcfg-route", "/foo/bar/route-Lalalala", TRUE, NULL);
+    do_test_utils_name("get-ifcfg-name-only-ifcfg-keys", "/foo/bar/keys-Lalalala", TRUE, NULL);
+    do_test_utils_name("get-ifcfg-name-no-path-ifcfg", "ifcfg-Lalalala", FALSE, "Lalalala");
+    do_test_utils_name("get-ifcfg-name-no-path-keys", "keys-Lalalala", FALSE, "Lalalala");
+    do_test_utils_name("get-ifcfg-name-no-path-route", "route-Lalalala", FALSE, "Lalalala");
+
+    do_test_utils_name("get-ifcfg-name-bad2-ifcfg", "/foo/bar/asdfasifcfg-Foobar", FALSE, NULL);
+    do_test_utils_name("get-ifcfg-name-bad2-keys", "/foo/bar/asdfaskeys-Foobar", FALSE, NULL);
+    do_test_utils_name("get-ifcfg-name-bad2-route", "/foo/bar/asdfasroute-Foobar", FALSE, NULL);
+}
+
+/*****************************************************************************/
+
+static void
+do_test_utils_path_ifcfg(const char *desc, const char *path, const char *expected)
+{
+    char *result;
+
+    result = utils_get_ifcfg_path(path);
+    g_assert_cmpstr(result, ==, expected);
+    g_free(result);
+}
+
+static void
+do_test_utils_path_keys(const char *desc, const char *path, const char *expected)
+{
+    char *result;
+
+    result = utils_get_keys_path(path);
+    g_assert_cmpstr(result, ==, expected);
+    g_free(result);
+}
+
+static void
+do_test_utils_path_route(const char *desc, const char *path, const char *expected)
+{
+    char *result;
+
+    result = utils_get_route_path(path);
+    g_assert_cmpstr(result, ==, expected);
+    g_free(result);
+}
+
+static void
+test_utils_path(void)
+{
+    do_test_utils_path_ifcfg("ifcfg-path-bad", "/foo/bar/adfasdfasdf", NULL);
+    do_test_utils_path_ifcfg("ifcfg-path-from-keys-no-path", "keys-BlahBlah", "ifcfg-BlahBlah");
+    do_test_utils_path_ifcfg("ifcfg-path-from-keys",
+                             "/foo/bar/keys-BlahBlah",
+                             "/foo/bar/ifcfg-BlahBlah");
+    do_test_utils_path_ifcfg("ifcfg-path-from-route",
+                             "/foo/bar/route-BlahBlah",
+                             "/foo/bar/ifcfg-BlahBlah");
+
+    do_test_utils_path_keys("keys-path-bad", "/foo/bar/asdfasdfasdfasdf", NULL);
+    do_test_utils_path_keys("keys-path-from-ifcfg-no-path", "ifcfg-FooBar", "keys-FooBar");
+    do_test_utils_path_keys("keys-path-from-ifcfg",
+                            "/foo/bar/ifcfg-FooBar",
+                            "/foo/bar/keys-FooBar");
+    do_test_utils_path_keys("keys-path-from-route",
+                            "/foo/bar/route-FooBar",
+                            "/foo/bar/keys-FooBar");
+
+    do_test_utils_path_route("route-path-bad", "/foo/bar/asdfasdfasdfasdf", NULL);
+    do_test_utils_path_route("route-path-from-ifcfg-no-path", "ifcfg-FooBar", "route-FooBar");
+    do_test_utils_path_route("route-path-from-ifcfg",
+                             "/foo/bar/ifcfg-FooBar",
+                             "/foo/bar/route-FooBar");
+    do_test_utils_path_route("route-path-from-keys",
+                             "/foo/bar/keys-FooBar",
+                             "/foo/bar/route-FooBar");
+}
+
+/*****************************************************************************/
+
+static void
+do_test_utils_ignored(const char *desc, const char *path, gboolean expected_ignored)
+{
+    gboolean result;
+
+    result = utils_should_ignore_file(path, FALSE);
+    g_assert(result == expected_ignored);
+}
+
+static void
+test_utils_ignore(void)
+{
+    do_test_utils_ignored("ignored-ifcfg", "ifcfg-FooBar", FALSE);
+    do_test_utils_ignored("ignored-keys", "keys-FooBar", FALSE);
+    do_test_utils_ignored("ignored-route", "route-FooBar", FALSE);
+    do_test_utils_ignored("ignored-bak", "ifcfg-FooBar" BAK_TAG, TRUE);
+    do_test_utils_ignored("ignored-tilde", "ifcfg-FooBar" TILDE_TAG, TRUE);
+    do_test_utils_ignored("ignored-orig", "ifcfg-FooBar" ORIG_TAG, TRUE);
+    do_test_utils_ignored("ignored-rej", "ifcfg-FooBar" REJ_TAG, TRUE);
+    do_test_utils_ignored("ignored-rpmnew", "ifcfg-FooBar" RPMNEW_TAG, TRUE);
+    do_test_utils_ignored("ignored-augnew", "ifcfg-FooBar" AUGNEW_TAG, TRUE);
+    do_test_utils_ignored("ignored-augtmp", "ifcfg-FooBar" AUGTMP_TAG, TRUE);
+}
+
+/*****************************************************************************/
+
+static void
+test_sriov_read(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingSriov *              s_sriov;
+    NMSriovVF *                   vf;
+    GVariant *                    variant;
+    GError *                      error = NULL;
+    char *                        str;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-sriov", NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0");
+
+    s_sriov = NM_SETTING_SRIOV(nm_connection_get_setting(connection, NM_TYPE_SETTING_SRIOV));
+    g_assert(s_sriov);
+
+    g_assert_cmpint(nm_setting_sriov_get_total_vfs(s_sriov), ==, 16);
+    g_assert_cmpint(nm_setting_sriov_get_num_vfs(s_sriov), ==, 3);
+    g_assert_cmpint(nm_setting_sriov_get_autoprobe_drivers(s_sriov), ==, NM_TERNARY_FALSE);
+
+    /* VF 3 */
+    vf = nm_setting_sriov_get_vf(s_sriov, 0);
+    g_assert(vf);
+    g_assert_cmpint(nm_sriov_vf_get_index(vf), ==, 3);
+
+    variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MAC);
+    g_assert(variant);
+    g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING));
+    g_assert_cmpstr(g_variant_get_string(variant, NULL), ==, "55:44:33:22:11:00");
+
+    variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK);
+    g_assert(variant);
+    g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN));
+    g_assert_cmpint(g_variant_get_boolean(variant), ==, TRUE);
+
+    /* VF 12 */
+    vf  = nm_setting_sriov_get_vf(s_sriov, 1);
+    str = nm_utils_sriov_vf_to_str(vf, FALSE, &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(str, ==, "12 min-tx-rate=100 trust=false vlans=1.200.ad");
+    g_free(str);
+
+    /* VF 15 */
+    vf  = nm_setting_sriov_get_vf(s_sriov, 2);
+    str = nm_utils_sriov_vf_to_str(vf, FALSE, &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(str, ==, "15 mac=01:23:45:67:89:ab max-tx-rate=200 vlans=2");
+    g_free(str);
+}
+
+static void
+test_sriov_write(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMSettingWired *              s_wired;
+    NMSettingSriov *              s_sriov;
+    NMSriovVF *                   vf;
+    gs_unref_ptrarray GPtrArray *vfs = NULL;
+    NMIPAddress *                addr;
+    GError *                     error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write SR-IOV config",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "eth0",
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+
+    /* SRIOV setting */
+    s_sriov = (NMSettingSriov *) nm_setting_sriov_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_sriov));
+
+    vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref);
+
+    vf = nm_utils_sriov_vf_from_str("2 mac=55:55:55:55:55:55 vlans=3.10.ad;10", &error);
+    nmtst_assert_success(vf, error);
+    g_ptr_array_add(vfs, vf);
+
+    vf = nm_utils_sriov_vf_from_str("19 spoof-check=true", &error);
+    nmtst_assert_success(vf, error);
+    g_ptr_array_add(vfs, vf);
+
+    g_object_set(s_sriov,
+                 NM_SETTING_SRIOV_TOTAL_VFS,
+                 64,
+                 NM_SETTING_SRIOV_VFS,
+                 vfs,
+                 NM_SETTING_SRIOV_AUTOPROBE_DRIVERS,
+                 NM_TERNARY_TRUE,
+                 NULL);
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-test-sriov-write.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+test_tc_read(void)
+{
+    NMConnection *     connection;
+    NMSettingTCConfig *s_tc;
+    NMTCQdisc *        qdisc;
+    NMTCTfilter *      filter;
+    char *             str;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-tc", NULL, TYPE_ETHERNET, NULL);
+
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0");
+
+    s_tc = nm_connection_get_setting_tc_config(connection);
+    g_assert(s_tc);
+
+    g_assert_cmpint(nm_setting_tc_config_get_num_qdiscs(s_tc), ==, 1);
+    qdisc = nm_setting_tc_config_get_qdisc(s_tc, 0);
+    g_assert(qdisc);
+    g_assert_cmpint(nm_tc_qdisc_get_parent(qdisc), ==, TC_H_ROOT);
+    g_assert_cmpint(nm_tc_qdisc_get_handle(qdisc), ==, TC_H_UNSPEC);
+    g_assert_cmpstr(nm_tc_qdisc_get_kind(qdisc), ==, "fq_codel");
+
+    g_assert_cmpint(nm_setting_tc_config_get_num_tfilters(s_tc), ==, 1);
+    filter = nm_setting_tc_config_get_tfilter(s_tc, 0);
+    g_assert(filter);
+    str = nm_utils_tc_tfilter_to_str(filter, NULL);
+    g_assert_cmpstr(str, ==, "parent 1234: matchall action simple sdata Hello");
+    g_free(str);
+
+    g_object_unref(connection);
+}
+
+static void
+test_tc_write(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMSettingWired *              s_wired;
+    NMSettingTCConfig *           s_tc;
+    NMTCQdisc *                   qdisc;
+    NMTCTfilter *                 tfilter;
+    NMIPAddress *                 addr;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write TC config",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "eth0",
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+
+    /* TC setting */
+    s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_tc));
+
+    qdisc = nm_tc_qdisc_new("pfifo_fast", TC_H_MAKE(0x2468 << 16, 0x2), &error);
+    g_assert_no_error(error);
+    nm_setting_tc_config_add_qdisc(s_tc, qdisc);
+    nm_tc_qdisc_unref(qdisc);
+
+    tfilter =
+        nm_utils_tc_tfilter_from_str("parent 1234: matchall action simple sdata Hello", &error);
+    g_assert_no_error(error);
+    nm_setting_tc_config_add_tfilter(s_tc, tfilter);
+    nm_tc_tfilter_unref(tfilter);
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-test-tc-write.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_BOND, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+test_well_known_keys(void)
+{
+    gsize i;
+
+    for (i = 0; i < G_N_ELEMENTS(nms_ifcfg_well_known_keys); i++) {
+        const NMSIfcfgKeyTypeInfo *ti = &nms_ifcfg_well_known_keys[i];
+
+        g_assert(ti->key_name);
+        g_assert(ti->key_name[0]);
+        g_assert(NM_FLAGS_HAS(ti->key_flags, NMS_IFCFG_KEY_TYPE_WELL_KNOWN));
+        g_assert(
+            nm_utils_is_power_of_two(ti->key_flags
+                                     & (NMS_IFCFG_KEY_TYPE_IS_PLAIN | NMS_IFCFG_KEY_TYPE_IS_NUMBERED
+                                        | NMS_IFCFG_KEY_TYPE_IS_PREFIX)));
+    }
+
+    for (i = 1; i < G_N_ELEMENTS(nms_ifcfg_well_known_keys); i++) {
+        const NMSIfcfgKeyTypeInfo *ti_prev = &nms_ifcfg_well_known_keys[i - 1];
+        const NMSIfcfgKeyTypeInfo *ti      = &nms_ifcfg_well_known_keys[i];
+
+        g_assert_cmpstr(ti_prev->key_name, <, ti->key_name);
+    }
+
+    for (i = 0; i < G_N_ELEMENTS(nms_ifcfg_well_known_keys); i++) {
+        const NMSIfcfgKeyTypeInfo *ti       = &nms_ifcfg_well_known_keys[i];
+        gs_free char *             key_name = NULL;
+        gssize                     idx;
+
+        g_assert(ti == nms_ifcfg_well_known_key_find_info(ti->key_name, &idx));
+        g_assert_cmpint(i, ==, idx);
+
+        key_name = g_strdup(ti->key_name);
+        g_assert(ti == nms_ifcfg_well_known_key_find_info(key_name, &idx));
+        g_assert_cmpint(i, ==, idx);
+    }
+
+#define _test_well_known(key, expected)                        \
+    G_STMT_START                                               \
+    {                                                          \
+        const NMSIfcfgKeyTypeInfo *_ti;                        \
+        const char *               _expected = (expected);     \
+                                                               \
+        _ti = nms_ifcfg_rh_utils_is_well_known_key("" key ""); \
+        if (!_expected) {                                      \
+            g_assert(!_ti);                                    \
+        } else {                                               \
+            g_assert(_ti);                                     \
+            g_assert_cmpstr(_ti->key_name, ==, _expected);     \
+        }                                                      \
+    }                                                          \
+    G_STMT_END
+
+#define _test_well_known_plain(key) _test_well_known("" key "", "" key "")
+
+    _test_well_known_plain("ONBOOT");
+    _test_well_known("NM_USER_", NULL);
+    _test_well_known("NM_USER_x", "NM_USER_");
+    _test_well_known("IPADDR", "IPADDR");
+    _test_well_known("IPADDR1", "IPADDR");
+    _test_well_known("IPADDRx", NULL);
+}
+
+/*****************************************************************************/
+
+static void
+_do_utils_has_route_file_new_syntax_size(gboolean    has_new_syntax,
+                                         const char *content,
+                                         gssize      content_len)
+{
+    nmtst_auto_unlinkfile char *testfile =
+        g_strdup(TEST_SCRATCH_DIR "/utils-has-route-file-new-syntax-test.txt");
+    gboolean val;
+
+    nmtst_file_set_contents_size(testfile, content, content_len);
+
+    val = utils_has_route_file_new_syntax(testfile);
+
+    g_assert_cmpint(val, ==, has_new_syntax);
+}
+#define _do_utils_has_route_file_new_syntax(has_new_syntax, content) \
+    _do_utils_has_route_file_new_syntax_size(has_new_syntax, (content), NM_STRLEN(content))
+
+static void
+test_utils_has_route_file_new_syntax(void)
+{
+    _do_utils_has_route_file_new_syntax(TRUE, "");
+    _do_utils_has_route_file_new_syntax(FALSE, "\0");
+    _do_utils_has_route_file_new_syntax(FALSE, "\n");
+    _do_utils_has_route_file_new_syntax(FALSE, "ADDRESS=bogus");
+    _do_utils_has_route_file_new_syntax(FALSE, "ADDRESS=bogus\0");
+    _do_utils_has_route_file_new_syntax(TRUE, "ADDRESS1=b\0ogus\0");
+    _do_utils_has_route_file_new_syntax(TRUE, "ADDRESS1=bogus\0");
+    _do_utils_has_route_file_new_syntax(TRUE, "\n\n\tADDRESS1=bogus\0");
+    _do_utils_has_route_file_new_syntax(FALSE, "\n\n\tADDRESS=bogus\n");
+    _do_utils_has_route_file_new_syntax(TRUE, "\n\n\tADDRESS=bogus\n  ADDRESS000=\n");
+    _do_utils_has_route_file_new_syntax(FALSE, "\n\n\tROUTE1=bogus\n  ADDRES=\n");
+    _do_utils_has_route_file_new_syntax(FALSE, "\n\n\tADDRESS=bogus\n  ADDRESS\000000=\n");
+}
+
+/*****************************************************************************/
+
+static void
+test_ethtool_names(void)
+{
+    static const struct {
+        NMEthtoolID ethtool_id;
+        const char *kernel_name;
+    } kernel_names[] = {
+        {NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"},
+        {NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"},
+        {NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"},
+        {NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"},
+        {NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"},
+        {NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"},
+        {NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"},
+        {NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"},
+    };
+    const struct {
+        guint nm_ethtool_id_first;
+        guint nm_ethtool_id_last;
+    } s_idxs[] = {
+        {_NM_ETHTOOL_ID_FEATURE_FIRST, _NM_ETHTOOL_ID_FEATURE_LAST},
+        {_NM_ETHTOOL_ID_COALESCE_FIRST, _NM_ETHTOOL_ID_COALESCE_LAST},
+        {_NM_ETHTOOL_ID_RING_FIRST, _NM_ETHTOOL_ID_RING_LAST},
+    };
+    const NMEthtoolData *data;
+    NMEthtoolID          id;
+    guint                i, k;
+
+    for (k = 0; k < sizeof(s_idxs) / sizeof(*s_idxs); ++k) {
+        for (id = s_idxs[k].nm_ethtool_id_first; id <= s_idxs[k].nm_ethtool_id_last; id++) {
+            const char *ifcfg_rh_name;
+
+            g_assert(id >= 0);
+            g_assert(id < G_N_ELEMENTS(_nm_ethtool_ifcfg_names));
+            ifcfg_rh_name = _nm_ethtool_ifcfg_names[id];
+            g_assert(ifcfg_rh_name && ifcfg_rh_name[0]);
+
+            for (i = s_idxs[k].nm_ethtool_id_first; i < s_idxs[k].nm_ethtool_id_last; i++) {
+                if (i != id)
+                    g_assert_cmpstr(ifcfg_rh_name, !=, _nm_ethtool_ifcfg_names[i]);
+            }
+
+            g_assert_cmpstr(nms_ifcfg_rh_utils_get_ethtool_name(id), ==, ifcfg_rh_name);
+
+            data = nms_ifcfg_rh_utils_get_ethtool_by_name(ifcfg_rh_name, nm_ethtool_id_to_type(id));
+
+            g_assert(data);
+            g_assert(data->id == id);
+        }
+    }
+
+    for (i = 0; i < G_N_ELEMENTS(kernel_names); i++) {
+        const char *name = kernel_names[i].kernel_name;
+
+        id = kernel_names[i].ethtool_id;
+
+        data = nms_ifcfg_rh_utils_get_ethtool_by_name(name, nm_ethtool_id_to_type(id));
+
+        g_assert(data);
+        g_assert(data->id == id);
+        g_assert_cmpstr(nms_ifcfg_rh_utils_get_ethtool_name(id), !=, name);
+    }
+}
+
+/*****************************************************************************/
+
+#define TPATH "/settings/plugins/ifcfg-rh/"
+
+#define TEST_IFCFG_WIFI_OPEN_SSID_LONG_QUOTED \
+    TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-long-quoted"
+#define TEST_IFCFG_WIFI_OPEN_SSID_LONG_HEX TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-long-hex"
+
+#define DEFAULT_HEX_PSK "7d308b11df1b4243b0f78e5f3fc68cdbb9a264ed0edf4c188edf329ff5b467f0"
+
+NMTST_DEFINE();
+
+int
+main(int argc, char **argv)
+{
+    int errsv;
+
+    nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT");
+
+    if (g_mkdir_with_parents(TEST_SCRATCH_DIR_TMP, 0755) != 0) {
+        errsv = errno;
+        g_error("failure to create test directory \"%s\": %s",
+                TEST_SCRATCH_DIR_TMP,
+                nm_strerror_native(errsv));
+    }
+
+    g_test_add_func(TPATH "svUnescape", test_svUnescape);
+
+    g_test_add_data_func(TPATH "write-unknown/1",
+                         TEST_IFCFG_DIR "/ifcfg-test-write-unknown-1",
+                         test_write_unknown);
+    g_test_add_data_func(TPATH "write-unknown/2",
+                         TEST_IFCFG_DIR "/ifcfg-test-write-unknown-2",
+                         test_write_unknown);
+    g_test_add_data_func(TPATH "write-unknown/3",
+                         TEST_IFCFG_DIR "/ifcfg-test-write-unknown-3",
+                         test_write_unknown);
+    g_test_add_data_func(TPATH "write-unknown/4",
+                         TEST_IFCFG_DIR "/ifcfg-test-write-unknown-4",
+                         test_write_unknown);
+
+    g_test_add_func(TPATH "vlan-trailing-spaces", test_read_vlan_trailing_spaces);
+
+    g_test_add_func(TPATH "unmanaged", test_read_unmanaged);
+    g_test_add_func(TPATH "unmanaged-unrecognized", test_read_unmanaged_unrecognized);
+    g_test_add_func(TPATH "unrecognized", test_read_unrecognized);
+    g_test_add_func(TPATH "basic", test_read_basic);
+    g_test_add_func(TPATH "miscellaneous-variables", test_read_miscellaneous_variables);
+    g_test_add_func(TPATH "variables-corner-cases", test_read_variables_corner_cases);
+    g_test_add_data_func(TPATH "no-prefix/8",
+                         GUINT_TO_POINTER(8),
+                         test_read_wired_static_no_prefix);
+    g_test_add_data_func(TPATH "no-prefix/16",
+                         GUINT_TO_POINTER(16),
+                         test_read_wired_static_no_prefix);
+    g_test_add_data_func(TPATH "no-prefix/24",
+                         GUINT_TO_POINTER(24),
+                         test_read_wired_static_no_prefix);
+    g_test_add_data_func(TPATH "static-ip6-only-gw/_NULL_",
+                         NULL,
+                         test_write_wired_static_ip6_only_gw);
+    g_test_add_data_func(TPATH "static-ip6-only-gw/::", "::", test_write_wired_static_ip6_only_gw);
+    g_test_add_data_func(TPATH "static-ip6-only-gw/2001:db8:8:4::2",
+                         "2001:db8:8:4::2",
+                         test_write_wired_static_ip6_only_gw);
+    g_test_add_data_func(TPATH "static-ip6-only-gw/::ffff:255.255.255.255",
+                         "::ffff:255.255.255.255",
+                         test_write_wired_static_ip6_only_gw);
+    g_test_add_func(TPATH "ip6/disabled", test_write_ip6_disabled);
+    g_test_add_func(TPATH "read-dns-options", test_read_dns_options);
+    g_test_add_func(TPATH "clear-master", test_clear_master);
+
+    nmtst_add_test_func(TPATH "read-static",
+                        test_read_wired_static,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-static",
+                        "System test-wired-static",
+                        GINT_TO_POINTER(TRUE));
+    nmtst_add_test_func(TPATH "read-static-bootproto",
+                        test_read_wired_static,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-static-bootproto",
+                        "System test-wired-static-bootproto",
+                        GINT_TO_POINTER(FALSE));
+
+    g_test_add_func(TPATH "read-netmask-1", test_read_netmask_1);
+
+    g_test_add_func(TPATH "read-dhcp", test_read_wired_dhcp);
+    g_test_add_func(TPATH "read-dhcp-plus-ip", test_read_wired_dhcp_plus_ip);
+    g_test_add_func(TPATH "read-shared-plus-ip", test_read_wired_shared_plus_ip);
+    g_test_add_func(TPATH "read-dhcp-send-hostname", test_read_write_wired_dhcp_send_hostname);
+    g_test_add_func(TPATH "read-dhcpv6-hostname-fallback",
+                    test_read_wired_dhcpv6_hostname_fallback);
+    g_test_add_func(TPATH "read-global-gateway", test_read_wired_global_gateway);
+    g_test_add_func(TPATH "read-global-gateway-ignore", test_read_wired_global_gateway_ignore);
+    g_test_add_func(TPATH "read-obsolete-gateway-n", test_read_wired_obsolete_gateway_n);
+    g_test_add_func(TPATH "read-never-default", test_read_wired_never_default);
+    g_test_add_func(TPATH "read-defroute-no", test_read_wired_defroute_no);
+    g_test_add_func(TPATH "read-defroute-no-gatewaydev-yes",
+                    test_read_wired_defroute_no_gatewaydev_yes);
+    g_test_add_func(TPATH "routes/read-static", test_read_wired_static_routes);
+    g_test_add_func(TPATH "routes/read-static-legacy", test_read_wired_static_routes_legacy);
+
+    nmtst_add_test_func(TPATH "wired/read/manual/1",
+                        test_read_wired_ipv4_manual,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-1",
+                        "System test-wired-ipv4-manual-1");
+    nmtst_add_test_func(TPATH "wired/read/manual/2",
+                        test_read_wired_ipv4_manual,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-2",
+                        "System test-wired-ipv4-manual-2");
+    nmtst_add_test_func(TPATH "wired/read/manual/3",
+                        test_read_wired_ipv4_manual,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-3",
+                        "System test-wired-ipv4-manual-3");
+    nmtst_add_test_func(TPATH "wired/read/manual/4",
+                        test_read_wired_ipv4_manual,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-ipv4-manual-4",
+                        "System test-wired-ipv4-manual-4");
+
+    g_test_add_func(TPATH "user/1", test_user_1);
+
+    g_test_add_func(TPATH "wired/ipv6-manual", test_read_wired_ipv6_manual);
+
+    nmtst_add_test_func(TPATH "wired-ipv6-only/0",
+                        test_read_wired_ipv6_only,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-ipv6-only",
+                        "System test-wired-ipv6-only");
+    nmtst_add_test_func(TPATH "wired-ipv6-only/1",
+                        test_read_wired_ipv6_only,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-ipv6-only-1",
+                        "System test-wired-ipv6-only-1");
+
+    g_test_add_func(TPATH "wired/dhcpv6-only", test_read_wired_dhcp6_only);
+    g_test_add_func(TPATH "wired/autoip", test_read_wired_autoip);
+    g_test_add_func(TPATH "wired/onboot/no", test_read_onboot_no);
+    g_test_add_func(TPATH "wired/no-ip", test_read_noip);
+    g_test_add_func(TPATH "802-1x/peap/mschapv2", test_read_wired_8021x_peap_mschapv2);
+
+    nmtst_add_test_func(TPATH "test-wired-8021x-tls/agent",
+                        test_read_wired_8021x_tls_secret_flags,
+                        TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-tls-agent",
+                        GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+    nmtst_add_test_func(
+        TPATH "test-wired-8021x-tls/always",
+        test_read_wired_8021x_tls_secret_flags,
+        TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-tls-always",
+        GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED));
+
+    g_test_add_func(TPATH "802-1x/subj-matches", test_read_write_802_1X_subj_matches);
+    g_test_add_func(TPATH "802-1x/ttls-eapgtc", test_read_802_1x_ttls_eapgtc);
+    g_test_add_func(TPATH "802-1x/password_raw", test_read_write_802_1x_password_raw);
+    g_test_add_func(TPATH "802-1x/tls-p12-no-client-cert", test_read_802_1x_tls_p12_no_client_cert);
+
+    g_test_add_data_func(TPATH "wired/read/aliases/good/0",
+                         GINT_TO_POINTER(0),
+                         test_read_wired_aliases_good);
+    g_test_add_data_func(TPATH "wired/read/aliases/good/3",
+                         GINT_TO_POINTER(3),
+                         test_read_wired_aliases_good);
+    g_test_add_func(TPATH "wired/read/aliases/bad1", test_read_wired_aliases_bad_1);
+    g_test_add_func(TPATH "wired/read/aliases/bad2", test_read_wired_aliases_bad_2);
+    g_test_add_func(TPATH "wifi/read/open", test_read_wifi_open);
+    g_test_add_func(TPATH "wifi/read/open/auto", test_read_wifi_open_auto);
+    g_test_add_func(TPATH "wifi/read/open/hex-ssid", test_read_wifi_open_ssid_hex);
+    g_test_add_func(TPATH "wifi/read/open-ssid/bad-hex", test_read_wifi_open_ssid_hex_bad);
+    g_test_add_data_func(TPATH "wifi/read/open-ssid/long-hex",
+                         TEST_IFCFG_WIFI_OPEN_SSID_LONG_HEX,
+                         test_read_wifi_open_ssid_bad);
+    g_test_add_data_func(TPATH "wifi/read/open-ssid/long-quoted",
+                         TEST_IFCFG_WIFI_OPEN_SSID_LONG_QUOTED,
+                         test_read_wifi_open_ssid_bad);
+    g_test_add_func(TPATH "wifi/read/open/quoted-ssid", test_read_wifi_open_ssid_quoted);
+    g_test_add_func(TPATH "wifi/read/wep", test_read_wifi_wep);
+    g_test_add_func(TPATH "wifi/read/wep/adhoc", test_read_wifi_wep_adhoc);
+    g_test_add_func(TPATH "wifi/read/wep/passphrase", test_read_wifi_wep_passphrase);
+    g_test_add_func(TPATH "wifi/read/wep/40-ascii", test_read_wifi_wep_40_ascii);
+    g_test_add_func(TPATH "wifi/read/wep/104-ascii", test_read_wifi_wep_104_ascii);
+    g_test_add_func(TPATH "wifi/read/leap", test_read_wifi_leap);
+
+    nmtst_add_test_func(TPATH "wifi-leap-secret-flags/agent",
+                        test_read_wifi_leap_secret_flags,
+                        TEST_IFCFG_DIR "/ifcfg-test-wifi-leap-agent",
+                        GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+    nmtst_add_test_func(
+        TPATH "wifi-leap-secret-flags/ask",
+        test_read_wifi_leap_secret_flags,
+        TEST_IFCFG_DIR "/ifcfg-test-wifi-leap-always-ask",
+        GINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED));
+
+    g_test_add_func(TPATH "wifi/read/wpa-psk", test_read_wifi_wpa_psk);
+    g_test_add_func(TPATH "wifi/read/wpa-psk/2", test_read_wifi_wpa_psk_2);
+    g_test_add_func(TPATH "wifi/read/wpa-psk/unquoted", test_read_wifi_wpa_psk_unquoted);
+    g_test_add_func(TPATH "wifi/read/wpa-psk/unquoted2", test_read_wifi_wpa_psk_unquoted2);
+    g_test_add_func(TPATH "wifi/read/wpa-psk/adhoc", test_read_wifi_wpa_psk_adhoc);
+    g_test_add_func(TPATH "wifi/read/wpa-psk/hex", test_read_wifi_wpa_psk_hex);
+    g_test_add_func(TPATH "wifi/read/sae", test_read_wifi_sae);
+    g_test_add_func(TPATH "wifi/read/owe", test_read_wifi_owe);
+    g_test_add_func(TPATH "wifi/read/dynamic-wep/leap", test_read_wifi_dynamic_wep_leap);
+    g_test_add_func(TPATH "wifi/read/wpa/eap/tls", test_read_wifi_wpa_eap_tls);
+    g_test_add_func(TPATH "wifi/read/wpa/eap/ttls/tls", test_read_wifi_wpa_eap_ttls_tls);
+    g_test_add_func(TPATH "wifi/read/wpa/eap-suite-b-192/tls",
+                    test_read_wifi_wpa_eap_suite_b_192_tls);
+    g_test_add_func(TPATH "wifi/read/dynamic-wep/eap/ttls/chap", test_read_wifi_wep_eap_ttls_chap);
+    g_test_add_func(TPATH "wifi/read-band-a", test_read_wifi_band_a);
+    g_test_add_func(TPATH "wifi/read-band-a-channel-mismatch",
+                    test_read_wifi_band_a_channel_mismatch);
+    g_test_add_func(TPATH "wifi/read-band-bg-channel-mismatch",
+                    test_read_wifi_band_bg_channel_mismatch);
+    g_test_add_func(TPATH "wifi/read-hidden", test_read_wifi_hidden);
+
+    nmtst_add_test_func(TPATH "wifi/read-mac-random-always",
+                        test_read_wifi_mac_random,
+                        "always",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_ALWAYS));
+    nmtst_add_test_func(TPATH "wifi/read-mac-random-never",
+                        test_read_wifi_mac_random,
+                        "never",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_NEVER));
+    nmtst_add_test_func(TPATH "wifi/read-mac-random-default",
+                        test_read_wifi_mac_random,
+                        "default",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_DEFAULT));
+    nmtst_add_test_func(TPATH "wifi/read-mac-random-missing",
+                        test_read_wifi_mac_random,
+                        "missing",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_DEFAULT));
+
+    nmtst_add_test_func(TPATH "wifi/write-mac-random-always",
+                        test_write_wifi_mac_random,
+                        "always",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_ALWAYS),
+                        "always");
+    nmtst_add_test_func(TPATH "wifi/write-mac-random-never",
+                        test_write_wifi_mac_random,
+                        "never",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_NEVER),
+                        "never");
+    nmtst_add_test_func(TPATH "wifi/write-mac-random-default",
+                        test_write_wifi_mac_random,
+                        "default",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_DEFAULT),
+                        "default");
+    nmtst_add_test_func(TPATH "wifi/write-mac-random-missing",
+                        test_write_wifi_mac_random,
+                        "missing",
+                        GINT_TO_POINTER(NM_SETTING_MAC_RANDOMIZATION_NEVER),
+                        "never");
+
+    g_test_add_func(TPATH "wifi/read/wep-no-keys", test_read_wifi_wep_no_keys);
+    g_test_add_func(TPATH "wifi/read/wep-agent-keys", test_read_wifi_wep_agent_keys);
+    g_test_add_func(TPATH "infiniband/read", test_read_infiniband);
+    g_test_add_func(TPATH "vlan/read", test_read_vlan_interface);
+    g_test_add_func(TPATH "vlan/read-flags-1", test_read_vlan_flags_1);
+    g_test_add_func(TPATH "vlan/read-flags-2", test_read_vlan_flags_2);
+    g_test_add_func(TPATH "vlan/read/only-vlanid", test_read_vlan_only_vlan_id);
+    g_test_add_func(TPATH "vlan/read/only-device", test_read_vlan_only_device);
+    g_test_add_func(TPATH "vlan/read/physdev", test_read_vlan_physdev);
+    g_test_add_func(TPATH "vlan/read/reorder-hdr-1", test_read_vlan_reorder_hdr_1);
+    g_test_add_func(TPATH "vlan/read/reorder-hdr-2", test_read_vlan_reorder_hdr_2);
+    g_test_add_func(TPATH "wired/read/read-wake-on-lan", test_read_wired_wake_on_lan);
+    g_test_add_func(TPATH "wired/read/read-auto-negotiate-off", test_read_wired_auto_negotiate_off);
+    g_test_add_func(TPATH "wired/read/read-auto-negotiate-on", test_read_wired_auto_negotiate_on);
+    g_test_add_func(TPATH "wired/read/unkwnown-ethtool-opt", test_read_wired_unknown_ethtool_opt);
+
+    g_test_add_func(TPATH "wired/write/static", test_write_wired_static);
+    g_test_add_func(TPATH "wired/write/static-with-generic", test_write_wired_static_with_generic);
+    g_test_add_func(TPATH "wired/write/static-ip6-only", test_write_wired_static_ip6_only);
+    g_test_add_func(TPATH "wired/write-static-routes", test_write_wired_static_routes);
+    g_test_add_func(TPATH "wired/read-write-static-routes-legacy",
+                    test_read_write_static_routes_legacy);
+    g_test_add_func(TPATH "wired/write/dhcp", test_write_wired_dhcp);
+    g_test_add_func(TPATH "wired/write-dhcp-plus-ip", test_write_wired_dhcp_plus_ip);
+    g_test_add_func(TPATH "wired/write/dhcp-8021x-peap-mschapv2",
+                    test_write_wired_dhcp_8021x_peap_mschapv2);
+    g_test_add_func(TPATH "wired/write/match", test_write_wired_match);
+    g_test_add_func(TPATH "wired/write/routing-rules", test_write_routing_rules);
+
+#define _add_test_write_wired_8021x_tls(testpath, scheme, flags) \
+    nmtst_add_test_func(testpath,                                \
+                        test_write_wired_8021x_tls,              \
+                        GINT_TO_POINTER(scheme),                 \
+                        GINT_TO_POINTER(flags))
+    _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/1",
+                                    NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                    NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+    _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/2",
+                                    NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                    NM_SETTING_SECRET_FLAG_NOT_SAVED);
+    _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/3",
+                                    NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                    NM_SETTING_SECRET_FLAG_AGENT_OWNED
+                                        | NM_SETTING_SECRET_FLAG_NOT_SAVED);
+    _add_test_write_wired_8021x_tls(TPATH "wired-8021x-tls/4",
+                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
+                                    NM_SETTING_SECRET_FLAG_NONE);
+
+    g_test_add_func(TPATH "wired/write-aliases", test_write_wired_aliases);
+    g_test_add_func(TPATH "ipv4/write-static-addresses-GATEWAY", test_write_gateway);
+    g_test_add_func(TPATH "wired/write-wake-on-lan", test_write_wired_wake_on_lan);
+    g_test_add_func(TPATH "wired/write-auto-negotiate-off", test_write_wired_auto_negotiate_off);
+    g_test_add_func(TPATH "wired/write-auto-negotiate-on", test_write_wired_auto_negotiate_on);
+    g_test_add_func(TPATH "wifi/write/open", test_write_wifi_open);
+    g_test_add_func(TPATH "wifi/write/open/hex-ssid", test_write_wifi_open_hex_ssid);
+    g_test_add_func(TPATH "wifi/write/wep", test_write_wifi_wep);
+    g_test_add_func(TPATH "wifi/write/wep/adhoc", test_write_wifi_wep_adhoc);
+    g_test_add_func(TPATH "wifi/write/wep/passphrase", test_write_wifi_wep_passphrase);
+    g_test_add_func(TPATH "wifi/write/wep/40-ascii", test_write_wifi_wep_40_ascii);
+    g_test_add_func(TPATH "wifi/write/wep/104-ascii", test_write_wifi_wep_104_ascii);
+    g_test_add_func(TPATH "wifi/write/leap", test_write_wifi_leap);
+    g_test_add_data_func(TPATH "wifi/write/leap/flags/agent",
+                         GUINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED),
+                         test_write_wifi_leap_secret_flags);
+    g_test_add_data_func(TPATH "wifi/write/leap/flags/not-saved",
+                         GUINT_TO_POINTER(NM_SETTING_SECRET_FLAG_NOT_SAVED),
+                         test_write_wifi_leap_secret_flags);
+    g_test_add_data_func(
+        TPATH "wifi/write/leap/flags/agent-and-not-saved",
+        GUINT_TO_POINTER(NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED),
+        test_write_wifi_leap_secret_flags);
+
+#define _add_test_write_wifi_wpa_psk(testpath, name, wep_group, wpa, wpa2, psk) \
+    nmtst_add_test_func(testpath,                                               \
+                        test_write_wifi_wpa_psk,                                \
+                        name,                                                   \
+                        GPOINTER_TO_INT(wep_group),                             \
+                        GPOINTER_TO_INT(wpa),                                   \
+                        GPOINTER_TO_INT(wpa2),                                  \
+                        psk)
+    _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-psk-write",
+                                 "Test Write Wifi WPA PSK",
+                                 FALSE,
+                                 TRUE,
+                                 FALSE,
+                                 DEFAULT_HEX_PSK);
+    _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa2-psk-write",
+                                 "Test Write Wifi WPA2 PSK",
+                                 FALSE,
+                                 FALSE,
+                                 TRUE,
+                                 DEFAULT_HEX_PSK);
+    _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-wpa2-psk-write",
+                                 "Test Write Wifi WPA WPA2 PSK",
+                                 FALSE,
+                                 TRUE,
+                                 TRUE,
+                                 DEFAULT_HEX_PSK);
+    _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wep-wpa-wpa2-psk-write",
+                                 "Test Write Wifi WEP WPA WPA2 PSK",
+                                 TRUE,
+                                 TRUE,
+                                 TRUE,
+                                 DEFAULT_HEX_PSK);
+    _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-wpa2-psk-passphrase-write",
+                                 "Test Write Wifi WPA WPA2 PSK Passphrase",
+                                 FALSE,
+                                 TRUE,
+                                 TRUE,
+                                 "really insecure passphrase04!");
+    _add_test_write_wifi_wpa_psk(TPATH "wifi-wpa-psk/wpa-wpa2-psk-passphrase-write-spec-chars",
+                                 "Test Write Wifi WPA WPA2 PSK Passphrase Special Chars",
+                                 FALSE,
+                                 TRUE,
+                                 TRUE,
+                                 "blah`oops\"grr'$*@~!%\\");
+
+    g_test_add_func(TPATH "wifi/write/wpa/psk/adhoc", test_write_wifi_wpa_psk_adhoc);
+    g_test_add_func(TPATH "wifi/write/wpa/eap/tls", test_write_wifi_wpa_eap_tls);
+    g_test_add_func(TPATH "wifi/write/wpa/eap/ttls/tls", test_write_wifi_wpa_eap_ttls_tls);
+    g_test_add_func(TPATH "wifi/write/wpa/eap/ttls/mschapv2",
+                    test_write_wifi_wpa_eap_ttls_mschapv2);
+    g_test_add_func(TPATH "wifi/write/dynamic-wep/leap", test_write_wifi_dynamic_wep_leap);
+    g_test_add_func(TPATH "wifi/write-wpa-then-open", test_write_wifi_wpa_then_open);
+    g_test_add_func(TPATH "wifi/write-wpa-then-wep-with-perms",
+                    test_write_wifi_wpa_then_wep_with_perms);
+    g_test_add_func(TPATH "wifi/write-hidden", test_write_wifi_hidden);
+    g_test_add_func(TPATH "wifi/write-band-a", test_write_wifi_band_a);
+    g_test_add_func(TPATH "wifi/write-ap-mode", test_write_wifi_ap_mode);
+
+    g_test_add_func(TPATH "s390/read-qeth-static", test_read_wired_qeth_static);
+    g_test_add_func(TPATH "s390/write-qeth-dhcp", test_write_wired_qeth_dhcp);
+    g_test_add_func(TPATH "s390/read-ctc-static", test_read_wired_ctc_static);
+    g_test_add_func(TPATH "s390/write-ctc-dhcp", test_write_wired_ctc_dhcp);
+
+    g_test_add_func(TPATH "permissions/read", test_read_permissions);
+    g_test_add_func(TPATH "permissions/write", test_write_permissions);
+    g_test_add_func(TPATH "wifi/write-wep-agent-keys", test_write_wifi_wep_agent_keys);
+    g_test_add_func(TPATH "infiniband/write", test_write_infiniband);
+    g_test_add_func(TPATH "vlan/write", test_write_vlan);
+    g_test_add_func(TPATH "vlan/write-flags", test_write_vlan_flags);
+    g_test_add_func(TPATH "vlan/write-only-vlanid", test_write_vlan_only_vlanid);
+    g_test_add_func(TPATH "vlan/write-vlan-reorder-hdr", test_write_vlan_reorder_hdr);
+    g_test_add_func(TPATH "wired/write-missing-ipv6", test_write_ethernet_missing_ipv6);
+    g_test_add_func(TPATH "write-dns-options", test_write_dns_options);
+
+    g_test_add_func(TPATH "ibft/ignored", test_read_ibft_ignored);
+
+    g_test_add_func(TPATH "dcb/read-basic", test_read_dcb_basic);
+    g_test_add_func(TPATH "dcb/write-basic", test_write_dcb_basic);
+    g_test_add_func(TPATH "dcb/default-app-priorities", test_read_dcb_default_app_priorities);
+    g_test_add_func(TPATH "dcb/bad-booleans", test_read_dcb_bad_booleans);
+    g_test_add_func(TPATH "dcb/short-booleans", test_read_dcb_short_booleans);
+    g_test_add_func(TPATH "dcb/bad-uints", test_read_dcb_bad_uints);
+    g_test_add_func(TPATH "dcb/short-uints", test_read_dcb_short_uints);
+    g_test_add_func(TPATH "dcb/bad-percent", test_read_dcb_bad_percent);
+    g_test_add_func(TPATH "dcb/short-percent", test_read_dcb_short_percent);
+    g_test_add_func(TPATH "dcb/pgpct-not-100", test_read_dcb_pgpct_not_100);
+    g_test_add_data_func(TPATH "fcoe/fabric",
+                         (gpointer) NM_SETTING_DCB_FCOE_MODE_FABRIC,
+                         test_read_fcoe_mode);
+    g_test_add_data_func(TPATH "fcoe/vn2vn",
+                         (gpointer) NM_SETTING_DCB_FCOE_MODE_VN2VN,
+                         test_read_fcoe_mode);
+    g_test_add_data_func(TPATH "fcoe/write-fabric",
+                         (gpointer) NM_SETTING_DCB_FCOE_MODE_FABRIC,
+                         test_write_fcoe_mode);
+    g_test_add_data_func(TPATH "fcoe/write-vn2vn",
+                         (gpointer) NM_SETTING_DCB_FCOE_MODE_VN2VN,
+                         test_write_fcoe_mode);
+
+    g_test_add_func(TPATH "bond/read-master", test_read_bond_main);
+    g_test_add_func(TPATH "bond/read-master-eth-type", test_read_bond_eth_type);
+    g_test_add_func(TPATH "bond/read-slave", test_read_bond_slave);
+    g_test_add_func(TPATH "bond/read-slave-ib", test_read_bond_slave_ib);
+    g_test_add_func(TPATH "bond/write-master", test_write_bond_main);
+    g_test_add_func(TPATH "bond/write-slave", test_write_bond_slave);
+    g_test_add_func(TPATH "bond/write-slave-ib", test_write_bond_slave_ib);
+    g_test_add_func(TPATH "bond/bonding-opts-numeric-mode", test_read_bond_opts_mode_numeric);
+
+    g_test_add_func(TPATH "bridge/read-master", test_read_bridge_main);
+    g_test_add_func(TPATH "bridge/write-master", test_write_bridge_main);
+    g_test_add_func(TPATH "bridge/read-component", test_read_bridge_component);
+    g_test_add_func(TPATH "bridge/write-component", test_write_bridge_component);
+    g_test_add_func(TPATH "bridge/read-missing-stp", test_read_bridge_missing_stp);
+
+    g_test_add_data_func(TPATH "team/read-master-1",
+                         TEST_IFCFG_DIR "/ifcfg-test-team-master-1",
+                         test_read_team_master);
+    g_test_add_data_func(TPATH "team/read-master-2",
+                         TEST_IFCFG_DIR "/ifcfg-test-team-master-2",
+                         test_read_team_master);
+    g_test_add_data_func(TPATH "team/read-master-invalid",
+                         TEST_IFCFG_DIR "/ifcfg-test-team-master-invalid",
+                         test_read_team_master_invalid);
+    g_test_add_func(TPATH "team/write-master", test_write_team_master);
+    g_test_add_data_func(TPATH "team/read-port-1",
+                         TEST_IFCFG_DIR "/ifcfg-test-team-port-1",
+                         test_read_team_port);
+    g_test_add_data_func(TPATH "team/read-port-2",
+                         TEST_IFCFG_DIR "/ifcfg-test-team-port-2",
+                         test_read_team_port);
+    g_test_add_func(TPATH "team/write-port", test_write_team_port);
+    g_test_add_func(TPATH "team/write-infiniband-port", test_write_team_infiniband_port);
+    g_test_add_func(TPATH "team/read-port-empty-config", test_read_team_port_empty_config);
+    g_test_add_func(TPATH "team/reread-slave", test_team_reread_slave);
+
+    g_test_add_func(TPATH "proxy/read-proxy-basic", test_read_proxy_basic);
+    g_test_add_func(TPATH "proxy/write-proxy-basic", test_write_proxy_basic);
+
+    g_test_add_func(TPATH "sit/read/ignore", test_sit_read_ignore);
+
+    /* Stuff we expect to fail for now */
+    g_test_add_func(TPATH "pppoe/write-wired", test_write_wired_pppoe);
+    g_test_add_func(TPATH "vpn/write", test_write_vpn);
+    g_test_add_data_func(TPATH "wwan/write-gsm",
+                         GUINT_TO_POINTER(TRUE),
+                         test_write_mobile_broadband);
+    g_test_add_data_func(TPATH "wwan/write-cdma",
+                         GUINT_TO_POINTER(FALSE),
+                         test_write_mobile_broadband);
+
+    g_test_add_func(TPATH "no-trailing-newline", test_ifcfg_no_trailing_newline);
+
+    g_test_add_func(TPATH "utils/name", test_utils_name);
+    g_test_add_func(TPATH "utils/path", test_utils_path);
+    g_test_add_func(TPATH "utils/ignore", test_utils_ignore);
+
+    g_test_add_func(TPATH "sriov/read", test_sriov_read);
+    g_test_add_func(TPATH "sriov/write", test_sriov_write);
+
+    g_test_add_func(TPATH "tc/read", test_tc_read);
+    g_test_add_func(TPATH "tc/write", test_tc_write);
+    g_test_add_func(TPATH "utils/test_well_known_keys", test_well_known_keys);
+    g_test_add_func(TPATH "utils/test_utils_has_route_file_new_syntax",
+                    test_utils_has_route_file_new_syntax);
+
+    g_test_add_func(TPATH "utils/test_ethtool_names", test_ethtool_names);
+
+    return g_test_run();
+}
diff --git a/src/core/settings/plugins/ifupdown/meson.build b/src/core/settings/plugins/ifupdown/meson.build
new file mode 100644
index 00000000..dd252783
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/meson.build
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+libnms_ifupdown_core = static_library(
+  'nms-ifupdown-core',
+  sources: files(
+    'nms-ifupdown-interface-parser.c',
+    'nms-ifupdown-parser.c',
+  ),
+  dependencies: core_default_dep,
+  c_args: daemon_c_flags,
+)
+
+libnm_settings_plugin_ifupdown = shared_module(
+  'nm-settings-plugin-ifupdown',
+  sources: 'nms-ifupdown-plugin.c',
+  dependencies: core_plugin_dep,
+  c_args: daemon_c_flags,
+  link_with: libnms_ifupdown_core,
+  link_args: ldflags_linker_script_settings,
+  link_depends: linker_script_settings,
+  install: true,
+  install_dir: nm_plugindir,
+)
+
+core_plugins += libnm_settings_plugin_ifupdown
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
new file mode 100644
index 00000000..a8a9c40a
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -0,0 +1,388 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Tom Parker <palfrey@tevp.net>
+ * Copyright (C) 2004 Tom Parker
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifupdown-interface-parser.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <wordexp.h>
+#include <libgen.h>
+
+#include "nm-utils.h"
+
+/*****************************************************************************/
+
+static void
+_ifparser_source(if_parser *parser, const char *path, const char *en_dir, int quiet, int dir);
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "ifupdown"
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG(level, ...)                          \
+    nm_log((level),                                 \
+           _NMLOG_DOMAIN,                           \
+           NULL,                                    \
+           NULL,                                    \
+           "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+static void
+add_block(if_parser *parser, const char *type, const char *name)
+{
+    if_block *ifb;
+    gsize     l_type, l_name;
+
+    l_type = strlen(type) + 1;
+    l_name = strlen(name) + 1;
+
+    ifb = g_malloc(sizeof(if_block) + l_type + l_name);
+    memcpy((char *) ifb->name, name, l_name);
+    ifb->type = &ifb->name[l_name];
+    memcpy((char *) ifb->type, type, l_type);
+    c_list_init(&ifb->data_lst_head);
+    c_list_link_tail(&parser->block_lst_head, &ifb->block_lst);
+}
+
+static void
+add_data(if_parser *parser, const char *key, const char *data)
+{
+    if_block *last_block;
+    if_data * ifd;
+    char *    idx;
+    gsize     l_key, l_data;
+
+    last_block = c_list_last_entry(&parser->block_lst_head, if_block, block_lst);
+
+    /* Check if there is a block where we can attach our data */
+    if (!last_block)
+        return;
+
+    l_key  = strlen(key) + 1;
+    l_data = strlen(data) + 1;
+
+    ifd = g_malloc(sizeof(if_data) + l_key + l_data);
+    memcpy((char *) ifd->key, key, l_key);
+    ifd->data = &ifd->key[l_key];
+    memcpy((char *) ifd->data, data, l_data);
+
+    /* Normalize keys. Convert '_' to '-', as ifupdown accepts both variants.
+     * When querying keys via ifparser_getkey(), use '-'. */
+    idx = (char *) ifd->key;
+    while ((idx = strchr(idx, '_')))
+        *(idx++) = '-';
+
+    c_list_link_tail(&last_block->data_lst_head, &ifd->data_lst);
+}
+
+/* join values in src with spaces into dst;  dst needs to be large enough */
+static char *
+join_values_with_spaces(char *dst, char **src)
+{
+    if (dst != NULL) {
+        *dst = '\0';
+        if (src != NULL && *src != NULL) {
+            strcat(dst, *src);
+
+            for (src++; *src != NULL; src++) {
+                strcat(dst, " ");
+                strcat(dst, *src);
+            }
+        }
+    }
+    return (dst);
+}
+
+static void
+_recursive_ifparser(if_parser *parser, const char *eni_file, int quiet)
+{
+    FILE *inp;
+    char  line[255];
+    int   skip_to_block  = 1;
+    int   skip_long_line = 0;
+    int   offs           = 0;
+
+    /* Check if interfaces file exists and open it */
+    if (!g_file_test(eni_file, G_FILE_TEST_EXISTS)) {
+        if (!quiet)
+            _LOGW("interfaces file %s doesn't exist", eni_file);
+        return;
+    }
+    inp = fopen(eni_file, "re");
+    if (inp == NULL) {
+        if (!quiet)
+            _LOGW("Can't open %s", eni_file);
+        return;
+    }
+    if (!quiet)
+        _LOGI("      interface-parser: parsing file %s", eni_file);
+
+    while (!feof(inp)) {
+        char *token[128]; /* 255 chars can only be split into 127 tokens */
+        char  value[255]; /* large enough to join previously split tokens */
+        char *safeptr;
+        int   toknum;
+        int   len = 0;
+
+        char *ptr = fgets(line + offs, 255 - offs, inp);
+        if (ptr == NULL)
+            break;
+
+        len = strlen(line);
+        /* skip over-long lines */
+        if (!feof(inp) && len > 0 && line[len - 1] != '\n') {
+            if (!skip_long_line) {
+                if (!quiet)
+                    _LOGW("Skipping over-long-line '%s...'", line);
+            }
+            skip_long_line = 1;
+            continue;
+        }
+
+        /* trailing '\n' found: remove it & reset offset to 0 */
+        if (len > 0 && line[len - 1] == '\n') {
+            line[--len] = '\0';
+            offs        = 0;
+        }
+
+        /* if we're in long_line_skip mode, terminate it for real next line */
+        if (skip_long_line) {
+            if (len == 0 || line[len - 1] != '\\')
+                skip_long_line = 0;
+            continue;
+        }
+
+        /* unwrap wrapped lines */
+        if (len > 0 && line[len - 1] == '\\') {
+            offs = len - 1;
+            continue;
+        }
+
+#define SPACES " \t"
+        /* tokenize input; */
+        for (toknum = 0, token[toknum] = strtok_r(line, SPACES, &safeptr); token[toknum] != NULL;
+             toknum++, token[toknum]   = strtok_r(NULL, SPACES, &safeptr))
+            ;
+
+        /* ignore comments and empty lines */
+        if (toknum == 0 || *token[0] == '#')
+            continue;
+
+        if (toknum < 2) {
+            if (!quiet) {
+                _LOGW("Can't parse interface line '%s'", join_values_with_spaces(value, token));
+            }
+            skip_to_block = 1;
+            continue;
+        }
+
+        /* There are six different stanzas:
+         * iface, mapping, auto, allow-*, source, and source-directory.
+         * Create a block for each of them except source and source-directory.  */
+
+        /* iface stanza takes at least 3 parameters */
+        if (nm_streq(token[0], "iface")) {
+            if (toknum < 4) {
+                if (!quiet) {
+                    _LOGW("Can't parse iface line '%s'", join_values_with_spaces(value, token));
+                }
+                continue;
+            }
+            add_block(parser, token[0], token[1]);
+            skip_to_block = 0;
+            add_data(parser, token[2], join_values_with_spaces(value, token + 3));
+        }
+        /* auto and allow-auto stanzas are equivalent,
+         * both can take multiple interfaces as parameters: add one block for each */
+        else if (NM_IN_STRSET(token[0], "auto", "allow-auto")) {
+            int i;
+
+            for (i = 1; i < toknum; i++)
+                add_block(parser, "auto", token[i]);
+            skip_to_block = 0;
+        } else if (nm_streq(token[0], "mapping")) {
+            add_block(parser, token[0], join_values_with_spaces(value, token + 1));
+            skip_to_block = 0;
+        }
+        /* allow-* can take multiple interfaces as parameters: add one block for each */
+        else if (g_str_has_prefix(token[0], "allow-")) {
+            int i;
+            for (i = 1; i < toknum; i++)
+                add_block(parser, token[0], token[i]);
+            skip_to_block = 0;
+        }
+        /* source and source-directory stanzas take one or more paths as parameters */
+        else if (NM_IN_STRSET(token[0], "source", "source-directory")) {
+            int   i;
+            char *en_dir;
+
+            skip_to_block = 0;
+            en_dir        = g_path_get_dirname(eni_file);
+            for (i = 1; i < toknum; ++i) {
+                if (nm_streq(token[0], "source-directory"))
+                    _ifparser_source(parser, token[i], en_dir, quiet, TRUE);
+                else
+                    _ifparser_source(parser, token[i], en_dir, quiet, FALSE);
+            }
+            g_free(en_dir);
+        } else {
+            if (skip_to_block) {
+                if (!quiet) {
+                    _LOGW("ignoring out-of-block data '%s'", join_values_with_spaces(value, token));
+                }
+            } else
+                add_data(parser, token[0], join_values_with_spaces(value, token + 1));
+        }
+    }
+    fclose(inp);
+
+    if (!quiet)
+        _LOGI("      interface-parser: finished parsing file %s", eni_file);
+}
+
+static void
+_ifparser_source(if_parser *parser, const char *path, const char *en_dir, int quiet, int dir)
+{
+    char *      abs_path;
+    const char *item;
+    wordexp_t   we;
+    GDir *      source_dir;
+    GError *    error = NULL;
+    uint        i;
+
+    if (g_path_is_absolute(path))
+        abs_path = g_strdup(path);
+    else
+        abs_path = g_build_filename(en_dir, path, NULL);
+
+    if (!quiet)
+        _LOGI("      interface-parser: source line includes interfaces file(s) %s", abs_path);
+
+    /* ifupdown uses WRDE_NOCMD for wordexp. */
+    if (wordexp(abs_path, &we, WRDE_NOCMD)) {
+        if (!quiet)
+            _LOGW("word expansion for %s failed", abs_path);
+    } else {
+        for (i = 0; i < we.we_wordc; i++) {
+            if (dir) {
+                source_dir = g_dir_open(we.we_wordv[i], 0, &error);
+                if (!source_dir) {
+                    if (!quiet) {
+                        _LOGW("Failed to open directory %s: %s", we.we_wordv[i], error->message);
+                    }
+                    g_clear_error(&error);
+                } else {
+                    while ((item = g_dir_read_name(source_dir)))
+                        _ifparser_source(parser, item, we.we_wordv[i], quiet, FALSE);
+                    g_dir_close(source_dir);
+                }
+            } else
+                _recursive_ifparser(parser, we.we_wordv[i], quiet);
+        }
+        wordfree(&we);
+    }
+    g_free(abs_path);
+}
+
+if_parser *
+ifparser_parse(const char *eni_file, int quiet)
+{
+    if_parser *parser;
+
+    parser = g_slice_new(if_parser);
+    c_list_init(&parser->block_lst_head);
+    _recursive_ifparser(parser, eni_file, quiet);
+    return parser;
+}
+
+static void
+_destroy_data(if_data *ifd)
+{
+    c_list_unlink_stale(&ifd->data_lst);
+    g_free(ifd);
+}
+
+static void
+_destroy_block(if_block *ifb)
+{
+    if_data *ifd;
+
+    while ((ifd = c_list_first_entry(&ifb->data_lst_head, if_data, data_lst)))
+        _destroy_data(ifd);
+    c_list_unlink_stale(&ifb->block_lst);
+    g_free(ifb);
+}
+
+void
+ifparser_destroy(if_parser *parser)
+{
+    if_block *ifb;
+
+    while ((ifb = c_list_first_entry(&parser->block_lst_head, if_block, block_lst)))
+        _destroy_block(ifb);
+    g_slice_free(if_parser, parser);
+}
+
+if_block *
+ifparser_getfirst(if_parser *parser)
+{
+    return c_list_first_entry(&parser->block_lst_head, if_block, block_lst);
+}
+
+guint
+ifparser_get_num_blocks(if_parser *parser)
+{
+    return c_list_length(&parser->block_lst_head);
+}
+
+if_block *
+ifparser_getif(if_parser *parser, const char *iface)
+{
+    if_block *ifb;
+
+    c_list_for_each_entry (ifb, &parser->block_lst_head, block_lst) {
+        if (nm_streq(ifb->type, "iface") && nm_streq(ifb->name, iface))
+            return ifb;
+    }
+    return NULL;
+}
+
+static if_data *
+ifparser_findkey(if_block *iface, const char *key)
+{
+    if_data *ifd;
+
+    c_list_for_each_entry (ifd, &iface->data_lst_head, data_lst) {
+        if (nm_streq(ifd->key, key))
+            return ifd;
+    }
+    return NULL;
+}
+
+const char *
+ifparser_getkey(if_block *iface, const char *key)
+{
+    if_data *ifd;
+
+    ifd = ifparser_findkey(iface, key);
+    return ifd ? ifd->data : NULL;
+}
+
+gboolean
+ifparser_haskey(if_block *iface, const char *key)
+{
+    return !!ifparser_findkey(iface, key);
+}
+
+guint
+ifparser_get_num_info(if_block *iface)
+{
+    return c_list_length(&iface->data_lst_head);
+}
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
new file mode 100644
index 00000000..dd0e15fb
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Tom Parker <palfrey@tevp.net>
+ * Copyright (C) 2004 Tom Parker
+ */
+
+#ifndef _INTERFACE_PARSER_H
+#define _INTERFACE_PARSER_H
+
+#include "c-list/src/c-list.h"
+
+typedef struct {
+    CList       data_lst;
+    const char *data;
+    const char  key[];
+} if_data;
+
+typedef struct {
+    CList       block_lst;
+    CList       data_lst_head;
+    const char *type;
+    const char  name[];
+} if_block;
+
+typedef struct {
+    CList block_lst_head;
+} if_parser;
+
+if_parser *ifparser_parse(const char *eni_file, int quiet);
+
+void ifparser_destroy(if_parser *parser);
+NM_AUTO_DEFINE_FCN0(if_parser *, _nm_auto_ifparser, ifparser_destroy);
+#define nm_auto_ifparser nm_auto(_nm_auto_ifparser)
+
+if_block *  ifparser_getif(if_parser *parser, const char *iface);
+if_block *  ifparser_getfirst(if_parser *parser);
+const char *ifparser_getkey(if_block *iface, const char *key);
+gboolean    ifparser_haskey(if_block *iface, const char *key);
+
+guint ifparser_get_num_blocks(if_parser *parser);
+guint ifparser_get_num_info(if_block *iface);
+
+#endif
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c
new file mode 100644
index 00000000..a4974a96
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -0,0 +1,686 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Alexander Sack <asac@ubuntu.com>
+ * Copyright (C) 2008 Canonical Ltd.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifupdown-parser.h"
+
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "nm-core-internal.h"
+#include "settings/nm-settings-plugin.h"
+
+#include "nms-ifupdown-plugin.h"
+#include "nms-ifupdown-parser.h"
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "ifupdown"
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG(level, ...)                          \
+    nm_log((level),                                 \
+           _NMLOG_DOMAIN,                           \
+           NULL,                                    \
+           NULL,                                    \
+           "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+#define _str_has_prefix(val, prefix, require_suffix)                   \
+    ({                                                                 \
+        const char *_val = (val);                                      \
+                                                                       \
+        (strncmp(_val, "" prefix "", NM_STRLEN(prefix)) == 0)          \
+            && (!(require_suffix) || _val[NM_STRLEN(prefix)] != '\0'); \
+    })
+
+static const char *
+_ifupdownplugin_guess_connection_type(if_block *block)
+{
+    const char *ret_type = NULL;
+
+    if (nm_streq0(ifparser_getkey(block, "inet"), "ppp"))
+        ret_type = NM_SETTING_PPP_SETTING_NAME;
+    else {
+        if_data *ifb;
+
+        c_list_for_each_entry (ifb, &block->data_lst_head, data_lst) {
+            if (_str_has_prefix(ifb->key, "wireless-", FALSE)
+                || _str_has_prefix(ifb->key, "wpa-", FALSE)) {
+                ret_type = NM_SETTING_WIRELESS_SETTING_NAME;
+                break;
+            }
+        }
+        if (!ret_type)
+            ret_type = NM_SETTING_WIRED_SETTING_NAME;
+    }
+
+    _LOGI("guessed connection type (%s) = %s", block->name, ret_type);
+    return ret_type;
+}
+
+struct _Mapping {
+    const char *   domain;
+    const gpointer target;
+};
+
+static gpointer
+map_by_mapping(struct _Mapping *mapping, const char *key)
+{
+    struct _Mapping *curr = mapping;
+
+    while (curr->domain) {
+        if (nm_streq(curr->domain, key))
+            return curr->target;
+        curr++;
+    }
+    return NULL;
+}
+
+static void
+update_wireless_setting_from_if_block(NMConnection *connection, if_block *block)
+{
+    if_data *       curr;
+    const char *    value     = ifparser_getkey(block, "inet");
+    struct _Mapping mapping[] = {{"ssid", "ssid"},
+                                 {"essid", "ssid"},
+                                 {"mode", "mode"},
+                                 {NULL, NULL}};
+
+    NMSettingWireless *wireless_setting = NULL;
+
+    if (nm_streq0(value, "ppp"))
+        return;
+
+    _LOGI("update wireless settings (%s).", block->name);
+    wireless_setting = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+
+    c_list_for_each_entry (curr, &block->data_lst_head, data_lst) {
+        if (_str_has_prefix(curr->key, "wireless-", TRUE)) {
+            const char *newkey = map_by_mapping(mapping, curr->key + NM_STRLEN("wireless-"));
+
+            _LOGI("wireless setting key: %s='%s'", newkey, curr->data);
+            if (nm_streq0(newkey, "ssid")) {
+                GBytes *ssid;
+                int     len = strlen(curr->data);
+
+                ssid = g_bytes_new(curr->data, len);
+                g_object_set(wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+                g_bytes_unref(ssid);
+                _LOGI("setting wireless ssid = %d", len);
+            } else if (nm_streq0(newkey, "mode")) {
+                if (!g_ascii_strcasecmp(curr->data, "Managed")
+                    || !g_ascii_strcasecmp(curr->data, "Auto"))
+                    g_object_set(wireless_setting,
+                                 NM_SETTING_WIRELESS_MODE,
+                                 NM_SETTING_WIRELESS_MODE_INFRA,
+                                 NULL);
+                else if (!g_ascii_strcasecmp(curr->data, "Ad-Hoc"))
+                    g_object_set(wireless_setting,
+                                 NM_SETTING_WIRELESS_MODE,
+                                 NM_SETTING_WIRELESS_MODE_ADHOC,
+                                 NULL);
+                else if (!g_ascii_strcasecmp(curr->data, "Master"))
+                    g_object_set(wireless_setting,
+                                 NM_SETTING_WIRELESS_MODE,
+                                 NM_SETTING_WIRELESS_MODE_AP,
+                                 NULL);
+                else
+                    _LOGW("Invalid mode '%s' (not 'Ad-Hoc', 'Ap', 'Managed', or 'Auto')",
+                          curr->data);
+            } else {
+                g_object_set(wireless_setting, newkey, curr->data, NULL);
+            }
+        } else if (_str_has_prefix(curr->key, "wpa-", TRUE)) {
+            const char *newkey = map_by_mapping(mapping, curr->key + NM_STRLEN("wpa-"));
+
+            if (nm_streq0(newkey, "ssid")) {
+                GBytes *ssid;
+                int     len = strlen(curr->data);
+
+                ssid = g_bytes_new(curr->data, len);
+                g_object_set(wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+                g_bytes_unref(ssid);
+                _LOGI("setting wpa ssid = %d", len);
+            } else if (newkey) {
+                g_object_set(wireless_setting, newkey, curr->data, NULL);
+                _LOGI("setting wpa newkey(%s)=data(%s)", newkey, curr->data);
+            }
+        }
+    }
+    nm_connection_add_setting(connection, (NMSetting *) wireless_setting);
+}
+
+typedef char *(*IfupdownStrDupeFunc)(gconstpointer value, gpointer data);
+typedef gpointer (*IfupdownStrToTypeFunc)(const char *value);
+
+static char *
+normalize_dupe_wireless_key(gpointer value, gpointer data)
+{
+    char *valuec     = value;
+    char *endc       = valuec + strlen(valuec);
+    char *delim      = valuec;
+    char *next       = delim;
+    char *result     = malloc(strlen(valuec) + 1);
+    char *result_cur = result;
+
+    while (*delim && (next = strchr(delim, '-')) != NULL) {
+        if (next == delim) {
+            delim++;
+            continue;
+        }
+        strncpy(result_cur, delim, next - delim);
+        result_cur += next - delim;
+        delim = next + 1;
+    }
+    if (*delim && strlen(valuec) > GPOINTER_TO_UINT(delim - valuec)) {
+        strncpy(result_cur, delim, endc - delim);
+        result_cur += endc - delim;
+    }
+    *result_cur = '\0';
+    return result;
+}
+
+static char *
+normalize_dupe(gpointer value, gpointer data)
+{
+    return g_strdup(value);
+}
+
+static char *
+normalize_tolower(gpointer value, gpointer data)
+{
+    return g_ascii_strdown(value, -1);
+}
+
+static char *
+normalize_psk(gpointer value, gpointer data)
+{
+    if (strlen(value) >= 8 && strlen(value) <= 64)
+        return g_strdup(value);
+    return NULL;
+}
+
+static gpointer
+string_to_gpointerint(const char *data)
+{
+    int result = (int) strtol(data, NULL, 10);
+    return GINT_TO_POINTER(result);
+}
+
+static gpointer
+string_to_glist_of_strings(const char *data)
+{
+    GSList *ret    = NULL;
+    char *  string = (char *) data;
+    while (string) {
+        char *next = NULL;
+        if ((next = strchr(string, ' ')) || (next = strchr(string, '\t'))
+            || (next = strchr(string, '\0'))) {
+            char *part = g_strndup(string, (next - string));
+            ret        = g_slist_append(ret, part);
+            if (*next)
+                string = next + 1;
+            else
+                string = NULL;
+        } else {
+            string = NULL;
+        }
+    }
+    return ret;
+}
+
+static void
+slist_free_all(gpointer slist)
+{
+    g_slist_free_full((GSList *) slist, g_free);
+}
+
+static void
+update_wireless_security_setting_from_if_block(NMConnection *connection, if_block *block)
+{
+    if_data *       curr;
+    const char *    value     = ifparser_getkey(block, "inet");
+    struct _Mapping mapping[] = {{"psk", "psk"},
+                                 {"identity", "leap-username"},
+                                 {"password", "leap-password"},
+                                 {"key", "wep-key0"},
+                                 {"key-mgmt", "key-mgmt"},
+                                 {"group", "group"},
+                                 {"pairwise", "pairwise"},
+                                 {"proto", "proto"},
+                                 {"pin", "pin"},
+                                 {"wep-key0", "wep-key0"},
+                                 {"wep-key1", "wep-key1"},
+                                 {"wep-key2", "wep-key2"},
+                                 {"wep-key3", "wep-key3"},
+                                 {"wep-tx-keyidx", "wep-tx-keyidx"},
+                                 {NULL, NULL}};
+
+    struct _Mapping dupe_mapping[] = {{"psk", normalize_psk},
+                                      {"identity", normalize_dupe},
+                                      {"password", normalize_dupe},
+                                      {"key", normalize_dupe_wireless_key},
+                                      {"key-mgmt", normalize_tolower},
+                                      {"group", normalize_tolower},
+                                      {"pairwise", normalize_tolower},
+                                      {"proto", normalize_tolower},
+                                      {"pin", normalize_dupe},
+                                      {"wep-key0", normalize_dupe_wireless_key},
+                                      {"wep-key1", normalize_dupe_wireless_key},
+                                      {"wep-key2", normalize_dupe_wireless_key},
+                                      {"wep-key3", normalize_dupe_wireless_key},
+                                      {"wep-tx-keyidx", normalize_dupe},
+                                      {NULL, NULL}};
+
+    struct _Mapping type_mapping[] = {{"group", string_to_glist_of_strings},
+                                      {"pairwise", string_to_glist_of_strings},
+                                      {"proto", string_to_glist_of_strings},
+                                      {"wep-tx-keyidx", string_to_gpointerint},
+                                      {NULL, NULL}};
+
+    struct _Mapping free_type_mapping[] = {{"group", slist_free_all},
+                                           {"pairwise", slist_free_all},
+                                           {"proto", slist_free_all},
+                                           {NULL, NULL}};
+
+    NMSettingWirelessSecurity *wireless_security_setting;
+    NMSettingWireless *        s_wireless;
+    gboolean                   security = FALSE;
+
+    if (nm_streq0(value, "ppp"))
+        return;
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_return_if_fail(s_wireless);
+
+    _LOGI("update wireless security settings (%s).", block->name);
+    wireless_security_setting = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new());
+
+    c_list_for_each_entry (curr, &block->data_lst_head, data_lst) {
+        if (_str_has_prefix(curr->key, "wireless-", TRUE)) {
+            const char *          key                  = curr->key + NM_STRLEN("wireless-");
+            char *                property_value       = NULL;
+            gpointer              typed_property_value = NULL;
+            const char *          newkey               = map_by_mapping(mapping, key);
+            IfupdownStrDupeFunc   dupe_func            = map_by_mapping(dupe_mapping, key);
+            IfupdownStrToTypeFunc type_map_func        = map_by_mapping(type_mapping, key);
+            GFreeFunc             free_func            = map_by_mapping(free_type_mapping, key);
+            if (!newkey || !dupe_func)
+                goto next;
+
+            property_value = (*dupe_func)(curr->data, connection);
+            _LOGI("setting wireless security key: %s=%s", newkey, property_value);
+
+            if (type_map_func) {
+                errno                = 0;
+                typed_property_value = (*type_map_func)(property_value);
+                if (errno)
+                    goto wireless_next;
+            }
+
+            g_object_set(wireless_security_setting,
+                         newkey,
+                         typed_property_value ?: property_value,
+                         NULL);
+            security = TRUE;
+
+wireless_next:
+            g_free(property_value);
+            if (typed_property_value && free_func)
+                (*free_func)(typed_property_value);
+
+        } else if (_str_has_prefix(curr->key, "wpa-", TRUE)) {
+            const char *          key                  = curr->key + NM_STRLEN("wpa-");
+            char *                property_value       = NULL;
+            gpointer              typed_property_value = NULL;
+            const char *          newkey               = map_by_mapping(mapping, key);
+            IfupdownStrDupeFunc   dupe_func            = map_by_mapping(dupe_mapping, key);
+            IfupdownStrToTypeFunc type_map_func        = map_by_mapping(type_mapping, key);
+            GFreeFunc             free_func            = map_by_mapping(free_type_mapping, key);
+            if (!newkey || !dupe_func)
+                goto next;
+
+            property_value = (*dupe_func)(curr->data, connection);
+            _LOGI("setting wpa security key: %s=%s",
+                  newkey,
+                  NM_IN_STRSET(newkey,
+                               "key",
+                               "leap-password",
+                               "pin",
+                               "psk",
+                               "wep-key0",
+                               "wep-key1",
+                               "wep-key2",
+                               "wep-key3")
+                      ? "<omitted>"
+                      : property_value);
+
+            if (type_map_func) {
+                errno                = 0;
+                typed_property_value = (*type_map_func)(property_value);
+                if (errno)
+                    goto wpa_next;
+            }
+
+            g_object_set(wireless_security_setting,
+                         newkey,
+                         typed_property_value ?: property_value,
+                         NULL);
+            security = TRUE;
+
+wpa_next:
+            g_free(property_value);
+            if (free_func && typed_property_value)
+                (*free_func)(typed_property_value);
+        }
+next:;
+    }
+
+    if (security)
+        nm_connection_add_setting(connection, NM_SETTING(wireless_security_setting));
+}
+
+static void
+update_wired_setting_from_if_block(NMConnection *connection, if_block *block)
+{
+    NMSettingWired *s_wired = NULL;
+    s_wired                 = NM_SETTING_WIRED(nm_setting_wired_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+}
+
+static void
+ifupdown_ip4_add_dns(NMSettingIPConfig *s_ip4, const char *dns)
+{
+    gs_free const char **list = NULL;
+    const char **        iter;
+    guint32              addr;
+
+    if (dns == NULL)
+        return;
+
+    list = nm_utils_strsplit_set(dns, " \t");
+    for (iter = list; iter && *iter; iter++) {
+        if (!inet_pton(AF_INET, *iter, &addr)) {
+            _LOGW("    ignoring invalid nameserver '%s'", *iter);
+            continue;
+        }
+
+        if (!nm_setting_ip_config_add_dns(s_ip4, *iter))
+            _LOGW("    duplicate DNS domain '%s'", *iter);
+    }
+}
+
+static gboolean
+update_ip4_setting_from_if_block(NMConnection *connection, if_block *block, GError **error)
+{
+    gs_unref_object NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    const char *                       type  = ifparser_getkey(block, "inet");
+
+    if (!nm_streq0(type, "static")) {
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+    } else {
+        guint32      tmp_mask;
+        NMIPAddress *addr;
+        const char * address_v;
+        const char * netmask_v;
+        const char * gateway_v;
+        const char * nameserver_v;
+        const char * nameservers_v;
+        const char * search_v;
+        guint32      netmask_int = 32;
+
+        /* Address */
+        address_v = ifparser_getkey(block, "address");
+        if (!address_v) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Missing IPv4 address");
+            return FALSE;
+        }
+
+        /* mask/prefix */
+        netmask_v = ifparser_getkey(block, "netmask");
+        if (netmask_v) {
+            if (strlen(netmask_v) < 7) {
+                netmask_int = atoi(netmask_v);
+            } else if (!inet_pton(AF_INET, netmask_v, &tmp_mask)) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Invalid IPv4 netmask '%s'",
+                            netmask_v);
+                return FALSE;
+            } else {
+                netmask_int = nm_utils_ip4_netmask_to_prefix(tmp_mask);
+            }
+        }
+
+        /* Add the new address to the setting */
+        addr = nm_ip_address_new(AF_INET, address_v, netmask_int, error);
+        if (!addr)
+            return FALSE;
+
+        if (nm_setting_ip_config_add_address(s_ip4, addr)) {
+            _LOGI("addresses count: %d", nm_setting_ip_config_get_num_addresses(s_ip4));
+        } else {
+            _LOGI("ignoring duplicate IP4 address");
+        }
+        nm_ip_address_unref(addr);
+
+        /* gateway */
+        gateway_v = ifparser_getkey(block, "gateway");
+        if (gateway_v) {
+            if (!nm_utils_ipaddr_is_valid(AF_INET, gateway_v)) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Invalid IPv4 gateway '%s'",
+                            gateway_v);
+                return FALSE;
+            }
+            if (!nm_setting_ip_config_get_gateway(s_ip4))
+                g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL);
+        }
+
+        nameserver_v = ifparser_getkey(block, "dns-nameserver");
+        ifupdown_ip4_add_dns(s_ip4, nameserver_v);
+
+        nameservers_v = ifparser_getkey(block, "dns-nameservers");
+        ifupdown_ip4_add_dns(s_ip4, nameservers_v);
+
+        if (!nm_setting_ip_config_get_num_dns(s_ip4))
+            _LOGI("No dns-nameserver configured in /etc/network/interfaces");
+
+        /* DNS searches */
+        search_v = ifparser_getkey(block, "dns-search");
+        if (search_v) {
+            gs_free const char **list = NULL;
+            const char **        iter;
+
+            list = nm_utils_strsplit_set(search_v, " \t");
+            for (iter = list; iter && *iter; iter++) {
+                if (!nm_setting_ip_config_add_dns_search(s_ip4, *iter))
+                    _LOGW("    duplicate DNS domain '%s'", *iter);
+            }
+        }
+
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
+    }
+
+    nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ip4)));
+    return TRUE;
+}
+
+static void
+ifupdown_ip6_add_dns(NMSettingIPConfig *s_ip6, const char *dns)
+{
+    gs_free const char **list = NULL;
+    const char **        iter;
+    struct in6_addr      addr;
+
+    if (dns == NULL)
+        return;
+
+    list = nm_utils_strsplit_set(dns, " \t");
+    for (iter = list; iter && *iter; iter++) {
+        if (!inet_pton(AF_INET6, *iter, &addr)) {
+            _LOGW("    ignoring invalid nameserver '%s'", *iter);
+            continue;
+        }
+
+        if (!nm_setting_ip_config_add_dns(s_ip6, *iter))
+            _LOGW("    duplicate DNS domain '%s'", *iter);
+    }
+}
+
+static gboolean
+update_ip6_setting_from_if_block(NMConnection *connection, if_block *block, GError **error)
+{
+    gs_unref_object NMSettingIPConfig *s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    const char *                       type  = ifparser_getkey(block, "inet6");
+
+    if (!NM_IN_STRSET(type, "static", "v4tunnel")) {
+        g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+    } else {
+        NMIPAddress *addr;
+        const char * address_v;
+        const char * prefix_v;
+        const char * gateway_v;
+        const char * nameserver_v;
+        const char * nameservers_v;
+        const char * search_v;
+        guint        prefix_int;
+
+        address_v = ifparser_getkey(block, "address");
+        if (!address_v) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "Missing IPv6 address");
+            return FALSE;
+        }
+
+        prefix_v = ifparser_getkey(block, "netmask");
+        if (prefix_v)
+            prefix_int = _nm_utils_ascii_str_to_int64(prefix_v, 10, 0, 128, G_MAXINT);
+        else
+            prefix_int = 128;
+
+        addr = nm_ip_address_new(AF_INET6, address_v, prefix_int, error);
+        if (!addr)
+            return FALSE;
+
+        if (nm_setting_ip_config_add_address(s_ip6, addr)) {
+            _LOGI("addresses count: %d", nm_setting_ip_config_get_num_addresses(s_ip6));
+        } else {
+            _LOGI("ignoring duplicate IP6 address");
+        }
+        nm_ip_address_unref(addr);
+
+        gateway_v = ifparser_getkey(block, "gateway");
+        if (gateway_v) {
+            if (!nm_utils_ipaddr_is_valid(AF_INET6, gateway_v)) {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Invalid IPv6 gateway '%s'",
+                            gateway_v);
+                return FALSE;
+            }
+            if (!nm_setting_ip_config_get_gateway(s_ip6))
+                g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL);
+        }
+
+        nameserver_v = ifparser_getkey(block, "dns-nameserver");
+        ifupdown_ip6_add_dns(s_ip6, nameserver_v);
+
+        nameservers_v = ifparser_getkey(block, "dns-nameservers");
+        ifupdown_ip6_add_dns(s_ip6, nameservers_v);
+
+        if (!nm_setting_ip_config_get_num_dns(s_ip6))
+            _LOGI("No dns-nameserver configured in /etc/network/interfaces");
+
+        search_v = ifparser_getkey(block, "dns-search");
+        if (search_v) {
+            gs_free const char **list = NULL;
+            const char **        iter;
+
+            list = nm_utils_strsplit_set(search_v, " \t");
+            for (iter = list; iter && *iter; iter++) {
+                if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter))
+                    _LOGW("    duplicate DNS domain '%s'", *iter);
+            }
+        }
+
+        g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL);
+    }
+
+    nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ip6)));
+    return TRUE;
+}
+
+NMConnection *
+ifupdown_new_connection_from_if_block(if_block *block, gboolean autoconnect, GError **error)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    const char *                  type;
+    gs_free char *                idstr = NULL;
+    gs_free char *                uuid  = NULL;
+    NMSettingConnection *         s_con;
+
+    connection = nm_simple_connection_new();
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    type  = _ifupdownplugin_guess_connection_type(block);
+    idstr = g_strconcat("Ifupdown (", block->name, ")", NULL);
+
+    uuid = nm_utils_uuid_generate_from_string(idstr, -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_TYPE,
+                 type,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 block->name,
+                 NM_SETTING_CONNECTION_ID,
+                 idstr,
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 (gboolean)(!!autoconnect),
+                 NULL);
+
+    _LOGD("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s",
+          block->name,
+          type,
+          idstr,
+          nm_setting_connection_get_uuid(s_con));
+
+    if (nm_streq(type, NM_SETTING_WIRED_SETTING_NAME))
+        update_wired_setting_from_if_block(connection, block);
+    else if (nm_streq(type, NM_SETTING_WIRELESS_SETTING_NAME)) {
+        update_wireless_setting_from_if_block(connection, block);
+        update_wireless_security_setting_from_if_block(connection, block);
+    }
+
+    if (ifparser_haskey(block, "inet6")) {
+        if (!update_ip6_setting_from_if_block(connection, block, error))
+            return FALSE;
+    } else {
+        if (!update_ip4_setting_from_if_block(connection, block, error))
+            return FALSE;
+    }
+
+    if (!nm_connection_normalize(connection, NULL, NULL, error))
+        return NULL;
+
+    return g_steal_pointer(&connection);
+}
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h
new file mode 100644
index 00000000..ced98dba
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Alexander Sack <asac@ubuntu.com>
+ * Copyright (C) 2008 Canonical Ltd.
+ */
+
+#ifndef __NMS_IFUPDOWN_PARSER_H__
+#define __NMS_IFUPDOWN_PARSER_H__
+
+#include "nm-connection.h"
+#include "nms-ifupdown-interface-parser.h"
+
+NMConnection *
+ifupdown_new_connection_from_if_block(if_block *block, gboolean autoconnect, GError **error);
+
+#endif /* __NMS_IFUPDOWN_PARSER_H__ */
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
new file mode 100644
index 00000000..34523f8d
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -0,0 +1,399 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Alexander Sack <asac@ubuntu.com>
+ * Copyright (C) 2007, 2008 Canonical Ltd.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-ifupdown-plugin.h"
+
+#include "nm-core-internal.h"
+#include "nm-core-utils.h"
+#include "nm-config.h"
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+
+#include "nms-ifupdown-interface-parser.h"
+#include "nms-ifupdown-parser.h"
+
+#define ENI_INTERFACES_FILE "/etc/network/interfaces"
+
+#define IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT TRUE
+
+/*****************************************************************************/
+
+typedef struct {
+    NMConnection *     connection;
+    NMSettingsStorage *storage;
+} StorageData;
+
+typedef struct {
+    /* Stores an entry for blocks/interfaces read from /e/n/i and (if exists)
+     * the StorageData associated with the block.
+     */
+    GHashTable *eni_ifaces;
+
+    bool ifupdown_managed : 1;
+
+    bool initialized : 1;
+
+    bool already_reloaded : 1;
+} NMSIfupdownPluginPrivate;
+
+struct _NMSIfupdownPlugin {
+    NMSettingsPlugin         parent;
+    NMSIfupdownPluginPrivate _priv;
+};
+
+struct _NMSIfupdownPluginClass {
+    NMSettingsPluginClass parent;
+};
+
+G_DEFINE_TYPE(NMSIfupdownPlugin, nms_ifupdown_plugin, NM_TYPE_SETTINGS_PLUGIN)
+
+#define NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMSIfupdownPlugin, NMS_IS_IFUPDOWN_PLUGIN)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "ifupdown"
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG(level, ...)                          \
+    nm_log((level),                                 \
+           _NMLOG_DOMAIN,                           \
+           NULL,                                    \
+           NULL,                                    \
+           "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+static GHashTable *load_eni_ifaces(NMSIfupdownPlugin *self);
+
+/*****************************************************************************/
+
+static void
+_storage_data_destroy(StorageData *sd)
+{
+    if (!sd)
+        return;
+    nm_g_object_unref(sd->connection);
+    nm_g_object_unref(sd->storage);
+    g_slice_free(StorageData, sd);
+}
+
+/*****************************************************************************/
+
+static void
+initialize(NMSIfupdownPlugin *self)
+{
+    NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self);
+    gboolean                  ifupdown_managed;
+
+    nm_assert(!priv->initialized);
+
+    priv->initialized = TRUE;
+
+    ifupdown_managed = nm_config_data_get_value_boolean(NM_CONFIG_GET_DATA_ORIG,
+                                                        NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
+                                                        NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
+                                                        !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT);
+    _LOGI("management mode: %s", ifupdown_managed ? "managed" : "unmanaged");
+    priv->ifupdown_managed = ifupdown_managed;
+
+    priv->eni_ifaces = load_eni_ifaces(self);
+}
+
+static void
+reload_connections(NMSettingsPlugin *                     plugin,
+                   NMSettingsPluginConnectionLoadCallback callback,
+                   gpointer                               user_data)
+{
+    NMSIfupdownPlugin *       self                = NMS_IFUPDOWN_PLUGIN(plugin);
+    NMSIfupdownPluginPrivate *priv                = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self);
+    gs_unref_hashtable GHashTable *eni_ifaces_old = NULL;
+    GHashTableIter                 iter;
+    StorageData *                  sd;
+    StorageData *                  sd2;
+    const char *                   block_name;
+
+    if (!priv->initialized)
+        initialize(self);
+    else if (!priv->already_reloaded) {
+        /* This is the first call to reload, but we are already initialized.
+         *
+         * This happens because during start NMSettings first queries unmanaged-specs,
+         * and then issues a reload call right away.
+         *
+         * On future reloads, we really want to load /e/n/i again. */
+        priv->already_reloaded = TRUE;
+    } else {
+        eni_ifaces_old   = priv->eni_ifaces;
+        priv->eni_ifaces = load_eni_ifaces(self);
+
+        g_hash_table_iter_init(&iter, eni_ifaces_old);
+        while (g_hash_table_iter_next(&iter, (gpointer *) &block_name, (gpointer *) &sd)) {
+            if (!sd)
+                continue;
+
+            sd2 = g_hash_table_lookup(priv->eni_ifaces, block_name);
+            if (!sd2)
+                continue;
+
+            nm_assert(nm_streq(nm_settings_storage_get_uuid(sd->storage),
+                               nm_settings_storage_get_uuid(sd2->storage)));
+            nm_g_object_ref_set(&sd2->storage, sd->storage);
+            g_hash_table_iter_remove(&iter);
+        }
+    }
+
+    if (!priv->ifupdown_managed)
+        _LOGD("load: no connections due to managed=false");
+
+    g_hash_table_iter_init(&iter, priv->eni_ifaces);
+    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &sd)) {
+        gs_unref_object NMConnection *connection = NULL;
+
+        if (!sd)
+            continue;
+
+        connection = g_steal_pointer(&sd->connection);
+
+        if (!priv->ifupdown_managed)
+            continue;
+
+        _LOGD("load: %s (%s)",
+              nm_settings_storage_get_uuid(sd->storage),
+              nm_connection_get_id(connection));
+        callback(plugin, sd->storage, connection, user_data);
+    }
+    if (eni_ifaces_old && priv->ifupdown_managed) {
+        g_hash_table_iter_init(&iter, eni_ifaces_old);
+        while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &sd)) {
+            if (!sd)
+                continue;
+            _LOGD("unload: %s", nm_settings_storage_get_uuid(sd->storage));
+            callback(plugin, sd->storage, NULL, user_data);
+        }
+    }
+}
+
+/*****************************************************************************/
+
+static GSList *
+_unmanaged_specs(GHashTable *eni_ifaces)
+{
+    gs_free const char **keys  = NULL;
+    GSList *             specs = NULL;
+    guint                i, len;
+
+    keys = nm_utils_strdict_get_keys(eni_ifaces, TRUE, &len);
+    for (i = len; i > 0;) {
+        i--;
+        specs = g_slist_prepend(specs,
+                                g_strdup_printf(NM_MATCH_SPEC_INTERFACE_NAME_TAG "=%s", keys[i]));
+    }
+    return specs;
+}
+
+static GSList *
+get_unmanaged_specs(NMSettingsPlugin *plugin)
+{
+    NMSIfupdownPlugin *       self = NMS_IFUPDOWN_PLUGIN(plugin);
+    NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self);
+
+    if (G_UNLIKELY(!priv->initialized))
+        initialize(self);
+
+    if (priv->ifupdown_managed)
+        return NULL;
+
+    _LOGD("unmanaged-specs: unmanaged devices count %u", g_hash_table_size(priv->eni_ifaces));
+
+    return _unmanaged_specs(priv->eni_ifaces);
+}
+
+/*****************************************************************************/
+
+static GHashTable *
+load_eni_ifaces(NMSIfupdownPlugin *self)
+{
+    gs_unref_hashtable GHashTable *eni_ifaces  = NULL;
+    gs_unref_hashtable GHashTable *auto_ifaces = NULL;
+    nm_auto_ifparser if_parser *parser         = NULL;
+    if_block *                  block;
+    StorageData *               sd;
+
+    eni_ifaces = g_hash_table_new_full(nm_str_hash,
+                                       g_str_equal,
+                                       g_free,
+                                       (GDestroyNotify) _storage_data_destroy);
+
+    parser = ifparser_parse(ENI_INTERFACES_FILE, 0);
+
+    c_list_for_each_entry (block, &parser->block_lst_head, block_lst) {
+        if (NM_IN_STRSET(block->type, "auto", "allow-hotplug")) {
+            if (!auto_ifaces)
+                auto_ifaces = g_hash_table_new(nm_str_hash, g_str_equal);
+            g_hash_table_add(auto_ifaces, (char *) block->name);
+        }
+    }
+
+    c_list_for_each_entry (block, &parser->block_lst_head, block_lst) {
+        if (NM_IN_STRSET(block->type, "auto", "allow-hotplug"))
+            continue;
+
+        if (nm_streq(block->type, "iface")) {
+            gs_free_error GError *local                = NULL;
+            gs_unref_object NMConnection *connection   = NULL;
+            gs_unref_object NMSettingsStorage *storage = NULL;
+            const char *                       uuid    = NULL;
+            StorageData *                      sd_repl;
+
+            /* Bridge configuration */
+            if (g_str_has_prefix(block->name, "br")) {
+                /* Try to find bridge ports */
+                const char *ports = ifparser_getkey(block, "bridge-ports");
+
+                if (ports) {
+                    int                  state       = 0;
+                    gs_free const char **port_ifaces = NULL;
+                    gsize                i;
+
+                    _LOGD("parse: found bridge ports %s for %s", ports, block->name);
+
+                    port_ifaces = nm_utils_strsplit_set(ports, " \t");
+                    for (i = 0; port_ifaces && port_ifaces[i]; i++) {
+                        const char *token = port_ifaces[i];
+
+                        /* Skip crazy stuff like regex or all */
+                        if (nm_streq(token, "all"))
+                            continue;
+
+                        /* Small SM to skip everything inside regex */
+                        if (nm_streq(token, "regex")) {
+                            state++;
+                            continue;
+                        }
+                        if (nm_streq(token, "noregex")) {
+                            state--;
+                            continue;
+                        }
+                        if (nm_streq(token, "none"))
+                            continue;
+                        if (state == 0) {
+                            sd = g_hash_table_lookup(eni_ifaces, block->name);
+                            if (!sd) {
+                                _LOGD("parse: adding bridge port \"%s\"", token);
+                                g_hash_table_insert(eni_ifaces, g_strdup(token), NULL);
+                            } else {
+                                _LOGD("parse: adding bridge port \"%s\" (have connection %s)",
+                                      token,
+                                      nm_settings_storage_get_uuid(sd->storage));
+                            }
+                        }
+                    }
+                }
+                continue;
+            }
+
+            /* Skip loopback configuration */
+            if (nm_streq(block->name, "lo"))
+                continue;
+
+            sd_repl = g_hash_table_lookup(eni_ifaces, block->name);
+            if (sd_repl) {
+                _LOGD("parse: replace connection \"%s\" (%s)",
+                      block->name,
+                      nm_settings_storage_get_uuid(sd_repl->storage));
+                storage = g_steal_pointer(&sd_repl->storage);
+                g_hash_table_remove(eni_ifaces, block->name);
+            }
+
+            connection = ifupdown_new_connection_from_if_block(
+                block,
+                auto_ifaces && g_hash_table_contains(auto_ifaces, block->name),
+                &local);
+
+            if (!connection) {
+                _LOGD("parse: adding place holder for \"%s\"%s%s%s",
+                      block->name,
+                      NM_PRINT_FMT_QUOTED(local, " (", local->message, ")", ""));
+                sd = NULL;
+            } else {
+                nmtst_connection_assert_unchanging(connection);
+                uuid = nm_connection_get_uuid(connection);
+
+                if (!storage)
+                    storage = nm_settings_storage_new(NM_SETTINGS_PLUGIN(self), uuid, NULL);
+
+                sd  = g_slice_new(StorageData);
+                *sd = (StorageData){
+                    .connection = g_steal_pointer(&connection),
+                    .storage    = g_steal_pointer(&storage),
+                };
+                _LOGD("parse: adding connection \"%s\" (%s)", block->name, uuid);
+            }
+
+            g_hash_table_replace(eni_ifaces, g_strdup(block->name), sd);
+            continue;
+        }
+
+        if (nm_streq(block->type, "mapping")) {
+            sd = g_hash_table_lookup(eni_ifaces, block->name);
+            if (!sd) {
+                _LOGD("parse: adding mapping \"%s\"", block->name);
+                g_hash_table_insert(eni_ifaces, g_strdup(block->name), NULL);
+            } else {
+                _LOGD("parse: adding mapping \"%s\" (have connection %s)",
+                      block->name,
+                      nm_settings_storage_get_uuid(sd->storage));
+            }
+            continue;
+        }
+    }
+
+    nm_clear_pointer(&auto_ifaces, g_hash_table_destroy);
+
+    return g_steal_pointer(&eni_ifaces);
+}
+
+/*****************************************************************************/
+
+static void
+nms_ifupdown_plugin_init(NMSIfupdownPlugin *self)
+{}
+
+static void
+dispose(GObject *object)
+{
+    NMSIfupdownPlugin *       plugin = NMS_IFUPDOWN_PLUGIN(object);
+    NMSIfupdownPluginPrivate *priv   = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(plugin);
+
+    nm_clear_pointer(&priv->eni_ifaces, g_hash_table_destroy);
+
+    G_OBJECT_CLASS(nms_ifupdown_plugin_parent_class)->dispose(object);
+}
+
+static void
+nms_ifupdown_plugin_class_init(NMSIfupdownPluginClass *klass)
+{
+    GObjectClass *         object_class = G_OBJECT_CLASS(klass);
+    NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass);
+
+    object_class->dispose = dispose;
+
+    plugin_class->plugin_name         = "ifupdown";
+    plugin_class->reload_connections  = reload_connections;
+    plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT NMSettingsPlugin *
+                nm_settings_plugin_factory(void)
+{
+    return g_object_new(NMS_TYPE_IFUPDOWN_PLUGIN, NULL);
+}
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h
new file mode 100644
index 00000000..edf1b317
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Alexander Sack <asac@ubuntu.com>
+ * Copyright (C) 2008 Canonical Ltd.
+ */
+
+#ifndef __NMS_IFUPDOWN_PLUGIN_H__
+#define __NMS_IFUPDOWN_PLUGIN_H__
+
+#define PLUGIN_NAME "ifupdown"
+
+#define NMS_TYPE_IFUPDOWN_PLUGIN (nms_ifupdown_plugin_get_type())
+#define NMS_IFUPDOWN_PLUGIN(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPlugin))
+#define NMS_IFUPDOWN_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass))
+#define NMS_IS_IFUPDOWN_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_IFUPDOWN_PLUGIN))
+#define NMS_IS_IFUPDOWN_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_IFUPDOWN_PLUGIN))
+#define NMS_IFUPDOWN_PLUGIN_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass))
+
+typedef struct _NMSIfupdownPlugin      NMSIfupdownPlugin;
+typedef struct _NMSIfupdownPluginClass NMSIfupdownPluginClass;
+
+GType nms_ifupdown_plugin_get_type(void);
+
+#endif /* __NMS_IFUPDOWN_PLUGIN_H__ */
diff --git a/src/core/settings/plugins/ifupdown/tests/meson.build b/src/core/settings/plugins/ifupdown/tests/meson.build
new file mode 100644
index 00000000..882287cb
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-ifupdown',
+  'test-ifupdown.c',
+  dependencies: libNetworkManagerTest_dep,
+  c_args: test_c_flags,
+  link_with: libnms_ifupdown_core,
+)
+
+test(
+  'ifupdown/test-ifupdown',
+  test_script,
+  args: test_args + [exe.full_path()],
+)
diff --git a/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c
new file mode 100644
index 00000000..6e3eb0e7
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -0,0 +1,648 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-core-internal.h"
+
+#include "settings/plugins/ifupdown/nms-ifupdown-interface-parser.h"
+#include "settings/plugins/ifupdown/nms-ifupdown-parser.h"
+
+#include "nm-test-utils-core.h"
+
+#define TEST_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/ifupdown/tests"
+
+/*****************************************************************************/
+
+#define _connection_from_if_block(block)                                      \
+    ({                                                                        \
+        NMConnection *_con;                                                   \
+        if_block *    _block = (block);                                       \
+        GError *      _local = NULL;                                          \
+                                                                              \
+        g_assert(_block);                                                     \
+        _con = ifupdown_new_connection_from_if_block(_block, FALSE, &_local); \
+        nmtst_assert_success(NM_IS_CONNECTION(_con), _local);                 \
+        nmtst_assert_connection_verifies_without_normalization(_con);         \
+        _con;                                                                 \
+    })
+
+#define _connection_first_from_parser(parser)                  \
+    ({                                                         \
+        if_parser *_parser = (parser);                         \
+                                                               \
+        g_assert(_parser);                                     \
+        _connection_from_if_block(ifparser_getfirst(_parser)); \
+    })
+
+/*****************************************************************************/
+
+typedef struct {
+    char *key;
+    char *data;
+} ExpectedKey;
+
+typedef struct {
+    char *  type;
+    char *  name;
+    GSList *keys;
+} ExpectedBlock;
+
+typedef struct {
+    GSList *blocks;
+} Expected;
+
+static ExpectedKey *
+expected_key_new(const char *key, const char *data)
+{
+    ExpectedKey *k;
+
+    k       = g_malloc0(sizeof(ExpectedKey));
+    k->key  = g_strdup(key);
+    k->data = g_strdup(data);
+    return k;
+}
+
+static void
+expected_key_free(gpointer ptr)
+{
+    ExpectedKey *k = ptr;
+
+    g_assert(k);
+    g_free(k->key);
+    g_free(k->data);
+    memset(k, 0, sizeof(ExpectedKey));
+    g_free(k);
+}
+
+static ExpectedBlock *
+expected_block_new(const char *type, const char *name)
+{
+    ExpectedBlock *b;
+
+    g_assert(type);
+    g_assert(name);
+    b = g_malloc0(sizeof(ExpectedBlock));
+    g_assert(b);
+    b->type = g_strdup(type);
+    b->name = g_strdup(name);
+    return b;
+}
+
+static void
+expected_block_free(gpointer ptr)
+{
+    ExpectedBlock *b = ptr;
+
+    g_assert(b);
+    g_slist_free_full(b->keys, expected_key_free);
+    g_free(b->type);
+    g_free(b->name);
+    memset(b, 0, sizeof(ExpectedBlock));
+    g_free(b);
+}
+
+static void
+expected_block_add_key(ExpectedBlock *b, ExpectedKey *k)
+{
+    g_assert(b);
+    g_assert(k);
+    b->keys = g_slist_append(b->keys, k);
+}
+
+static Expected *
+expected_new(void)
+{
+    return g_malloc0(sizeof(Expected));
+}
+
+static void
+expected_add_block(Expected *e, ExpectedBlock *b)
+{
+    g_assert(e);
+    g_assert(b);
+    e->blocks = g_slist_append(e->blocks, b);
+}
+
+static void
+expected_free(Expected *e)
+{
+    g_assert(e);
+    g_slist_free_full(e->blocks, expected_block_free);
+    memset(e, 0, sizeof(Expected));
+    g_free(e);
+}
+
+NM_AUTO_DEFINE_FCN_VOID0(Expected *, _nm_auto_free_expected, expected_free);
+#define nm_auto_free_expected nm_auto(_nm_auto_free_expected)
+
+static void
+compare_expected_to_ifparser(if_parser *parser, Expected *e)
+{
+    if_block *n;
+    GSList *  biter, *kiter;
+
+    g_assert_cmpint(g_slist_length(e->blocks), ==, ifparser_get_num_blocks(parser));
+
+    biter = e->blocks;
+    c_list_for_each_entry (n, &parser->block_lst_head, block_lst) {
+        if_data *      m;
+        ExpectedBlock *b = biter->data;
+
+        g_assert(b->type && n->type);
+        g_assert_cmpstr(b->type, ==, n->type);
+        g_assert(b->name);
+        g_assert_cmpstr(b->name, ==, n->name);
+
+        g_assert_cmpint(g_slist_length(b->keys), ==, ifparser_get_num_info(n));
+
+        kiter = b->keys;
+        c_list_for_each_entry (m, &n->data_lst_head, data_lst) {
+            ExpectedKey *k = kiter->data;
+
+            g_assert(k->key);
+            g_assert_cmpstr(k->key, ==, m->key);
+            g_assert(k->data && m->data);
+            g_assert_cmpstr(k->data, ==, m->data);
+
+            kiter = g_slist_next(kiter);
+        }
+        g_assert(!kiter);
+
+        biter = g_slist_next(biter);
+    }
+    g_assert(!biter);
+}
+
+static void
+dump_blocks(if_parser *parser)
+{
+    if_block *n;
+
+    g_message("\n***************************************************");
+    c_list_for_each_entry (n, &parser->block_lst_head, block_lst) {
+        if_data *m;
+
+        // each block start with its type & name
+        // (single quotes used to show typ & name baoundaries)
+        g_print("'%s' '%s'\n", n->type, n->name);
+
+        // each key-value pair within a block is indented & separated by a tab
+        // (single quotes used to show type & name boundaries)
+        c_list_for_each_entry (m, &n->data_lst_head, data_lst)
+            g_print("\t'%s'\t'%s'\n", m->key, m->data);
+
+        // blocks are separated by an empty line
+        g_print("\n");
+    }
+    g_message("##################################################\n");
+}
+
+static if_parser *
+init_ifparser_with_file(const char *file)
+{
+    if_parser *   parser;
+    gs_free char *tmp = NULL;
+
+    tmp    = g_strdup_printf("%s/%s", TEST_DIR, file);
+    parser = ifparser_parse(tmp, 1);
+    g_assert(parser);
+    return parser;
+}
+
+static void
+test1_ignore_line_before_first_block(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test1");
+
+    e = expected_new();
+    b = expected_block_new("auto", "eth0");
+    expected_add_block(e, b);
+    b = expected_block_new("iface", "eth0");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test2_wrapped_line(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test2");
+
+    e = expected_new();
+    b = expected_block_new("auto", "lo");
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test3_wrapped_multiline_multiarg(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test3");
+
+    e = expected_new();
+    b = expected_block_new("allow-hotplug", "eth0");
+    expected_add_block(e, b);
+    b = expected_block_new("allow-hotplug", "wlan0");
+    expected_add_block(e, b);
+    b = expected_block_new("allow-hotplug", "bnep0");
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test4_allow_auto_is_auto(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test4");
+
+    e = expected_new();
+    b = expected_block_new("auto", "eth0");
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test5_allow_auto_multiarg(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test5");
+
+    e = expected_new();
+    b = expected_block_new("allow-hotplug", "eth0");
+    expected_add_block(e, b);
+    b = expected_block_new("allow-hotplug", "wlan0");
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test6_mixed_whitespace(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test6");
+
+    e = expected_new();
+    b = expected_block_new("iface", "lo");
+    expected_block_add_key(b, expected_key_new("inet", "loopback"));
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test7_long_line(void)
+{
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test7");
+
+    g_assert_cmpint(ifparser_get_num_blocks(parser), ==, 0);
+}
+
+static void
+test8_long_line_wrapped(void)
+{
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test8");
+
+    g_assert_cmpint(ifparser_get_num_blocks(parser), ==, 0);
+}
+
+static void
+test9_wrapped_lines_in_block(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test9");
+
+    e = expected_new();
+    b = expected_block_new("iface", "eth0");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "static"));
+    expected_block_add_key(b, expected_key_new("address", "10.250.2.3"));
+    expected_block_add_key(b, expected_key_new("netmask", "255.255.255.192"));
+    expected_block_add_key(b, expected_key_new("broadcast", "10.250.2.63"));
+    expected_block_add_key(b, expected_key_new("gateway", "10.250.2.50"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test11_complex_wrap(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test11");
+
+    e = expected_new();
+    b = expected_block_new("iface", "pppoe");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "manual"));
+    expected_block_add_key(b, expected_key_new("pre-up", "/sbin/ifconfig eth0 up"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test12_complex_wrap_split_word(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test12");
+
+    e = expected_new();
+    b = expected_block_new("iface", "pppoe");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "manual"));
+    expected_block_add_key(b, expected_key_new("up", "ifup ppp0=dsl"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test13_more_mixed_whitespace(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test13");
+
+    e = expected_new();
+    b = expected_block_new("iface", "dsl");
+    expected_block_add_key(b, expected_key_new("inet", "ppp"));
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test14_mixed_whitespace_block_start(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test14");
+
+    e = expected_new();
+    b = expected_block_new("iface", "wlan0");
+    expected_block_add_key(b, expected_key_new("inet", "manual"));
+    expected_add_block(e, b);
+    b = expected_block_new("iface", "wlan-adpm");
+    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
+    expected_add_block(e, b);
+    b = expected_block_new("iface", "wlan-default");
+    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test15_trailing_space(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test15");
+
+    e = expected_new();
+    b = expected_block_new("iface", "bnep0");
+    expected_block_add_key(b, expected_key_new("inet", "static"));
+    expected_add_block(e, b);
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test16_missing_newline(void)
+{
+    nm_auto_free_expected Expected *e  = NULL;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test16");
+
+    e = expected_new();
+    expected_add_block(e, expected_block_new("mapping", "eth0"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test17_read_static_ipv4(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingWired *              s_wired;
+    NMIPAddress *                 ip4_addr;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test17-wired-static-verify-ip4");
+
+    connection = _connection_first_from_parser(parser);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Ifupdown (eth0)");
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv4 SETTING ===== */
+    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_MANUAL);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.0.0.3");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "10.0.0.1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "10.0.0.2");
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 0), ==, "example.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 1), ==, "foo.example.com");
+}
+
+static void
+test18_read_static_ipv6(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip6;
+    NMSettingWired *              s_wired;
+    NMIPAddress *                 ip6_addr;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test18-wired-static-verify-ip6");
+
+    connection = _connection_first_from_parser(parser);
+
+    /* ===== CONNECTION SETTING ===== */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Ifupdown (myip6tunnel)");
+
+    /* ===== WIRED SETTING ===== */
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    /* ===== IPv6 SETTING ===== */
+    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_MANUAL);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1);
+    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0);
+    g_assert(ip6_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "fc00::1");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "fc00::2");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "fc00::3");
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "example.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "foo.example.com");
+}
+
+static void
+test19_read_static_ipv4_plen(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMIPAddress *                 ip4_addr;
+    nm_auto_ifparser if_parser *parser =
+        init_ifparser_with_file("test19-wired-static-verify-ip4-plen");
+
+    connection = _connection_first_from_parser(parser);
+
+    /* ===== IPv4 SETTING ===== */
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip4_addr != NULL);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.0.0.3");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8);
+}
+
+static void
+test20_source_stanza(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test20-source-stanza");
+
+    e = expected_new();
+
+    b = expected_block_new("auto", "eth0");
+    expected_add_block(e, b);
+    b = expected_block_new("iface", "eth0");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
+
+    b = expected_block_new("auto", "eth1");
+    expected_add_block(e, b);
+    b = expected_block_new("iface", "eth1");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test21_source_dir_stanza(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test21-source-dir-stanza");
+
+    e = expected_new();
+
+    b = expected_block_new("auto", "eth0");
+    expected_add_block(e, b);
+    b = expected_block_new("iface", "eth0");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+static void
+test22_duplicate_stanzas(void)
+{
+    nm_auto_free_expected Expected *e = NULL;
+    ExpectedBlock *                 b;
+    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test22-duplicate-stanzas");
+
+    e = expected_new();
+
+    b = expected_block_new("iface", "br10");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "manual"));
+    expected_block_add_key(b, expected_key_new("bridge-ports", "enp6s0.15"));
+    expected_block_add_key(b, expected_key_new("bridge-stp", "off"));
+    expected_block_add_key(b, expected_key_new("bridge-maxwait", "0"));
+    expected_block_add_key(b, expected_key_new("bridge-fd", "0"));
+    b = expected_block_new("iface", "br10");
+    expected_add_block(e, b);
+    expected_block_add_key(b, expected_key_new("inet", "auto"));
+    expected_block_add_key(b, expected_key_new("bridge-ports", "enp6s0.15"));
+
+    compare_expected_to_ifparser(parser, e);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE();
+
+int
+main(int argc, char **argv)
+{
+    nmtst_init_assert_logging(&argc, &argv, "WARN", "DEFAULT");
+
+    (void) dump_blocks;
+
+    g_test_add_func("/ifupdate/ignore_line_before_first_block",
+                    test1_ignore_line_before_first_block);
+    g_test_add_func("/ifupdate/wrapped_line", test2_wrapped_line);
+    g_test_add_func("/ifupdate/wrapped_multiline_multiarg", test3_wrapped_multiline_multiarg);
+    g_test_add_func("/ifupdate/allow_auto_is_auto", test4_allow_auto_is_auto);
+    g_test_add_func("/ifupdate/allow_auto_multiarg", test5_allow_auto_multiarg);
+    g_test_add_func("/ifupdate/mixed_whitespace", test6_mixed_whitespace);
+    g_test_add_func("/ifupdate/long_line", test7_long_line);
+    g_test_add_func("/ifupdate/long_line_wrapped", test8_long_line_wrapped);
+    g_test_add_func("/ifupdate/wrapped_lines_in_block", test9_wrapped_lines_in_block);
+    g_test_add_func("/ifupdate/complex_wrap", test11_complex_wrap);
+    g_test_add_func("/ifupdate/complex_wrap_split_word", test12_complex_wrap_split_word);
+    g_test_add_func("/ifupdate/more_mixed_whitespace", test13_more_mixed_whitespace);
+    g_test_add_func("/ifupdate/mixed_whitespace_block_start", test14_mixed_whitespace_block_start);
+    g_test_add_func("/ifupdate/trailing_space", test15_trailing_space);
+    g_test_add_func("/ifupdate/missing_newline", test16_missing_newline);
+    g_test_add_func("/ifupdate/read_static_ipv4", test17_read_static_ipv4);
+    g_test_add_func("/ifupdate/read_static_ipv6", test18_read_static_ipv6);
+    g_test_add_func("/ifupdate/read_static_ipv4_plen", test19_read_static_ipv4_plen);
+    g_test_add_func("/ifupdate/source_stanza", test20_source_stanza);
+    g_test_add_func("/ifupdate/source_dir_stanza", test21_source_dir_stanza);
+    g_test_add_func("/ifupdate/test22-duplicate-stanzas", test22_duplicate_stanzas);
+
+    return g_test_run();
+}
diff --git a/src/core/settings/plugins/ifupdown/tests/test1 b/src/core/settings/plugins/ifupdown/tests/test1
new file mode 100644
index 00000000..74c23b45
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test1
@@ -0,0 +1,6 @@
+# case 1: line before 1st block (must be ignored)
+address 10.250.2.3
+
+auto eth0
+iface eth0 inet dhcp
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test11 b/src/core/settings/plugins/ifupdown/tests/test11
new file mode 100644
index 00000000..89561dd7
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test11
@@ -0,0 +1,5 @@
+iface pppoe inet manual
+# case 11: wrapped line (without leading space on the wrapped part, wrap within a multi-word value)
+	pre-up /sbin/ifconfig \
+eth0 up
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test12 b/src/core/settings/plugins/ifupdown/tests/test12
new file mode 100644
index 00000000..6096842e
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test12
@@ -0,0 +1,5 @@
+iface pppoe inet manual
+# case 12: wrapped line, splitting a word (must be joined again)
+	up ifup ppp0\
+=dsl
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test13 b/src/core/settings/plugins/ifupdown/tests/test13
new file mode 100644
index 00000000..c001f7ef
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test13
@@ -0,0 +1,3 @@
+# case 13: variations of tabs & spaces
+iface dsl	inet ppp 
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test14 b/src/core/settings/plugins/ifupdown/tests/test14
new file mode 100644
index 00000000..4a153ab3
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test14
@@ -0,0 +1,5 @@
+# case 14: variations of tabs and spaces (all must be recognized as lines starting an iface block)
+iface	wlan0 inet manual
+ 	iface wlan-adpm inet dhcp
+iface   wlan-default   inet   dhcp
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test15 b/src/core/settings/plugins/ifupdown/tests/test15
new file mode 100644
index 00000000..c3ceca24
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test15
@@ -0,0 +1,3 @@
+# case 15: trailing space (must be ignored)
+iface bnep0 inet static 
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test16 b/src/core/settings/plugins/ifupdown/tests/test16
new file mode 100644
index 00000000..f4f74fb5
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test16
@@ -0,0 +1,2 @@
+# case 16: last line that is not followed by LF (added with 'echo -n "mapping eth0" >> /e/n/i')
+mapping eth0
\ No newline at end of file
diff --git a/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 b/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4
new file mode 100644
index 00000000..9e5243a7
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4
@@ -0,0 +1,5 @@
+iface eth0 inet static
+  address 10.0.0.3
+  netmask 255.0.0.0
+  dns-search example.com foo.example.com
+  dns-nameservers 10.0.0.1 10.0.0.2
diff --git a/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 b/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6
new file mode 100644
index 00000000..29546a61
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6
@@ -0,0 +1,6 @@
+iface myip6tunnel inet6 v4tunnel
+  address fc00::1
+  netmask 64
+  endpoint 78.35.24.124
+  dns-nameservers fc00::2 fc00::3
+  dns-search example.com foo.example.com
diff --git a/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen b/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen
new file mode 100644
index 00000000..441106da
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen
@@ -0,0 +1,3 @@
+iface eth0 inet static
+  address 10.0.0.3
+  netmask 8
diff --git a/src/core/settings/plugins/ifupdown/tests/test2 b/src/core/settings/plugins/ifupdown/tests/test2
new file mode 100644
index 00000000..7462b352
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test2
@@ -0,0 +1,4 @@
+# case 2: wrapped line
+auto \
+lo 
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test20-source-stanza b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza
new file mode 100644
index 00000000..5cfe1730
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza
@@ -0,0 +1 @@
+source test20-source-stanza.eth*
diff --git a/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0
new file mode 100644
index 00000000..81922cea
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0
@@ -0,0 +1,2 @@
+auto eth0
+iface eth0 inet dhcp
diff --git a/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1
new file mode 100644
index 00000000..b8a783f5
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1
@@ -0,0 +1,2 @@
+auto eth1
+iface eth1 inet dhcp
diff --git a/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza
new file mode 100644
index 00000000..d0604ddc
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza
@@ -0,0 +1 @@
+source-directory test21-source-dir-stanza.d
diff --git a/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0 b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0
new file mode 100644
index 00000000..81922cea
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0
@@ -0,0 +1,2 @@
+auto eth0
+iface eth0 inet dhcp
diff --git a/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas b/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas
new file mode 100644
index 00000000..c13c2e7e
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas
@@ -0,0 +1,8 @@
+iface br10 inet manual
+    bridge_ports    enp6s0.15
+    bridge_stp      off
+    bridge_maxwait  0
+    bridge_fd       0
+
+iface br10 inet auto
+    bridge_ports enp6s0.15
diff --git a/src/core/settings/plugins/ifupdown/tests/test3 b/src/core/settings/plugins/ifupdown/tests/test3
new file mode 100644
index 00000000..f6293bbd
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test3
@@ -0,0 +1,5 @@
+# case 3: line wrapped over multiple lines & multi-argument allow-*
+allow-hotplug eth0 \
+		wlan0 \
+		bnep0
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test4 b/src/core/settings/plugins/ifupdown/tests/test4
new file mode 100644
index 00000000..46a40bc9
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test4
@@ -0,0 +1,3 @@
+# case 4: 'allow-auto' is synonymous to 'auto'
+allow-auto eth0
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test5 b/src/core/settings/plugins/ifupdown/tests/test5
new file mode 100644
index 00000000..b69fc42b
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test5
@@ -0,0 +1,3 @@
+# case 5: multi-argument allow-* (even worse: trailing space)
+allow-hotplug eth0 wlan0 
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test6 b/src/core/settings/plugins/ifupdown/tests/test6
new file mode 100644
index 00000000..50ac69bd
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test6
@@ -0,0 +1,3 @@
+# case 6: mix between tabs and spaces
+   	iface  	 lo    inet	loopback  
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test7 b/src/core/settings/plugins/ifupdown/tests/test7
new file mode 100644
index 00000000..03cb131a
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test7
@@ -0,0 +1,3 @@
+# case 7: over-long line (must be ignored completely)
+123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test8 b/src/core/settings/plugins/ifupdown/tests/test8
new file mode 100644
index 00000000..311f7e15
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test8
@@ -0,0 +1,5 @@
+# case 8: over-long line that wraps to consecutive lines (must be ignored completely)
+123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 \
+allow-test	eth0 \
+eth0
+
diff --git a/src/core/settings/plugins/ifupdown/tests/test9 b/src/core/settings/plugins/ifupdown/tests/test9
new file mode 100644
index 00000000..7d94563a
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/test9
@@ -0,0 +1,10 @@
+iface eth0 inet static
+# case 9: wrapped lines inside a block (to be on the safe side)
+	address \
+		10.250.2.3
+	netmask \
+		255.255.255.192
+
+	broadcast 10.250.2.63
+	gateway 10.250.2.50
+
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
new file mode 100644
index 00000000..789bfc1a
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -0,0 +1,1348 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-plugin.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-io-utils.h"
+
+#include "nm-connection.h"
+#include "nm-setting.h"
+#include "nm-setting-connection.h"
+#include "nm-utils.h"
+#include "nm-config.h"
+#include "nm-core-internal.h"
+#include "nm-keyfile-internal.h"
+
+#include "systemd/nm-sd-utils-shared.h"
+
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+#include "settings/nm-settings-utils.h"
+
+#include "nms-keyfile-storage.h"
+#include "nms-keyfile-writer.h"
+#include "nms-keyfile-reader.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+    NMConfig *config;
+
+    /* there can/could be multiple read-only directories. For example, one
+     * could set dirname_libs to
+     *   - /usr/lib/NetworkManager/profiles/
+     *   - /etc/NetworkManager/system-connections
+     * and leave dirname_etc unset. In this case, there would be multiple
+     * read-only directories.
+     *
+     * Directories that come later have higher priority and shadow profiles
+     * from earlier directories.
+     *
+     * Currently, this is only an array with zero or one elements. It could be
+     * easily extended to support multiple read-only directories.
+     */
+    char *dirname_libs[2];
+    char *dirname_etc;
+    char *dirname_run;
+
+    NMSettUtilStorages storages;
+
+} NMSKeyfilePluginPrivate;
+
+struct _NMSKeyfilePlugin {
+    NMSettingsPlugin        parent;
+    NMSKeyfilePluginPrivate _priv;
+};
+
+struct _NMSKeyfilePluginClass {
+    NMSettingsPluginClass parent;
+};
+
+G_DEFINE_TYPE(NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
+
+#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN, NMSettingsPlugin)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "keyfile"
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG(level, ...)                          \
+    nm_log((level),                                 \
+           _NMLOG_DOMAIN,                           \
+           NULL,                                    \
+           NULL,                                    \
+           "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+static const char *
+_extra_flags_to_string(char *   str,
+                       gsize    str_len,
+                       gboolean is_nm_generated,
+                       gboolean is_volatile,
+                       gboolean is_external)
+{
+    const char *str0 = str;
+
+    if (!is_nm_generated && !is_volatile && !is_external)
+        nm_utils_strbuf_append_str(&str, &str_len, "");
+    else {
+        char ch = '(';
+
+        nm_utils_strbuf_append_c(&str, &str_len, ' ');
+        if (is_nm_generated) {
+            nm_utils_strbuf_append_c(&str, &str_len, ch);
+            nm_utils_strbuf_append_str(&str, &str_len, "nm-generated");
+            ch = ',';
+        }
+        if (is_volatile) {
+            nm_utils_strbuf_append_c(&str, &str_len, ch);
+            nm_utils_strbuf_append_str(&str, &str_len, "volatile");
+            ch = ',';
+        }
+        if (is_external) {
+            nm_utils_strbuf_append_c(&str, &str_len, ch);
+            nm_utils_strbuf_append_str(&str, &str_len, "external");
+            ch = ',';
+        }
+        nm_utils_strbuf_append_c(&str, &str_len, ')');
+    }
+
+    return str0;
+}
+
+static gboolean
+_ignore_filename(NMSKeyfileStorageType storage_type, const char *filename)
+{
+    /* for backward-compatibility, we don't require an extension for
+     * files under "/etc/...". */
+    return nm_keyfile_utils_ignore_filename(filename,
+                                            (storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC));
+}
+
+static const char *
+_get_plugin_dir(NMSKeyfilePluginPrivate *priv)
+{
+    /* the plugin dir is only needed to generate connection.uuid value via
+     * nm_keyfile_read_ensure_uuid(). This is either the configured /etc
+     * directory, of the compile-time default (in case the /etc directory
+     * is disabled). */
+    return priv->dirname_etc ?: NM_KEYFILE_PATH_NAME_ETC_DEFAULT;
+}
+
+static gboolean
+_path_detect_storage_type(const char *           full_filename,
+                          const char *const *    dirname_libs,
+                          const char *           dirname_etc,
+                          const char *           dirname_run,
+                          NMSKeyfileStorageType *out_storage_type,
+                          const char **          out_dirname,
+                          const char **          out_filename,
+                          gboolean *             out_is_nmmeta_file,
+                          gboolean *             out_failed_due_to_invalid_filename)
+{
+    NMSKeyfileStorageType storage_type;
+    const char *          filename = NULL;
+    const char *          dirname  = NULL;
+    guint                 i;
+    gboolean              is_nmmeta_file = FALSE;
+
+    NM_SET_OUT(out_failed_due_to_invalid_filename, FALSE);
+
+    if (full_filename[0] != '/')
+        return FALSE;
+
+    if (dirname_run && (filename = nm_utils_file_is_in_path(full_filename, dirname_run))) {
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+        dirname      = dirname_run;
+    } else if (dirname_etc && (filename = nm_utils_file_is_in_path(full_filename, dirname_etc))) {
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+        dirname      = dirname_etc;
+    } else {
+        for (i = 0; dirname_libs && dirname_libs[i]; i++) {
+            if ((filename = nm_utils_file_is_in_path(full_filename, dirname_libs[i]))) {
+                storage_type = NMS_KEYFILE_STORAGE_TYPE_LIB(i);
+                dirname      = dirname_libs[i];
+                break;
+            }
+        }
+        if (!dirname)
+            return FALSE;
+    }
+
+    if (_ignore_filename(storage_type, filename)) {
+        /* we accept nmmeta files, but only in /etc and /run directories. */
+
+        if (!NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC)
+            || !nms_keyfile_nmmeta_check_filename(filename, NULL)) {
+            NM_SET_OUT(out_failed_due_to_invalid_filename, TRUE);
+            return FALSE;
+        }
+
+        is_nmmeta_file = TRUE;
+    }
+
+    NM_SET_OUT(out_storage_type, storage_type);
+    NM_SET_OUT(out_dirname, dirname);
+    NM_SET_OUT(out_filename, filename);
+    NM_SET_OUT(out_is_nmmeta_file, is_nmmeta_file);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static NMConnection *
+_read_from_file(const char * full_filename,
+                const char * plugin_dir,
+                struct stat *out_stat,
+                NMTernary *  out_is_nm_generated,
+                NMTernary *  out_is_volatile,
+                NMTernary *  out_is_external,
+                char **      out_shadowed_storage,
+                NMTernary *  out_shadowed_owned,
+                GError **    error)
+{
+    NMConnection *connection;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+
+    connection = nms_keyfile_reader_from_file(full_filename,
+                                              plugin_dir,
+                                              out_stat,
+                                              out_is_nm_generated,
+                                              out_is_volatile,
+                                              out_is_external,
+                                              out_shadowed_storage,
+                                              out_shadowed_owned,
+                                              error);
+
+    nm_assert(!connection
+              || (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+    nm_assert(!connection || nm_utils_is_uuid(nm_connection_get_uuid(connection)));
+
+    return connection;
+}
+
+/*****************************************************************************/
+
+static void
+_nm_assert_storage(gpointer plugin /* NMSKeyfilePlugin  */,
+                   gpointer storage /* NMSKeyfileStorage */,
+                   gboolean tracked)
+{
+#if NM_MORE_ASSERTS
+    NMSettUtilStorageByUuidHead *sbuh;
+    const char *                 uuid;
+
+    nm_assert(!plugin || NMS_IS_KEYFILE_PLUGIN(plugin));
+    nm_assert(NMS_IS_KEYFILE_STORAGE(storage));
+    nm_assert(!plugin || plugin == nm_settings_storage_get_plugin(storage));
+
+    nm_assert(({
+        const char *f = nms_keyfile_storage_get_filename(storage);
+        f &&        f[0] == '/';
+    }));
+
+    uuid = nms_keyfile_storage_get_uuid(storage);
+
+    nm_assert(nm_utils_is_uuid(uuid));
+
+    nm_assert(((NMSKeyfileStorage *) storage)->is_meta_data
+              || !(((NMSKeyfileStorage *) storage)->u.conn_data.connection)
+              || (NM_IS_CONNECTION((((NMSKeyfileStorage *) storage)->u.conn_data.connection))
+                  && nm_streq0(uuid,
+                               nm_connection_get_uuid(
+                                   (((NMSKeyfileStorage *) storage)->u.conn_data.connection)))));
+
+    nm_assert(
+        !tracked || !plugin
+        || c_list_contains(&NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages._storage_lst_head,
+                           &NMS_KEYFILE_STORAGE(storage)->parent._storage_lst));
+
+    nm_assert(!tracked || !plugin
+              || storage
+                     == g_hash_table_lookup(
+                         NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages.idx_by_filename,
+                         nms_keyfile_storage_get_filename(storage)));
+
+    if (tracked && plugin) {
+        sbuh = g_hash_table_lookup(NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages.idx_by_uuid,
+                                   &uuid);
+        nm_assert(sbuh);
+        nm_assert(c_list_contains(&sbuh->_storage_by_uuid_lst_head,
+                                  &((NMSKeyfileStorage *) storage)->parent._storage_by_uuid_lst));
+    }
+#endif
+}
+
+/*****************************************************************************/
+
+static NMSKeyfileStorage *
+_load_file(NMSKeyfilePlugin *    self,
+           const char *          dirname,
+           const char *          filename,
+           NMSKeyfileStorageType storage_type,
+           GError **             error)
+{
+    NMSKeyfilePluginPrivate *priv;
+    gs_unref_object NMConnection *connection = NULL;
+    NMTernary                     is_nm_generated_opt;
+    NMTernary                     is_volatile_opt;
+    NMTernary                     is_external_opt;
+    NMTernary                     shadowed_owned_opt;
+    gs_free char *                shadowed_storage = NULL;
+    gs_free_error GError *local                    = NULL;
+    gs_free char *        full_filename            = NULL;
+    struct stat           st;
+
+    if (_ignore_filename(storage_type, filename)) {
+        gs_free char *nmmeta                    = NULL;
+        gs_free char *loaded_path               = NULL;
+        gs_free char *shadowed_storage_filename = NULL;
+
+        if (!nms_keyfile_nmmeta_check_filename(filename, NULL)) {
+            if (error)
+                nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "skip due to invalid filename");
+            else
+                _LOGT("load: \"%s/%s\": skip file due to invalid filename", dirname, filename);
+            return NULL;
+        }
+        if (!nms_keyfile_nmmeta_read(dirname,
+                                     filename,
+                                     &full_filename,
+                                     &nmmeta,
+                                     &loaded_path,
+                                     &shadowed_storage_filename,
+                                     NULL)) {
+            if (error)
+                nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "skip unreadable nmmeta file");
+            else
+                _LOGT("load: \"%s/%s\": skip unreadable nmmeta file", dirname, filename);
+            return NULL;
+        }
+        nm_assert(loaded_path);
+        if (!NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC)) {
+            if (error)
+                nm_utils_error_set(error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   "skip nmmeta file from read-only directory");
+            else
+                _LOGT("load: \"%s/%s\": skip nmmeta file from read-only directory",
+                      dirname,
+                      filename);
+            return NULL;
+        }
+        if (!nm_streq(loaded_path, NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL)) {
+            if (error)
+                nm_utils_error_set(error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   "skip nmmeta file not symlinking %s",
+                                   NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
+            else
+                _LOGT("load: \"%s/%s\": skip nmmeta file not symlinking to %s",
+                      dirname,
+                      filename,
+                      NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
+            return NULL;
+        }
+
+        return nms_keyfile_storage_new_tombstone(self,
+                                                 nmmeta,
+                                                 full_filename,
+                                                 storage_type,
+                                                 shadowed_storage_filename);
+    }
+
+    full_filename = g_build_filename(dirname, filename, NULL);
+
+    priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    connection = _read_from_file(full_filename,
+                                 _get_plugin_dir(priv),
+                                 &st,
+                                 &is_nm_generated_opt,
+                                 &is_volatile_opt,
+                                 &is_external_opt,
+                                 &shadowed_storage,
+                                 &shadowed_owned_opt,
+                                 &local);
+    if (!connection) {
+        if (error)
+            g_propagate_error(error, g_steal_pointer(&local));
+        else
+            _LOGW("load: \"%s\": failed to load connection: %s", full_filename, local->message);
+        return NULL;
+    }
+
+    return nms_keyfile_storage_new_connection(self,
+                                              g_steal_pointer(&connection),
+                                              full_filename,
+                                              storage_type,
+                                              is_nm_generated_opt,
+                                              is_volatile_opt,
+                                              is_external_opt,
+                                              shadowed_storage,
+                                              shadowed_owned_opt,
+                                              &st.st_mtim);
+}
+
+static NMSKeyfileStorage *
+_load_file_from_path(NMSKeyfilePlugin *    self,
+                     const char *          full_filename,
+                     NMSKeyfileStorageType storage_type,
+                     GError **             error)
+{
+    gs_free char *f_dirname_free = NULL;
+    const char *  f_filename;
+    const char *  f_dirname;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+
+    f_filename = strrchr(full_filename, '/');
+    f_dirname  = nm_strndup_a(300, full_filename, f_filename - full_filename, &f_dirname_free);
+    f_filename++;
+    return _load_file(self, f_dirname, f_filename, storage_type, error);
+}
+
+static void
+_load_dir(NMSKeyfilePlugin *    self,
+          NMSKeyfileStorageType storage_type,
+          const char *          dirname,
+          NMSettUtilStorages *  storages)
+{
+    const char *       filename;
+    GDir *             dir;
+    gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+
+    dir = g_dir_open(dirname, 0, NULL);
+    if (!dir)
+        return;
+
+    dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free);
+
+    while ((filename = g_dir_read_name(dir))) {
+        gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+        filename = g_strdup(filename);
+        if (!g_hash_table_add(dupl_filenames, (char *) filename))
+            continue;
+
+        storage = _load_file(self, dirname, filename, storage_type, NULL);
+        if (!storage)
+            continue;
+
+        nm_sett_util_storages_add_take(storages, g_steal_pointer(&storage));
+    }
+
+    g_dir_close(dir);
+
+#if NM_MORE_ASSERTS
+    {
+        NMSKeyfileStorage *storage;
+
+        c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst)
+            nm_assert(NMS_IS_KEYFILE_STORAGE(storage));
+    }
+#endif
+}
+
+/*****************************************************************************/
+
+static void
+_storages_consolidate(NMSKeyfilePlugin *                     self,
+                      NMSettUtilStorages *                   storages_new,
+                      gboolean                               replace_all,
+                      GHashTable *                           storages_replaced,
+                      NMSettingsPluginConnectionLoadCallback callback,
+                      gpointer                               user_data)
+{
+    NMSKeyfilePluginPrivate *priv                  = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    CList                    lst_conn_info_deleted = C_LIST_INIT(lst_conn_info_deleted);
+    gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+    CList                        storages_deleted;
+    NMSKeyfileStorage *          storage_safe;
+    NMSKeyfileStorage *          storage_new;
+    NMSKeyfileStorage *          storage_old;
+    NMSKeyfileStorage *          storage;
+    guint                        i;
+
+    storages_modified = g_ptr_array_new_with_free_func(g_object_unref);
+    c_list_init(&storages_deleted);
+
+    c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst)
+        storage_old->is_dirty = TRUE;
+
+    c_list_for_each_entry_safe (storage_new,
+                                storage_safe,
+                                &storages_new->_storage_lst_head,
+                                parent._storage_lst) {
+        storage_old =
+            nm_sett_util_storages_lookup_by_filename(&priv->storages,
+                                                     nms_keyfile_storage_get_filename(storage_new));
+
+        nm_sett_util_storages_steal(storages_new, storage_new);
+
+        if (!storage_old
+            || !nm_streq(nms_keyfile_storage_get_uuid(storage_new),
+                         nms_keyfile_storage_get_uuid(storage_old))) {
+            if (storage_old) {
+                nm_sett_util_storages_steal(&priv->storages, storage_old);
+                c_list_link_tail(&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+            }
+            storage_new->is_dirty = FALSE;
+            nm_sett_util_storages_add_take(&priv->storages, storage_new);
+            g_ptr_array_add(storages_modified, g_object_ref(storage_new));
+            continue;
+        }
+
+        storage_old->is_dirty = FALSE;
+        nms_keyfile_storage_copy_content(storage_old, storage_new);
+        nms_keyfile_storage_destroy(storage_new);
+        g_ptr_array_add(storages_modified, g_object_ref(storage_old));
+    }
+
+    c_list_for_each_entry_safe (storage_old,
+                                storage_safe,
+                                &priv->storages._storage_lst_head,
+                                parent._storage_lst) {
+        if (!storage_old->is_dirty)
+            continue;
+        if (replace_all
+            || (storages_replaced && g_hash_table_contains(storages_replaced, storage_old))) {
+            nm_sett_util_storages_steal(&priv->storages, storage_old);
+            c_list_link_tail(&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+        }
+    }
+
+    /* raise events. */
+
+    for (i = 0; i < storages_modified->len; i++) {
+        storage           = storages_modified->pdata[i];
+        storage->is_dirty = TRUE;
+    }
+
+    for (i = 0; i < storages_modified->len; i++) {
+        gs_unref_object NMConnection *connection = NULL;
+
+        storage = storages_modified->pdata[i];
+
+        if (!storage->is_dirty) {
+            /* the entry is no longer is_dirty. In the meantime we already emitted
+             * another signal for it. */
+            continue;
+        }
+        storage->is_dirty = FALSE;
+
+        if (c_list_is_empty(&storage->parent._storage_lst)) {
+            /* hm? The profile was deleted in the meantime? That is only possible
+             * if the signal handler called again into the plugin. In any case, the event
+             * was already emitted. Skip. */
+            continue;
+        }
+
+        nm_assert(
+            storage
+            == nm_sett_util_storages_lookup_by_filename(&priv->storages,
+                                                        nms_keyfile_storage_get_filename(storage)));
+
+        connection = nms_keyfile_storage_steal_connection(storage);
+
+        callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), connection, user_data);
+    }
+
+    while ((storage = c_list_first_entry(&storages_deleted,
+                                         NMSKeyfileStorage,
+                                         parent._storage_by_uuid_lst))) {
+        c_list_unlink(&storage->parent._storage_by_uuid_lst);
+        callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), NULL, user_data);
+        nms_keyfile_storage_destroy(storage);
+    }
+}
+
+static void
+reload_connections(NMSettingsPlugin *                     plugin,
+                   NMSettingsPluginConnectionLoadCallback callback,
+                   gpointer                               user_data)
+{
+    NMSKeyfilePlugin *                                  self = NMS_KEYFILE_PLUGIN(plugin);
+    NMSKeyfilePluginPrivate *                           priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new =
+        NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_keyfile_storage_destroy);
+    int i;
+
+    _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_RUN, priv->dirname_run, &storages_new);
+    if (priv->dirname_etc)
+        _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_ETC, priv->dirname_etc, &storages_new);
+    for (i = 0; priv->dirname_libs[i]; i++)
+        _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_LIB(i), priv->dirname_libs[i], &storages_new);
+
+    _storages_consolidate(self, &storages_new, TRUE, NULL, callback, user_data);
+}
+
+static void
+load_connections(NMSettingsPlugin *                     plugin,
+                 NMSettingsPluginConnectionLoadEntry *  entries,
+                 gsize                                  n_entries,
+                 NMSettingsPluginConnectionLoadCallback callback,
+                 gpointer                               user_data)
+{
+    NMSKeyfilePlugin *                                  self = NMS_KEYFILE_PLUGIN(plugin);
+    NMSKeyfilePluginPrivate *                           priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new =
+        NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_keyfile_storage_destroy);
+    gs_unref_hashtable GHashTable *dupl_filenames    = NULL;
+    gs_unref_hashtable GHashTable *storages_replaced = NULL;
+    gs_unref_hashtable GHashTable *loaded_uuids      = NULL;
+    const char *                   loaded_uuid;
+    GHashTableIter                 h_iter;
+    gsize                          i;
+
+    if (n_entries == 0)
+        return;
+
+    dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+
+    loaded_uuids = g_hash_table_new(nm_str_hash, g_str_equal);
+
+    storages_replaced = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL);
+
+    for (i = 0; i < n_entries; i++) {
+        NMSettingsPluginConnectionLoadEntry *const entry = &entries[i];
+        NMSKeyfileStorageType                      storage_type;
+        gs_free_error GError *               local = NULL;
+        const char *                         f_filename;
+        const char *                         f_dirname;
+        const char *                         full_filename;
+        gs_free char *                       full_filename_keep = NULL;
+        gboolean                             is_nmmeta_file;
+        NMSettingsPluginConnectionLoadEntry *dupl_content_entry;
+        gboolean                             failed_due_to_invalid_filename;
+        gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+        if (entry->handled)
+            continue;
+
+        if (!_path_detect_storage_type(entry->filename,
+                                       (const char *const *) priv->dirname_libs,
+                                       priv->dirname_etc,
+                                       priv->dirname_run,
+                                       &storage_type,
+                                       &f_dirname,
+                                       &f_filename,
+                                       &is_nmmeta_file,
+                                       &failed_due_to_invalid_filename)) {
+            if (failed_due_to_invalid_filename) {
+                entry->handled = TRUE;
+                nm_utils_error_set(&entry->error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   "filename is not valid for a keyfile");
+            }
+            continue;
+        }
+
+        full_filename_keep = g_build_filename(f_dirname, f_filename, NULL);
+
+        if ((dupl_content_entry = g_hash_table_lookup(dupl_filenames, full_filename_keep))) {
+            /* we already visited this file. */
+            entry->handled = dupl_content_entry->handled;
+            if (dupl_content_entry->error) {
+                g_set_error_literal(&entry->error,
+                                    dupl_content_entry->error->domain,
+                                    dupl_content_entry->error->code,
+                                    dupl_content_entry->error->message);
+            }
+            continue;
+        }
+
+        entry->handled = TRUE;
+
+        full_filename = full_filename_keep;
+        if (!g_hash_table_insert(dupl_filenames, g_steal_pointer(&full_filename_keep), entry))
+            nm_assert_not_reached();
+
+        storage = _load_file(self, f_dirname, f_filename, storage_type, &local);
+        if (!storage) {
+            if (nm_utils_file_stat(full_filename, NULL) == -ENOENT) {
+                NMSKeyfileStorage *storage2;
+
+                /* the file does not exist. We take that as indication to unload the file
+                 * that was previously loaded... */
+                storage2 = nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename);
+                if (storage2)
+                    g_hash_table_add(storages_replaced, g_object_ref(storage2));
+                continue;
+            }
+            g_propagate_error(&entry->error, g_steal_pointer(&local));
+            continue;
+        }
+
+        g_hash_table_add(loaded_uuids, (char *) nms_keyfile_storage_get_uuid(storage));
+
+        nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage));
+    }
+
+    /* now we visit all UUIDs that are about to change... */
+    g_hash_table_iter_init(&h_iter, loaded_uuids);
+    while (g_hash_table_iter_next(&h_iter, (gpointer *) &loaded_uuid, NULL)) {
+        NMSKeyfileStorage *          storage;
+        NMSettUtilStorageByUuidHead *sbuh;
+
+        sbuh = nm_sett_util_storages_lookup_by_uuid(&priv->storages, loaded_uuid);
+        if (!sbuh)
+            continue;
+
+        c_list_for_each_entry (storage,
+                               &sbuh->_storage_by_uuid_lst_head,
+                               parent._storage_by_uuid_lst) {
+            const char *    full_filename = nms_keyfile_storage_get_filename(storage);
+            gs_unref_object NMSKeyfileStorage *storage_new = NULL;
+            gs_free_error GError *local                    = NULL;
+
+            if (g_hash_table_contains(dupl_filenames, full_filename)) {
+                /* already re-loaded. */
+                continue;
+            }
+
+            /* @storage has a UUID that was just loaded from disk, but we have an entry in cache.
+             * Reload that file too despite not being told to do so. The reason is to get
+             * the latest file timestamp so that we get the priorities right. */
+
+            storage_new = _load_file_from_path(self, full_filename, storage->storage_type, &local);
+            if (storage_new && !nm_streq(loaded_uuid, nms_keyfile_storage_get_uuid(storage_new))) {
+                /* the file now references a different UUID. We are not told to reload
+                 * that file, so this means the existing storage (with the previous
+                 * filename and UUID tuple) is no longer valid. */
+                g_clear_object(&storage_new);
+            }
+
+            g_hash_table_add(storages_replaced, g_object_ref(storage));
+            if (storage_new)
+                nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage_new));
+        }
+    }
+
+    nm_clear_pointer(&loaded_uuids, g_hash_table_destroy);
+    nm_clear_pointer(&dupl_filenames, g_hash_table_destroy);
+
+    _storages_consolidate(self, &storages_new, FALSE, storages_replaced, callback, user_data);
+}
+
+gboolean
+nms_keyfile_plugin_add_connection(NMSKeyfilePlugin *  self,
+                                  NMConnection *      connection,
+                                  gboolean            in_memory,
+                                  gboolean            is_nm_generated,
+                                  gboolean            is_volatile,
+                                  gboolean            is_external,
+                                  const char *        shadowed_storage,
+                                  gboolean            shadowed_owned,
+                                  NMSettingsStorage **out_storage,
+                                  NMConnection **     out_connection,
+                                  GError **           error)
+{
+    NMSKeyfilePluginPrivate *priv               = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    gs_unref_object NMConnection *reread        = NULL;
+    gs_free char *                full_filename = NULL;
+    NMSKeyfileStorageType         storage_type;
+    gs_unref_object NMSKeyfileStorage *storage = NULL;
+    GError *                           local   = NULL;
+    const char *                       uuid;
+    gboolean                           reread_same;
+    struct timespec                    mtime;
+    char                               strbuf[100];
+
+    nm_assert(NM_IS_CONNECTION(connection));
+    nm_assert(out_storage && !*out_storage);
+    nm_assert(out_connection && !*out_connection);
+
+    nm_assert(in_memory
+              || (!is_nm_generated && !is_volatile && !is_external && !shadowed_storage
+                  && !shadowed_owned));
+
+    uuid = nm_connection_get_uuid(connection);
+
+    /* Note that even if the caller requests persistent storage, we may switch to in-memory, if
+     * no /etc directory is configured. */
+    storage_type = !in_memory && priv->dirname_etc ? NMS_KEYFILE_STORAGE_TYPE_ETC
+                                                   : NMS_KEYFILE_STORAGE_TYPE_RUN;
+
+    if (!nms_keyfile_writer_connection(
+            connection,
+            is_nm_generated,
+            is_volatile,
+            is_external,
+            shadowed_storage,
+            shadowed_owned,
+            storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc : priv->dirname_run,
+            _get_plugin_dir(priv),
+            NULL,
+            FALSE,
+            FALSE,
+            nm_sett_util_allow_filename_cb,
+            NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, NULL),
+            &full_filename,
+            &reread,
+            &reread_same,
+            &local)) {
+        _LOGT("commit: %s (%s) failed to add: %s",
+              nm_connection_get_uuid(connection),
+              nm_connection_get_id(connection),
+              local->message);
+        g_propagate_error(error, local);
+        return FALSE;
+    }
+
+    if (!reread || reread_same)
+        nm_g_object_ref_set(&reread, connection);
+
+    nm_assert(_nm_connection_verify(reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(nm_streq0(nm_connection_get_uuid(connection), nm_connection_get_uuid(reread)));
+
+    nm_assert(full_filename && full_filename[0] == '/');
+    nm_assert(!nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename));
+
+    _LOGT("commit: %s (%s) added as \"%s\"%s%s%s%s",
+          uuid,
+          nm_connection_get_id(connection),
+          full_filename,
+          _extra_flags_to_string(strbuf, sizeof(strbuf), is_nm_generated, is_volatile, is_external),
+          NM_PRINT_FMT_QUOTED(shadowed_storage,
+                              " (shadows \"",
+                              shadowed_storage,
+                              shadowed_owned ? "\", owned)" : "\")",
+                              ""));
+
+    storage =
+        nms_keyfile_storage_new_connection(self,
+                                           g_steal_pointer(&reread),
+                                           full_filename,
+                                           storage_type,
+                                           is_nm_generated ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           is_volatile ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           is_external ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           shadowed_storage,
+                                           shadowed_owned ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           nm_sett_util_stat_mtime(full_filename, FALSE, &mtime));
+
+    nm_sett_util_storages_add_take(&priv->storages, g_object_ref(storage));
+
+    *out_connection = nms_keyfile_storage_steal_connection(storage);
+    *out_storage    = NM_SETTINGS_STORAGE(g_steal_pointer(&storage));
+
+    return TRUE;
+}
+
+static gboolean
+add_connection(NMSettingsPlugin *  plugin,
+               NMConnection *      connection,
+               NMSettingsStorage **out_storage,
+               NMConnection **     out_connection,
+               GError **           error)
+{
+    return nms_keyfile_plugin_add_connection(NMS_KEYFILE_PLUGIN(plugin),
+                                             connection,
+                                             FALSE,
+                                             FALSE,
+                                             FALSE,
+                                             FALSE,
+                                             NULL,
+                                             FALSE,
+                                             out_storage,
+                                             out_connection,
+                                             error);
+}
+
+gboolean
+nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *  self,
+                                     NMSettingsStorage * storage_x,
+                                     NMConnection *      connection,
+                                     gboolean            is_nm_generated,
+                                     gboolean            is_volatile,
+                                     gboolean            is_external,
+                                     const char *        shadowed_storage,
+                                     gboolean            shadowed_owned,
+                                     gboolean            force_rename,
+                                     NMSettingsStorage **out_storage,
+                                     NMConnection **     out_connection,
+                                     GError **           error)
+{
+    NMSKeyfilePluginPrivate *priv                  = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    NMSKeyfileStorage *      storage               = NMS_KEYFILE_STORAGE(storage_x);
+    gs_unref_object NMConnection *connection_clone = NULL;
+    gs_unref_object NMConnection *reread           = NULL;
+    gs_free char *                full_filename    = NULL;
+    gs_free_error GError *local                    = NULL;
+    struct timespec       mtime;
+    const char *          previous_filename;
+    gboolean              reread_same;
+    const char *          uuid;
+    char                  strbuf[100];
+
+    _nm_assert_storage(self, storage, TRUE);
+    nm_assert(NM_IS_CONNECTION(connection));
+    nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(nm_streq(nms_keyfile_storage_get_uuid(storage), nm_connection_get_uuid(connection)));
+    nm_assert(!error || !*error);
+    nm_assert(NM_IN_SET(storage->storage_type,
+                        NMS_KEYFILE_STORAGE_TYPE_ETC,
+                        NMS_KEYFILE_STORAGE_TYPE_RUN));
+    nm_assert(!storage->is_meta_data);
+    nm_assert(storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN
+              || (!is_nm_generated && !is_volatile && !is_external && !shadowed_storage
+                  && !shadowed_owned));
+    nm_assert(!shadowed_owned || shadowed_storage);
+    nm_assert(priv->dirname_etc || storage->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC);
+
+    previous_filename = nms_keyfile_storage_get_filename(storage);
+    uuid              = nms_keyfile_storage_get_uuid(storage);
+
+    if (!nms_keyfile_writer_connection(
+            connection,
+            is_nm_generated,
+            is_volatile,
+            is_external,
+            shadowed_storage,
+            shadowed_owned,
+            storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc
+                                                                  : priv->dirname_run,
+            _get_plugin_dir(priv),
+            previous_filename,
+            FALSE,
+            FALSE,
+            nm_sett_util_allow_filename_cb,
+            NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, previous_filename),
+            &full_filename,
+            &reread,
+            &reread_same,
+            &local)) {
+        _LOGW("commit: failure to write %s (%s) to \"%s\": %s",
+              uuid,
+              nm_connection_get_id(connection_clone),
+              previous_filename,
+              local->message);
+        g_propagate_error(error, g_steal_pointer(&local));
+        return FALSE;
+    }
+
+    nm_assert(full_filename && nm_streq(full_filename, previous_filename));
+
+    if (!reread || reread_same)
+        nm_g_object_ref_set(&reread, connection);
+
+    nm_assert(_nm_connection_verify(reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(nm_streq(nm_connection_get_uuid(reread), uuid));
+
+    _LOGT("commit: \"%s\": profile %s (%s) written%s%s%s%s",
+          full_filename,
+          uuid,
+          nm_connection_get_id(connection),
+          _extra_flags_to_string(strbuf, sizeof(strbuf), is_nm_generated, is_volatile, is_external),
+          NM_PRINT_FMT_QUOTED(shadowed_storage,
+                              shadowed_owned ? " (owns \"" : " (shadows \"",
+                              shadowed_storage,
+                              "\")",
+                              ""));
+
+    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;
+
+    *out_storage    = g_object_ref(NM_SETTINGS_STORAGE(storage));
+    *out_connection = g_steal_pointer(&reread);
+    return TRUE;
+}
+
+static gboolean
+update_connection(NMSettingsPlugin *  plugin,
+                  NMSettingsStorage * storage,
+                  NMConnection *      connection,
+                  NMSettingsStorage **out_storage,
+                  NMConnection **     out_connection,
+                  GError **           error)
+{
+    return nms_keyfile_plugin_update_connection(NMS_KEYFILE_PLUGIN(plugin),
+                                                storage,
+                                                connection,
+                                                FALSE,
+                                                FALSE,
+                                                FALSE,
+                                                NULL,
+                                                FALSE,
+                                                FALSE,
+                                                out_storage,
+                                                out_connection,
+                                                error);
+}
+
+static gboolean
+delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError **error)
+{
+    NMSKeyfilePlugin *       self              = NMS_KEYFILE_PLUGIN(plugin);
+    NMSKeyfilePluginPrivate *priv              = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    gs_unref_object NMSKeyfileStorage *storage = g_object_ref(NMS_KEYFILE_STORAGE(storage_x));
+    const char *                       remove_from_disk_errmsg = NULL;
+    const char *                       operation_message;
+    const char *                       previous_filename;
+    const char *                       uuid;
+    gboolean                           success = TRUE;
+
+    _nm_assert_storage(self, storage, TRUE);
+    nm_assert(!error || !*error);
+
+    previous_filename = nms_keyfile_storage_get_filename(storage);
+    uuid              = nms_keyfile_storage_get_uuid(storage);
+
+    if (!NM_IN_SET(storage->storage_type,
+                   NMS_KEYFILE_STORAGE_TYPE_ETC,
+                   NMS_KEYFILE_STORAGE_TYPE_RUN)) {
+        nm_utils_error_set(error,
+                           NM_UTILS_ERROR_UNKNOWN,
+                           "profile in read-only storage cannot be deleted");
+        success           = FALSE;
+        operation_message = "dropped readonly file from memory";
+    } else if (unlink(previous_filename) != 0) {
+        int errsv;
+
+        errsv = errno;
+        if (errsv != ENOENT) {
+            remove_from_disk_errmsg = nm_strerror_native(errsv);
+            operation_message       = "failed to delete from disk";
+            success                 = FALSE;
+            nm_utils_error_set_errno(error,
+                                     errsv,
+                                     "failure to delete \"%s\": %s",
+                                     previous_filename);
+        } else
+            operation_message = "does not exist on disk";
+    } else
+        operation_message = "deleted from disk";
+
+    _LOGT("commit: deleted \"%s\", %s %s (%s%s%s%s)",
+          previous_filename,
+          storage->is_meta_data ? "meta-data" : "profile",
+          uuid,
+          operation_message,
+          NM_PRINT_FMT_QUOTED(remove_from_disk_errmsg, ": ", remove_from_disk_errmsg, "", ""));
+
+    if (success) {
+        nm_sett_util_storages_steal(&priv->storages, storage);
+        nms_keyfile_storage_destroy(storage);
+    }
+
+    return success;
+}
+
+/**
+ * nms_keyfile_plugin_set_nmmeta_tombstone:
+ * @self: the #NMSKeyfilePlugin instance
+ * @simulate: if %TRUE, don't do anything on the filename but just pretend
+ *   that the loaded UUID file gets tracked/untracked. In this mode, the function
+ *   cannot fail (except on hard-failure, see below).
+ *   The idea is that you first try without simulate to write to disk.
+ *   If that fails, you might still want to forcefully pretend (in-memory
+ *   only) that this uuid is marked as tombstone (or not), as desired.
+ *   So you repeate the call with @simulate %TRUE.
+ * @uuid: the UUID for which to write/delete the nmmeta file
+ * @in_memory: the storage type, either /etc or /run. Note that if @self
+ *   has no /etc directory configured, this results in a hard failure.
+ * @set: if %TRUE, write the symlink to point to /dev/null. If %FALSE,
+ *   delete the nmmeta file (if it exists).
+ * @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
+ *   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.
+ *
+ * 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).
+ *
+ * A hard failure can only happen if @self has no /etc directory configured
+ * and @in_memory is FALSE. In such case even @simulate call fails (which
+ * otherwise would always succeed).
+ * Also, if you get a hard-failure (with @simulate %FALSE) there is no point
+ * in retrying with @simulate %TRUE (contrary to all other cases!).
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nms_keyfile_plugin_set_nmmeta_tombstone(NMSKeyfilePlugin *  self,
+                                        gboolean            simulate,
+                                        const char *        uuid,
+                                        gboolean            in_memory,
+                                        gboolean            set,
+                                        const char *        shadowed_storage,
+                                        NMSettingsStorage **out_storage,
+                                        gboolean *          out_hard_failure)
+{
+    NMSKeyfilePluginPrivate *priv;
+    gboolean                 hard_failure = FALSE;
+    NMSKeyfileStorage *      storage;
+    gs_unref_object NMSKeyfileStorage *storage_result = NULL;
+    gboolean                           nmmeta_errno;
+    gs_free char *                     nmmeta_filename = NULL;
+    NMSKeyfileStorageType              storage_type;
+    const char *                       loaded_path;
+    const char *                       dirname;
+
+    nm_assert(NMS_IS_KEYFILE_PLUGIN(self));
+    nm_assert(nm_utils_is_uuid(uuid));
+    nm_assert(!out_storage || !*out_storage);
+    nm_assert(!shadowed_storage || (set && in_memory));
+
+    priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    loaded_path = set ? NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL : NULL;
+
+    if (in_memory) {
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+        dirname      = priv->dirname_run;
+    } else {
+        if (!priv->dirname_etc) {
+            _LOGT("commit: cannot %s%s nmmeta file for %s as there is no /etc directory",
+                  simulate ? "simulate " : "",
+                  loaded_path ? "write" : "delete",
+                  uuid);
+            nmmeta_errno = 0;
+            hard_failure = TRUE;
+            goto out;
+        }
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+        dirname      = priv->dirname_etc;
+    }
+
+    if (simulate) {
+        nmmeta_errno    = 0;
+        nmmeta_filename = nms_keyfile_nmmeta_filename(dirname, uuid, FALSE);
+    } else {
+        nmmeta_errno = nms_keyfile_nmmeta_write(dirname,
+                                                uuid,
+                                                loaded_path,
+                                                FALSE,
+                                                shadowed_storage,
+                                                &nmmeta_filename);
+    }
+
+    _LOGT("commit: %s nmmeta file \"%s\"%s%s%s%s%s%s %s%s%s%s",
+          loaded_path ? "writing" : "deleting",
+          nmmeta_filename,
+          NM_PRINT_FMT_QUOTED(loaded_path, " (pointing to \"", loaded_path, "\")", ""),
+          NM_PRINT_FMT_QUOTED(shadowed_storage, " (shadows \"", shadowed_storage, "\")", ""),
+          simulate ? "simulated" : (nmmeta_errno < 0 ? "failed" : "succeeded"),
+          NM_PRINT_FMT_QUOTED(nmmeta_errno < 0,
+                              " (",
+                              nm_strerror_native(nm_errno_native(nmmeta_errno)),
+                              ")",
+                              ""));
+
+    if (nmmeta_errno < 0)
+        goto out;
+
+    storage = nm_sett_util_storages_lookup_by_filename(&priv->storages, nmmeta_filename);
+
+    nm_assert(!storage
+              || (storage->is_meta_data && storage->storage_type == storage_type
+                  && nm_streq(nms_keyfile_storage_get_uuid(storage), uuid)));
+
+    if (loaded_path) {
+        if (!storage) {
+            storage = nms_keyfile_storage_new_tombstone(self,
+                                                        uuid,
+                                                        nmmeta_filename,
+                                                        storage_type,
+                                                        shadowed_storage);
+            nm_sett_util_storages_add_take(&priv->storages, storage);
+        } else {
+            g_free(storage->u.meta_data.shadowed_storage);
+            storage->u.meta_data.shadowed_storage = g_strdup(shadowed_storage);
+        }
+
+        storage_result = g_object_ref(storage);
+    } else {
+        if (storage)
+            storage_result = nm_sett_util_storages_steal(&priv->storages, storage);
+    }
+
+out:
+    nm_assert(nmmeta_errno <= 0);
+    nm_assert(nmmeta_errno < 0 || !hard_failure);
+    nm_assert(nmmeta_errno == 0 || !storage_result);
+
+    NM_SET_OUT(out_hard_failure, hard_failure);
+    NM_SET_OUT(out_storage, (NMSettingsStorage *) g_steal_pointer(&storage_result));
+    return nmmeta_errno >= 0;
+}
+
+/*****************************************************************************/
+
+static void
+config_changed_cb(NMConfig *          config,
+                  NMConfigData *      config_data,
+                  NMConfigChangeFlags changes,
+                  NMConfigData *      old_data,
+                  NMSKeyfilePlugin *  self)
+{
+    gs_free char *old_value = NULL;
+    gs_free char *new_value = NULL;
+
+    old_value = nm_config_data_get_value(old_data,
+                                         NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                         NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES,
+                                         NM_CONFIG_GET_VALUE_TYPE_SPEC);
+    new_value = nm_config_data_get_value(config_data,
+                                         NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                         NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES,
+                                         NM_CONFIG_GET_VALUE_TYPE_SPEC);
+
+    if (!nm_streq0(old_value, new_value))
+        _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self));
+}
+
+static GSList *
+get_unmanaged_specs(NMSettingsPlugin *config)
+{
+    NMSKeyfilePluginPrivate *priv  = NMS_KEYFILE_PLUGIN_GET_PRIVATE(config);
+    gs_free char *           value = NULL;
+
+    value = nm_config_data_get_value(nm_config_get_data(priv->config),
+                                     NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                     NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES,
+                                     NM_CONFIG_GET_VALUE_TYPE_SPEC);
+    return nm_match_spec_split(value);
+}
+
+/*****************************************************************************/
+
+static void
+nms_keyfile_plugin_init(NMSKeyfilePlugin *plugin)
+{
+    NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin);
+
+    priv->config = g_object_ref(nm_config_get());
+
+    priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT(priv->storages,
+                                                                     nms_keyfile_storage_destroy);
+
+    /* dirname_libs are a set of read-only directories with lower priority than /etc or /run.
+     * There is nothing complicated about having multiple of such directories, so dirname_libs
+     * is a list (which currently only has at most one directory). */
+    priv->dirname_libs[0] = nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_LIB), FALSE);
+    priv->dirname_libs[1] = NULL;
+    priv->dirname_run     = nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_RUN), FALSE);
+    priv->dirname_etc     = nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG,
+                                                 NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
+                                                 NM_CONFIG_GET_VALUE_STRIP);
+    if (priv->dirname_etc && priv->dirname_etc[0] == '\0') {
+        /* special case: configure an empty keyfile path so that NM has no writable keyfile
+         * directory. In this case, NM will only honor dirname_libs and dirname_run, meaning
+         * it cannot persist profile to non-volatile memory. */
+        nm_clear_g_free(&priv->dirname_etc);
+    } else if (!priv->dirname_etc || priv->dirname_etc[0] != '/') {
+        /* either invalid path or unspecified. Use the default. */
+        g_free(priv->dirname_etc);
+        priv->dirname_etc =
+            nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_ETC_DEFAULT), FALSE);
+    } else
+        nm_sd_utils_path_simplify(priv->dirname_etc, FALSE);
+
+    /* no duplicates */
+    if (NM_IN_STRSET(priv->dirname_libs[0], priv->dirname_etc, priv->dirname_run))
+        nm_clear_g_free(&priv->dirname_libs[0]);
+    if (NM_IN_STRSET(priv->dirname_etc, priv->dirname_run))
+        nm_clear_g_free(&priv->dirname_etc);
+
+    nm_assert(!priv->dirname_libs[0] || priv->dirname_libs[0][0] == '/');
+    nm_assert(!priv->dirname_etc || priv->dirname_etc[0] == '/');
+    nm_assert(priv->dirname_run && priv->dirname_run[0] == '/');
+}
+
+static void
+constructed(GObject *object)
+{
+    NMSKeyfilePlugin *       self = NMS_KEYFILE_PLUGIN(object);
+    NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    G_OBJECT_CLASS(nms_keyfile_plugin_parent_class)->constructed(object);
+
+    if (nm_config_data_has_value(nm_config_get_data_orig(priv->config),
+                                 NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME,
+                                 NM_CONFIG_GET_VALUE_RAW))
+        _LOGW("'hostname' option is deprecated and has no effect");
+
+    if (nm_config_data_has_value(nm_config_get_data_orig(priv->config),
+                                 NM_CONFIG_KEYFILE_GROUP_MAIN,
+                                 NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
+                                 NM_CONFIG_GET_VALUE_RAW))
+        _LOGW("'monitor-connection-files' option is deprecated and has no effect");
+
+    g_signal_connect(G_OBJECT(priv->config),
+                     NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+                     G_CALLBACK(config_changed_cb),
+                     self);
+}
+
+NMSKeyfilePlugin *
+nms_keyfile_plugin_new(void)
+{
+    return g_object_new(NMS_TYPE_KEYFILE_PLUGIN, NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSKeyfilePlugin *       self = NMS_KEYFILE_PLUGIN(object);
+    NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    if (priv->config)
+        g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, object);
+
+    nm_sett_util_storages_clear(&priv->storages);
+
+    nm_clear_g_free(&priv->dirname_libs[0]);
+    nm_clear_g_free(&priv->dirname_etc);
+    nm_clear_g_free(&priv->dirname_run);
+
+    g_clear_object(&priv->config);
+
+    G_OBJECT_CLASS(nms_keyfile_plugin_parent_class)->dispose(object);
+}
+
+static void
+nms_keyfile_plugin_class_init(NMSKeyfilePluginClass *klass)
+{
+    GObjectClass *         object_class = G_OBJECT_CLASS(klass);
+    NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass);
+
+    object_class->constructed = constructed;
+    object_class->dispose     = dispose;
+
+    plugin_class->plugin_name         = "keyfile";
+    plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+    plugin_class->reload_connections  = reload_connections;
+    plugin_class->load_connections    = load_connections;
+    plugin_class->add_connection      = add_connection;
+    plugin_class->update_connection   = update_connection;
+    plugin_class->delete_connection   = delete_connection;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h
new file mode 100644
index 00000000..5d9f2da3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_PLUGIN_H__
+#define __NMS_KEYFILE_PLUGIN_H__
+
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+
+#include "nms-keyfile-utils.h"
+
+#define NMS_TYPE_KEYFILE_PLUGIN (nms_keyfile_plugin_get_type())
+#define NMS_KEYFILE_PLUGIN(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePlugin))
+#define NMS_KEYFILE_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass))
+#define NMS_IS_KEYFILE_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_KEYFILE_PLUGIN))
+#define NMS_IS_KEYFILE_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_KEYFILE_PLUGIN))
+#define NMS_KEYFILE_PLUGIN_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass))
+
+typedef struct _NMSKeyfilePlugin      NMSKeyfilePlugin;
+typedef struct _NMSKeyfilePluginClass NMSKeyfilePluginClass;
+
+GType nms_keyfile_plugin_get_type(void);
+
+NMSKeyfilePlugin *nms_keyfile_plugin_new(void);
+
+gboolean nms_keyfile_plugin_add_connection(NMSKeyfilePlugin *  self,
+                                           NMConnection *      connection,
+                                           gboolean            in_memory,
+                                           gboolean            is_nm_generated,
+                                           gboolean            is_volatile,
+                                           gboolean            is_external,
+                                           const char *        shadowed_storage,
+                                           gboolean            shadowed_owned,
+                                           NMSettingsStorage **out_storage,
+                                           NMConnection **     out_connection,
+                                           GError **           error);
+
+gboolean nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *  self,
+                                              NMSettingsStorage * storage,
+                                              NMConnection *      connection,
+                                              gboolean            is_nm_generated,
+                                              gboolean            is_volatile,
+                                              gboolean            is_external,
+                                              const char *        shadowed_storage,
+                                              gboolean            shadowed_owned,
+                                              gboolean            force_rename,
+                                              NMSettingsStorage **out_storage,
+                                              NMConnection **     out_connection,
+                                              GError **           error);
+
+gboolean nms_keyfile_plugin_set_nmmeta_tombstone(NMSKeyfilePlugin *  self,
+                                                 gboolean            simulate,
+                                                 const char *        uuid,
+                                                 gboolean            in_memory,
+                                                 gboolean            set,
+                                                 const char *        shadowed_storage,
+                                                 NMSettingsStorage **out_storage,
+                                                 gboolean *          out_hard_failure);
+
+#endif /* __NMS_KEYFILE_PLUGIN_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-reader.c b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c
new file mode 100644
index 00000000..70b047f3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-reader.h"
+
+#include <sys/stat.h>
+
+#include "nm-keyfile-internal.h"
+
+#include "NetworkManagerUtils.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+static const char *
+_fmt_warn(const NMKeyfileHandlerData *handler_data, char **out_message)
+{
+    const char *group   = handler_data->kf_group_name;
+    const char *message = _nm_keyfile_handler_data_warn_get_message(handler_data);
+
+    if (group) {
+        NMSetting * setting       = handler_data->cur_setting;
+        const char *property_name = handler_data->cur_property;
+        const char *setting_name  = setting ? nm_setting_get_name(setting) : NULL;
+        char *      res;
+
+        if (setting_name) {
+            if (property_name && !strcmp(group, setting_name))
+                res = g_strdup_printf("%s.%s: %s", group, property_name, message);
+            else if (property_name)
+                res = g_strdup_printf("%s/%s.%s: %s", group, setting_name, property_name, message);
+            else if (!strcmp(group, setting_name))
+                res = g_strdup_printf("%s: %s", group, message);
+            else
+                res = g_strdup_printf("%s/%s: %s", group, setting_name, message);
+        } else
+            res = g_strdup_printf("%s: %s", group, message);
+        *out_message = res;
+        return res;
+    }
+
+    return message;
+}
+
+typedef struct {
+    bool verbose;
+} ReadInfo;
+
+static gboolean
+_handler_read(GKeyFile *            keyfile,
+              NMConnection *        connection,
+              NMKeyfileHandlerType  handler_type,
+              NMKeyfileHandlerData *handler_data,
+              void *                user_data)
+{
+    const ReadInfo *read_info = user_data;
+
+    if (handler_type == NM_KEYFILE_HANDLER_TYPE_WARN) {
+        const NMKeyfileHandlerDataWarn *warn_data = &handler_data->warn;
+        NMLogLevel                      level;
+        char *                          message_free = NULL;
+
+        if (!read_info->verbose)
+            return TRUE;
+
+        if (warn_data->severity > NM_KEYFILE_WARN_SEVERITY_WARN)
+            level = LOGL_ERR;
+        else if (warn_data->severity >= NM_KEYFILE_WARN_SEVERITY_WARN)
+            level = LOGL_WARN;
+        else if (warn_data->severity == NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE)
+            level = LOGL_WARN;
+        else
+            level = LOGL_INFO;
+
+        nm_log(level,
+               LOGD_SETTINGS,
+               NULL,
+               nm_connection_get_uuid(connection),
+               "keyfile: %s",
+               _fmt_warn(handler_data, &message_free));
+        g_free(message_free);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+NMConnection *
+nms_keyfile_reader_from_keyfile(GKeyFile *  key_file,
+                                const char *filename,
+                                const char *base_dir,
+                                const char *profile_dir,
+                                gboolean    verbose,
+                                GError **   error)
+{
+    NMConnection *connection;
+    ReadInfo      read_info = {
+        .verbose = verbose,
+    };
+    gs_free char *base_dir_free         = NULL;
+    gs_free char *profile_filename_free = NULL;
+    gs_free char *filename_id           = NULL;
+    const char *  profile_filename      = NULL;
+
+    nm_assert(filename && filename[0]);
+    nm_assert(!base_dir || base_dir[0] == '/');
+    nm_assert(!profile_dir || profile_dir[0] == '/');
+
+    if (base_dir)
+        nm_assert(!strchr(filename, '/'));
+    else {
+        const char *s;
+
+        nm_assert(filename[0] == '/');
+
+        /* @base_dir may be NULL, in which case @filename must be an absolute path,
+         * and the directory is taken as the @base_dir. */
+        s        = strrchr(filename, '/');
+        base_dir = nm_strndup_a(255, filename, s - filename, &base_dir_free);
+        if (!profile_dir || nm_streq(base_dir, profile_dir))
+            profile_filename = filename;
+        filename = &s[1];
+    }
+
+    connection = nm_keyfile_read(key_file,
+                                 base_dir,
+                                 NM_KEYFILE_HANDLER_FLAGS_NONE,
+                                 _handler_read,
+                                 &read_info,
+                                 error);
+    if (!connection)
+        return NULL;
+
+    if (g_str_has_suffix(filename, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) {
+        gsize l = strlen(filename);
+
+        if (l > NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
+            filename_id = g_strndup(filename, l - NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMCONNECTION));
+    }
+
+    nm_keyfile_read_ensure_id(connection, filename_id ?: filename);
+
+    if (!profile_filename) {
+        profile_filename_free = g_build_filename(profile_dir ?: base_dir, filename, NULL);
+        profile_filename      = profile_filename_free;
+    }
+    nm_keyfile_read_ensure_uuid(connection, profile_filename);
+
+    return connection;
+}
+
+NMConnection *
+nms_keyfile_reader_from_file(const char * full_filename,
+                             const char * profile_dir,
+                             struct stat *out_stat,
+                             NMTernary *  out_is_nm_generated,
+                             NMTernary *  out_is_volatile,
+                             NMTernary *  out_is_external,
+                             char **      out_shadowed_storage,
+                             NMTernary *  out_shadowed_owned,
+                             GError **    error)
+{
+    nm_auto_unref_keyfile GKeyFile *key_file     = NULL;
+    NMConnection *                  connection   = NULL;
+    GError *                        verify_error = NULL;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+    nm_assert(!profile_dir || profile_dir[0] == '/');
+
+    NM_SET_OUT(out_is_nm_generated, NM_TERNARY_DEFAULT);
+    NM_SET_OUT(out_is_volatile, NM_TERNARY_DEFAULT);
+    NM_SET_OUT(out_is_external, NM_TERNARY_DEFAULT);
+    NM_SET_OUT(out_shadowed_owned, NM_TERNARY_DEFAULT);
+
+    if (!nms_keyfile_utils_check_file_permissions(NMS_KEYFILE_FILETYPE_KEYFILE,
+                                                  full_filename,
+                                                  out_stat,
+                                                  error))
+        return NULL;
+
+    key_file = g_key_file_new();
+    if (!g_key_file_load_from_file(key_file, full_filename, G_KEY_FILE_NONE, error))
+        return NULL;
+
+    connection =
+        nms_keyfile_reader_from_keyfile(key_file, full_filename, NULL, profile_dir, TRUE, error);
+    if (!connection)
+        return NULL;
+
+    /* Normalize and verify the connection */
+    if (!nm_connection_normalize(connection, NULL, NULL, &verify_error)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "invalid connection: %s",
+                    verify_error->message);
+        g_clear_error(&verify_error);
+        g_object_unref(connection);
+        connection = NULL;
+    }
+
+    NM_SET_OUT(out_is_nm_generated,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+                                       NM_TERNARY_DEFAULT));
+
+    NM_SET_OUT(out_is_volatile,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_VOLATILE,
+                                       NM_TERNARY_DEFAULT));
+
+    NM_SET_OUT(out_is_external,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_EXTERNAL,
+                                       NM_TERNARY_DEFAULT));
+
+    NM_SET_OUT(out_shadowed_storage,
+               g_key_file_get_string(key_file,
+                                     NM_KEYFILE_GROUP_NMMETA,
+                                     NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+                                     NULL));
+
+    NM_SET_OUT(out_shadowed_owned,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+                                       NM_TERNARY_DEFAULT));
+
+    return connection;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-reader.h b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h
new file mode 100644
index 00000000..247457d8
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_READER_H__
+#define __NMS_KEYFILE_READER_H__
+
+#include "nm-connection.h"
+
+NMConnection *nms_keyfile_reader_from_keyfile(GKeyFile *  key_file,
+                                              const char *filename,
+                                              const char *base_dir,
+                                              const char *profile_dir,
+                                              gboolean    verbose,
+                                              GError **   error);
+
+struct stat;
+
+NMConnection *nms_keyfile_reader_from_file(const char * full_filename,
+                                           const char * profile_dir,
+                                           struct stat *out_stat,
+                                           NMTernary *  out_is_nm_generated,
+                                           NMTernary *  out_is_volatile,
+                                           NMTernary *  out_is_external,
+                                           char **      out_shadowed_storage,
+                                           NMTernary *  out_shadowed_owned,
+                                           GError **    error);
+
+#endif /* __NMS_KEYFILE_READER_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
new file mode 100644
index 00000000..89be817a
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -0,0 +1,272 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-storage.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nms-keyfile-plugin.h"
+
+/*****************************************************************************/
+
+struct _NMSKeyfileStorageClass {
+    NMSettingsStorageClass parent;
+};
+
+G_DEFINE_TYPE(NMSKeyfileStorage, nms_keyfile_storage, NM_TYPE_SETTINGS_STORAGE)
+
+/*****************************************************************************/
+
+void
+nms_keyfile_storage_copy_content(NMSKeyfileStorage *dst, const NMSKeyfileStorage *src)
+{
+    nm_assert(src != dst);
+    nm_assert(nm_streq(nms_keyfile_storage_get_uuid(dst), nms_keyfile_storage_get_uuid(src)));
+    nm_assert(
+        nms_keyfile_storage_get_filename(dst)
+        && nm_streq(nms_keyfile_storage_get_filename(dst), nms_keyfile_storage_get_filename(src)));
+    nm_assert(dst->storage_type == src->storage_type);
+    nm_assert(dst->is_meta_data == src->is_meta_data);
+
+    if (dst->is_meta_data) {
+        gs_free char *shadowed_storage_to_free = NULL;
+
+        shadowed_storage_to_free          = g_steal_pointer(&dst->u.meta_data.shadowed_storage);
+        dst->u.meta_data                  = src->u.meta_data;
+        dst->u.meta_data.shadowed_storage = g_strdup(dst->u.meta_data.shadowed_storage);
+    } else {
+        gs_unref_object NMConnection *connection_to_free       = NULL;
+        gs_free char *                shadowed_storage_to_free = NULL;
+
+        connection_to_free       = g_steal_pointer(&dst->u.conn_data.connection);
+        shadowed_storage_to_free = g_steal_pointer(&dst->u.conn_data.shadowed_storage);
+        dst->u.conn_data         = src->u.conn_data;
+        nm_g_object_ref(dst->u.conn_data.connection);
+        dst->u.conn_data.shadowed_storage = g_strdup(dst->u.conn_data.shadowed_storage);
+    }
+}
+
+NMConnection *
+nms_keyfile_storage_steal_connection(NMSKeyfileStorage *self)
+{
+    nm_assert(NMS_IS_KEYFILE_STORAGE(self));
+    nm_assert(self->is_meta_data || NM_IS_CONNECTION(self->u.conn_data.connection));
+
+    return self->is_meta_data ? NULL : g_steal_pointer(&self->u.conn_data.connection);
+}
+
+/*****************************************************************************/
+
+static int
+cmp_fcn(const NMSKeyfileStorage *a, const NMSKeyfileStorage *b)
+{
+    nm_assert(NMS_IS_KEYFILE_STORAGE(a));
+    nm_assert(NMS_IS_KEYFILE_STORAGE(b));
+    nm_assert(a != b);
+
+    /* sort by storage-type, which also has a numeric value according to their
+     * (inverse) priority. */
+    NM_CMP_FIELD_UNSAFE(b, a, storage_type);
+
+    /* meta-data is more important. */
+    NM_CMP_FIELD_UNSAFE(a, b, is_meta_data);
+
+    if (a->is_meta_data) {
+        nm_assert(
+            nm_streq(nms_keyfile_storage_get_filename(a), nms_keyfile_storage_get_filename(b)));
+        NM_CMP_FIELD_UNSAFE(a, b, u.meta_data.is_tombstone);
+    } else {
+        /* newer files are more important. */
+        NM_CMP_FIELD(a, b, u.conn_data.stat_mtime.tv_sec);
+        NM_CMP_FIELD(a, b, u.conn_data.stat_mtime.tv_nsec);
+
+        NM_CMP_DIRECT_STRCMP(nms_keyfile_storage_get_filename(a),
+                             nms_keyfile_storage_get_filename(b));
+    }
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+static void
+nms_keyfile_storage_init(NMSKeyfileStorage *self)
+{}
+
+static NMSKeyfileStorage *
+_storage_new(NMSKeyfilePlugin *    plugin,
+             const char *          uuid,
+             const char *          filename,
+             gboolean              is_meta_data,
+             NMSKeyfileStorageType storage_type)
+
+{
+    NMSKeyfileStorage *self;
+
+    nm_assert(NMS_IS_KEYFILE_PLUGIN(plugin));
+    nm_assert(nm_utils_is_uuid(uuid));
+    nm_assert(filename && filename[0] == '/');
+
+    self = g_object_new(NMS_TYPE_KEYFILE_STORAGE,
+                        NM_SETTINGS_STORAGE_PLUGIN,
+                        plugin,
+                        NM_SETTINGS_STORAGE_UUID,
+                        uuid,
+                        NM_SETTINGS_STORAGE_FILENAME,
+                        filename,
+                        NULL);
+
+    *((bool *) &self->is_meta_data)                  = is_meta_data;
+    *((NMSKeyfileStorageType *) &self->storage_type) = storage_type;
+
+    return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_tombstone(NMSKeyfilePlugin *    plugin,
+                                  const char *          uuid,
+                                  const char *          filename,
+                                  NMSKeyfileStorageType storage_type,
+                                  const char *          shadowed_storage)
+{
+    NMSKeyfileStorage *self;
+
+    nm_assert(nm_utils_is_uuid(uuid));
+    nm_assert(filename && filename[0] == '/');
+    nm_assert(nms_keyfile_nmmeta_check_filename(filename, NULL));
+    nm_assert(NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC, NMS_KEYFILE_STORAGE_TYPE_RUN));
+
+    self                           = _storage_new(plugin, uuid, filename, TRUE, storage_type);
+    self->u.meta_data.is_tombstone = TRUE;
+    if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN)
+        self->u.meta_data.shadowed_storage = g_strdup(shadowed_storage);
+    return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_connection(NMSKeyfilePlugin *     plugin,
+                                   NMConnection *         connection_take /* pass reference */,
+                                   const char *           filename,
+                                   NMSKeyfileStorageType  storage_type,
+                                   NMTernary              is_nm_generated_opt,
+                                   NMTernary              is_volatile_opt,
+                                   NMTernary              is_external_opt,
+                                   const char *           shadowed_storage,
+                                   NMTernary              shadowed_owned_opt,
+                                   const struct timespec *stat_mtime)
+{
+    NMSKeyfileStorage *self;
+
+    nm_assert(NMS_IS_KEYFILE_PLUGIN(plugin));
+    nm_assert(NM_IS_CONNECTION(connection_take));
+    nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(filename && filename[0] == '/');
+    nm_assert(storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN
+              && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST);
+    nmtst_connection_assert_unchanging(connection_take);
+
+    self = _storage_new(plugin,
+                        nm_connection_get_uuid(connection_take),
+                        filename,
+                        FALSE,
+                        storage_type);
+
+    self->u.conn_data.connection = connection_take; /* take reference. */
+
+    self->u.conn_data.shadowed_storage = g_strdup(shadowed_storage);
+
+    if (stat_mtime)
+        self->u.conn_data.stat_mtime = *stat_mtime;
+
+    if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+        self->u.conn_data.is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE);
+        self->u.conn_data.is_volatile     = (is_volatile_opt == NM_TERNARY_TRUE);
+        self->u.conn_data.is_external     = (is_external_opt == NM_TERNARY_TRUE);
+        self->u.conn_data.shadowed_owned =
+            shadowed_storage && (shadowed_owned_opt == NM_TERNARY_TRUE);
+    }
+
+    return self;
+}
+
+static void
+_storage_clear(NMSKeyfileStorage *self)
+{
+    c_list_unlink(&self->parent._storage_lst);
+    c_list_unlink(&self->parent._storage_by_uuid_lst);
+    if (self->is_meta_data)
+        nm_clear_g_free(&self->u.meta_data.shadowed_storage);
+    else {
+        g_clear_object(&self->u.conn_data.connection);
+        nm_clear_g_free(&self->u.conn_data.shadowed_storage);
+        self->u.conn_data.shadowed_owned = FALSE;
+    }
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSKeyfileStorage *self = NMS_KEYFILE_STORAGE(object);
+
+    _storage_clear(self);
+
+    G_OBJECT_CLASS(nms_keyfile_storage_parent_class)->dispose(object);
+}
+
+void
+nms_keyfile_storage_destroy(NMSKeyfileStorage *self)
+{
+    _storage_clear(self);
+    g_object_unref(self);
+}
+
+static void
+nms_keyfile_storage_class_init(NMSKeyfileStorageClass *klass)
+{
+    GObjectClass *          object_class  = G_OBJECT_CLASS(klass);
+    NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS(klass);
+
+    object_class->dispose = dispose;
+
+    storage_class->cmp_fcn = (int (*)(NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
+}
+
+/*****************************************************************************/
+
+#include "settings/nm-settings-connection.h"
+
+void
+nm_settings_storage_load_sett_flags(NMSettingsStorage *           self,
+                                    NMSettingsConnectionIntFlags *sett_flags,
+                                    NMSettingsConnectionIntFlags *sett_mask)
+{
+    NMSKeyfileStorage *s;
+
+    *sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
+    *sett_mask  = NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                 | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                 | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
+
+    if (!NMS_IS_KEYFILE_STORAGE(self))
+        return;
+
+    s = NMS_KEYFILE_STORAGE(self);
+
+    if (s->is_meta_data)
+        return;
+    if (s->storage_type != NMS_KEYFILE_STORAGE_TYPE_RUN)
+        return;
+
+    if (s->u.conn_data.is_nm_generated)
+        *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED;
+
+    if (s->u.conn_data.is_volatile)
+        *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+
+    if (s->u.conn_data.is_external)
+        *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.h b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h
new file mode 100644
index 00000000..008070df
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h
@@ -0,0 +1,255 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_STORAGE_H__
+#define __NMS_KEYFILE_STORAGE_H__
+
+#include "c-list/src/c-list.h"
+#include "settings/nm-settings-storage.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+#define NMS_TYPE_KEYFILE_STORAGE (nms_keyfile_storage_get_type())
+#define NMS_KEYFILE_STORAGE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorage))
+#define NMS_KEYFILE_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+#define NMS_IS_KEYFILE_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_IS_KEYFILE_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_KEYFILE_STORAGE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+
+typedef struct {
+    /* whether this is a tombstone to hide a UUID (via symlink to /dev/null). */
+    char *shadowed_storage;
+    bool  is_tombstone : 1;
+} NMSettingsMetaData;
+
+typedef struct {
+    NMSettingsStorage parent;
+
+    /* The connection. Note that there are tombstones (loaded-uuid files to /dev/null)
+     * that don't have a connection.
+     *
+     * Also, we don't actually remember the loaded connection after returning it
+     * to NMSettings. So, also for regular storages (non-tombstones) this field
+     * is often cleared. */
+    union {
+        struct {
+            NMConnection *connection;
+
+            /* when we move a profile from permanent storage to unsaved (/run), then
+             * we may leave the profile on disk (depending on options for Update2()).
+             *
+             * Later, when we save the profile again to disk, we want to re-use that filename.
+             * Likewise, we delete the (now in-memory) profile, we may want to also delete
+             * the original filename.
+             *
+             * This is the original filename, and we store it inside [.nmmeta] in the
+             * keyfile in /run. Note that we don't store this in the .nmmeta file, because
+             * the information is tied to the particular keyfile in /run, not to all UUIDs
+             * in general. */
+            char *shadowed_storage;
+
+            /* the timestamp (stat's mtime) of the keyfile. For meta-data this
+             * is irrelevant. The purpose is that if the same storage type (directory) has
+             * multiple files with the same UUID, then the newer file gets preferred. */
+            struct timespec stat_mtime;
+
+            /* these flags are only relevant for storages with %NMS_KEYFILE_STORAGE_TYPE_RUN
+             * (and non-metadata). This is to persist and reload these settings flags to
+             * /run.
+             *
+             * Note that these flags are not stored in as meta-data. The reason is that meta-data
+             * is per UUID. But these flags are only relevant for a particular keyfile on disk.
+             * That is, it must be tied to the actual keyfile, and not to the UUID. */
+            bool is_nm_generated : 1;
+            bool is_volatile : 1;
+            bool is_external : 1;
+
+            /* if shadowed_storage is set, then this flag indicates whether the file
+             * is owned. The difference comes into play when deleting the in-memory,
+             * shadowing profile: a owned profile will also be deleted. */
+            bool shadowed_owned : 1;
+
+        } conn_data;
+
+        /* the content from the .nmmeta file. Note that the nmmeta file has the UUID
+         * in the filename, that means there can be only two variants of this file:
+         * in /etc and in /run. As such, this is really meta-data about the entire profile
+         * (the UUID), and not about the individual keyfile. */
+        NMSettingsMetaData meta_data;
+
+    } u;
+
+    /* The storage type. This is directly related to the filename. Since
+     * the filename cannot change, this value is unchanging. */
+    const NMSKeyfileStorageType storage_type;
+
+    /* whether union "u" has meta_data or conn_data. Since the type of the storage
+     * depends on the (immutable) filename, this is also const. */
+    const bool is_meta_data;
+
+    /* this flag is only used during reload to mark and prune old entries. */
+    bool is_dirty : 1;
+
+} NMSKeyfileStorage;
+
+typedef struct _NMSKeyfileStorageClass NMSKeyfileStorageClass;
+
+GType nms_keyfile_storage_get_type(void);
+
+struct _NMSKeyfilePlugin;
+
+NMSKeyfileStorage *nms_keyfile_storage_new_tombstone(struct _NMSKeyfilePlugin *self,
+                                                     const char *              uuid,
+                                                     const char *              filename,
+                                                     NMSKeyfileStorageType     storage_type,
+                                                     const char *              shadowed_storage);
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_connection(struct _NMSKeyfilePlugin *self,
+                                   NMConnection *            connection_take /* pass reference */,
+                                   const char *              filename,
+                                   NMSKeyfileStorageType     storage_type,
+                                   NMTernary                 is_nm_generated_opt,
+                                   NMTernary                 is_volatile_opt,
+                                   NMTernary                 is_external_opt,
+                                   const char *              shadowed_storage,
+                                   NMTernary                 shadowed_owned_opt,
+                                   const struct timespec *   stat_mtime);
+
+void nms_keyfile_storage_destroy(NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+void nms_keyfile_storage_copy_content(NMSKeyfileStorage *dst, const NMSKeyfileStorage *src);
+
+NMConnection *nms_keyfile_storage_steal_connection(NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+static inline const char *
+nms_keyfile_storage_get_uuid(const NMSKeyfileStorage *self)
+{
+    return nm_settings_storage_get_uuid((const NMSettingsStorage *) self);
+}
+
+static inline const char *
+nms_keyfile_storage_get_filename(const NMSKeyfileStorage *self)
+{
+    return nm_settings_storage_get_filename((const NMSettingsStorage *) self);
+}
+
+/*****************************************************************************/
+
+static inline gboolean
+nm_settings_storage_is_keyfile_run(const NMSettingsStorage *self)
+{
+    return NMS_IS_KEYFILE_STORAGE(self)
+           && (((NMSKeyfileStorage *) self)->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN);
+}
+
+static inline gboolean
+nm_settings_storage_is_keyfile_lib(const NMSettingsStorage *self)
+{
+    return NMS_IS_KEYFILE_STORAGE(self)
+           && (((NMSKeyfileStorage *) self)->storage_type >= NMS_KEYFILE_STORAGE_TYPE_LIB_BASE);
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data(const NMSettingsStorage *storage)
+{
+    const NMSKeyfileStorage *self;
+
+    if (!NMS_IS_KEYFILE_STORAGE(storage))
+        return NULL;
+
+    self = (NMSKeyfileStorage *) storage;
+
+    if (!self->is_meta_data)
+        return NULL;
+
+    return &self->u.meta_data;
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data_alive(const NMSettingsStorage *storage)
+{
+    const NMSettingsMetaData *meta_data;
+
+    meta_data = nm_settings_storage_is_meta_data(storage);
+
+    if (!meta_data)
+        return NULL;
+
+    /* Regular (all other) storages are alive as long as they report a NMConnection, and
+     * they will be dropped, once they have no more connection.
+     *
+     * Meta-data storages are special: they never report a NMConnection.
+     * So, a meta-data storage is alive as long as it is tracked by the
+     * settings plugin.
+     *
+     * This function is used to ckeck for that. */
+
+    if (c_list_is_empty(&storage->_storage_lst))
+        return NULL;
+
+    return meta_data;
+}
+
+static inline const char *
+nm_settings_storage_get_shadowed_storage(const NMSettingsStorage *storage,
+                                         gboolean *               out_shadowed_owned)
+{
+    if (NMS_IS_KEYFILE_STORAGE(storage)) {
+        const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+        if (self->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+            if (!self->is_meta_data) {
+                if (self->u.conn_data.shadowed_storage) {
+                    NM_SET_OUT(out_shadowed_owned, self->u.conn_data.shadowed_owned);
+                    return self->u.conn_data.shadowed_storage;
+                }
+            } else {
+                NM_SET_OUT(out_shadowed_owned, FALSE);
+                return self->u.meta_data.shadowed_storage;
+            }
+        }
+    }
+
+    NM_SET_OUT(out_shadowed_owned, FALSE);
+    return NULL;
+}
+
+static inline const char *
+nm_settings_storage_get_filename_for_shadowed_storage(const NMSettingsStorage *storage)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), NULL);
+
+    if (!storage->_filename)
+        return NULL;
+
+    if (NMS_IS_KEYFILE_STORAGE(storage)) {
+        const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+        if (self->is_meta_data || self->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC)
+            return NULL;
+    }
+
+    return storage->_filename;
+}
+
+/*****************************************************************************/
+
+enum _NMSettingsConnectionIntFlags;
+
+void nm_settings_storage_load_sett_flags(NMSettingsStorage *                 self,
+                                         enum _NMSettingsConnectionIntFlags *sett_flags,
+                                         enum _NMSettingsConnectionIntFlags *sett_mask);
+
+#endif /* __NMS_KEYFILE_STORAGE_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.c b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
new file mode 100644
index 00000000..f3dffdae
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -0,0 +1,389 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-utils.h"
+
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "nm-glib-aux/nm-io-utils.h"
+#include "nm-keyfile-internal.h"
+#include "nm-utils.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+#include "nm-config.h"
+
+/*****************************************************************************/
+
+#define NMMETA_KF_GROUP_NAME_NMMETA                "nmmeta"
+#define NMMETA_KF_KEY_NAME_NMMETA_UUID             "uuid"
+#define NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH      "loaded-path"
+#define NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE "shadowed-storage"
+
+/*****************************************************************************/
+
+const char *
+nms_keyfile_nmmeta_check_filename(const char *filename, guint *out_uuid_len)
+{
+    const char *uuid;
+    const char *s;
+    gsize       len;
+
+    s = strrchr(filename, '/');
+    if (s)
+        filename = &s[1];
+
+    len = strlen(filename);
+    if (len <= NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA)
+        || memcmp(&filename[len - NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA)],
+                  NM_KEYFILE_PATH_SUFFIX_NMMETA,
+                  NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA))
+               != 0) {
+        /* the filename does not have the right suffix. */
+        return NULL;
+    }
+
+    len -= NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA);
+
+    if (!NM_IN_SET(len, 36, 40)) {
+        /* the remaining part of the filename has not the right length to
+         * contain a UUID (according to nm_utils_is_uuid()). */
+        return NULL;
+    }
+
+    uuid = nm_strndup_a(100, filename, len, NULL);
+    if (!nm_utils_is_uuid(uuid))
+        return NULL;
+
+    NM_SET_OUT(out_uuid_len, len);
+    return filename;
+}
+
+char *
+nms_keyfile_nmmeta_filename(const char *dirname, const char *uuid, gboolean temporary)
+{
+    char  filename[250];
+    char *s;
+
+    nm_assert(dirname && dirname[0] == '/');
+    nm_assert(nm_utils_is_uuid(uuid) && !strchr(uuid, '/'));
+
+    if (g_snprintf(filename,
+                   sizeof(filename),
+                   "%s%s%s",
+                   uuid,
+                   NM_KEYFILE_PATH_SUFFIX_NMMETA,
+                   temporary ? "~" : "")
+        >= sizeof(filename)) {
+        /* valid uuids are limited in length (nm_utils_is_uuid). The buffer should always
+         * be large enough. */
+        nm_assert_not_reached();
+    }
+
+    s = g_build_filename(dirname, filename, NULL);
+
+    nm_assert(nm_keyfile_utils_ignore_filename(s, FALSE));
+
+    return s;
+}
+
+gboolean
+nms_keyfile_nmmeta_read(const char * dirname,
+                        const char * filename,
+                        char **      out_full_filename,
+                        char **      out_uuid,
+                        char **      out_loaded_path,
+                        char **      out_shadowed_storage,
+                        struct stat *out_st)
+{
+    const char *  uuid;
+    guint         uuid_len;
+    gs_free char *full_filename    = NULL;
+    gs_free char *loaded_path      = NULL;
+    gs_free char *shadowed_storage = NULL;
+    struct stat   st_stack;
+    struct stat * st = out_st ?: &st_stack;
+
+    nm_assert(dirname && dirname[0] == '/');
+    nm_assert(filename && filename[0] && !strchr(filename, '/'));
+
+    uuid = nms_keyfile_nmmeta_check_filename(filename, &uuid_len);
+    if (!uuid)
+        return FALSE;
+
+    full_filename = g_build_filename(dirname, filename, NULL);
+
+    if (!nms_keyfile_utils_check_file_permissions(NMS_KEYFILE_FILETYPE_NMMETA,
+                                                  full_filename,
+                                                  st,
+                                                  NULL))
+        return FALSE;
+
+    if (S_ISREG(st->st_mode)) {
+        nm_auto_unref_keyfile GKeyFile *kf     = NULL;
+        gs_free char *                  v_uuid = NULL;
+
+        kf = g_key_file_new();
+
+        if (!g_key_file_load_from_file(kf, full_filename, G_KEY_FILE_NONE, NULL))
+            return FALSE;
+
+        v_uuid = g_key_file_get_string(kf,
+                                       NMMETA_KF_GROUP_NAME_NMMETA,
+                                       NMMETA_KF_KEY_NAME_NMMETA_UUID,
+                                       NULL);
+        if (!nm_streq0(v_uuid, uuid))
+            return FALSE;
+
+        loaded_path      = g_key_file_get_string(kf,
+                                            NMMETA_KF_GROUP_NAME_NMMETA,
+                                            NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH,
+                                            NULL);
+        shadowed_storage = g_key_file_get_string(kf,
+                                                 NMMETA_KF_GROUP_NAME_NMMETA,
+                                                 NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE,
+                                                 NULL);
+
+        if (!loaded_path && !shadowed_storage) {
+            /* if there is no useful information in the file, it is the same as if
+             * the file is not present. Signal failure. */
+            return FALSE;
+        }
+
+    } else {
+        loaded_path = nm_utils_read_link_absolute(full_filename, NULL);
+        if (!loaded_path)
+            return FALSE;
+    }
+
+    NM_SET_OUT(out_uuid, g_strndup(uuid, uuid_len));
+    NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename));
+    NM_SET_OUT(out_loaded_path, g_steal_pointer(&loaded_path));
+    NM_SET_OUT(out_shadowed_storage, g_steal_pointer(&shadowed_storage));
+    return TRUE;
+}
+
+gboolean
+nms_keyfile_nmmeta_read_from_file(const char *full_filename,
+                                  char **     out_dirname,
+                                  char **     out_filename,
+                                  char **     out_uuid,
+                                  char **     out_loaded_path,
+                                  char **     out_shadowed_storage)
+{
+    gs_free char *dirname  = NULL;
+    gs_free char *filename = NULL;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+
+    filename = g_path_get_basename(full_filename);
+    dirname  = g_path_get_dirname(full_filename);
+
+    if (!nms_keyfile_nmmeta_read(dirname,
+                                 filename,
+                                 NULL,
+                                 out_uuid,
+                                 out_loaded_path,
+                                 out_shadowed_storage,
+                                 NULL))
+        return FALSE;
+
+    NM_SET_OUT(out_dirname, g_steal_pointer(&dirname));
+    NM_SET_OUT(out_filename, g_steal_pointer(&filename));
+    return TRUE;
+}
+
+int
+nms_keyfile_nmmeta_write(const char *dirname,
+                         const char *uuid,
+                         const char *loaded_path,
+                         gboolean    loaded_path_allow_relative,
+                         const char *shadowed_storage,
+                         char **     out_full_filename)
+{
+    gs_free char *full_filename_tmp = NULL;
+    gs_free char *full_filename     = NULL;
+    int           errsv;
+
+    nm_assert(dirname && dirname[0] == '/');
+    nm_assert(nm_utils_is_uuid(uuid) && !strchr(uuid, '/'));
+    nm_assert(!loaded_path || loaded_path[0] == '/');
+    nm_assert(!shadowed_storage || loaded_path);
+
+    full_filename_tmp = nms_keyfile_nmmeta_filename(dirname, uuid, TRUE);
+
+    nm_assert(g_str_has_suffix(full_filename_tmp, "~"));
+    nm_assert(nm_utils_file_is_in_path(full_filename_tmp, dirname));
+
+    (void) unlink(full_filename_tmp);
+
+    if (!loaded_path) {
+        full_filename_tmp[strlen(full_filename_tmp) - 1] = '\0';
+        errsv                                            = 0;
+        if (unlink(full_filename_tmp) != 0) {
+            errsv = -NM_ERRNO_NATIVE(errno);
+            if (errsv == -ENOENT)
+                errsv = 0;
+        }
+        NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp));
+        return errsv;
+    }
+
+    if (loaded_path_allow_relative) {
+        const char *f;
+
+        f = nm_utils_file_is_in_path(loaded_path, dirname);
+        if (f) {
+            /* @loaded_path points to a file directly in @dirname.
+             * Don't use absolute paths. */
+            loaded_path = f;
+        }
+    }
+
+    full_filename = g_strndup(full_filename_tmp, strlen(full_filename_tmp) - 1);
+
+    if (shadowed_storage) {
+        nm_auto_unref_keyfile GKeyFile *kf       = NULL;
+        gs_free char *                  contents = NULL;
+        gsize                           length;
+
+        kf = g_key_file_new();
+
+        g_key_file_set_string(kf,
+                              NMMETA_KF_GROUP_NAME_NMMETA,
+                              NMMETA_KF_KEY_NAME_NMMETA_UUID,
+                              uuid);
+        g_key_file_set_string(kf,
+                              NMMETA_KF_GROUP_NAME_NMMETA,
+                              NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH,
+                              loaded_path);
+        g_key_file_set_string(kf,
+                              NMMETA_KF_GROUP_NAME_NMMETA,
+                              NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE,
+                              shadowed_storage);
+
+        contents = g_key_file_to_data(kf, &length, NULL);
+
+        if (!nm_utils_file_set_contents(full_filename, contents, length, 0600, &errsv, NULL)) {
+            NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp));
+            return -NM_ERRNO_NATIVE(errsv);
+        }
+    } else {
+        /* we only have the "loaded_path" to store. That is commonly used for the tombstones to
+         * link to /dev/null. A symlink is sufficient to store that amount of information.
+         * No need to bother with a keyfile. */
+        if (symlink(loaded_path, full_filename_tmp) != 0) {
+            errsv                                            = -NM_ERRNO_NATIVE(errno);
+            full_filename_tmp[strlen(full_filename_tmp) - 1] = '\0';
+            NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp));
+            return errsv;
+        }
+
+        if (rename(full_filename_tmp, full_filename) != 0) {
+            errsv = -NM_ERRNO_NATIVE(errno);
+            (void) unlink(full_filename_tmp);
+            NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename));
+            return errsv;
+        }
+    }
+
+    NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename));
+    return 0;
+}
+
+/*****************************************************************************/
+
+gboolean
+nms_keyfile_utils_check_file_permissions_stat(NMSKeyfileFiletype filetype,
+                                              const struct stat *st,
+                                              GError **          error)
+{
+    g_return_val_if_fail(st, FALSE);
+
+    if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+        if (!S_ISREG(st->st_mode)) {
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "file is not a regular file");
+            return FALSE;
+        }
+    } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
+        if (!S_ISLNK(st->st_mode) && !S_ISREG(st->st_mode)) {
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "file is neither a symlink nor a regular file");
+            return FALSE;
+        }
+    } else
+        g_return_val_if_reached(FALSE);
+
+    if (!NM_FLAGS_HAS(nm_utils_get_testing(), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) {
+        if (st->st_uid != 0) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "File owner (%lld) is insecure",
+                        (long long) st->st_uid);
+            return FALSE;
+        }
+
+        if (S_ISREG(st->st_mode) && (st->st_mode & 0077)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "File permissions (%03o) are insecure",
+                        st->st_mode);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+gboolean
+nms_keyfile_utils_check_file_permissions(NMSKeyfileFiletype filetype,
+                                         const char *       filename,
+                                         struct stat *      out_st,
+                                         GError **          error)
+{
+    struct stat st;
+    int         errsv;
+
+    g_return_val_if_fail(filename && filename[0] == '/', FALSE);
+
+    if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+        if (stat(filename, &st) != 0) {
+            errsv = errno;
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "cannot access file: %s",
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+    } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
+        if (lstat(filename, &st) != 0) {
+            errsv = errno;
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "cannot access file: %s",
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+    } else
+        g_return_val_if_reached(FALSE);
+
+    if (!nms_keyfile_utils_check_file_permissions_stat(filetype, &st, error))
+        return FALSE;
+
+    NM_SET_OUT(out_st, st);
+    return TRUE;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.h b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h
new file mode 100644
index 00000000..419a17ed
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2018 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_UTILS_H__
+#define __NMS_KEYFILE_UTILS_H__
+
+#include "NetworkManagerUtils.h"
+
+typedef enum {
+    NMS_KEYFILE_FILETYPE_KEYFILE,
+    NMS_KEYFILE_FILETYPE_NMMETA,
+} NMSKeyfileFiletype;
+
+typedef enum {
+    NMS_KEYFILE_STORAGE_TYPE_RUN      = 1, /* read-write, runtime only, e.g. /run */
+    NMS_KEYFILE_STORAGE_TYPE_ETC      = 2, /* read-write, persistent,   e.g. /etc     */
+    NMS_KEYFILE_STORAGE_TYPE_LIB_BASE = 3, /* read-only,                e.g. /usr/lib */
+
+    _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST = 1000,
+} NMSKeyfileStorageType;
+
+static inline NMSKeyfileStorageType
+NMS_KEYFILE_STORAGE_TYPE_LIB(guint run_idx)
+{
+    nm_assert(run_idx <= (_NMS_KEYFILE_STORAGE_TYPE_LIB_LAST - NMS_KEYFILE_STORAGE_TYPE_LIB_BASE));
+    return NMS_KEYFILE_STORAGE_TYPE_LIB_BASE + run_idx;
+}
+
+/*****************************************************************************/
+
+const char *nms_keyfile_nmmeta_check_filename(const char *filename, guint *out_uuid_len);
+
+char *nms_keyfile_nmmeta_filename(const char *dirname, const char *uuid, gboolean temporary);
+
+gboolean nms_keyfile_nmmeta_read(const char * dirname,
+                                 const char * filename,
+                                 char **      out_full_filename,
+                                 char **      out_uuid,
+                                 char **      out_loaded_path,
+                                 char **      out_shadowed_storage,
+                                 struct stat *out_st);
+
+gboolean nms_keyfile_nmmeta_read_from_file(const char *full_filename,
+                                           char **     out_dirname,
+                                           char **     out_filename,
+                                           char **     out_uuid,
+                                           char **     out_loaded_path,
+                                           char **     out_shadowed_storage);
+
+int nms_keyfile_nmmeta_write(const char *dirname,
+                             const char *uuid,
+                             const char *loaded_path,
+                             gboolean    loaded_path_allow_relative,
+                             const char *shadowed_storage,
+                             char **     out_full_filename);
+
+/*****************************************************************************/
+
+struct stat;
+gboolean nms_keyfile_utils_check_file_permissions_stat(NMSKeyfileFiletype filetype,
+                                                       const struct stat *st,
+                                                       GError **          error);
+
+gboolean nms_keyfile_utils_check_file_permissions(NMSKeyfileFiletype filetype,
+                                                  const char *       filename,
+                                                  struct stat *      out_st,
+                                                  GError **          error);
+
+#endif /* __NMS_KEYFILE_UTILS_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
new file mode 100644
index 00000000..0e2c94b5
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -0,0 +1,489 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-writer.h"
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "nm-keyfile-internal.h"
+
+#include "nms-keyfile-utils.h"
+#include "nms-keyfile-reader.h"
+
+#include "nm-glib-aux/nm-io-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+    const char *keyfile_dir;
+} WriteInfo;
+
+static void
+cert_writer(NMConnection *                    connection,
+            GKeyFile *                        file,
+            NMSetting8021x *                  setting,
+            const NMSetting8021xSchemeVtable *vtable,
+            WriteInfo *                       info,
+            GError **                         error)
+{
+    const char *           setting_name = nm_setting_get_name(NM_SETTING(setting));
+    NMSetting8021xCKScheme scheme;
+    NMSetting8021xCKFormat format;
+    const char *           path = NULL, *ext = "pem";
+
+    scheme = vtable->scheme_func(setting);
+    if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+        char *      tmp           = NULL;
+        const char *accepted_path = NULL;
+
+        path = vtable->path_func(setting);
+        g_assert(path);
+
+        if (g_str_has_prefix(path, info->keyfile_dir)) {
+            const char *p = path + strlen(info->keyfile_dir);
+
+            /* If the path is rooted in the keyfile directory, just use a
+             * relative path instead of an absolute one.
+             */
+            if (*p == '/') {
+                while (*p == '/')
+                    p++;
+                if (p[0]) {
+                    /* If @p looks like an integer list, the following detection will fail too and
+                     * we will file:// qualify the path below. We thus avoid writing a path string
+                     * that would be interpreted as legacy binary format by reader. */
+                    tmp = nm_keyfile_detect_unqualified_path_scheme(info->keyfile_dir,
+                                                                    p,
+                                                                    -1,
+                                                                    FALSE,
+                                                                    NULL);
+                    if (tmp) {
+                        nm_clear_g_free(&tmp);
+                        accepted_path = p;
+                    }
+                }
+            }
+        }
+        if (!accepted_path) {
+            /* What we are about to write, must also be understood by the reader.
+             * Otherwise, add a file:// prefix */
+            tmp =
+                nm_keyfile_detect_unqualified_path_scheme(info->keyfile_dir, path, -1, FALSE, NULL);
+            if (tmp) {
+                nm_clear_g_free(&tmp);
+                accepted_path = path;
+            }
+        }
+
+        if (!accepted_path)
+            accepted_path = tmp = g_strconcat(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
+        nm_keyfile_plugin_kf_set_string(file, setting_name, vtable->setting_key, accepted_path);
+        g_free(tmp);
+    } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
+        nm_keyfile_plugin_kf_set_string(file,
+                                        setting_name,
+                                        vtable->setting_key,
+                                        vtable->uri_func(setting));
+    } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+        GBytes *      blob;
+        const guint8 *blob_data;
+        gsize         blob_len;
+        gboolean      success;
+        GError *      local = NULL;
+        char *        new_path;
+
+        blob = vtable->blob_func(setting);
+        g_assert(blob);
+        blob_data = g_bytes_get_data(blob, &blob_len);
+
+        if (vtable->format_func) {
+            /* Get the extension for a private key */
+            format = vtable->format_func(setting);
+            if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
+                ext = "p12";
+        } else {
+            /* DER or PEM format certificate? */
+            if (blob_len > 2 && blob_data[0] == 0x30 && blob_data[1] == 0x82)
+                ext = "der";
+        }
+
+        /* Write the raw data out to the standard file so that we can use paths
+         * from now on instead of pushing around the certificate data.
+         */
+        new_path = g_strdup_printf("%s/%s-%s.%s",
+                                   info->keyfile_dir,
+                                   nm_connection_get_uuid(connection),
+                                   vtable->file_suffix,
+                                   ext);
+
+        /* FIXME(keyfile-parse-in-memory): writer must not access/write to the file system before
+         * being sure that the entire profile can be written and all circumstances are good to
+         * proceed. That means, while writing we must only collect the blogs in-memory, and write
+         * them all in the end together (or not at all). */
+        success = nm_utils_file_set_contents(new_path,
+                                             (const char *) blob_data,
+                                             blob_len,
+                                             0600,
+                                             NULL,
+                                             &local);
+        if (success) {
+            /* Write the path value to the keyfile.
+             * We know, that basename(new_path) starts with a UUID, hence no conflict with "data:;base64,"  */
+            nm_keyfile_plugin_kf_set_string(file,
+                                            setting_name,
+                                            vtable->setting_key,
+                                            strrchr(new_path, '/') + 1);
+        } else {
+            nm_log_warn(LOGD_SETTINGS,
+                        "keyfile: %s.%s: failed to write certificate to file %s: %s",
+                        setting_name,
+                        vtable->setting_key,
+                        new_path,
+                        local->message);
+            g_error_free(local);
+        }
+        g_free(new_path);
+    } else {
+        /* scheme_func() returns UNKNOWN in all other cases. The only valid case
+         * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this
+         * case, we don't expect the writer to be called, because the default value
+         * will not be serialized.
+         * The only other reason for the scheme to be UNKNOWN is an invalid cert.
+         * But our connection verifies, so that cannot happen either. */
+        g_return_if_reached();
+    }
+}
+
+static gboolean
+_handler_write(NMConnection *        connection,
+               GKeyFile *            keyfile,
+               NMKeyfileHandlerType  type,
+               NMKeyfileHandlerData *type_data,
+               void *                user_data)
+{
+    if (type == NM_KEYFILE_HANDLER_TYPE_WRITE_CERT) {
+        cert_writer(connection,
+                    keyfile,
+                    NM_SETTING_802_1X(type_data->cur_setting),
+                    type_data->write_cert.vtable,
+                    user_data,
+                    type_data->p_error);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static gboolean
+_internal_write_connection(NMConnection *                  connection,
+                           gboolean                        is_nm_generated,
+                           gboolean                        is_volatile,
+                           gboolean                        is_external,
+                           const char *                    shadowed_storage,
+                           gboolean                        shadowed_owned,
+                           const char *                    keyfile_dir,
+                           const char *                    profile_dir,
+                           gboolean                        with_extension,
+                           uid_t                           owner_uid,
+                           pid_t                           owner_grp,
+                           const char *                    existing_path,
+                           gboolean                        existing_path_read_only,
+                           gboolean                        force_rename,
+                           NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                           gpointer                        allow_filename_user_data,
+                           char **                         out_path,
+                           NMConnection **                 out_reread,
+                           gboolean *                      out_reread_same,
+                           GError **                       error)
+{
+    nm_auto_unref_keyfile GKeyFile *kf_file        = NULL;
+    gs_free char *                  kf_content_buf = NULL;
+    gsize                           kf_content_len;
+    gs_free char *                  path = NULL;
+    const char *                    id;
+    WriteInfo                       info = {0};
+    gs_free_error GError *local_err      = NULL;
+    int                   errsv;
+    gboolean              rename;
+    int                   i_path;
+    gs_unref_object NMConnection *reread      = NULL;
+    gboolean                      reread_same = FALSE;
+
+    g_return_val_if_fail(!out_path || !*out_path, FALSE);
+    g_return_val_if_fail(keyfile_dir && keyfile_dir[0] == '/', FALSE);
+
+    nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+
+    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));
+
+    id = nm_connection_get_id(connection);
+    nm_assert(id && *id);
+
+    info.keyfile_dir = keyfile_dir;
+
+    kf_file =
+        nm_keyfile_write(connection, NM_KEYFILE_HANDLER_FLAGS_NONE, _handler_write, &info, error);
+    if (!kf_file)
+        return FALSE;
+
+    if (is_nm_generated) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+                               TRUE);
+    }
+
+    if (is_volatile) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_VOLATILE,
+                               TRUE);
+    }
+
+    if (is_external) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_EXTERNAL,
+                               TRUE);
+    }
+
+    if (shadowed_storage) {
+        g_key_file_set_string(kf_file,
+                              NM_KEYFILE_GROUP_NMMETA,
+                              NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+                              shadowed_storage);
+    }
+
+    if (shadowed_owned) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+                               TRUE);
+    }
+
+    kf_content_buf = g_key_file_to_data(kf_file, &kf_content_len, error);
+    if (!kf_content_buf)
+        return FALSE;
+
+    if (!g_file_test(keyfile_dir, G_FILE_TEST_IS_DIR))
+        (void) g_mkdir_with_parents(keyfile_dir, 0755);
+
+    for (i_path = -2; i_path < 10000; i_path++) {
+        gs_free char *path_candidate = NULL;
+        gboolean      is_existing_path;
+
+        if (i_path == -2) {
+            if (!existing_path || rename)
+                continue;
+            path_candidate = g_strdup(existing_path);
+        } else if (i_path == -1) {
+            gs_free char *filename_escaped = NULL;
+
+            filename_escaped = nm_keyfile_utils_create_filename(id, with_extension);
+            path_candidate   = g_build_filename(keyfile_dir, filename_escaped, NULL);
+        } else {
+            gs_free char *filename_escaped = NULL;
+            gs_free char *filename         = NULL;
+
+            if (i_path == 0)
+                filename = g_strdup_printf("%s-%s", id, nm_connection_get_uuid(connection));
+            else
+                filename =
+                    g_strdup_printf("%s-%s-%d", id, nm_connection_get_uuid(connection), i_path);
+
+            filename_escaped = nm_keyfile_utils_create_filename(filename, with_extension);
+
+            path_candidate = g_strdup_printf("%s/%s", keyfile_dir, filename_escaped);
+        }
+
+        is_existing_path = existing_path && nm_streq(existing_path, path_candidate);
+
+        if (is_existing_path && rename)
+            continue;
+
+        if (allow_filename_cb && !allow_filename_cb(path_candidate, allow_filename_user_data))
+            continue;
+
+        if (!is_existing_path) {
+            if (g_file_test(path_candidate, G_FILE_TEST_EXISTS))
+                continue;
+        }
+
+        path = g_steal_pointer(&path_candidate);
+        break;
+    }
+
+    if (!path) {
+        gs_free char *ss = NULL;
+
+        /* this really should not happen, we tried hard to find an unused name... bail out. */
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "could not find suitable keyfile file name (%s already used)",
+                    ss = ({
+                        gs_free char *filename_escaped = NULL;
+
+                        filename_escaped = nm_keyfile_utils_create_filename(id, with_extension);
+                        g_build_filename(keyfile_dir, filename_escaped, NULL);
+                    }));
+
+        return FALSE;
+    }
+
+    if (out_reread || out_reread_same) {
+        gs_free_error GError *reread_error = NULL;
+
+        reread =
+            nms_keyfile_reader_from_keyfile(kf_file, path, NULL, profile_dir, FALSE, &reread_error);
+
+        if (!reread || !nm_connection_normalize(reread, NULL, NULL, &reread_error)) {
+            nm_log_err(
+                LOGD_SETTINGS,
+                "BUG: the profile cannot be stored in keyfile format without becoming unusable: %s",
+                reread_error->message);
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "keyfile writer produces an invalid connection: %s",
+                        reread_error->message);
+            nm_assert_not_reached();
+            return FALSE;
+        }
+
+        if (out_reread_same) {
+            reread_same =
+                !!nm_connection_compare(reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
+
+            nm_assert(reread_same
+                      == nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+            nm_assert(reread_same == ({
+                          gs_unref_hashtable GHashTable *_settings = NULL;
+
+                          (nm_connection_diff(reread,
+                                              connection,
+                                              NM_SETTING_COMPARE_FLAG_EXACT,
+                                              &_settings)
+                           && !_settings);
+                      }));
+        }
+    }
+
+    nm_utils_file_set_contents(path, kf_content_buf, kf_content_len, 0600, NULL, &local_err);
+    if (local_err) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "error writing to file '%s': %s",
+                    path,
+                    local_err->message);
+        return FALSE;
+    }
+
+    if (chown(path, owner_uid, owner_grp) < 0) {
+        errsv = errno;
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "error chowning '%s': %s (%d)",
+                    path,
+                    nm_strerror_native(errsv),
+                    errsv);
+        unlink(path);
+        return FALSE;
+    }
+
+    /* In case of updating the connection and changing the file path,
+     * we need to remove the old one, not to end up with two connections.
+     */
+    if (existing_path && !existing_path_read_only && !nm_streq(path, existing_path))
+        unlink(existing_path);
+
+    NM_SET_OUT(out_reread, g_steal_pointer(&reread));
+    NM_SET_OUT(out_reread_same, reread_same);
+    NM_SET_OUT(out_path, g_steal_pointer(&path));
+
+    return TRUE;
+}
+
+gboolean
+nms_keyfile_writer_connection(NMConnection *                  connection,
+                              gboolean                        is_nm_generated,
+                              gboolean                        is_volatile,
+                              gboolean                        is_external,
+                              const char *                    shadowed_storage,
+                              gboolean                        shadowed_owned,
+                              const char *                    keyfile_dir,
+                              const char *                    profile_dir,
+                              const char *                    existing_path,
+                              gboolean                        existing_path_read_only,
+                              gboolean                        force_rename,
+                              NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                              gpointer                        allow_filename_user_data,
+                              char **                         out_path,
+                              NMConnection **                 out_reread,
+                              gboolean *                      out_reread_same,
+                              GError **                       error)
+{
+    return _internal_write_connection(connection,
+                                      is_nm_generated,
+                                      is_volatile,
+                                      is_external,
+                                      shadowed_storage,
+                                      shadowed_owned,
+                                      keyfile_dir,
+                                      profile_dir,
+                                      TRUE,
+                                      0,
+                                      0,
+                                      existing_path,
+                                      existing_path_read_only,
+                                      force_rename,
+                                      allow_filename_cb,
+                                      allow_filename_user_data,
+                                      out_path,
+                                      out_reread,
+                                      out_reread_same,
+                                      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)
+{
+    return _internal_write_connection(connection,
+                                      FALSE,
+                                      FALSE,
+                                      FALSE,
+                                      NULL,
+                                      FALSE,
+                                      keyfile_dir,
+                                      keyfile_dir,
+                                      FALSE,
+                                      owner_uid,
+                                      owner_grp,
+                                      NULL,
+                                      FALSE,
+                                      FALSE,
+                                      NULL,
+                                      NULL,
+                                      out_path,
+                                      out_reread,
+                                      out_reread_same,
+                                      error);
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
new file mode 100644
index 00000000..dffd3b04
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_WRITER_H__
+#define __NMS_KEYFILE_WRITER_H__
+
+#include "nm-connection.h"
+
+typedef gboolean (*NMSKeyfileWriterAllowFilenameCb)(const char *check_filename,
+                                                    gpointer    allow_filename_user_data);
+
+gboolean nms_keyfile_writer_connection(NMConnection *                  connection,
+                                       gboolean                        is_nm_generated,
+                                       gboolean                        is_volatile,
+                                       gboolean                        is_external,
+                                       const char *                    shadowed_storage,
+                                       gboolean                        shadowed_owned,
+                                       const char *                    keyfile_dir,
+                                       const char *                    profile_dir,
+                                       const char *                    existing_path,
+                                       gboolean                        existing_path_read_only,
+                                       gboolean                        force_rename,
+                                       NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                                       gpointer                        allow_filename_user_data,
+                                       char **                         out_path,
+                                       NMConnection **                 out_reread,
+                                       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);
+
+#endif /* __NMS_KEYFILE_WRITER_H__ */
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT
new file mode 100644
index 00000000..162bf72e
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT
@@ -0,0 +1,24 @@
+
+[connection]
+id=AT&T Data Connect BT
+uuid=089130ab-ce28-46e4-ad77-d44869b03d19
+type=bluetooth
+autoconnect=false
+
+[ipv4]
+method=auto
+
+[gsm]
+number=*99#
+username=ISP@CINGULARGPRS.COM
+password=CINGULAR1
+apn=ISP.CINGULAR
+
+[serial]
+baud=115200
+parity=o
+
+[bluetooth]
+bdaddr=00:11:22:33:44:55
+type=dun
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain
new file mode 100644
index 00000000..11e1ec6f
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain
@@ -0,0 +1,24 @@
+
+[connection]
+id=AT&T Data Connect
+uuid=15d742f1-2b5a-421e-9f27-fcb1fc26d72c
+type=gsm
+autoconnect=false
+
+[ipv4]
+method=auto
+
+[gsm]
+number=*99#
+username=ISP@CINGULARGPRS.COM
+password=CINGULAR1
+apn=ISP.CINGULAR
+network-id=24005
+pin=2345
+device-id=da812de91eec16620b06cd0ca5cbc7ea25245222
+sim-id=89148000000060671234
+sim-operator-id=310260
+
+[serial]
+baud=115200
+parity=111
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component
new file mode 100644
index 00000000..f5463ed0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component
@@ -0,0 +1,15 @@
+[connection]
+id=Test Bridge Component
+uuid=d7b4f96c-c45e-4298-bef8-f48574f8c1c0
+type=802-3-ethernet
+master=br0
+slave-type=bridge
+
+[802-3-ethernet]
+mac-address=00:22:15:59:62:97
+
+[bridge-port]
+priority=28
+hairpin-mode=true
+path-cost=100
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main
new file mode 100644
index 00000000..fc9fd8f8
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main
@@ -0,0 +1,18 @@
+[connection]
+id=Test Bridge Main
+uuid=8f061643-fe41-4d4c-a8d9-097d26e2ad3a
+type=bridge
+
+[bridge]
+interface-name=br0
+stp=true
+forward-delay=2
+priority=32744
+hello-time=7
+max-age=39
+ageing-time=235352
+multicast-snooping=false
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property
new file mode 100644
index 00000000..52b395b3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property
@@ -0,0 +1,8 @@
+[connection]
+id=Test Wired Connection IP6
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+
+[ipv6]
+method=auto
+ip6-privacy=2
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property
new file mode 100644
index 00000000..3a46611d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property
@@ -0,0 +1,11 @@
+[connection]
+id=Test Flags Property
+uuid=05a5ec81-fa72-4b7c-9f85-4a0dfd36c84f
+type=gsm
+
+[gsm]
+number=*99#
+username=username
+password-flags=5
+apn=my.apn
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection
new file mode 100644
index 00000000..3d58fee0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection
@@ -0,0 +1,41 @@
+
+[serial]
+baud=115200
+bits=8
+parity=110
+stopbits=1
+send-delay=0
+
+[connection]
+id=Test GSM Connection
+uuid=05a5ec81-fa72-4b7c-9f85-4a0dfd36c84f
+type=gsm
+autoconnect=false
+timestamp=0
+
+[gsm]
+number=*99#
+username=username
+apn=my.apn
+network-type=0
+band=0
+
+[ppp]
+noauth=false
+refuse-eap=false
+refuse-pap=false
+refuse-chap=false
+refuse-mschap=false
+refuse-mschapv2=false
+nobsdcomp=false
+nodeflate=false
+no-vj-comp=false
+require-mppe=false
+require-mppe-128=false
+mppe-stateful=false
+crtscts=false
+baud=0
+mru=0
+mtu=0
+lcp-echo-failure=5
+lcp-echo-interval=30
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection
new file mode 100644
index 00000000..3a984b77
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection
@@ -0,0 +1,13 @@
+[connection]
+id=Test InfiniBand Connection
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=infiniband
+
+[infiniband]
+mac-address=00:11:22:33:44:55:66:77:88:99:01:12:23:34:45:56:67:78:89:90
+transport-mode=datagram
+mtu=1400
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID
new file mode 100644
index 00000000..2bacb725
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID
@@ -0,0 +1,11 @@
+[connection]
+id=Test 
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=101
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2
new file mode 100644
index 00000000..20240251
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2
@@ -0,0 +1,11 @@
+[connection]
+id=Test SSID - escaping semicolon in string
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=11\\;12\\;13\\;
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID
new file mode 100644
index 00000000..6d2bc0fa
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID
@@ -0,0 +1,11 @@
+[connection]
+id=Test 
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=98;108;97;104;49;50;51;52;
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format
new file mode 100644
index 00000000..b2bf9155
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format
@@ -0,0 +1,13 @@
+[connection]
+id=Test InfiniBand Connection
+uuid=5680a56d-c99f-45ad-a6dd-b44d5c398c12
+type=infiniband
+
+[infiniband]
+mac-address=0;17;34;51;68;85;102;119;136;153;1;18;35;52;69;86;103;120;137;144;
+transport-mode=datagram
+mtu=1400
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format
new file mode 100644
index 00000000..9427b16c
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format
@@ -0,0 +1,10 @@
+[connection]
+id=Test MAC Old Format
+uuid=8980a26d-c99f-4aad-a6bd-b439bc348ca4
+type=802-3-ethernet
+
+[802-3-ethernet]
+mac-address=00:11:aa:BB:CC:55
+cloned-mac-address=00;22;170;187;204;254;
+mtu=1400
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID
new file mode 100644
index 00000000..138380d0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID
@@ -0,0 +1,9 @@
+# id and uuid keys are missing
+# They will be auto-genetrated by NetworkManager
+
+[connection]
+type=ethernet
+autoconnect=true
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags
new file mode 100644
index 00000000..330adda0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags
@@ -0,0 +1,15 @@
+# VLAN setting with missing 'flags' key
+# vlan.flags will be set to 0 (even if the default 'flags' property value is 1)
+
+[connection]
+id=Test Missing Vlan Flags
+uuid=803ebe47-8c31-401d-b47b-03fc0d34eb11
+type=vlan
+autoconnect=true
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
+
+[vlan]
+id=444
+parent=em1
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting
new file mode 100644
index 00000000..95d7b9a9
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting
@@ -0,0 +1,11 @@
+# Settings with all default values are not written, including
+# VLAN settings with a VLAN ID of 0, which is the default value.
+
+[connection]
+id=Test Missing Vlan Setting
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=vlan
+autoconnect=true
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name
new file mode 100644
index 00000000..f27cd4a3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name
@@ -0,0 +1,13 @@
+
+[connection]
+id=Test Wired Connection
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=ethernet
+
+[ethernet]
+mac-address=00:11:22:33:44:55
+mtu=1400
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names
new file mode 100644
index 00000000..d9ac5864
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names
@@ -0,0 +1,16 @@
+[connection]
+id=Test New Wireless Group Names
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=wifi
+
+[wifi]
+ssid=foobar
+mode=infrastructure
+
+[wifi-security]
+key-mgmt=wpa-psk
+psk=s3cu4e passphrase
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID
new file mode 100644
index 00000000..4a3b56d2
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID
@@ -0,0 +1,11 @@
+[connection]
+id=Test 
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=blah blah ssid 1234
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config
new file mode 100644
index 00000000..14280eef
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config
@@ -0,0 +1,11 @@
+
+[connection]
+id=Test TC Config
+uuid=32a51666-95ea-1337-b371-dcf5e54b1001
+type=802-3-ethernet
+
+[tc]
+qdisc.root=handle 1234 fq_codel
+qdisc.ffff:fff1=ingress
+tfilter.1234:=matchall action drop
+tfilter.ffff:=matchall action simple sdata Hello
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
new file mode 100644
index 00000000..f9ccc003
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
@@ -0,0 +1,72 @@
+
+[connection]
+id=Test Wired Connection
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+autoconnect=true
+timestamp=6654332
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
+speed=0
+duplex=full
+auto-negotiate=true
+mtu=1400
+
+[ipv4]
+method=manual
+dns=4.2.2.1;bogus;4.2.2.2;
+addresses1=192.168.0.5;24;192.168.0.1;
+addresses2=1.2.3.4;16;1.2.1.1;
+address=2.3.4.5/24,2.3.4.6
+address3=3.4.5.6/16
+address4=4.5.6.7/1.2.3.4
+address5=5.6.7.8
+routes1=1.2.3.0/24,2.3.4.8,99
+route=5.6.7.8/32
+routes2=1.1.1.2/12,
+routes3=1.1.1.3/13,,
+routes7=1.1.1.7/17,0.0.0.0
+routes4=1.1.1.4/14,2.2.2.4
+address30=1.2.3.130/24
+routes5=1.1.1.5/15,2.2.2.5,
+routes6=1.1.1.6/16,2.2.2.6,0
+routes8=1.1.1.8/18,0.0.0.0,
+routes9=1.1.1.9/19,0.0.0.0,0
+route10=1.1.1.10/21,,0
+routes10=1.1.1.10/20,,0
+routes11=1.1.1.11/21,,21
+routes11_options=cwnd=10,lock-cwnd=true,mtu=1430,src=7.7.7.7,type=unicast
+routes12=1.2.3.4/32
+routes12_options=type=local
+address30=1.2.3.30/24
+addresses30=1.2.3.30/25
+addresses31=1.2.3.31/25
+address31=1.2.3.31/24
+ignore-auto-routes=false
+ignore-auto-dns=false
+
+[ipv6]
+method=manual
+dns=1111:dddd::aaaa;1::cafe;
+dns-search=super-domain.com;redhat.com;gnu.org;
+addresses1=abcd:1234:ffff::cdde/64
+addresses2=1:2:3:4:5:6:7:8/96
+address=2:3:4:5:6:7:8:9/64,2:3:4:5:1:2:3:4
+address3=3:4:5:6:7:8:9:0/128
+address4=3:4:5:6:7:8:9:14
+address5=3:4:5:6:7:8:9:15,
+address6=3:4:5:6:7:8:9:16,66
+address7=3:4:5:6:7:8:9:17,67,
+address8=3:4:5:6:7:8:9:18,68,::
+address9=3:4:5:6:7:8:9:19,69,1::09
+routes1=a:b:c:d::/64;f:e:d:c:1:2:3:4;99;
+route=d:e:f:0:1:2:3:4/64,f:e:d:c:1:2:3:4
+route2=8:7:6:5:4:3:2:1/128
+route3=6:7:8:9:0:1:2:3/126,,1
+route4=7:8:9:0:1:2:3:4/125/::,5
+route5=8:9:0:1:2:3:4:5/124,6
+route6=8:9:0:1:2:3:4:6/123,,
+route6_options=from=abce::/63
+ignore-auto-routes=false
+ignore-auto-dns=false
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6
new file mode 100644
index 00000000..a42dd5d2
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6
@@ -0,0 +1,20 @@
+
+[connection]
+id=Test Wired Connection IP6
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+autoconnect=true
+timestamp=6654332
+
+[802-3-ethernet]
+auto-negotiate=true
+mtu=1400
+
+[ipv4]
+method=disabled
+
+[ipv6]
+method=manual
+addresses1=abcd:1234:ffff::cdde/64,abcd:1234:ffff::cdd1
+dns=1111:dddd::aaaa;1::cafe;
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case
new file mode 100644
index 00000000..29aef494
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case
@@ -0,0 +1,32 @@
+
+[connection]
+id=Test Wired Connection MAC Case
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+autoconnect=true
+timestamp=6654332
+
+[802-3-ethernet]
+mac-address=00:11:aa:BB:CC:55
+speed=0
+duplex=full
+auto-negotiate=true
+mtu=1400
+
+[ipv4]
+method=manual
+dns=4.2.2.1;4.2.2.2;
+addresses1=192.168.0.5;24;192.168.0.1;
+addresses2=1.2.3.4;16;1.2.1.1;
+ignore-auto-routes=false
+ignore-auto-dns=false
+
+[ipv6]
+method=manual
+dns=1111:dddd::aaaa;1::cafe;
+dns-search=super-domain.com;redhat.com;gnu.org;
+addresses1=abcd:1234:ffff::cdde/64
+addresses2=1:2:3:4:5:6:7:8/96
+routes1=a:b:c:d::/64;f:e:d:c:1:2:3:4;99;
+ignore-auto-routes=false
+ignore-auto-dns=false
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob
new file mode 100644
index 00000000..62e6ae31
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=48;130;2;52;48;130;1;161;2;16;2;173;102;126;78;69;254;94;87;111;60;152;25;94;221;192;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;30;23;13;57;52;49;49;48;57;48;48;48;48;48;48;90;23;13;49;48;48;49;48;55;50;51;53;57;53;57;90;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;129;155;48;13;6;9;42;134;72;134;247;13;1;1;1;5;0;3;129;137;0;48;129;133;2;126;0;146;206;122;193;174;131;62;90;170;137;131;87;172;37;1;118;12;173;174;142;44;55;206;235;53;120;100;84;3;229;132;64;81;201;191;143;8;226;138;130;8;210;22;134;55;85;233;177;33;2;173;118;104;129;154;5;162;75;201;75;37;102;34;86;108;136;7;143;247;129;89;109;132;7;101;112;19;113;118;62;155;119;76;227;80;137;86;152;72;185;29;167;41;26;19;46;74;17;89;156;30;21;213;73;84;44;115;58;105;130;177;151;57;156;109;112;103;72;229;221;45;214;200;30;123;2;3;1;0;1;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;3;126;0;101;221;126;225;178;236;176;226;58;224;236;113;70;154;25;17;184;211;199;160;180;3;64;38;2;62;9;156;225;18;179;209;90;246;55;165;183;97;3;182;91;22;105;59;198;68;8;12;136;83;12;107;151;73;199;62;53;220;108;185;187;170;223;92;187;58;47;147;96;182;169;75;77;242;32;247;205;95;127;100;123;142;220;0;92;215;250;119;202;57;22;89;111;14;234;211;181;131;127;77;77;66;86;118;180;201;95;4;248;56;248;235;210;95;117;95;205;123;252;229;142;128;124;252;80;
+client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New
new file mode 100644
index 00000000..4cd86686
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=test-ca-cert.pem
+client-cert=test-key-and-cert.pem
+private-key=test-key-and-cert.pem
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old
new file mode 100644
index 00000000..d3da598c
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;67;65;47;101;97;112;116;101;115;116;95;99;97;95;99;101;114;116;46;112;101;109;0;
+client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing
new file mode 100644
index 00000000..2b39538e
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=/some/random/cert/path.pem
+client-cert=test-key-and-cert.pem
+private-key=test-key-and-cert.pem
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection
new file mode 100644
index 00000000..b1949d0d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection
@@ -0,0 +1,22 @@
+[connection]
+id=Test Wireless Connection
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+autoconnect=false
+timestamp=1226604314
+
+[802-11-wireless]
+ssid=110;109;45;116;101;115;116;45;97;112;
+mode=infrastructure
+channel=0
+rate=0
+tx-power=0
+mtu=0
+bssid=00:1a:33:44:99:82
+
+[ipv4]
+method=auto
+ignore-auto-routes=false
+ignore-auto-dns=false
+never-default=false
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection
new file mode 100644
index 00000000..16d4b45e
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection
@@ -0,0 +1,33 @@
+[connection]
+id=dcb connection 1
+uuid=ac3c251e-260f-49b6-8ceb-12d37ea00751
+type=ethernet
+autoconnect=false
+permissions=
+secondaries=
+
+[ethernet]
+mac-address-blacklist=
+
+[dcb]
+app-fcoe-flags=1
+app-fip-flags=1
+app-iscsi-flags=1
+priority-bandwidth=0;0;0;0;0;0;0;0;
+priority-flow-control=0;0;0;0;0;0;0;0;
+priority-flow-control-flags=1
+priority-group-bandwidth=100;0;0;0;0;0;0;0;
+priority-group-flags=1
+priority-group-id=0;0;0;0;0;0;0;0;
+priority-strict-bandwidth=0;0;0;0;0;0;0;0;
+priority-traffic-class=0;0;0;0;0;0;0;0;
+
+[ipv4]
+dns-search=
+method=auto
+
+[ipv6]
+addr-gen-mode=stable-privacy
+dns-search=
+ip6-privacy=0
+method=auto
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1
new file mode 100644
index 00000000..cac135ad
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1
@@ -0,0 +1,2 @@
+[connection]
+type=802-3-ethernet
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2
new file mode 100644
index 00000000..bbf2d8d6
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2
@@ -0,0 +1 @@
+[802-3-ethernet]
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1
new file mode 100644
index 00000000..d3122d53
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1
@@ -0,0 +1,4 @@
+[connection]
+type=802-3-ethernet
+master=br0
+slave-type=bridge
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2
new file mode 100644
index 00000000..eb1cdace
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2
@@ -0,0 +1,7 @@
+[connection]
+master=br0
+
+[802-3-ethernet]
+
+[bridge-port]
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3
new file mode 100644
index 00000000..7419e97d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3
@@ -0,0 +1,4 @@
+[connection]
+master=br0
+slave-type=bridge
+[802-3-ethernet]
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4
new file mode 100644
index 00000000..626b6f68
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4
@@ -0,0 +1,4 @@
+[connection]
+type=802-3-ethernet
+master=br0
+[bridge-port]
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem
new file mode 100644
index 00000000..ef1be20d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
+VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw
+FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE
+AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx
+NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ
+QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55
+IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3
+DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU
+xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc
+Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3
+FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4
+HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm
+J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P
+T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF
+ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO
+BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL
+EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl
+c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC
+AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc
+JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT
+p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t
+qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn
+B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp
+3nniskIjbH0xjgZf/nVMyLnjxg==
+-----END CERTIFICATE-----
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem
new file mode 100644
index 00000000..dec9aa1b
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem
@@ -0,0 +1,118 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,4DE0615F23D82107
+
+QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz
+L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7
+zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU
+h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW
+ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv
+QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na
+eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo
+cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m
+RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8
+xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr
+T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI
+ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo
+RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ
+EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt
+lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5
+r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4
+aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W
+GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e
+DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE
+lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO
+8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3
+VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt
+G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ
+tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW
+QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1
+-----END RSA PRIVATE KEY-----
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com
+        Validity
+            Not Before: Mar 10 15:13:16 2009 GMT
+            Not After : Mar  8 15:13:16 2019 GMT
+        Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a:
+                    82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48:
+                    ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49:
+                    c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8:
+                    03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14:
+                    ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9:
+                    c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c:
+                    60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d:
+                    44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b:
+                    f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d:
+                    c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75:
+                    d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2:
+                    06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38:
+                    ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba:
+                    ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36:
+                    2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11:
+                    a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f:
+                    a3:43
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C
+            X509v3 Authority Key Identifier: 
+                keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10
+                DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com
+                serial:EB:E7:64:FB:79:F7:22:19
+
+    Signature Algorithm: md5WithRSAEncryption
+        7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82:
+        f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89:
+        b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b:
+        12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76:
+        1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48:
+        32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02:
+        85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1:
+        fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c:
+        cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43:
+        8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71:
+        1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19:
+        07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69:
+        7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62:
+        8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7:
+        3b:65:fe:f9
+-----BEGIN CERTIFICATE-----
+MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx
+EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO
+TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx
+HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN
+MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl
+MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG
+A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR
+XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA
+vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9
+jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF
+TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX
+gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj
+ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy
+YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww
+gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx
+FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD
+VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3
+IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/
+DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2
+8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr
+vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD
+T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa
+PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk=
+-----END CERTIFICATE-----
diff --git a/src/core/settings/plugins/keyfile/tests/meson.build b/src/core/settings/plugins/keyfile/tests/meson.build
new file mode 100644
index 00000000..3c89bcf9
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-keyfile-settings',
+  'test-keyfile-settings.c',
+  dependencies: libNetworkManagerTest_dep,
+  c_args: test_c_flags,
+)
+
+test(
+  'test-keyfile-settings',
+  test_script,
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
+)
diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
new file mode 100644
index 00000000..348b0ed6
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -0,0 +1,2923 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <linux/pkt_sched.h>
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
+
+#include "nm-core-internal.h"
+
+#include "settings/plugins/keyfile/nms-keyfile-reader.h"
+#include "settings/plugins/keyfile/nms-keyfile-writer.h"
+#include "settings/plugins/keyfile/nms-keyfile-utils.h"
+
+#include "nm-test-utils-core.h"
+
+#define TEST_KEYFILES_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/keyfile/tests/keyfiles"
+#define TEST_SCRATCH_DIR  NM_BUILD_BUILDDIR "/src/core/settings/plugins/keyfile/tests/keyfiles"
+
+/*****************************************************************************/
+
+static void
+check_ip_address(NMSettingIPConfig *config, int idx, const char *address, int plen)
+{
+    NMIPAddress *ip4 = nm_setting_ip_config_get_address(config, idx);
+
+    g_assert(ip4);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4), ==, address);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4), ==, plen);
+}
+
+static void
+check_ip_route(NMSettingIPConfig *config,
+               int                idx,
+               const char *       destination,
+               int                plen,
+               const char *       next_hop,
+               gint64             metric)
+{
+    NMIPRoute *route = nm_setting_ip_config_get_route(config, idx);
+
+    g_assert(route);
+    g_assert_cmpstr(nm_ip_route_get_dest(route), ==, destination);
+    g_assert_cmpint(nm_ip_route_get_prefix(route), ==, plen);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(route), ==, next_hop);
+    g_assert_cmpint(nm_ip_route_get_metric(route), ==, metric);
+}
+
+#define keyfile_read_connection_from_file(full_filename)                                  \
+    ({                                                                                    \
+        gs_free_error GError *_error = NULL;                                              \
+        NMConnection *        _connection;                                                \
+                                                                                          \
+        g_assert(full_filename &&full_filename[0] == '/');                                \
+                                                                                          \
+        _connection =                                                                     \
+            nms_keyfile_reader_from_file(full_filename,                                   \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         (nmtst_get_rand_uint32() % 2) ? &_error : NULL); \
+        nmtst_assert_success(_connection, _error);                                        \
+        nmtst_assert_connection_verifies_without_normalization(_connection);              \
+                                                                                          \
+        _connection;                                                                      \
+    })
+
+static void
+assert_reread(NMConnection *connection, gboolean normalize_connection, const char *testfile)
+{
+    gs_unref_object NMConnection *reread           = NULL;
+    gs_unref_object NMConnection *connection_clone = NULL;
+    NMSettingConnection *         s_con;
+
+    g_assert(NM_IS_CONNECTION(connection));
+
+    reread = keyfile_read_connection_from_file(testfile);
+
+    if (!normalize_connection && (s_con = nm_connection_get_setting_connection(connection))
+        && !nm_setting_connection_get_master(s_con)
+        && !nm_connection_get_setting_proxy(connection)) {
+        connection_clone = nmtst_clone_connection(connection);
+        connection       = connection_clone;
+        nm_connection_add_setting(connection, nm_setting_proxy_new());
+    }
+
+    nmtst_assert_connection_equals(connection, normalize_connection, reread, FALSE);
+}
+
+static void
+assert_reread_and_unlink(NMConnection *connection,
+                         gboolean      normalize_connection,
+                         const char *  testfile)
+{
+    assert_reread(connection, normalize_connection, testfile);
+    unlink(testfile);
+}
+
+static void
+assert_reread_same(NMConnection *connection, NMConnection *reread)
+{
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+write_test_connection_reread(NMConnection * connection,
+                             char **        testfile,
+                             NMConnection **out_reread,
+                             gboolean *     out_reread_same)
+{
+    uid_t           owner_uid;
+    gid_t           owner_grp;
+    gboolean        success;
+    GError *        error   = NULL;
+    GError **       p_error = (nmtst_get_rand_uint32() % 2) ? &error : NULL;
+    gs_unref_object NMConnection *connection_normalized = NULL;
+
+    g_assert(NM_IS_CONNECTION(connection));
+    g_assert(testfile && !*testfile);
+
+    owner_uid = geteuid();
+    owner_grp = getegid();
+
+    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);
+    g_assert_no_error(error);
+    g_assert(success);
+    g_assert(*testfile && (*testfile)[0]);
+}
+
+static void
+write_test_connection(NMConnection *connection, char **testfile)
+{
+    gs_unref_object NMConnection *reread      = NULL;
+    gboolean                      reread_same = FALSE;
+
+    write_test_connection_reread(connection, testfile, &reread, &reread_same);
+    assert_reread_same(connection, reread);
+    g_assert(reread_same);
+}
+
+static void
+write_test_connection_and_reread(NMConnection *connection, gboolean normalize_connection)
+{
+    gs_free char *testfile = NULL;
+
+    g_assert(NM_IS_CONNECTION(connection));
+
+    write_test_connection(connection, &testfile);
+    assert_reread_and_unlink(connection, normalize_connection, testfile);
+}
+
+static GKeyFile *
+keyfile_load_from_file(const char *testfile)
+{
+    GKeyFile *keyfile;
+    GError *  error = NULL;
+    gboolean  success;
+
+    g_assert(testfile && *testfile);
+
+    keyfile = g_key_file_new();
+    success = g_key_file_load_from_file(keyfile, testfile, G_KEY_FILE_NONE, &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    return keyfile;
+}
+
+static void
+_setting_copy_property_gbytes(NMConnection *src,
+                              NMConnection *dst,
+                              const char *  setting_name,
+                              const char *  property_name)
+{
+    gs_unref_bytes GBytes *blob = NULL;
+    NMSetting *            s_src;
+    NMSetting *            s_dst;
+
+    g_assert(NM_IS_CONNECTION(src));
+    g_assert(NM_IS_CONNECTION(dst));
+    g_assert(setting_name);
+    g_assert(property_name);
+
+    s_src = nm_connection_get_setting_by_name(src, setting_name);
+    g_assert(NM_IS_SETTING(s_src));
+    s_dst = nm_connection_get_setting_by_name(dst, setting_name);
+    g_assert(NM_IS_SETTING(s_dst));
+
+    g_object_get(s_src, property_name, &blob, NULL);
+    g_object_set(s_dst, property_name, blob, NULL);
+}
+
+/*****************************************************************************/
+
+static void
+test_read_valid_wired_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPRoute *                   route;
+    const char *                  mac;
+    char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses:*semicolon at the end*addresses1*");
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses:*semicolon at the end*addresses2*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address4*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address5*");
+    NMTST_EXPECT_NM_WARN("*ipv4.dns: ignoring invalid DNS server IPv4 address 'bogus'*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes2*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes3*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes5*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes8*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address4*");
+    NMTST_EXPECT_NM_INFO("*ipv6.address*semicolon at the end*address5*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address5*");
+    NMTST_EXPECT_NM_INFO("*ipv6.address*semicolon at the end*address7*");
+    NMTST_EXPECT_NM_INFO("*ipv6.routes*semicolon at the end*routes1*");
+    NMTST_EXPECT_NM_INFO("*ipv6.route*semicolon at the end*route6*");
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection");
+    g_test_assert_expected_messages();
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "4e80a56d-c99f-4aad-a6dd-b449bc398c57");
+    g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 6654332);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1400);
+
+    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_MANUAL);
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
+
+    /* IPv4 addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 10);
+    check_ip_address(s_ip4, 0, "2.3.4.5", 24);
+    check_ip_address(s_ip4, 1, "192.168.0.5", 24);
+    check_ip_address(s_ip4, 2, "1.2.3.4", 16);
+    check_ip_address(s_ip4, 3, "3.4.5.6", 16);
+    check_ip_address(s_ip4, 4, "4.5.6.7", 24);
+    check_ip_address(s_ip4, 5, "5.6.7.8", 24);
+    check_ip_address(s_ip4, 6, "1.2.3.30", 24);
+    check_ip_address(s_ip4, 7, "1.2.3.30", 25);
+    check_ip_address(s_ip4, 8, "1.2.3.31", 24);
+    check_ip_address(s_ip4, 9, "1.2.3.31", 25);
+
+    /* IPv4 gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "2.3.4.6");
+
+    /* IPv4 routes */
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 14);
+    check_ip_route(s_ip4, 0, "5.6.7.8", 32, NULL, -1);
+    check_ip_route(s_ip4, 1, "1.2.3.0", 24, "2.3.4.8", 99);
+    check_ip_route(s_ip4, 2, "1.1.1.2", 12, NULL, -1);
+    check_ip_route(s_ip4, 3, "1.1.1.3", 13, NULL, -1);
+    check_ip_route(s_ip4, 4, "1.1.1.4", 14, "2.2.2.4", -1);
+    check_ip_route(s_ip4, 5, "1.1.1.5", 15, "2.2.2.5", -1);
+    check_ip_route(s_ip4, 6, "1.1.1.6", 16, "2.2.2.6", 0);
+    check_ip_route(s_ip4, 7, "1.1.1.7", 17, NULL, -1);
+    check_ip_route(s_ip4, 8, "1.1.1.8", 18, NULL, -1);
+    check_ip_route(s_ip4, 9, "1.1.1.9", 19, NULL, 0);
+    check_ip_route(s_ip4, 10, "1.1.1.10", 21, NULL, 0);
+    check_ip_route(s_ip4, 11, "1.1.1.10", 20, NULL, 0);
+    check_ip_route(s_ip4, 12, "1.1.1.11", 21, NULL, 21);
+    check_ip_route(s_ip4, 13, "1.2.3.4", 32, NULL, -1);
+
+    /* Route attributes */
+    route = nm_setting_ip_config_get_route(s_ip4, 12);
+    g_assert(route);
+
+    nmtst_assert_route_attribute_uint32(route, NM_IP_ROUTE_ATTRIBUTE_CWND, 10);
+    nmtst_assert_route_attribute_uint32(route, NM_IP_ROUTE_ATTRIBUTE_MTU, 1430);
+    nmtst_assert_route_attribute_boolean(route, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, TRUE);
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_SRC, "7.7.7.7");
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "unicast");
+
+    route = nm_setting_ip_config_get_route(s_ip4, 13);
+    g_assert(route);
+
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
+
+    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_MANUAL);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1111:dddd::aaaa");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1::cafe");
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 3);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "super-domain.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "redhat.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 2), ==, "gnu.org");
+
+    /* IPv6 addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 10);
+    check_ip_address(s_ip6, 0, "2:3:4:5:6:7:8:9", 64);
+    check_ip_address(s_ip6, 1, "abcd:1234:ffff::cdde", 64);
+    check_ip_address(s_ip6, 2, "1:2:3:4:5:6:7:8", 96);
+    check_ip_address(s_ip6, 3, "3:4:5:6:7:8:9:0", 128);
+    check_ip_address(s_ip6, 4, "3:4:5:6:7:8:9:14", 64);
+    check_ip_address(s_ip6, 5, "3:4:5:6:7:8:9:15", 64);
+    check_ip_address(s_ip6, 6, "3:4:5:6:7:8:9:16", 66);
+    check_ip_address(s_ip6, 7, "3:4:5:6:7:8:9:17", 67);
+    check_ip_address(s_ip6, 8, "3:4:5:6:7:8:9:18", 68);
+    check_ip_address(s_ip6, 9, "3:4:5:6:7:8:9:19", 69);
+
+    /* IPv6 gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2:3:4:5:1:2:3:4");
+
+    /* Routes */
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 7);
+    check_ip_route(s_ip6, 0, "d:e:f:0:1:2:3:4", 64, "f:e:d:c:1:2:3:4", -1);
+    check_ip_route(s_ip6, 1, "a:b:c:d::", 64, "f:e:d:c:1:2:3:4", 99);
+    check_ip_route(s_ip6, 2, "8:7:6:5:4:3:2:1", 128, NULL, -1);
+    check_ip_route(s_ip6, 3, "6:7:8:9:0:1:2:3", 126, NULL, 1);
+    check_ip_route(s_ip6, 4, "7:8:9:0:1:2:3:4", 125, NULL, 5);
+    check_ip_route(s_ip6, 5, "8:9:0:1:2:3:4:5", 124, NULL, 6);
+    check_ip_route(s_ip6, 6, "8:9:0:1:2:3:4:6", 123, NULL, -1);
+
+    /* Route attributes */
+    route = nm_setting_ip_config_get_route(s_ip6, 6);
+    g_assert(route);
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_FROM, "abce::/63");
+}
+
+static void
+add_one_ip_address(NMSettingIPConfig *s_ip, const char *addr, guint32 prefix)
+{
+    NMIPAddress * ip_addr;
+    gs_free_error GError *error = NULL;
+
+    ip_addr = nm_ip_address_new(NM_IS_SETTING_IP4_CONFIG(s_ip) ? AF_INET : AF_INET6,
+                                addr,
+                                prefix,
+                                &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip, ip_addr);
+    nm_ip_address_unref(ip_addr);
+}
+
+static void
+add_one_ip_route(NMSettingIPConfig *s_ip,
+                 const char *       dest,
+                 const char *       nh,
+                 guint32            prefix,
+                 gint64             metric)
+{
+    NMIPRoute *   route;
+    gs_free_error GError *error = NULL;
+
+    g_assert(prefix > 0);
+    route = nm_ip_route_new(NM_IS_SETTING_IP4_CONFIG(s_ip) ? AF_INET : AF_INET6,
+                            dest,
+                            prefix,
+                            nh,
+                            metric,
+                            &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_route(s_ip, route);
+    nm_ip_route_unref(route);
+}
+
+static void
+test_write_wired_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPRoute *                   rt;
+    const char *                  mac         = "99:88:77:66:55:44";
+    const char *                  dns1        = "4.2.2.1";
+    const char *                  dns2        = "4.2.2.2";
+    const char *                  address1    = "192.168.0.5";
+    const char *                  address2    = "1.2.3.4";
+    const char *                  gw          = "192.168.0.1";
+    const char *                  route1      = "10.10.10.2";
+    const char *                  route1_nh   = "10.10.10.1";
+    const char *                  route2      = "1.1.1.1";
+    const char *                  route2_nh   = "1.2.1.1";
+    const char *                  route3      = "2.2.2.2";
+    const char *                  route3_nh   = NULL;
+    const char *                  route4      = "3.3.3.3";
+    const char *                  route4_nh   = NULL;
+    const char *                  dns6_1      = "1::cafe";
+    const char *                  dns6_2      = "2::cafe";
+    const char *                  address6_1  = "abcd::beef";
+    const char *                  address6_2  = "dcba::beef";
+    const char *                  route6_1    = "1:2:3:4:5:6:7:8";
+    const char *                  route6_1_nh = "8:7:6:5:4:3:2:1";
+    const char *                  route6_2    = "2001::1000";
+    const char *                  route6_2_nh = "2001::1111";
+    const char *                  route6_3    = "4:5:6:7:8:9:0:1";
+    const char *                  route6_3_nh = "::";
+    const char *                  route6_4    = "5:6:7:8:9:0:1:2";
+    const char *                  route6_4_nh = "::";
+    guint64                       timestamp   = 0x12345678L;
+    GError *                      error       = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work Wired",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* Wired setting */
+
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, 900, NULL);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 gw,
+                 NULL);
+
+    /* Addresses */
+    add_one_ip_address(s_ip4, address1, 24);
+    add_one_ip_address(s_ip4, address2, 8);
+
+    /* Routes */
+    add_one_ip_route(s_ip4, route1, route1_nh, 24, 3);
+    add_one_ip_route(s_ip4, route2, route2_nh, 8, 1);
+    add_one_ip_route(s_ip4, route3, route3_nh, 7, -1);
+
+    rt = nm_ip_route_new(AF_INET, route4, 6, route4_nh, 4, &error);
+    g_assert_no_error(error);
+    nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32(10));
+    nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(1492));
+    nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("1.2.3.4"));
+    g_assert(nm_setting_ip_config_add_route(s_ip4, rt));
+    nm_ip_route_unref(rt);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip4, dns1);
+    nm_setting_ip_config_add_dns(s_ip4, dns2);
+
+    /* IP6 setting */
+
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL);
+
+    /* Addresses */
+    add_one_ip_address(s_ip6, address6_1, 64);
+    add_one_ip_address(s_ip6, address6_2, 56);
+
+    /* Routes */
+    add_one_ip_route(s_ip6, route6_1, route6_1_nh, 64, 3);
+    add_one_ip_route(s_ip6, route6_2, route6_2_nh, 56, 1);
+    add_one_ip_route(s_ip6, route6_3, route6_3_nh, 63, 5);
+    add_one_ip_route(s_ip6, route6_4, route6_4_nh, 62, -1);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip6, dns6_1);
+    nm_setting_ip_config_add_dns(s_ip6, dns6_2);
+
+    /* DNS searches */
+    nm_setting_ip_config_add_dns_search(s_ip6, "wallaceandgromit.com");
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_ip6_wired_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection_IP6");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection IP6");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "4e80a56d-c99f-4aad-a6dd-b449bc398c57");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    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_DISABLED);
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0);
+
+    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_MANUAL);
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1);
+    check_ip_address(s_ip6, 0, "abcd:1234:ffff::cdde", 64);
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "abcd:1234:ffff::cdd1");
+}
+
+static void
+test_write_ip6_wired_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  dns     = "1::cafe";
+    const char *                  address = "abcd::beef";
+    const char *                  gw      = "dcba::beef";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work Wired IP6",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
+
+    /* IP6 setting */
+
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 gw,
+                 NULL);
+
+    /* Addresses */
+    add_one_ip_address(s_ip6, address, 64);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip6, dns);
+
+    /* DNS searches */
+    nm_setting_ip_config_add_dns_search(s_ip6, "wallaceandgromit.com");
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_wired_mac_case(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0xaa, 0xbb, 0xcc, 0x55};
+
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses*semicolon at the end*addresses1*");
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses*semicolon at the end*addresses2*");
+    NMTST_EXPECT_NM_INFO("*ipv6.routes*semicolon at the end*routes1*");
+    connection =
+        keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection_MAC_Case");
+    g_test_assert_expected_messages();
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection MAC Case");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "4e80a56d-c99f-4aad-a6dd-b449bc398c57");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+}
+
+static void
+test_read_mac_old_format(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    char                          expected_mac[ETH_ALEN] = {0x00, 0x11, 0xaa, 0xbb, 0xcc, 0x55};
+    char expected_cloned_mac[ETH_ALEN]                   = {0x00, 0x16, 0xaa, 0xbb, 0xcc, 0xfe};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_MAC_Old_Format");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, ETH_ALEN));
+
+    mac = nm_setting_wired_get_cloned_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_cloned_mac, ETH_ALEN));
+}
+
+static void
+test_read_mac_ib_old_format(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingInfiniband *         s_ib;
+    const char *                  mac;
+    guint8 expected_mac[INFINIBAND_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                                            0x77, 0x88, 0x99, 0x01, 0x12, 0x23, 0x34,
+                                            0x45, 0x56, 0x67, 0x78, 0x89, 0x90};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_MAC_IB_Old_Format");
+
+    s_ib = nm_connection_get_setting_infiniband(connection);
+    g_assert(s_ib);
+
+    /* MAC address */
+    mac = nm_setting_infiniband_get_mac_address(s_ib);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+}
+
+static void
+test_read_valid_wireless_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingIPConfig *           s_ip4;
+    const char *                  bssid;
+    const guint8                  expected_bssid[ETH_ALEN] = {0x00, 0x1a, 0x33, 0x44, 0x99, 0x82};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wireless_Connection");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wireless Connection");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "2f962388-e5f3-45af-a62c-ac220b8f7baa");
+    g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 1226604314);
+    g_assert(nm_setting_connection_get_autoconnect(s_con) == FALSE);
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+    bssid = nm_setting_wireless_get_bssid(s_wireless);
+    g_assert(bssid);
+    g_assert(nm_utils_hwaddr_matches(bssid, -1, expected_bssid, sizeof(expected_bssid)));
+
+    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);
+}
+
+static void
+test_write_wireless_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  bssid = "aa:b9:a1:74:55:44";
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {0x31, 0x33, 0x33, 0x37};
+    guint64                       timestamp = 0x12344433L;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work Wireless",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* Wireless setting */
+
+    s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wireless));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+
+    g_object_set(s_wireless,
+                 NM_SETTING_WIRELESS_BSSID,
+                 bssid,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRED_MTU,
+                 1000,
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_string_ssid(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wireless;
+    GBytes *                      ssid;
+    const guint8 *                ssid_data;
+    gsize                         ssid_len;
+    const char *                  expected_ssid = "blah blah ssid 1234";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_String_SSID");
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+
+    ssid_data = g_bytes_get_data(ssid, &ssid_len);
+    g_assert_cmpmem(ssid_data, ssid_len, expected_ssid, strlen(expected_ssid));
+}
+
+static void
+test_write_string_ssid(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingIPConfig *           s_ip4;
+    char *                        tmp;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {65, 49, 50, 51, 32, 46, 92, 46, 36, 37, 126, 93};
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "String SSID Test",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+
+    s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wireless));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wireless, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as a string */
+    keyfile = keyfile_load_from_file(testfile);
+    tmp     = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, NULL);
+    g_assert(tmp);
+    g_assert_cmpmem(tmp, strlen(tmp), tmpssid, sizeof(tmpssid));
+    g_free(tmp);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_read_intlist_ssid(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const guint8 *                ssid_data;
+    gsize                         ssid_len;
+    const char *                  expected_ssid = "blah1234";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlist_SSID");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid != NULL);
+    ssid_data = g_bytes_get_data(ssid, &ssid_len);
+    g_assert_cmpmem(ssid_data, ssid_len, expected_ssid, strlen(expected_ssid));
+}
+
+static void
+test_write_intlist_ssid(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {65, 49, 50, 51, 0, 50, 50};
+    gs_free_error GError *error             = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+    int *                           intlist;
+    gsize                           len = 0, i;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Intlist SSID Test",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+    s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    g_assert(s_wifi);
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as an int list */
+    keyfile = keyfile_load_from_file(testfile);
+
+    intlist = g_key_file_get_integer_list(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &len, &error);
+    g_assert_no_error(error);
+    g_assert(intlist);
+    g_assert_cmpint(len, ==, sizeof(tmpssid));
+
+    for (i = 0; i < len; i++)
+        g_assert_cmpint(intlist[i], ==, tmpssid[i]);
+    g_free(intlist);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_read_intlike_ssid(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "101";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlike_SSID");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid);
+    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+}
+
+static void
+test_read_intlike_ssid_2(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "11;12;13;";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlike_SSID_2");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid);
+    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+}
+
+static void
+test_write_intlike_ssid(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {49, 48, 49};
+    gs_free_error GError *error             = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+    char *                          tmp;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Intlike SSID Test",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+    s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    g_assert(s_wifi);
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as a plain "101" */
+    keyfile = keyfile_load_from_file(testfile);
+
+    tmp = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &error);
+    g_assert_no_error(error);
+    g_assert(tmp);
+    g_assert_cmpstr(tmp, ==, "101");
+    g_free(tmp);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_write_intlike_ssid_2(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {49, 49, 59, 49, 50, 59, 49, 51, 59};
+    gs_free_error GError *error             = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+    char *                          tmp;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Intlike SSID Test 2",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+    s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    g_assert(s_wifi);
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as a plain "11;12;13;" */
+    keyfile = keyfile_load_from_file(testfile);
+
+    tmp = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &error);
+    g_assert_no_error(error);
+    g_assert(tmp);
+    g_assert_cmpstr(tmp, ==, "11\\;12\\;13\\;");
+    g_free(tmp);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_read_bt_dun_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBluetooth *          s_bluetooth;
+    NMSettingSerial *             s_serial;
+    NMSettingGsm *                s_gsm;
+    const char *                  bdaddr;
+    const guint8                  expected_bdaddr[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/ATT_Data_Connect_BT");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "AT&T Data Connect BT");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "089130ab-ce28-46e4-ad77-d44869b03d19");
+
+    s_bluetooth = nm_connection_get_setting_bluetooth(connection);
+    g_assert(s_bluetooth);
+    bdaddr = nm_setting_bluetooth_get_bdaddr(s_bluetooth);
+    g_assert(bdaddr);
+    g_assert(nm_utils_hwaddr_matches(bdaddr, -1, expected_bdaddr, sizeof(expected_bdaddr)));
+    g_assert_cmpstr(nm_setting_bluetooth_get_connection_type(s_bluetooth),
+                    ==,
+                    NM_SETTING_BLUETOOTH_TYPE_DUN);
+
+    s_gsm = nm_connection_get_setting_gsm(connection);
+    g_assert(s_gsm);
+    g_assert_cmpstr(nm_setting_gsm_get_apn(s_gsm), ==, "ISP.CINGULAR");
+    g_assert_cmpstr(nm_setting_gsm_get_username(s_gsm), ==, "ISP@CINGULARGPRS.COM");
+    g_assert_cmpstr(nm_setting_gsm_get_password(s_gsm), ==, "CINGULAR1");
+
+    s_serial = nm_connection_get_setting_serial(connection);
+    g_assert(s_serial);
+    g_assert(nm_setting_serial_get_parity(s_serial) == NM_SETTING_SERIAL_PARITY_ODD);
+}
+
+static void
+test_write_bt_dun_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBluetooth *          s_bt;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingGsm *                s_gsm;
+    const char *                  bdaddr    = "aa:b9:a1:74:55:44";
+    guint64                       timestamp = 0x12344433L;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "T-Mobile Funkadelic",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_BLUETOOTH_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* Bluetooth setting */
+
+    s_bt = NM_SETTING_BLUETOOTH(nm_setting_bluetooth_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_bt));
+
+    g_object_set(s_bt,
+                 NM_SETTING_BLUETOOTH_BDADDR,
+                 bdaddr,
+                 NM_SETTING_BLUETOOTH_TYPE,
+                 NM_SETTING_BLUETOOTH_TYPE_DUN,
+                 NULL);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* GSM setting */
+    s_gsm = NM_SETTING_GSM(nm_setting_gsm_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_gsm));
+
+    g_object_set(s_gsm,
+                 NM_SETTING_GSM_APN,
+                 "internet2.voicestream.com",
+                 NM_SETTING_GSM_USERNAME,
+                 "george.clinton",
+                 NM_SETTING_GSM_PASSWORD,
+                 "parliament",
+                 NULL);
+
+    write_test_connection_and_reread(connection, TRUE);
+}
+
+static void
+test_read_gsm_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingSerial *             s_serial;
+    NMSettingGsm *                s_gsm;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/ATT_Data_Connect_Plain");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "AT&T Data Connect");
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_GSM_SETTING_NAME);
+
+    /* Plain GSM, so no BT setting expected */
+    g_assert(nm_connection_get_setting_bluetooth(connection) == NULL);
+
+    s_gsm = nm_connection_get_setting_gsm(connection);
+    g_assert(s_gsm);
+    g_assert_cmpstr(nm_setting_gsm_get_apn(s_gsm), ==, "ISP.CINGULAR");
+    g_assert_cmpstr(nm_setting_gsm_get_username(s_gsm), ==, "ISP@CINGULARGPRS.COM");
+    g_assert_cmpstr(nm_setting_gsm_get_password(s_gsm), ==, "CINGULAR1");
+    g_assert_cmpstr(nm_setting_gsm_get_network_id(s_gsm), ==, "24005");
+    g_assert_cmpstr(nm_setting_gsm_get_pin(s_gsm), ==, "2345");
+    g_assert_cmpstr(nm_setting_gsm_get_device_id(s_gsm),
+                    ==,
+                    "da812de91eec16620b06cd0ca5cbc7ea25245222");
+    g_assert_cmpstr(nm_setting_gsm_get_sim_id(s_gsm), ==, "89148000000060671234");
+    g_assert_cmpstr(nm_setting_gsm_get_sim_operator_id(s_gsm), ==, "310260");
+
+    s_serial = nm_connection_get_setting_serial(connection);
+    g_assert(s_serial);
+    g_assert_cmpint(nm_setting_serial_get_parity(s_serial), ==, NM_SETTING_SERIAL_PARITY_ODD);
+}
+
+static void
+test_write_gsm_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingGsm *                s_gsm;
+    guint64                       timestamp = 0x12344433L;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "T-Mobile Funkadelic 2",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_GSM_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* GSM setting */
+    s_gsm = NM_SETTING_GSM(nm_setting_gsm_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_gsm));
+
+    g_object_set(s_gsm,
+                 NM_SETTING_GSM_APN,
+                 "internet2.voicestream.com",
+                 NM_SETTING_GSM_USERNAME,
+                 "george.clinton.again",
+                 NM_SETTING_GSM_PASSWORD,
+                 "parliament2",
+                 NM_SETTING_GSM_PIN,
+                 "123456",
+                 NM_SETTING_GSM_NETWORK_ID,
+                 "254098",
+                 NM_SETTING_GSM_HOME_ONLY,
+                 TRUE,
+                 NM_SETTING_GSM_DEVICE_ID,
+                 "da812de91eec16620b06cd0ca5cbc7ea25245222",
+                 NM_SETTING_GSM_SIM_ID,
+                 "89148000000060671234",
+                 NM_SETTING_GSM_SIM_OPERATOR_ID,
+                 "310260",
+                 NULL);
+
+    write_test_connection_and_reread(connection, TRUE);
+}
+
+static void
+test_read_wired_8021x_tls_blob_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+    GBytes *                      blob;
+
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.client-cert: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.private-key: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Blob");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_BLOB);
+
+    /* Make sure it's not a path, since it's a blob */
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(scheme == NM_SETTING_802_1X_CK_SCHEME_PATH));
+    tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_test_assert_expected_messages();
+    g_assert(tmp == NULL);
+
+    /* Validate the path */
+    blob = nm_setting_802_1x_get_ca_cert_blob(s_8021x);
+    g_assert(blob != NULL);
+    g_assert_cmpint(g_bytes_get_size(blob), ==, 568);
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem");
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem");
+}
+
+static void
+test_read_wired_8021x_tls_bad_path_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+    char *                        tmp2;
+
+    NMTST_EXPECT_NM_WARN("*does not exist*");
+    connection =
+        keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Path_Missing");
+    g_test_assert_expected_messages();
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_PATH);
+
+    tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, "/some/random/cert/path.pem");
+
+    tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-key-and-cert.pem");
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    g_free(tmp2);
+}
+
+static void
+test_read_wired_8021x_tls_old_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.ca-cert: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem' does not exist*");
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.client-cert: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.private-key: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Old");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem") == 0);
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0);
+}
+
+static void
+test_read_wired_8021x_tls_new_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+    char *                        tmp2;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_New");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-ca-cert.pem");
+    tmp  = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+
+    tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-key-and-cert.pem");
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    g_free(tmp2);
+}
+
+#define TEST_WIRED_TLS_CA_CERT     TEST_KEYFILES_DIR "/test-ca-cert.pem"
+#define TEST_WIRED_TLS_CLIENT_CERT TEST_KEYFILES_DIR "/test-key-and-cert.pem"
+#define TEST_WIRED_TLS_PRIVKEY     TEST_KEYFILES_DIR "/test-key-and-cert.pem"
+
+static NMConnection *
+create_wired_tls_connection(NMSetting8021xCKScheme scheme)
+{
+    NMTST_UUID_INIT(uuid);
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMSetting *          s_wired;
+    NMSetting8021x *     s_8021x;
+    gboolean             success;
+    gs_free_error GError *error = NULL;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection != NULL);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Wired Really Secure TLS",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_assert(s_ip4);
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    /* Wired setting */
+    s_wired = nm_setting_wired_new();
+    g_assert(s_wired);
+    nm_connection_add_setting(connection, s_wired);
+
+    /* 802.1x setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    g_assert(s_8021x);
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    nm_setting_802_1x_add_eap_method(s_8021x, "tls");
+    g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL);
+
+    success = nm_setting_802_1x_set_ca_cert(s_8021x, TEST_WIRED_TLS_CA_CERT, scheme, NULL, &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    success = nm_setting_802_1x_set_client_cert(s_8021x,
+                                                TEST_WIRED_TLS_CLIENT_CERT,
+                                                scheme,
+                                                NULL,
+                                                &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    success = nm_setting_802_1x_set_private_key(s_8021x,
+                                                TEST_WIRED_TLS_PRIVKEY,
+                                                "test1",
+                                                scheme,
+                                                NULL,
+                                                &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    return connection;
+}
+
+static char *
+get_path(const char *file, gboolean relative)
+{
+    return relative ? g_path_get_basename(file) : g_strdup(file);
+}
+
+static void
+test_write_wired_8021x_tls_connection_path(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    char *                        tmp, *tmp2;
+    gboolean                      success;
+    gs_free char *                testfile      = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile     = NULL;
+    gboolean                        relative    = FALSE;
+    gboolean                        reread_same = FALSE;
+
+    connection = create_wired_tls_connection(NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert(connection != NULL);
+
+    write_test_connection_reread(connection, &testfile, &reread, &reread_same);
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CA_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CLIENT_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_PRIVATE_KEY);
+    assert_reread_same(connection, reread);
+    g_clear_object(&reread);
+
+    /* Read the connection back in and compare it to the one we just wrote out */
+    reread = keyfile_read_connection_from_file(testfile);
+
+    success = nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT);
+    if (!reread) {
+        g_warning("Written and re-read connection weren't the same");
+        g_assert(success);
+    }
+
+    /* Ensure the cert and key values are properly written out */
+    keyfile = keyfile_load_from_file(testfile);
+
+    /* Depending on whether this test is being run from 'make check' or
+     * 'make distcheck' we might be using relative paths (check) or
+     * absolute ones (distcheck).
+     */
+    tmp2 = g_path_get_dirname(testfile);
+    if (g_strcmp0(tmp2, TEST_KEYFILES_DIR) == 0)
+        relative = TRUE;
+    g_free(tmp2);
+
+    /* CA cert */
+    tmp  = g_key_file_get_string(keyfile,
+                                NM_SETTING_802_1X_SETTING_NAME,
+                                NM_SETTING_802_1X_CA_CERT,
+                                NULL);
+    tmp2 = get_path(TEST_WIRED_TLS_CA_CERT, relative);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+    g_free(tmp);
+
+    /* Client cert */
+    tmp  = g_key_file_get_string(keyfile,
+                                NM_SETTING_802_1X_SETTING_NAME,
+                                NM_SETTING_802_1X_CLIENT_CERT,
+                                NULL);
+    tmp2 = get_path(TEST_WIRED_TLS_CLIENT_CERT, relative);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+    g_free(tmp);
+
+    /* Private key */
+    tmp  = g_key_file_get_string(keyfile,
+                                NM_SETTING_802_1X_SETTING_NAME,
+                                NM_SETTING_802_1X_PRIVATE_KEY,
+                                NULL);
+    tmp2 = get_path(TEST_WIRED_TLS_PRIVKEY, relative);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+    g_free(tmp);
+
+    unlink(testfile);
+}
+
+static void
+test_write_wired_8021x_tls_connection_blob(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSetting8021x *              s_8021x;
+    gs_free char *                testfile = NULL;
+    char *                        new_ca_cert;
+    char *                        new_client_cert;
+    char *                        new_priv_key;
+    const char *                  uuid;
+    gboolean                      reread_same = FALSE;
+    GBytes *                      password_raw;
+
+#define PASSWORD_RAW "password-raw\0test"
+
+    connection = create_wired_tls_connection(NM_SETTING_802_1X_CK_SCHEME_BLOB);
+    g_assert(connection != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    password_raw = g_bytes_new(PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW));
+    g_object_set(s_8021x, NM_SETTING_802_1X_PASSWORD_RAW, password_raw, NULL);
+    g_bytes_unref(password_raw);
+
+    write_test_connection_reread(connection, &testfile, &reread, &reread_same);
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CA_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CLIENT_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_PRIVATE_KEY);
+    assert_reread_same(connection, reread);
+    g_clear_object(&reread);
+
+    /* Check that the new certs got written out */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    uuid = nm_setting_connection_get_uuid(s_con);
+    g_assert(uuid);
+
+    new_ca_cert = g_strdup_printf("%s/%s-ca-cert.pem", TEST_SCRATCH_DIR, uuid);
+    g_assert(new_ca_cert);
+    g_assert(g_file_test(new_ca_cert, G_FILE_TEST_EXISTS));
+
+    new_client_cert = g_strdup_printf("%s/%s-client-cert.pem", TEST_SCRATCH_DIR, uuid);
+    g_assert(new_client_cert);
+    g_assert(g_file_test(new_client_cert, G_FILE_TEST_EXISTS));
+
+    new_priv_key = g_strdup_printf("%s/%s-private-key.pem", TEST_SCRATCH_DIR, uuid);
+    g_assert(new_priv_key);
+    g_assert(g_file_test(new_priv_key, G_FILE_TEST_EXISTS));
+
+    /* Read the connection back in and compare it to the one we just wrote out */
+    reread = keyfile_read_connection_from_file(testfile);
+
+    /* Ensure the re-read connection's certificates use the path scheme */
+    s_8021x = nm_connection_get_setting_802_1x(reread);
+    g_assert(s_8021x);
+    g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert(nm_setting_802_1x_get_client_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert(nm_setting_802_1x_get_private_key_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+
+    password_raw = nm_setting_802_1x_get_password_raw(s_8021x);
+    g_assert(password_raw);
+    g_assert(nm_utils_gbytes_equal_mem(password_raw, PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW)));
+
+    unlink(testfile);
+
+    /* Clean up written certs */
+    unlink(new_ca_cert);
+    g_free(new_ca_cert);
+
+    unlink(new_client_cert);
+    g_free(new_client_cert);
+
+    unlink(new_priv_key);
+    g_free(new_priv_key);
+}
+
+static void
+test_read_dcb_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_dcb_connection");
+}
+
+static void
+test_read_infiniband_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingInfiniband *         s_ib;
+    const char *                  mac;
+    guint8      expected_mac[INFINIBAND_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                                            0x77, 0x88, 0x99, 0x01, 0x12, 0x23, 0x34,
+                                            0x45, 0x56, 0x67, 0x78, 0x89, 0x90};
+    const char *expected_id                   = "Test InfiniBand Connection";
+    const char *expected_uuid                 = "4e80a56d-c99f-4aad-a6dd-b449bc398c57";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_InfiniBand_Connection");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid);
+
+    s_ib = nm_connection_get_setting_infiniband(connection);
+    g_assert(s_ib);
+
+    mac = nm_setting_infiniband_get_mac_address(s_ib);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+}
+
+static void
+test_write_infiniband_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingInfiniband *         s_ib;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *mac = "99:88:77:66:55:44:ab:bc:cd:de:ef:f0:0a:1b:2c:3d:4e:5f:6f:ba";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work InfiniBand",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_INFINIBAND_SETTING_NAME,
+                 NULL);
+
+    /* InfiniBand setting */
+    s_ib = (NMSettingInfiniband *) nm_setting_infiniband_new();
+    g_assert(s_ib);
+    nm_connection_add_setting(connection, NM_SETTING(s_ib));
+
+    g_object_set(s_ib,
+                 NM_SETTING_INFINIBAND_MAC_ADDRESS,
+                 mac,
+                 NM_SETTING_INFINIBAND_MTU,
+                 900,
+                 NM_SETTING_INFINIBAND_TRANSPORT_MODE,
+                 "datagram",
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_assert(s_ip6);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_bridge_main(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingBridge *             s_bridge;
+    const char *                  expected_id   = "Test Bridge Main";
+    const char *                  expected_uuid = "8f061643-fe41-4d4c-a8d9-097d26e2ad3a";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Bridge_Main");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid);
+    g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "br0");
+
+    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);
+
+    s_bridge = nm_connection_get_setting_bridge(connection);
+    g_assert(s_bridge);
+    g_assert_cmpuint(nm_setting_bridge_get_forward_delay(s_bridge), ==, 2);
+    g_assert_cmpuint(nm_setting_bridge_get_stp(s_bridge), ==, TRUE);
+    g_assert_cmpuint(nm_setting_bridge_get_priority(s_bridge), ==, 32744);
+    g_assert_cmpuint(nm_setting_bridge_get_hello_time(s_bridge), ==, 7);
+    g_assert_cmpuint(nm_setting_bridge_get_max_age(s_bridge), ==, 39);
+    g_assert_cmpuint(nm_setting_bridge_get_ageing_time(s_bridge), ==, 235352);
+    g_assert_cmpuint(nm_setting_bridge_get_multicast_snooping(s_bridge), ==, FALSE);
+}
+
+static void
+test_write_bridge_main(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridge *             s_bridge;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bridge Main",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_BRIDGE_SETTING_NAME,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "br0",
+                 NULL);
+
+    /* Bridge setting */
+    s_bridge = (NMSettingBridge *) nm_setting_bridge_new();
+    g_assert(s_bridge);
+    nm_connection_add_setting(connection, NM_SETTING(s_bridge));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NULL);
+
+    add_one_ip_address(s_ip4, "1.2.3.4", 24);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_assert(s_ip6);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_bridge_component(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridgePort *         s_port;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    guint8                        expected_mac[ETH_ALEN] = {0x00, 0x22, 0x15, 0x59, 0x62, 0x97};
+    const char *                  expected_id            = "Test Bridge Component";
+    const char *                  expected_uuid          = "d7b4f96c-c45e-4298-bef8-f48574f8c1c0";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Bridge_Component");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid);
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0");
+    g_assert(nm_setting_connection_is_slave_type(s_con, NM_SETTING_BRIDGE_SETTING_NAME));
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+
+    s_port = nm_connection_get_setting_bridge_port(connection);
+    g_assert(s_port);
+    g_assert(nm_setting_bridge_port_get_hairpin_mode(s_port));
+    g_assert_cmpuint(nm_setting_bridge_port_get_priority(s_port), ==, 28);
+    g_assert_cmpuint(nm_setting_bridge_port_get_path_cost(s_port), ==, 100);
+}
+
+static void
+test_write_bridge_component(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridgePort *         s_port;
+    NMSettingWired *              s_wired;
+    const char *                  mac = "99:88:77:66:55:44";
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bridge Component",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "br0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_BRIDGE_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    g_assert(s_wired);
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, 1300, NULL);
+
+    /* BridgePort setting */
+    s_port = (NMSettingBridgePort *) nm_setting_bridge_port_new();
+    g_assert(s_port);
+    nm_connection_add_setting(connection, NM_SETTING(s_port));
+
+    g_object_set(s_port,
+                 NM_SETTING_BRIDGE_PORT_PRIORITY,
+                 3,
+                 NM_SETTING_BRIDGE_PORT_PATH_COST,
+                 99,
+                 NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_new_wired_group_name(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    guint8                        expected_mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_New_Wired_Group_Name");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1400);
+
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+}
+
+static void
+test_write_new_wired_group_name(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    nm_auto_unref_keyfile GKeyFile *kf       = NULL;
+    NMSettingConnection *           s_con;
+    NMSettingWired *                s_wired;
+    gs_free char *                  testfile = NULL;
+    gs_free_error GError *error              = NULL;
+    char *                s;
+    int                   mtu;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired New Group Name",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    g_assert(s_wired);
+    g_object_set(s_wired, NM_SETTING_WIRED_MTU, 1400, NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    write_test_connection(connection, &testfile);
+
+    assert_reread(connection, TRUE, testfile);
+
+    /* Look at the keyfile itself to ensure we wrote out the new group names and type */
+    kf = keyfile_load_from_file(testfile);
+
+    s = g_key_file_get_string(kf,
+                              NM_SETTING_CONNECTION_SETTING_NAME,
+                              NM_SETTING_CONNECTION_TYPE,
+                              &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, "ethernet");
+    g_free(s);
+
+    mtu = g_key_file_get_integer(kf, "ethernet", NM_SETTING_WIRED_MTU, &error);
+    g_assert_no_error(error);
+    g_assert_cmpint(mtu, ==, 1400);
+
+    unlink(testfile);
+}
+
+static void
+test_read_new_wireless_group_names(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "foobar";
+
+    connection =
+        keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_New_Wireless_Group_Names");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid);
+    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, NM_SETTING_WIRELESS_MODE_INFRA);
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "s3cu4e passphrase");
+}
+
+static void
+test_write_new_wireless_group_names(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    nm_auto_unref_keyfile GKeyFile *kf       = NULL;
+    NMSettingConnection *           s_con;
+    NMSettingWireless *             s_wifi;
+    NMSettingWirelessSecurity *     s_wsec;
+    GBytes *                        ssid;
+    unsigned char                   tmpssid[]    = {0x31, 0x33, 0x33, 0x37};
+    const char *                    expected_psk = "asdfasdfasdfa12315";
+    gs_free char *                  testfile     = NULL;
+    gs_free_error GError *error                  = NULL;
+    char *                s;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write New Wireless Group Names",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wi-Fi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 NM_SETTING_WIRELESS_MODE_INFRA,
+                 NULL);
+    g_bytes_unref(ssid);
+
+    /* Wi-Fi security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "wpa-psk",
+                 NM_SETTING_WIRELESS_SECURITY_PSK,
+                 expected_psk,
+                 NULL);
+
+    write_test_connection(connection, &testfile);
+
+    assert_reread(connection, TRUE, testfile);
+
+    /* Look at the keyfile itself to ensure we wrote out the new group names and type */
+    kf = keyfile_load_from_file(testfile);
+
+    s = g_key_file_get_string(kf,
+                              NM_SETTING_CONNECTION_SETTING_NAME,
+                              NM_SETTING_CONNECTION_TYPE,
+                              &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, "wifi");
+    g_free(s);
+
+    s = g_key_file_get_string(kf, "wifi", NM_SETTING_WIRELESS_MODE, &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, NM_SETTING_WIRELESS_MODE_INFRA);
+    g_free(s);
+
+    s = g_key_file_get_string(kf, "wifi-security", NM_SETTING_WIRELESS_SECURITY_PSK, &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, expected_psk);
+    g_free(s);
+
+    unlink(testfile);
+}
+
+static void
+test_read_missing_vlan_setting(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Missing_Vlan_Setting");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 0);
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
+}
+
+static void
+test_read_missing_vlan_flags(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Missing_Vlan_Flags");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 444);
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "em1");
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
+}
+
+static void
+test_read_missing_id_uuid(void)
+{
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_free char *                expected_uuid = NULL;
+    const char *                  FILENAME      = TEST_KEYFILES_DIR "/Test_Missing_ID_UUID";
+
+    expected_uuid = _nm_utils_uuid_generate_from_strings("keyfile", FILENAME, NULL);
+
+    connection = keyfile_read_connection_from_file(FILENAME);
+
+    g_assert_cmpstr(nm_connection_get_id(connection), ==, "Test_Missing_ID_UUID");
+    g_assert_cmpstr(nm_connection_get_uuid(connection), ==, expected_uuid);
+}
+
+static void
+test_read_minimal(void)
+{
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_unref_object NMConnection *con_archetype = NULL;
+    NMSettingConnection *         s_con;
+
+    con_archetype = nmtst_create_minimal_connection("Test_minimal_x",
+                                                    "a15bd68f-c32b-40b8-8d27-49e472a85919",
+                                                    NM_SETTING_WIRED_SETTING_NAME,
+                                                    &s_con);
+    nmtst_connection_normalize(con_archetype);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_1");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_2");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+}
+
+static void
+test_read_minimal_slave(void)
+{
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_unref_object NMConnection *con_archetype = NULL;
+    NMSettingConnection *         s_con;
+
+    con_archetype = nmtst_create_minimal_connection("Test_minimal_slave_x",
+                                                    "a56b4ca5-7075-43d4-82c7-5d0cb15f7654",
+                                                    NM_SETTING_WIRED_SETTING_NAME,
+                                                    &s_con);
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "br0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 "bridge",
+                 NULL);
+    nmtst_connection_normalize(con_archetype);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_1");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_2");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_3");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_4");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+}
+
+static void
+test_read_enum_property(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Enum_Property");
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpint(nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)),
+                    ==,
+                    NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR);
+}
+
+static void
+test_write_enum_property(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Enum Property",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP6 setting */
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                 NM_SETTING_IP6_CONFIG_IP6_PRIVACY,
+                 NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+                 NULL);
+
+    nmtst_connection_normalize(connection);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_flags_property(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingGsm *                s_gsm;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Flags_Property");
+
+    s_gsm = nm_connection_get_setting_gsm(connection);
+    g_assert(s_gsm);
+    g_assert_cmpint(nm_setting_gsm_get_password_flags(s_gsm),
+                    ==,
+                    NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED);
+}
+
+static void
+test_write_flags_property(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSetting *                   s_gsm;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Flags Property",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_GSM_SETTING_NAME,
+                 NULL);
+
+    /* GSM setting */
+    s_gsm = nm_setting_gsm_new();
+    nm_connection_add_setting(connection, s_gsm);
+    g_object_set(s_gsm,
+                 NM_SETTING_GSM_APN,
+                 "myapn",
+                 NM_SETTING_GSM_USERNAME,
+                 "adfasdfasdf",
+                 NM_SETTING_GSM_PASSWORD_FLAGS,
+                 NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+                 NULL);
+
+    nmtst_connection_normalize(connection);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+test_read_tc_config(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingTCConfig *           s_tc;
+    NMTCQdisc *                   qdisc1, *qdisc2;
+    NMTCAction *                  action1, *action2;
+    NMTCTfilter *                 tfilter1, *tfilter2;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_TC_Config");
+
+    s_tc = nm_connection_get_setting_tc_config(connection);
+    g_assert(s_tc);
+
+    g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 2);
+
+    qdisc1 = nm_setting_tc_config_get_qdisc(s_tc, 0);
+    g_assert(qdisc1);
+    g_assert(g_strcmp0(nm_tc_qdisc_get_kind(qdisc1), "fq_codel") == 0);
+    g_assert(nm_tc_qdisc_get_handle(qdisc1) == TC_H_MAKE(0x1234 << 16, 0x0000));
+    g_assert(nm_tc_qdisc_get_parent(qdisc1) == TC_H_ROOT);
+
+    qdisc2 = nm_setting_tc_config_get_qdisc(s_tc, 1);
+    g_assert(qdisc2);
+    g_assert(g_strcmp0(nm_tc_qdisc_get_kind(qdisc2), "ingress") == 0);
+    g_assert(nm_tc_qdisc_get_handle(qdisc2) == TC_H_MAKE(TC_H_INGRESS, 0));
+    g_assert(nm_tc_qdisc_get_parent(qdisc2) == TC_H_INGRESS);
+
+    g_assert(nm_setting_tc_config_get_num_tfilters(s_tc) == 2);
+
+    tfilter1 = nm_setting_tc_config_get_tfilter(s_tc, 0);
+    g_assert(tfilter1);
+    g_assert(g_strcmp0(nm_tc_tfilter_get_kind(tfilter1), "matchall") == 0);
+    g_assert(nm_tc_tfilter_get_handle(tfilter1) == TC_H_UNSPEC);
+    g_assert(nm_tc_tfilter_get_parent(tfilter1) == TC_H_MAKE(0x1234 << 16, 0x0000));
+
+    action1 = nm_tc_tfilter_get_action(tfilter1);
+    g_assert(action1);
+    g_assert(g_strcmp0(nm_tc_action_get_kind(action1), "drop") == 0);
+
+    tfilter2 = nm_setting_tc_config_get_tfilter(s_tc, 1);
+    g_assert(tfilter2);
+    g_assert(g_strcmp0(nm_tc_tfilter_get_kind(tfilter2), "matchall") == 0);
+    g_assert(nm_tc_tfilter_get_handle(tfilter2) == TC_H_UNSPEC);
+    g_assert(nm_tc_tfilter_get_parent(tfilter2) == TC_H_MAKE(TC_H_INGRESS, 0));
+
+    action2 = nm_tc_tfilter_get_action(tfilter2);
+    g_assert(action2);
+    g_assert(g_strcmp0(nm_tc_action_get_kind(action2), "simple") == 0);
+    g_assert(
+        g_strcmp0(g_variant_get_bytestring(nm_tc_action_get_attribute(action2, "sdata")), "Hello")
+        == 0);
+}
+
+static void
+test_write_tc_config(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSetting *                   s_tc;
+    NMTCQdisc *                   qdisc1, *qdisc2;
+    NMTCTfilter *                 tfilter1, *tfilter2;
+    NMTCAction *                  action;
+    GError *                      error = NULL;
+
+    connection =
+        nmtst_create_minimal_connection("Test TC", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+    s_tc = nm_setting_tc_config_new();
+
+    qdisc1 = nm_tc_qdisc_new("fq_codel", TC_H_ROOT, &error);
+    nmtst_assert_success(qdisc1, error);
+    nm_tc_qdisc_set_handle(qdisc1, TC_H_MAKE(0x1234 << 16, 0x0000));
+    nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc1);
+
+    qdisc2 = nm_tc_qdisc_new("ingress", TC_H_INGRESS, &error);
+    nmtst_assert_success(qdisc2, error);
+    nm_tc_qdisc_set_handle(qdisc2, TC_H_MAKE(TC_H_INGRESS, 0));
+    nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc2);
+
+    tfilter1 = nm_tc_tfilter_new("matchall", TC_H_MAKE(0x1234 << 16, 0x0000), &error);
+    nmtst_assert_success(tfilter1, error);
+    action = nm_tc_action_new("drop", &error);
+    nmtst_assert_success(action, error);
+    nm_tc_tfilter_set_action(tfilter1, action);
+    nm_tc_action_unref(action);
+    nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter1);
+    nm_tc_tfilter_unref(tfilter1);
+
+    tfilter2 = nm_tc_tfilter_new("matchall", TC_H_MAKE(TC_H_INGRESS, 0), &error);
+    nmtst_assert_success(tfilter2, error);
+    action = nm_tc_action_new("simple", &error);
+    nmtst_assert_success(action, error);
+    nm_tc_action_set_attribute(action, "sdata", g_variant_new_bytestring("Hello"));
+    nm_tc_tfilter_set_action(tfilter2, action);
+    nm_tc_action_unref(action);
+    nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter2);
+    nm_tc_tfilter_unref(tfilter2);
+
+    nm_connection_add_setting(connection, s_tc);
+
+    nmtst_connection_normalize(connection);
+    write_test_connection_and_reread(connection, FALSE);
+
+    nm_tc_qdisc_unref(qdisc1);
+    nm_tc_qdisc_unref(qdisc2);
+}
+
+/*****************************************************************************/
+
+static void
+_escape_filename(gboolean with_extension, const char *filename, gboolean would_be_ignored)
+{
+    gs_free char *esc = NULL;
+
+    g_assert(filename && filename[0]);
+
+    if (!!would_be_ignored != !!nm_keyfile_utils_ignore_filename(filename, with_extension)) {
+        if (would_be_ignored)
+            g_error("We expect filename \"%s\" to be ignored, but it isn't", filename);
+        else
+            g_error("We expect filename \"%s\" not to be ignored, but it is", filename);
+    }
+
+    esc = nm_keyfile_utils_create_filename(filename, with_extension);
+    g_assert(esc && esc[0]);
+    g_assert(!strchr(esc, '/'));
+
+    if (nm_keyfile_utils_ignore_filename(esc, with_extension))
+        g_error("Escaping filename \"%s\" yielded \"%s\", but this is ignored", filename, esc);
+}
+
+static void
+test_nm_keyfile_plugin_utils_escape_filename(void)
+{
+    _escape_filename(FALSE, "ab", FALSE);
+    _escape_filename(FALSE, ".vim-file.swp", TRUE);
+    _escape_filename(FALSE, ".vim-file.Swp", TRUE);
+    _escape_filename(FALSE, ".vim-file.SWP", TRUE);
+    _escape_filename(FALSE, ".vim-file.swpx", TRUE);
+    _escape_filename(FALSE, ".vim-file.Swpx", TRUE);
+    _escape_filename(FALSE, ".vim-file.SWPX", TRUE);
+    _escape_filename(FALSE, ".pem-file.pem", TRUE);
+    _escape_filename(FALSE, ".pem-file.Pem", TRUE);
+    _escape_filename(FALSE, ".pem-file.PEM", TRUE);
+    _escape_filename(FALSE, ".pem-file.der", TRUE);
+    _escape_filename(FALSE, ".pem-file.Der", TRUE);
+    _escape_filename(FALSE, ".mkstemp.ABCEDF", TRUE);
+    _escape_filename(FALSE, ".mkstemp.abcdef", TRUE);
+    _escape_filename(FALSE, ".mkstemp.123456", TRUE);
+    _escape_filename(FALSE, ".mkstemp.A23456", TRUE);
+    _escape_filename(FALSE, ".#emacs-locking", TRUE);
+    _escape_filename(FALSE, "file-with-tilde~", TRUE);
+    _escape_filename(FALSE, ".file-with-dot", TRUE);
+    _escape_filename(FALSE, "/some/path/with/trailing/slash/", TRUE);
+    _escape_filename(FALSE, "/some/path/without/trailing/slash", FALSE);
+
+    _escape_filename(TRUE, "lala", TRUE);
+}
+
+/*****************************************************************************/
+
+static void
+_assert_keyfile_nmmeta(const char *dirname,
+                       const char *uuid,
+                       const char *loaded_path,
+                       gboolean    allow_relative,
+                       const char *exp_full_filename,
+                       const char *exp_uuid,
+                       const char *exp_symlink_target,
+                       const char *exp_loaded_path)
+{
+    gs_free char *full_filename  = NULL;
+    gs_free char *symlink_target = NULL;
+    gs_free char *uuid2          = NULL;
+    gs_free char *loaded_path2   = NULL;
+    gs_free char *dirname3       = NULL;
+    gs_free char *filename3      = NULL;
+    gs_free char *uuid3          = NULL;
+    gs_free char *loaded_path3   = NULL;
+    gboolean      success;
+    gs_free char *filename = NULL;
+
+    g_assert(dirname && dirname[0] == '/');
+    g_assert(exp_full_filename && exp_full_filename[0]);
+    g_assert(!exp_loaded_path || exp_loaded_path[0] == '/');
+
+    filename = g_path_get_basename(exp_full_filename);
+
+    full_filename = nms_keyfile_nmmeta_filename(dirname, uuid, FALSE);
+    g_assert_cmpstr(full_filename, ==, full_filename);
+    nm_clear_g_free(&full_filename);
+
+    g_assert_cmpint(
+        nms_keyfile_nmmeta_write(dirname, uuid, loaded_path, allow_relative, NULL, &full_filename),
+        ==,
+        0);
+    g_assert_cmpstr(full_filename, ==, exp_full_filename);
+    nm_clear_g_free(&full_filename);
+
+    if (exp_symlink_target)
+        g_assert(g_file_test(exp_full_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_SYMLINK));
+    else
+        g_assert(!g_file_test(exp_full_filename, G_FILE_TEST_EXISTS));
+    symlink_target = g_file_read_link(exp_full_filename, NULL);
+    g_assert_cmpstr(symlink_target, ==, exp_symlink_target);
+
+    success = nms_keyfile_nmmeta_read(dirname,
+                                      filename,
+                                      &full_filename,
+                                      &uuid2,
+                                      &loaded_path2,
+                                      NULL,
+                                      NULL);
+    g_assert_cmpint(!!exp_uuid, ==, success);
+    if (success)
+        g_assert_cmpstr(full_filename, ==, exp_full_filename);
+    else
+        g_assert_cmpstr(full_filename, ==, NULL);
+    nm_clear_g_free(&full_filename);
+    g_assert_cmpstr(uuid2, ==, exp_uuid);
+    g_assert_cmpstr(loaded_path2, ==, exp_loaded_path);
+
+    success = nms_keyfile_nmmeta_read_from_file(exp_full_filename,
+                                                &dirname3,
+                                                &filename3,
+                                                &uuid3,
+                                                &loaded_path3,
+                                                NULL);
+    g_assert_cmpint(!!exp_uuid, ==, success);
+    if (success) {
+        g_assert_cmpstr(dirname3, ==, dirname);
+        g_assert_cmpstr(filename3, ==, filename);
+    } else {
+        g_assert_cmpstr(dirname3, ==, NULL);
+        g_assert_cmpstr(filename3, ==, NULL);
+    }
+    g_assert_cmpstr(uuid3, ==, exp_uuid);
+    g_assert_cmpstr(loaded_path3, ==, exp_loaded_path);
+}
+
+static void
+test_nmmeta(void)
+{
+    const char *  uuid          = "3c03fd17-ddc3-4100-a954-88b6fafff959";
+    gs_free char *filename      = g_strdup_printf("%s%s", uuid, NM_KEYFILE_PATH_SUFFIX_NMMETA);
+    gs_free char *full_filename = g_strdup_printf("%s/%s", TEST_SCRATCH_DIR, filename);
+    const char *  loaded_path0  = NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL;
+    const char *  loaded_path1  = "/some/where/but/not/scratch/dir";
+    const char *  filename2     = "foo1";
+    gs_free char *loaded_path2  = g_strdup_printf("%s/%s", TEST_SCRATCH_DIR, filename2);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, uuid, NULL, FALSE, full_filename, NULL, NULL, NULL);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, uuid, NULL, TRUE, full_filename, NULL, NULL, NULL);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path0,
+                           FALSE,
+                           full_filename,
+                           uuid,
+                           loaded_path0,
+                           loaded_path0);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path0,
+                           TRUE,
+                           full_filename,
+                           uuid,
+                           loaded_path0,
+                           loaded_path0);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path1,
+                           FALSE,
+                           full_filename,
+                           uuid,
+                           loaded_path1,
+                           loaded_path1);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path1,
+                           TRUE,
+                           full_filename,
+                           uuid,
+                           loaded_path1,
+                           loaded_path1);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path2,
+                           FALSE,
+                           full_filename,
+                           uuid,
+                           loaded_path2,
+                           loaded_path2);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path2,
+                           TRUE,
+                           full_filename,
+                           uuid,
+                           filename2,
+                           loaded_path2);
+
+    (void) unlink(full_filename);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE();
+
+int
+main(int argc, char **argv)
+{
+    int errsv;
+
+    _nm_utils_set_testing(NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK);
+
+    nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT");
+
+    if (g_mkdir_with_parents(TEST_SCRATCH_DIR, 0755) != 0) {
+        errsv = errno;
+        g_error("failure to create test directory \"%s\": %s",
+                TEST_SCRATCH_DIR,
+                nm_strerror_native(errsv));
+    }
+
+    /* The tests */
+    g_test_add_func("/keyfile/test_read_valid_wired_connection", test_read_valid_wired_connection);
+    g_test_add_func("/keyfile/test_write_wired_connection", test_write_wired_connection);
+
+    g_test_add_func("/keyfile/test_read_ip6_wired_connection", test_read_ip6_wired_connection);
+    g_test_add_func("/keyfile/test_write_ip6_wired_connection", test_write_ip6_wired_connection);
+
+    g_test_add_func("/keyfile/test_read_wired_mac_case", test_read_wired_mac_case);
+    g_test_add_func("/keyfile/test_read_mac_old_format", test_read_mac_old_format);
+    g_test_add_func("/keyfile/test_read_mac_ib_old_format", test_read_mac_ib_old_format);
+
+    g_test_add_func("/keyfile/test_read_valid_wireless_connection",
+                    test_read_valid_wireless_connection);
+    g_test_add_func("/keyfile/test_write_wireless_connection", test_write_wireless_connection);
+
+    g_test_add_func("/keyfile/test_read_string_ssid", test_read_string_ssid);
+    g_test_add_func("/keyfile/test_write_string_ssid", test_write_string_ssid);
+
+    g_test_add_func("/keyfile/test_read_intlist_ssid", test_read_intlist_ssid);
+    g_test_add_func("/keyfile/test_write_intlist_ssid", test_write_intlist_ssid);
+
+    g_test_add_func("/keyfile/test_read_intlike_ssid", test_read_intlike_ssid);
+    g_test_add_func("/keyfile/test_write_intlike_ssid", test_write_intlike_ssid);
+
+    g_test_add_func("/keyfile/test_read_intlike_ssid_2", test_read_intlike_ssid_2);
+    g_test_add_func("/keyfile/test_write_intlike_ssid_2", test_write_intlike_ssid_2);
+
+    g_test_add_func("/keyfile/test_read_bt_dun_connection", test_read_bt_dun_connection);
+    g_test_add_func("/keyfile/test_write_bt_dun_connection", test_write_bt_dun_connection);
+
+    g_test_add_func("/keyfile/test_read_gsm_connection", test_read_gsm_connection);
+    g_test_add_func("/keyfile/test_write_gsm_connection", test_write_gsm_connection);
+
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_blob_connection",
+                    test_read_wired_8021x_tls_blob_connection);
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_bad_path_connection",
+                    test_read_wired_8021x_tls_bad_path_connection);
+
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_old_connection",
+                    test_read_wired_8021x_tls_old_connection);
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_new_connection",
+                    test_read_wired_8021x_tls_new_connection);
+    g_test_add_func("/keyfile/test_write_wired_8021x_tls_connection_path",
+                    test_write_wired_8021x_tls_connection_path);
+    g_test_add_func("/keyfile/test_write_wired_8021x_tls_connection_blob",
+                    test_write_wired_8021x_tls_connection_blob);
+
+    g_test_add_func("/keyfile/test_read_dcb_connection", test_read_dcb_connection);
+
+    g_test_add_func("/keyfile/test_read_infiniband_connection", test_read_infiniband_connection);
+    g_test_add_func("/keyfile/test_write_infiniband_connection", test_write_infiniband_connection);
+
+    g_test_add_func("/keyfile/test_read_bridge_main", test_read_bridge_main);
+    g_test_add_func("/keyfile/test_write_bridge_main", test_write_bridge_main);
+    g_test_add_func("/keyfile/test_read_bridge_component", test_read_bridge_component);
+    g_test_add_func("/keyfile/test_write_bridge_component", test_write_bridge_component);
+
+    g_test_add_func("/keyfile/test_read_new_wired_group_name", test_read_new_wired_group_name);
+    g_test_add_func("/keyfile/test_write_new_wired_group_name", test_write_new_wired_group_name);
+    g_test_add_func("/keyfile/test_read_new_wireless_group_names",
+                    test_read_new_wireless_group_names);
+    g_test_add_func("/keyfile/test_write_new_wireless_group_names",
+                    test_write_new_wireless_group_names);
+
+    g_test_add_func("/keyfile/test_read_missing_vlan_setting", test_read_missing_vlan_setting);
+    g_test_add_func("/keyfile/test_read_missing_vlan_flags", test_read_missing_vlan_flags);
+    g_test_add_func("/keyfile/test_read_missing_id_uuid", test_read_missing_id_uuid);
+
+    g_test_add_func("/keyfile/test_read_minimal", test_read_minimal);
+    g_test_add_func("/keyfile/test_read_minimal_slave", test_read_minimal_slave);
+
+    g_test_add_func("/keyfile/test_read_enum_property", test_read_enum_property);
+    g_test_add_func("/keyfile/test_write_enum_property", test_write_enum_property);
+    g_test_add_func("/keyfile/test_read_flags_property", test_read_flags_property);
+    g_test_add_func("/keyfile/test_write_flags_property", test_write_flags_property);
+
+    g_test_add_func("/keyfile/test_read_tc_config", test_read_tc_config);
+    g_test_add_func("/keyfile/test_write_tc_config", test_write_tc_config);
+
+    g_test_add_func("/keyfile/test_nm_keyfile_plugin_utils_escape_filename",
+                    test_nm_keyfile_plugin_utils_escape_filename);
+
+    g_test_add_func("/keyfile/test_nmmeta", test_nmmeta);
+
+    return g_test_run();
+}
diff --git a/src/core/settings/plugins/meson.build b/src/core/settings/plugins/meson.build
new file mode 100644
index 00000000..d4338a30
--- /dev/null
+++ b/src/core/settings/plugins/meson.build
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+if enable_ifcfg_rh
+  subdir('ifcfg-rh')
+endif
+
+if enable_ifupdown
+  subdir('ifupdown')
+endif
+
+if enable_tests
+  subdir('keyfile/tests')
+endif