summary refs log tree commit diff
path: root/src/settings
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-06-28 18:58:14 +0200
committerMichael Biebl <biebl@debian.org>2020-06-28 18:58:14 +0200
commita54ac63bbf9b2c71026ac9028a8ffaf186cf3c82 (patch)
tree6a32883bd916c4096357b35298beff6db8bcd0b5 /src/settings
parent45e8e1149027529194982212c804c0468aa01d98 (diff)
New upstream version 1.25.90 upstream/1.25.90
Diffstat (limited to 'src/settings')
-rw-r--r--src/settings/nm-settings-connection.c127
-rw-r--r--src/settings/nm-settings-connection.h10
-rw-r--r--src/settings/nm-settings.c44
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf13
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c301
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c98
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h15
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c307
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c50
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c44
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.h2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c49
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.h1
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.c8
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.h2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c58
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.h1
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile-settings.c1
19 files changed, 808 insertions, 325 deletions
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 48e3dce2..ca46de68 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -1409,6 +1409,54 @@ update_complete (NMSettingsConnection *self,
 	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,
@@ -1444,6 +1492,11 @@ update_auth_cb (NMSettingsConnection *self,
 			 * 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);
 		}
 	}
 
@@ -1482,7 +1535,8 @@ update_auth_cb (NMSettingsConnection *self,
 	                                ? 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_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
@@ -1975,6 +2029,7 @@ NM_UTILS_FLAGS2STR_DEFINE (_settings_connection_flags_to_string, NMSettingsConne
 	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
@@ -2136,15 +2191,24 @@ void
 nm_settings_connection_update_timestamp (NMSettingsConnection *self,
                                          guint64 timestamp)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	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;
 
@@ -2185,10 +2249,10 @@ _nm_settings_connection_register_kf_dbs (NMSettingsConnection *self,
 		if (timestamp != G_MAXUINT64) {
 			priv->timestamp = timestamp;
 			priv->timestamp_set = TRUE;
-			_LOGT ("read timestamp %"G_GUINT64_FORMAT" from keyfile database \"%s\"",
+			_LOGT ("timestamp: read timestamp %"G_GUINT64_FORMAT" from keyfile database \"%s\"",
 			       timestamp, nm_key_file_db_get_filename (priv->kf_db_timestamps));
 		} else
-			_LOGT ("no timestamp from keyfile database \"%s\"",
+			_LOGT ("timestamp: no timestamp from keyfile database \"%s\"",
 			       nm_key_file_db_get_filename (priv->kf_db_timestamps));
 	}
 
@@ -2319,54 +2383,6 @@ nm_settings_connection_add_seen_bssid (NMSettingsConnection *self,
 
 /*****************************************************************************/
 
-static int
-_autoconnect_retries_initial (NMSettingsConnection *self)
-{
-	NMSettingConnection *s_con;
-	int retries = -1;
-
-	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
-	if (s_con)
-		retries = nm_setting_connection_get_autoconnect_retries (s_con);
-
-	/* -1 means 'default' */
-	if (retries == -1)
-		retries = nm_config_data_get_autoconnect_retries_default (NM_CONFIG_GET_DATA);
-
-	/* 0 means 'forever', which is translated to a retry count of -1 */
-	if (retries == 0)
-		retries = AUTOCONNECT_RETRIES_FOREVER;
-
-	nm_assert (retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-	return retries;
-}
-
-static void
-_autoconnect_retries_set (NMSettingsConnection *self,
-                          int retries,
-                          gboolean is_reset)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-
-	g_return_if_fail (retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-
-	if (priv->autoconnect_retries != retries) {
-		_LOGT ("autoconnect: retries set %d%s", retries,
-		       is_reset ? " (reset)" : "");
-		priv->autoconnect_retries = retries;
-	}
-
-	if (retries)
-		priv->autoconnect_retries_blocked_until = 0;
-	else {
-		/* NOTE: the blocked time must be identical for all connections, otherwise
-		 * the tracking of resetting the retry count in NMPolicy needs adjustment
-		 * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
-		 * the next-timeout every time a connection gets blocked). */
-		priv->autoconnect_retries_blocked_until = nm_utils_get_monotonic_timestamp_sec () + AUTOCONNECT_RESET_RETRIES_TIMER;
-	}
-}
-
 /**
  * nm_settings_connection_autoconnect_retries_get:
  * @self: the settings connection
@@ -2423,9 +2439,9 @@ NM_UTILS_FLAGS2STR_DEFINE (_autoconnect_blocked_reason_to_string, NMSettingsAuto
 );
 
 NMSettingsAutoconnectBlockedReason
-nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self, NMSettingsAutoconnectBlockedReason mask)
+nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self)
 {
-	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_blocked_reason & (mask ?: NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL);
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_blocked_reason;
 }
 
 gboolean
@@ -2467,7 +2483,8 @@ nm_settings_connection_autoconnect_is_blocked (NMSettingsConnection *self)
 		return TRUE;
 
 	flags = priv->flags;
-	if (NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+	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;
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index dfc3786c..17573bbe 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -147,6 +147,8 @@ typedef enum {
  *  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.
@@ -161,8 +163,9 @@ typedef enum _NMSettingsConnectionIntFlags {
 	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                = 0x08,
+	NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE                = 0x10,
 
 	_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST,
 
@@ -170,11 +173,13 @@ typedef enum _NMSettingsConnectionIntFlags {
 	                                                          | 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,
@@ -337,8 +342,7 @@ void nm_settings_connection_autoconnect_retries_reset (NMSettingsConnection *sel
 
 gint32 nm_settings_connection_autoconnect_retries_blocked_until (NMSettingsConnection *self);
 
-NMSettingsAutoconnectBlockedReason nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self,
-                                                                                          NMSettingsAutoconnectBlockedReason mask);
+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);
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 874efbfc..5899b079 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -1091,12 +1091,14 @@ _connection_changed_update (NMSettings *self,
 	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
-		 * and never volatile. */
+		/* 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_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_VOLATILE
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
 	}
 
 	nm_settings_connection_set_flags_full (sett_conn,
@@ -1200,7 +1202,8 @@ _connection_changed_delete (NMSettings *self,
 
 	nm_settings_connection_set_flags (sett_conn,
 	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE
-	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
+	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
 	                                  FALSE);
 
 	_emit_connection_removed (self, sett_conn);
@@ -1491,6 +1494,7 @@ _add_connection_to_first_plugin (NMSettings *self,
 			                                             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,
@@ -1501,6 +1505,7 @@ _add_connection_to_first_plugin (NMSettings *self,
 				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,
@@ -1578,6 +1583,7 @@ _update_connection_to_plugin (NMSettings *self,
 		                                                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,
@@ -1693,8 +1699,9 @@ nm_settings_add_connection (NMSettings *self,
 
 	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_VOLATILE
-	                              | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+	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;
 	}
@@ -1992,7 +1999,8 @@ nm_settings_update_connection (NMSettings *self,
 			nm_assert (cur_in_memory);
 			nm_assert (!NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
 			                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL));
 
 			default_wired_clear_tag (self, device, sett_conn, FALSE);
 
@@ -2011,11 +2019,13 @@ nm_settings_update_connection (NMSettings *self,
 
 	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_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)) {
-		/* we update the nm-generated/volatile setting of a profile (which is inherrently
+	                     | 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);
@@ -2043,11 +2053,14 @@ nm_settings_update_connection (NMSettings *self,
 		 * 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_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_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_VOLATILE
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
 	}
 
 	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) {
@@ -3374,7 +3387,8 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 			continue;
 
 		if (NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
-		                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+		                    NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		                  | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 			continue;
 
 		return TRUE;
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf b/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
index cc6ccb5c..c7a1730a 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
+++ b/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
@@ -2,11 +2,10 @@
  "-//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>
+    <policy context="default">
+        <allow send_destination="com.redhat.ifcfgrh1"/>
+    </policy>
+    <policy user="root">
+        <allow own="com.redhat.ifcfgrh1"/>
+    </policy>
 </busconfig>
-
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 338c22ed..d65ae555 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -582,6 +582,11 @@ make_connection_setting (const char *file,
 			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 (),
@@ -1532,29 +1537,45 @@ make_user_setting (shvarFile *ifcfg)
 	       : NULL;
 }
 
-static NMSetting *
-make_match_setting (shvarFile *ifcfg)
+
+static void
+make_match_setting_prop (const char *v,
+                         NMSettingMatch **s_match,
+                         void (*add_fcn) (NMSettingMatch *s_match, const char *value))
 {
-	NMSettingMatch *s_match = NULL;
 	gs_free const char **strv = NULL;
-	gs_free char *value = NULL;
-	const char *v;
 	gsize i;
 
-	v = svGetValueStr (ifcfg, "MATCH_INTERFACE_NAME", &value);
-	if (!v)
-		return NULL;
-
 	strv = nm_utils_escaped_tokens_split (v, NM_ASCII_SPACES);
 	if (strv) {
 		for (i = 0; strv[i]; i++) {
-			if (!s_match)
-				s_match = (NMSettingMatch *) nm_setting_match_new ();
-			nm_setting_match_add_interface_name (s_match, strv[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;
 
-	return (NMSetting *) s_match;
+	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 *
@@ -4244,6 +4265,130 @@ wireless_connection_from_ifcfg (const char *file,
 	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,
@@ -4253,59 +4398,58 @@ parse_ethtool_option (const char *value,
                       const char **out_duplex,
                       NMSettingEthtool **out_s_ethtool)
 {
-	gs_free const char **words = NULL;
 	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] && words[0][0] == '-') {
-		/* /sbin/ethtool $opts */
-		if (NM_IN_STRSET (words[0], "-K", "--features", "--offload")) {
-			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 ());
+	if (words[0])
+		ethtool_type = _get_ethtool_type_by_name (words[0]);
 
-			for (i = 2; words[i]; ) {
-				const char *opt = words[i];
-				const char *opt_val = words[++i];
-				const NMEthtoolData *d = NULL;
-				NMTernary onoff = NM_TERNARY_DEFAULT;
+	if (ethtool_type != NM_ETHTOOL_TYPE_UNKNOWN) {
+		if (!words[1]) {
+			/* first argument must be the interface name. This is invalid. */
+			return;
+		}
 
-				if (nm_streq0 (opt_val, "on"))
-					onoff = NM_TERNARY_TRUE;
-				else if (nm_streq0 (opt_val, "off"))
-					onoff = NM_TERNARY_FALSE;
+		if (!*out_s_ethtool)
+			*out_s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ());
 
-				d = nms_ifcfg_rh_utils_get_ethtool_by_name (opt);
+		/* skip ethtool type && interface name */
+		w_iter = &words[2];
 
-				if (!d) {
-					if (onoff != NM_TERNARY_DEFAULT) {
-						/* the next value is just the on/off argument. Skip it too. */
-						i++;
-					}
+		while (w_iter && *w_iter) {
+			if (ethtool_type == NM_ETHTOOL_TYPE_FEATURE) {
+				w_iter = _next_ethtool_options_nmternary (w_iter,
+				                                          ethtool_type,
+				                                          &ifcfg_option);
 
-					/* silently ignore unsupported offloading features. */
-					continue;
+				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);
 				}
-
-				i++;
-
-				if (onoff == NM_TERNARY_DEFAULT) {
-					PARSE_WARNING ("Expects on/off argument for feature '%s'", opt);
-					continue;
+			}
+			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);
 				}
-
-				nm_setting_ethtool_set_feature (*out_s_ethtool,
-				                                d->optname,
-				                                onoff);
 			}
 		}
+
 		return;
 	}
 
@@ -5166,24 +5310,33 @@ handle_bridge_option (NMSetting *setting,
 		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_querier",          NM_SETTING_BRIDGE_MULTICAST_QUERIER,          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 },
-		{ "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, },
+		{ "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;
@@ -5236,6 +5389,16 @@ handle_bridge_option (NMSetting *setting,
 				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;
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 15a772bc..d1500dbd 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -522,9 +522,30 @@ nms_ifcfg_rh_utils_user_key_decode (const char *name, GString *str_buffer)
 /*****************************************************************************/
 
 const char *const _nm_ethtool_ifcfg_names[] = {
-#define ETHT_NAME(eid, ename) \
-[eid - _NM_ETHTOOL_ID_FEATURE_FIRST] = ""ename""
-	/* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
+#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"),
@@ -577,11 +598,45 @@ const char *const _nm_ethtool_ifcfg_names[] = {
 	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_by_name,
+	_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; },
@@ -660,12 +715,39 @@ NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
 	{ "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)
+nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name,
+                                        NMEthtoolType ethtool_type)
 {
 	NMEthtoolID id;
 
-	id = _get_ethtoolid_by_name (name);
+	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;
 
@@ -892,11 +974,15 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
 	_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 ),
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 6c31b269..459c5ae9 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
 	NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[236];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[240];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info (const char *key, gssize *out_idx);
 
@@ -141,18 +141,19 @@ const NMSIfcfgKeyTypeInfo *nms_ifcfg_rh_utils_is_well_known_key (const char *key
 
 /*****************************************************************************/
 
-extern const char *const _nm_ethtool_ifcfg_names[_NM_ETHTOOL_ID_FEATURE_NUM];
+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_FEATURE_FIRST && ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
-	nm_assert ((ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST) < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
-	nm_assert (_nm_ethtool_ifcfg_names[ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]);
+	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 - _NM_ETHTOOL_ID_FEATURE_FIRST];
+	return _nm_ethtool_ifcfg_names[ethtool_id];
 }
 
-const NMEthtoolData *nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name);
+const NMEthtoolData *nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name,
+                                                             NMEthtoolType ethtool_type);
 
 #endif  /* _UTILS_H_ */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index e126c77f..199e8e4e 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1091,6 +1091,27 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	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)
 {
@@ -1172,7 +1193,10 @@ write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **erro
 	if (s_ethtool) {
 		NMEthtoolID ethtool_id;
 		NMSettingConnection *s_con;
-		const char *iface = NULL;
+		const char *iface;
+		gboolean is_first;
+		guint32 u32;
+		gboolean b;
 
 		s_con = nm_connection_get_setting_connection (connection);
 		if (s_con) {
@@ -1184,28 +1208,45 @@ write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **erro
 			                                       || (ch >= '0' && ch <= '9')
 			                                       || NM_IN_SET (ch, '_'))))
 				iface = NULL;
-		}
-
-		if (!str)
-			str = g_string_sized_new (30);
-		else
-			g_string_append (str, " ; ");
-		g_string_append (str, "-K ");
-		g_string_append (str, iface ?: "net0");
+		} 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++) {
-			const NMEthtoolData *ed = nm_ethtool_data[ethtool_id];
-			NMTernary val;
+			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);
+		}
 
-			val = nm_setting_ethtool_get_feature (s_ethtool, ed->optname);
-			if (val == NM_TERNARY_DEFAULT)
+		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 (str, val == NM_TERNARY_TRUE ? " on" : " off");
+			g_string_append_printf (str, " %"G_GUINT32_FORMAT, u32);
 		}
 	}
 
@@ -1374,38 +1415,22 @@ write_team_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
 	return TRUE;
 }
 
-static guint32
-get_setting_default_uint (NMSetting *setting, const char *prop)
+static gboolean
+get_setting_default_boolean (gpointer setting, const char *prop)
 {
-	GParamSpec *pspec;
-	GValue val = G_VALUE_INIT;
-	guint32 ret = 0;
-
-	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop);
-	g_assert (pspec);
-	g_value_init (&val, pspec->value_type);
-	g_param_value_set_default (pspec, &val);
-	g_assert (G_VALUE_HOLDS_UINT (&val));
-	ret = g_value_get_uint (&val);
-	g_value_unset (&val);
-	return ret;
+	return NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN (g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop));
 }
 
-static gboolean
-get_setting_default_boolean (NMSetting *setting, const char *prop)
+static guint
+get_setting_default_uint (gpointer setting, const char *prop)
 {
-	GParamSpec *pspec;
-	GValue val = G_VALUE_INIT;
-	gboolean ret = 0;
-
-	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop);
-	g_assert (pspec);
-	g_value_init (&val, pspec->value_type);
-	g_param_value_set_default (pspec, &val);
-	g_assert (G_VALUE_HOLDS_BOOLEAN (&val));
-	ret = g_value_get_boolean (&val);
-	g_value_unset (&val);
-	return ret;
+	return NM_G_PARAM_SPEC_GET_DEFAULT_UINT (g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop));
+}
+
+static guint64
+get_setting_default_uint64 (gpointer setting, const char *prop)
+{
+	return NM_G_PARAM_SPEC_GET_DEFAULT_UINT64 (g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop));
 }
 
 static gboolean
@@ -1448,7 +1473,8 @@ static gboolean
 write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingBridge *s_bridge;
-	guint32 i;
+	guint32 u32;
+	guint64 u64;
 	gboolean b;
 	const char *s;
 	GString *opts;
@@ -1471,32 +1497,32 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 	if (nm_setting_bridge_get_stp (s_bridge)) {
 		svSetValueStr (ifcfg, "STP", "yes");
 
-		i = nm_setting_bridge_get_forward_delay (s_bridge);
-		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_FORWARD_DELAY))
-			svSetValueInt64 (ifcfg, "DELAY", i);
+		u32 = nm_setting_bridge_get_forward_delay (s_bridge);
+		if (u32 != get_setting_default_uint (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));
 
-		i = nm_setting_bridge_get_hello_time (s_bridge);
-		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_HELLO_TIME)) {
+		u32 = nm_setting_bridge_get_hello_time (s_bridge);
+		if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_HELLO_TIME)) {
 			if (opts->len)
 				g_string_append_c (opts, ' ');
-			g_string_append_printf (opts, "hello_time=%u", i);
+			g_string_append_printf (opts, "hello_time=%u", u32);
 		}
 
-		i = nm_setting_bridge_get_max_age (s_bridge);
-		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MAX_AGE)) {
+		u32 = nm_setting_bridge_get_max_age (s_bridge);
+		if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_MAX_AGE)) {
 			if (opts->len)
 				g_string_append_c (opts, ' ');
-			g_string_append_printf (opts, "max_age=%u", i);
+			g_string_append_printf (opts, "max_age=%u", u32);
 		}
 	}
 
-	i = nm_setting_bridge_get_ageing_time (s_bridge);
-	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_AGEING_TIME)) {
+	u32 = nm_setting_bridge_get_ageing_time (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_AGEING_TIME)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "ageing_time=%u", i);
+		g_string_append_printf (opts, "ageing_time=%u", u32);
 	}
 
 	s = nm_setting_bridge_get_group_address (s_bridge);
@@ -1506,34 +1532,98 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "group_address=%s", s);
 	}
 
-	i = nm_setting_bridge_get_group_forward_mask (s_bridge);
-	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) {
+	u32 = nm_setting_bridge_get_group_forward_mask (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "group_fwd_mask=%u", i);
+		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_uint (s_bridge, NM_SETTING_BRIDGE_MULTICAST_HASH_MAX)) {
+		if (opts->len)
+			g_string_append_c (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_uint (s_bridge, NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT)) {
+		if (opts->len)
+			g_string_append_c (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_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (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_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (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_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_QUERIER)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERIER)) {
 		if (opts->len)
 			g_string_append_c (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_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (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_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (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_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (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_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR)) {
 		if (opts->len)
 			g_string_append_c (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_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) {
 		if (opts->len)
 			g_string_append_c (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_uint (s_bridge, NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT)) {
+		if (opts->len)
+			g_string_append_c (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_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (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) {
 		if (opts->len)
@@ -1542,17 +1632,17 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 	}
 
 	b = nm_setting_bridge_get_vlan_filtering (s_bridge);
-	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_FILTERING)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_VLAN_FILTERING)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
 		g_string_append_printf (opts, "vlan_filtering=%u", (guint32) b);
 	}
 
-	i = nm_setting_bridge_get_vlan_default_pvid (s_bridge);
-	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID)) {
+	u32 = nm_setting_bridge_get_vlan_default_pvid (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "default_pvid=%u", i);
+		g_string_append_printf (opts, "default_pvid=%u", u32);
 	}
 
 	s = nm_setting_bridge_get_vlan_protocol (s_bridge);
@@ -1563,7 +1653,7 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 	}
 
 	b = nm_setting_bridge_get_vlan_stats_enabled (s_bridge);
-	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_STATS_ENABLED)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_VLAN_STATS_ENABLED)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
 		g_string_append_printf (opts, "vlan_stats_enabled=%u", (guint) b);
@@ -1591,7 +1681,7 @@ static gboolean
 write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
 	NMSettingBridgePort *s_port;
-	guint32 i;
+	guint32 u32;
 	GString *string;
 
 	s_port = nm_connection_get_setting_bridge_port (connection);
@@ -1601,15 +1691,15 @@ write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **
 	/* Bridge options */
 	string = g_string_sized_new (32);
 
-	i = nm_setting_bridge_port_get_priority (s_port);
-	if (i != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PRIORITY))
-		g_string_append_printf (string, "priority=%u", i);
+	u32 = nm_setting_bridge_port_get_priority (s_port);
+	if (u32 != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PRIORITY))
+		g_string_append_printf (string, "priority=%u", u32);
 
-	i = nm_setting_bridge_port_get_path_cost (s_port);
-	if (i != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PATH_COST)) {
+	u32 = nm_setting_bridge_port_get_path_cost (s_port);
+	if (u32 != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PATH_COST)) {
 		if (string->len)
 			g_string_append_c (string, ' ');
-		g_string_append_printf (string, "path_cost=%u", i);
+		g_string_append_printf (string, "path_cost=%u", u32);
 	}
 
 	if (nm_setting_bridge_port_get_hairpin_mode (s_port)) {
@@ -1804,7 +1894,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	NMSettingConnectionMdns mdns;
 	NMSettingConnectionLlmnr llmnr;
 	guint32 vuint32;
-	const char *tmp;
+	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));
@@ -1879,6 +1969,9 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	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
@@ -2301,36 +2394,61 @@ write_tc_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	return TRUE;
 }
 
-static gboolean
-write_match_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+static void
+write_match_setting (NMConnection *connection, shvarFile *ifcfg)
 {
-	NMSettingMatch *s_match;
 	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 TRUE;
-
-	num = nm_setting_match_get_num_interface_names (s_match);
-	for (i = 0; i < num; i++) {
-		const char *name;
-
-		name = nm_setting_match_get_interface_name (s_match, i);
-		if (!name || !name[0])
-			continue;
+		return;
 
-		if (!str)
-			str = g_string_new ("");
-		else
-			g_string_append_c (str, ' ');
-		nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
+	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);
 	}
 
-	if (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);
+	}
 
-	return TRUE;
+	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
@@ -3090,8 +3208,7 @@ do_write_construct (NMConnection *connection,
 	if (!write_user_setting (connection, ifcfg, error))
 		return FALSE;
 
-	if (!write_match_setting (connection, ifcfg, error))
-		return FALSE;
+	write_match_setting (connection, ifcfg);
 
 	write_sriov_setting (connection, ifcfg);
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
index f0cd36cb..9499278e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
@@ -1,7 +1,9 @@
 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
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index ffedff01..a5025f3b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -4690,6 +4690,9 @@ test_write_wired_match (void)
 	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);
@@ -10364,37 +10367,48 @@ test_ethtool_names (void)
 		{ 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;
-	int i;
+	guint i, k;
 
-	for (id = _NM_ETHTOOL_ID_FEATURE_FIRST; id <= _NM_ETHTOOL_ID_FEATURE_LAST; id++) {
-		const char *ifcfg_rh_name;
-		int idx;
+	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;
 
-		idx = id - _NM_ETHTOOL_ID_FEATURE_FIRST;
-		g_assert (idx >= 0);
-		g_assert (idx < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
-		ifcfg_rh_name = _nm_ethtool_ifcfg_names[idx];
-		g_assert (ifcfg_rh_name && ifcfg_rh_name[0]);
+			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 = 0; i < G_N_ELEMENTS (_nm_ethtool_ifcfg_names); i++) {
-			if (i != idx)
-				g_assert_cmpstr (ifcfg_rh_name, !=, _nm_ethtool_ifcfg_names[i]);
-		}
+			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);
+			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);
-		g_assert (data);
-		g_assert (data->id == id);
+			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);
+
+		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);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 2906b399..c84bd78d 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -89,22 +89,33 @@ G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
 /*****************************************************************************/
 
 static const char *
-_extra_flags_to_string (char *str, gsize str_len, gboolean is_nm_generated, gboolean is_volatile)
+_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_volatile
+	    && !is_external)
 		nm_utils_strbuf_append_str (&str, &str_len, "");
 	else {
-		nm_utils_strbuf_append_str (&str, &str_len, " (");
+		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");
-			if (is_volatile)
-				nm_utils_strbuf_append_c (&str, &str_len, ',');
+			ch = ',';
 		}
-		if (is_volatile)
+		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, ')');
 	}
 
@@ -202,6 +213,7 @@ _read_from_file (const char *full_filename,
                  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)
@@ -215,6 +227,7 @@ _read_from_file (const char *full_filename,
 	                                           out_stat,
 	                                           out_is_nm_generated,
 	                                           out_is_volatile,
+	                                           out_is_external,
 	                                           out_shadowed_storage,
 	                                           out_shadowed_owned,
 	                                           error);
@@ -284,8 +297,9 @@ _load_file (NMSKeyfilePlugin *self,
 {
 	NMSKeyfilePluginPrivate *priv;
 	gs_unref_object NMConnection *connection = NULL;
-	NMTernary is_volatile_opt;
 	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;
@@ -350,6 +364,7 @@ _load_file (NMSKeyfilePlugin *self,
 	                              &st,
 	                              &is_nm_generated_opt,
 	                              &is_volatile_opt,
+	                              &is_external_opt,
 	                              &shadowed_storage,
 	                              &shadowed_owned_opt,
 	                              &local);
@@ -367,6 +382,7 @@ _load_file (NMSKeyfilePlugin *self,
 	                                           storage_type,
 	                                           is_nm_generated_opt,
 	                                           is_volatile_opt,
+	                                           is_external_opt,
 	                                           shadowed_storage,
 	                                           shadowed_owned_opt,
 	                                           &st.st_mtim);
@@ -732,6 +748,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
                                    gboolean in_memory,
                                    gboolean is_nm_generated,
                                    gboolean is_volatile,
+                                   gboolean is_external,
                                    const char *shadowed_storage,
                                    gboolean shadowed_owned,
                                    NMSettingsStorage **out_storage,
@@ -756,6 +773,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	nm_assert (   in_memory
 	           || (   !is_nm_generated
 	               && !is_volatile
+	               && !is_external
 	               && !shadowed_storage
 	               && !shadowed_owned));
 
@@ -770,6 +788,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	if (!nms_keyfile_writer_connection (connection,
 	                                    is_nm_generated,
 	                                    is_volatile,
+	                                    is_external,
 	                                    shadowed_storage,
 	                                    shadowed_owned,
 	                                      storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
@@ -807,7 +826,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	       uuid,
 	       nm_connection_get_id (connection),
 	       full_filename,
-	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       _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,
@@ -816,6 +835,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	                                              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));
@@ -840,6 +860,7 @@ add_connection (NMSettingsPlugin *plugin,
 	                                          FALSE,
 	                                          FALSE,
 	                                          FALSE,
+	                                          FALSE,
 	                                          NULL,
 	                                          FALSE,
 	                                          out_storage,
@@ -853,6 +874,7 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
                                       NMConnection *connection,
                                       gboolean is_nm_generated,
                                       gboolean is_volatile,
+                                      gboolean is_external,
                                       const char *shadowed_storage,
                                       gboolean shadowed_owned,
                                       gboolean force_rename,
@@ -883,6 +905,7 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
 	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);
@@ -895,6 +918,7 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
 	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
@@ -933,11 +957,12 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
 	       full_filename,
 	       uuid,
 	       nm_connection_get_id (connection),
-	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       _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;
 
@@ -959,6 +984,7 @@ update_connection (NMSettingsPlugin *plugin,
 	                                             connection,
 	                                             FALSE,
 	                                             FALSE,
+	                                             FALSE,
 	                                             NULL,
 	                                             FALSE,
 	                                             FALSE,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
index e885f16c..603f0c26 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
@@ -31,6 +31,7 @@ gboolean nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
                                             gboolean in_memory,
                                             gboolean is_nm_generated,
                                             gboolean is_volatile,
+                                            gboolean is_external,
                                             const char *shadowed_storage,
                                             gboolean shadowed_owned,
                                             NMSettingsStorage **out_storage,
@@ -42,6 +43,7 @@ gboolean nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
                                                NMConnection *connection,
                                                gboolean is_nm_generated,
                                                gboolean is_volatile,
+                                               gboolean is_external,
                                                const char *shadowed_storage,
                                                gboolean shadowed_owned,
                                                gboolean force_rename,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index e8c9f9fe..577709d8 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -17,11 +17,15 @@
 /*****************************************************************************/
 
 static const char *
-_fmt_warn (const char *group, NMSetting *setting, const char *property_name, const char *message, char **out_message)
+_fmt_warn (const NMKeyfileHandlerData *handler_data, char **out_message)
 {
-	const char *setting_name = setting ? nm_setting_get_name (setting) : NULL;
+	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) {
@@ -37,30 +41,30 @@ _fmt_warn (const char *group, NMSetting *setting, const char *property_name, con
 			res = g_strdup_printf ("%s: %s", group, message);
 		*out_message = res;
 		return res;
-	} else
-		return message;
+	}
+
+	return message;
 }
 
 typedef struct {
 	bool verbose;
-} HandlerReadData;
+} ReadInfo;
 
 static gboolean
 _handler_read (GKeyFile *keyfile,
                NMConnection *connection,
-               NMKeyfileReadType type,
-               void *type_data,
-               void *user_data,
-               GError **error)
+               NMKeyfileHandlerType handler_type,
+               NMKeyfileHandlerData *handler_data,
+               void *user_data)
 {
-	const HandlerReadData *handler_data = user_data;
+	const ReadInfo *read_info = user_data;
 
-	if (type == NM_KEYFILE_READ_TYPE_WARN) {
-		NMKeyfileReadTypeDataWarn *warn_data = type_data;
+	if (handler_type == NM_KEYFILE_HANDLER_TYPE_WARN) {
+		const NMKeyfileHandlerDataWarn *warn_data = &handler_data->warn;
 		NMLogLevel level;
 		char *message_free = NULL;
 
-		if (!handler_data->verbose)
+		if (!read_info->verbose)
 			return TRUE;
 
 		if (warn_data->severity > NM_KEYFILE_WARN_SEVERITY_WARN)
@@ -72,15 +76,17 @@ _handler_read (GKeyFile *keyfile,
 		else
 			level = LOGL_INFO;
 
-		nm_log (level, LOGD_SETTINGS, NULL,
+		nm_log (level,
+		        LOGD_SETTINGS,
+		        NULL,
 		        nm_connection_get_uuid (connection),
 		        "keyfile: %s",
-		        _fmt_warn (warn_data->group, warn_data->setting,
-		                   warn_data->property_name, warn_data->message,
+		        _fmt_warn (handler_data,
 		                   &message_free));
 		g_free (message_free);
 		return TRUE;
 	}
+
 	return FALSE;
 }
 
@@ -93,7 +99,7 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
                                  GError **error)
 {
 	NMConnection *connection;
-	HandlerReadData data = {
+	ReadInfo read_info = {
 		.verbose = verbose,
 	};
 	gs_free char *base_dir_free = NULL;
@@ -122,7 +128,7 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
 		filename = &s[1];
 	}
 
-	connection = nm_keyfile_read (key_file, base_dir, _handler_read, &data, error);
+	connection = nm_keyfile_read (key_file, base_dir, NM_KEYFILE_HANDLER_FLAGS_NONE, _handler_read, &read_info, error);
 	if (!connection)
 		return NULL;
 
@@ -150,6 +156,7 @@ nms_keyfile_reader_from_file (const char *full_filename,
                               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)
@@ -163,6 +170,7 @@ nms_keyfile_reader_from_file (const char *full_filename,
 
 	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);
 
 	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_KEYFILE,
 	                                               full_filename,
@@ -198,6 +206,11 @@ nms_keyfile_reader_from_file (const char *full_filename,
 	                                                      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,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.h b/src/settings/plugins/keyfile/nms-keyfile-reader.h
index 307d6ffe..828715fa 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.h
@@ -23,6 +23,7 @@ NMConnection *nms_keyfile_reader_from_file (const char *full_filename,
                                             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);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.c b/src/settings/plugins/keyfile/nms-keyfile-storage.c
index bcc06795..15f20295 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-storage.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -155,6 +155,7 @@ nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin,
                                     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)
@@ -181,6 +182,7 @@ nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin,
 	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);
 	}
@@ -243,7 +245,8 @@ nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
 
 	*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_VOLATILE
+	             | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
 
 	if (!NMS_IS_KEYFILE_STORAGE (self))
 		return;
@@ -260,4 +263,7 @@ nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
 
 	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/settings/plugins/keyfile/nms-keyfile-storage.h b/src/settings/plugins/keyfile/nms-keyfile-storage.h
index 00a034d4..bd8a4367 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-storage.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.h
@@ -65,6 +65,7 @@ typedef struct {
 			 * 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,
@@ -112,6 +113,7 @@ NMSKeyfileStorage *nms_keyfile_storage_new_connection (struct _NMSKeyfilePlugin
                                                        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);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 7554becc..feea6065 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -28,21 +28,22 @@ typedef struct {
 static void
 cert_writer (NMConnection *connection,
              GKeyFile *file,
-             NMKeyfileWriteTypeDataCert *cert_data,
+             NMSetting8021x *setting,
+             const NMSetting8021xSchemeVtable *vtable,
              WriteInfo *info,
              GError **error)
 {
-	const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting));
+	const char *setting_name = nm_setting_get_name (NM_SETTING (setting));
 	NMSetting8021xCKScheme scheme;
 	NMSetting8021xCKFormat format;
 	const char *path = NULL, *ext = "pem";
 
-	scheme = cert_data->vtable->scheme_func (cert_data->setting);
+	scheme = vtable->scheme_func (setting);
 	if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
 		char *tmp = NULL;
 		const char *accepted_path = NULL;
 
-		path = cert_data->vtable->path_func (cert_data->setting);
+		path = vtable->path_func (setting);
 		g_assert (path);
 
 		if (g_str_has_prefix (path, info->keyfile_dir)) {
@@ -78,11 +79,11 @@ cert_writer (NMConnection *connection,
 
 		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, cert_data->vtable->setting_key, accepted_path);
+		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, cert_data->vtable->setting_key,
-		                                 cert_data->vtable->uri_func (cert_data->setting));
+		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;
@@ -91,13 +92,13 @@ cert_writer (NMConnection *connection,
 		GError *local = NULL;
 		char *new_path;
 
-		blob = cert_data->vtable->blob_func (cert_data->setting);
+		blob = vtable->blob_func (setting);
 		g_assert (blob);
 		blob_data = g_bytes_get_data (blob, &blob_len);
 
-		if (cert_data->vtable->format_func) {
+		if (vtable->format_func) {
 			/* Get the extension for a private key */
-			format = cert_data->vtable->format_func (cert_data->setting);
+			format = vtable->format_func (setting);
 			if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
 				ext = "p12";
 		} else {
@@ -110,7 +111,7 @@ cert_writer (NMConnection *connection,
 		 * 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),
-		                            cert_data->vtable->file_suffix, ext);
+		                            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
@@ -125,10 +126,10 @@ cert_writer (NMConnection *connection,
 		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, cert_data->vtable->setting_key, strrchr (new_path, '/') + 1);
+			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, cert_data->vtable->setting_key, new_path, local->message);
+			             setting_name, vtable->setting_key, new_path, local->message);
 			g_error_free (local);
 		}
 		g_free (new_path);
@@ -146,15 +147,17 @@ cert_writer (NMConnection *connection,
 static gboolean
 _handler_write (NMConnection *connection,
                 GKeyFile *keyfile,
-                NMKeyfileWriteType type,
-                void *type_data,
-                void *user_data,
-                GError **error)
+                NMKeyfileHandlerType type,
+                NMKeyfileHandlerData *type_data,
+                void *user_data)
 {
-	if (type == NM_KEYFILE_WRITE_TYPE_CERT) {
-		cert_writer (connection, keyfile,
-		             (NMKeyfileWriteTypeDataCert *) type_data,
-		             (WriteInfo *) user_data, error);
+	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;
@@ -164,6 +167,7 @@ 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,
@@ -211,7 +215,7 @@ _internal_write_connection (NMConnection *connection,
 
 	info.keyfile_dir = keyfile_dir;
 
-	kf_file = nm_keyfile_write (connection, _handler_write, &info, error);
+	kf_file = nm_keyfile_write (connection, NM_KEYFILE_HANDLER_FLAGS_NONE, _handler_write, &info, error);
 	if (!kf_file)
 		return FALSE;
 
@@ -229,6 +233,13 @@ _internal_write_connection (NMConnection *connection,
 		                        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,
@@ -375,6 +386,7 @@ 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,
@@ -392,6 +404,7 @@ nms_keyfile_writer_connection (NMConnection *connection,
 	return _internal_write_connection (connection,
 	                                   is_nm_generated,
 	                                   is_volatile,
+	                                   is_external,
 	                                   shadowed_storage,
 	                                   shadowed_owned,
 	                                   keyfile_dir,
@@ -423,6 +436,7 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	return _internal_write_connection (connection,
 	                                   FALSE,
 	                                   FALSE,
+	                                   FALSE,
 	                                   NULL,
 	                                   FALSE,
 	                                   keyfile_dir,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.h b/src/settings/plugins/keyfile/nms-keyfile-writer.h
index 98ec8a6b..73a2e333 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -15,6 +15,7 @@ typedef gboolean (*NMSKeyfileWriterAllowFilenameCb) (const char *check_filename,
 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,
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
index d2da09da..1a9482e5 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -63,6 +63,7 @@ check_ip_route (NMSettingIPConfig *config, int idx, const char *destination, int
 	                                            NULL, \
 	                                            NULL, \
 	                                            NULL, \
+	                                            NULL, \
 	                                            (nmtst_get_rand_uint32 () % 2) ? &_error : NULL); \
 	nmtst_assert_success (_connection, _error); \
 	nmtst_assert_connection_verifies_without_normalization (_connection); \