about summary refs log tree commit diff
path: root/src/settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings')
-rw-r--r--src/settings/nm-agent-manager.c2
-rw-r--r--src/settings/nm-settings-connection.c2
-rw-r--r--src/settings/nm-settings.c12
-rw-r--r--src/settings/plugins/README33
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c23
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c66
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.h8
-rw-r--r--src/settings/plugins/ibft/tests/test-ibft.c17
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c2
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c401
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c164
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c79
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h8
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected19
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c130
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c32
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c12
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c2
21 files changed, 710 insertions, 307 deletions
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 8924c39f..814dee85 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -24,7 +24,7 @@
 
 #include <pwd.h>
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-dbus-interface.h"
 #include "nm-secret-agent.h"
 #include "nm-auth-utils.h"
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 8d1f9583..3fdaa598 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -25,7 +25,7 @@
 
 #include "c-list/src/c-list.h"
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-config.h"
 #include "nm-config-data.h"
 #include "nm-dbus-interface.h"
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index fd1d316a..8e18a33e 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -36,7 +36,7 @@
 #include <selinux/selinux.h>
 #endif
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-setting-8021x.h"
@@ -60,7 +60,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 
-#include "nm-utils/nm-c-list.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-dbus-object.h"
 #include "devices/nm-device-ethernet.h"
 #include "nm-settings-connection.h"
@@ -1138,7 +1138,7 @@ pk_add_cb (NMAuthChain *chain,
 	NMAuthCallResult result;
 	GError *error = NULL;
 	NMConnection *connection = NULL;
-	NMSettingsConnection *added = NULL;
+	gs_unref_object NMSettingsConnection *added = NULL;
 	NMSettingsAddCallback callback;
 	gpointer callback_data;
 	NMAuthSubject *subject;
@@ -1169,6 +1169,12 @@ pk_add_cb (NMAuthChain *chain,
 
 		save_to_disk = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "save-to-disk"));
 		added = nm_settings_add_connection (self, connection, save_to_disk, &error);
+
+		/* The callback may remove the connection from the settings manager (e.g.
+		 * because it's found to be incompatible with the device on AddAndActivate).
+		 * But we need to keep it alive for a bit longer, precisely to check wehther
+		 * it's still known to the setting manager. */
+		g_object_ref (added);
 	}
 
 	callback = nm_auth_chain_get_data (chain, "callback");
diff --git a/src/settings/plugins/README b/src/settings/plugins/README
deleted file mode 100644
index 307d0bb1..00000000
--- a/src/settings/plugins/README
+++ /dev/null
@@ -1,33 +0,0 @@
-Plugins generally have three components:
-
-1) plugin object: manages the individual "connections", which are
-  just objects wrapped around on-disk config data.  The plugin handles requests
-  to add new connections via the NM D-Bus API, and also watches config
-  directories for changes to configuration data.  Plugins implement the
-  NMSettingsPlugin interface.  See plugin.c.
-
-2) "connections": subclasses of NMSettingsConnection.  They handle updates to
-  configuration data, deletion, etc.  See NMKeyfileConnection.
-
-3) reader/writer code: typically a separate static library that gets linked
-  into the main plugin shared object, so they can be unit tested separately
-  from the plugin.  This code should read config data from disk and create
-  an NMConnection from it, and be capable of taking an NMConnection and writing
-  out appropriate configuration data to disk.
-
-NM will first call the "factory" function that every module must provide, which
-is nm_settings_plugin_factory().  That function creates and returns a singleton
-instance of the plugin's main object, which implements NMSettingsPlugin.
-That interface is implemented via the object definition in G_DEFINE_TYPE_EXTENDED
-in plugin.c, which registers the interface setup function
-settings_plugin_interface_init(), which when called actually sets up the vtables
-for the functions defined by NMSettingsPluginInterface.  Thus there are two
-entry points into the plugin:  nm_settings_plugin_factory() and
-the NMSettingsPluginInterface methods.
-
-The plugin also emits various signals (defined by NMSettingsPluginInterface)
-which NetworkManager listens for.  These include notifications of new
-connections if they were created via changes to the on-disk files.  The
-"connection" objects can also emit signals (defined by the NMSettingsConnection
-and NMConnection superclasses) when the connections' backing storage gets
-changed or deleted.
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
index 00b25068..47051995 100644
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ b/src/settings/plugins/ibft/nms-ibft-plugin.c
@@ -64,31 +64,30 @@ static void
 read_connections (NMSIbftPlugin *self)
 {
 	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-	GSList *blocks = NULL, *iter;
-	GError *error = NULL;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
+	GSList *iter;
+	gs_free_error GError *error = NULL;
 	NMSIbftConnection *connection;
 
 	if (!nms_ibft_reader_load_blocks ("/sbin/iscsiadm", &blocks, &error)) {
 		nm_log_dbg (LOGD_SETTINGS, "ibft: failed to read iscsiadm records: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
 	for (iter = blocks; iter; iter = iter->next) {
 		connection = nms_ibft_connection_new (iter->data, &error);
-		if (connection) {
-			nm_log_info (LOGD_SETTINGS, "ibft: read connection '%s'",
-			             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (connection)));
-			g_hash_table_insert (priv->connections,
-			                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection))),
-			                     connection);
-		} else {
+		if (!connection) {
 			nm_log_warn (LOGD_SETTINGS, "ibft: failed to read iscsiadm record: %s", error->message);
 			g_clear_error (&error);
+			continue;
 		}
-	}
 
-	g_slist_free_full (blocks, (GDestroyNotify) g_ptr_array_unref);
+		nm_log_info (LOGD_SETTINGS, "ibft: read connection '%s'",
+		             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (connection)));
+		g_hash_table_insert (priv->connections,
+		                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection))),
+		                     connection);
+	}
 }
 
 static GSList *
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
index c6c14376..fde30383 100644
--- a/src/settings/plugins/ibft/nms-ibft-reader.c
+++ b/src/settings/plugins/ibft/nms-ibft-reader.c
@@ -46,8 +46,7 @@ remove_most_whitespace (const char *src)
 	char *s_new, *s2;
 	const char *svalue;
 
-	while (*src && g_ascii_isspace (*src))
-		src++;
+	src = nm_str_skip_leading_spaces (src);
 
 	svalue = strchr (src, '=');
 	if (!svalue || svalue == src)
@@ -94,24 +93,25 @@ nms_ibft_reader_load_blocks (const char *iscsiadm_path,
 {
 	const char *argv[4] = { iscsiadm_path, "-m", "fw", NULL };
 	const char *envp[1] = { NULL };
-	GSList *blocks = NULL;
-	char *out = NULL, *err = NULL;
-	int status = 0;
-	char **lines = NULL, **iter;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
+	gs_free char *out = NULL;
+	gs_free char *err = NULL;
+	gs_free const char **lines = NULL;
 	GPtrArray *block_lines = NULL;
-	gboolean success = FALSE;
+	gsize i;
+	int status = 0;
 
 	g_return_val_if_fail (iscsiadm_path != NULL, FALSE);
 	g_return_val_if_fail (out_blocks != NULL && *out_blocks == NULL, FALSE);
 
 	if (!g_spawn_sync ("/", (char **) argv, (char **) envp, 0,
 	                   NULL, NULL, &out, &err, &status, error))
-		goto done;
+		return FALSE;
 
 	if (!WIFEXITED (status)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "iBFT: %s exited abnormally.", iscsiadm_path);
-		goto done;
+		return FALSE;
 	}
 
 	if (WEXITSTATUS (status) != 0) {
@@ -127,59 +127,47 @@ nms_ibft_reader_load_blocks (const char *iscsiadm_path,
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "iBFT: %s exited with error %d.  Message: '%s'",
 		             iscsiadm_path, WEXITSTATUS (status), err ?: "(none)");
-		goto done;
+		return FALSE;
 	}
 
 	nm_log_dbg (LOGD_SETTINGS, "iBFT records:\n%s", out);
 
-	lines = g_strsplit_set (out, "\n\r", -1);
-	for (iter = lines; iter && *iter; iter++) {
-		if (!*iter[0])
-			continue;
+	lines = nm_utils_strsplit_set (out, "\n\r");
+	for (i = 0; lines && lines[i]; i++) {
+		const char *ss = lines[i];
 
-		if (!g_ascii_strncasecmp (*iter, TAG_BEGIN, NM_STRLEN (TAG_BEGIN))) {
+		if (!g_ascii_strncasecmp (ss, TAG_BEGIN, NM_STRLEN (TAG_BEGIN))) {
 			if (block_lines) {
 				PARSE_WARNING ("malformed iscsiadm record: missing END RECORD.");
-				g_ptr_array_unref (block_lines);
+				nm_clear_pointer (&block_lines, g_ptr_array_unref);
 			}
 			/* Start new record */
 			block_lines = g_ptr_array_new_full (15, g_free);
-		} else if (!g_ascii_strncasecmp (*iter, TAG_END, NM_STRLEN (TAG_END))) {
+		} else if (!g_ascii_strncasecmp (ss, TAG_END, NM_STRLEN (TAG_END))) {
 			if (block_lines) {
 				if (block_lines->len)
-					blocks = g_slist_prepend (blocks, block_lines);
+					blocks = g_slist_prepend (blocks, g_steal_pointer (&block_lines));
 				else
-					g_ptr_array_unref (block_lines);
-				block_lines = NULL;
+					g_ptr_array_unref (g_steal_pointer (&block_lines));
 			}
 		} else if (block_lines) {
-			char *s = remove_most_whitespace (*iter);
+			char *s = remove_most_whitespace (ss);
 
-			if (s)
+			if (!s) {
+				PARSE_WARNING ("malformed iscsiadm record: no = in '%s'.", ss);
+				nm_clear_pointer (&block_lines, g_ptr_array_unref);
+			} else
 				g_ptr_array_add (block_lines, s);
-			else {
-				PARSE_WARNING ("malformed iscsiadm record: no = in '%s'.", *iter);
-				g_clear_pointer (&block_lines, g_ptr_array_unref);
-			}
 		}
 	}
 
 	if (block_lines) {
 		PARSE_WARNING ("malformed iscsiadm record: missing # END RECORD.");
-		g_clear_pointer (&block_lines, g_ptr_array_unref);
+		nm_clear_pointer (&block_lines, g_ptr_array_unref);
 	}
-	success = TRUE;
-
-done:
-	if (lines)
-		g_strfreev (lines);
-	g_free (out);
-	g_free (err);
-	if (success)
-		*out_blocks = blocks;
-	else
-		g_slist_free_full (blocks, (GDestroyNotify) g_ptr_array_unref);
-	return success;
+
+	*out_blocks = g_steal_pointer (&blocks);
+	return TRUE;
 }
 
 #define ISCSI_HWADDR_TAG     "iface.hwaddress"
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.h b/src/settings/plugins/ibft/nms-ibft-reader.h
index 27500cc5..baa81e99 100644
--- a/src/settings/plugins/ibft/nms-ibft-reader.h
+++ b/src/settings/plugins/ibft/nms-ibft-reader.h
@@ -23,6 +23,14 @@
 
 #include "nm-connection.h"
 
+static inline void
+_nm_auto_free_ibft_blocks (GSList **p_blocks)
+{
+	if (*p_blocks)
+		g_slist_free_full (*p_blocks, (GDestroyNotify) g_ptr_array_unref);
+}
+#define nm_auto_free_ibft_blocks nm_auto (_nm_auto_free_ibft_blocks)
+
 gboolean nms_ibft_reader_load_blocks (const char *iscsiadm_path,
                                       GSList **out_blocks,
                                       GError **error);
diff --git a/src/settings/plugins/ibft/tests/test-ibft.c b/src/settings/plugins/ibft/tests/test-ibft.c
index 4c45f574..f5b584a1 100644
--- a/src/settings/plugins/ibft/tests/test-ibft.c
+++ b/src/settings/plugins/ibft/tests/test-ibft.c
@@ -40,16 +40,16 @@
 static GPtrArray *
 read_block (const char *iscsiadm_path, const char *expected_mac)
 {
-	GSList *blocks = NULL, *iter;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
+	GSList *iter;
 	GPtrArray *block = NULL;
 	GError *error = NULL;
 	gboolean success;
 
 	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-	g_assert (blocks);
+	nmtst_assert_success (success, error);
 
+	g_assert (blocks);
 	for (iter = blocks; iter; iter = iter->next) {
 		const char *s_hwaddr = NULL;
 
@@ -63,7 +63,6 @@ read_block (const char *iscsiadm_path, const char *expected_mac)
 	}
 	g_assert (block);
 
-	g_slist_free_full (blocks, (GDestroyNotify) g_ptr_array_unref);
 	return block;
 }
 
@@ -176,7 +175,7 @@ static void
 test_read_ibft_malformed (gconstpointer user_data)
 {
 	const char *iscsiadm_path = user_data;
-	GSList *blocks = NULL;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
 	GError *error = NULL;
 	gboolean success;
 
@@ -185,9 +184,9 @@ test_read_ibft_malformed (gconstpointer user_data)
 	NMTST_EXPECT_NM_WARN ("*malformed iscsiadm record*");
 
 	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-	g_assert (blocks == NULL);
+	nmtst_assert_success (success, error);
+
+	g_assert (!blocks);
 
 	g_test_assert_expected_messages ();
 }
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index 89272edb..5160dbf0 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -30,7 +30,7 @@
 #include <sys/stat.h>
 #include <gmodule.h>
 
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 #include "nm-setting-connection.h"
 #include "settings/nm-settings-plugin.h"
 #include "nm-config.h"
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 7c1db225..e5423b18 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -31,7 +31,7 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
-#include "nm-utils/nm-secret-utils.h"
+#include "nm-glib-aux/nm-secret-utils.h"
 #include "nm-connection.h"
 #include "nm-dbus-interface.h"
 #include "nm-setting-connection.h"
@@ -53,7 +53,7 @@
 #include "nm-setting-generic.h"
 #include "nm-core-internal.h"
 #include "nm-utils.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "platform/nm-platform.h"
 #include "NetworkManagerUtils.h"
@@ -82,7 +82,8 @@ static char *
 get_full_file_path (const char *ifcfg_path, const char *file_path)
 {
 	const char *base = file_path;
-	char *p, *ret, *dirname;
+	gs_free char *dirname = NULL;
+	char *p;
 
 	g_return_val_if_fail (ifcfg_path != NULL, NULL);
 	g_return_val_if_fail (file_path != NULL, NULL);
@@ -95,9 +96,7 @@ get_full_file_path (const char *ifcfg_path, const char *file_path)
 		base = p + 1;
 
 	dirname = g_path_get_dirname (ifcfg_path);
-	ret = g_build_path ("/", dirname, base, NULL);
-	g_free (dirname);
-	return ret;
+	return g_build_path ("/", dirname, base, NULL);
 }
 
 /*****************************************************************************/
@@ -207,7 +206,7 @@ _cert_get_cert_bytes (const char *ifcfg_path,
 {
 	gs_free char *path = NULL;
 
-	if (g_str_has_prefix (value, "pkcs11:"))
+	if (NM_STR_HAS_PREFIX (value, "pkcs11:"))
 		return _nm_setting_802_1x_cert_value_to_bytes (NM_SETTING_802_1X_CK_SCHEME_PKCS11, (guint8 *) value, -1, error);
 
 	path = get_full_file_path (ifcfg_path, value);
@@ -379,7 +378,7 @@ make_connection_setting (const char *file,
 	NMSettingConnection *s_con;
 	NMSettingConnectionLldp lldp;
 	const char *ifcfg_name = NULL;
-	char *new_id;
+	gs_free char *new_id = NULL;
 	const char *uuid;
 	gs_free char *uuid_free = NULL;
 	gs_free char *value = NULL;
@@ -396,7 +395,6 @@ make_connection_setting (const char *file,
 
 	new_id = make_connection_name (ifcfg, ifcfg_name, suggested, prefix);
 	g_object_set (s_con, NM_SETTING_CONNECTION_ID, new_id, NULL);
-	g_free (new_id);
 
 	/* Try for a UUID key before falling back to hashing the file name */
 	uuid = svGetValueStr (ifcfg, "UUID", &uuid_free);
@@ -457,7 +455,7 @@ make_connection_setting (const char *file,
 	if (v) {
 		gs_free const char **items = NULL;
 
-		items = nm_utils_strsplit_set (v, " ", FALSE);
+		items = nm_utils_strsplit_set (v, " ");
 		for (iter = items; iter && *iter; iter++) {
 			if (!nm_setting_connection_add_permission (s_con, "user", *iter, NULL))
 				PARSE_WARNING ("invalid USERS item '%s'", *iter);
@@ -473,7 +471,7 @@ make_connection_setting (const char *file,
 	if (v) {
 		gs_free const char **items = NULL;
 
-		items = nm_utils_strsplit_set (v, " \t", FALSE);
+		items = nm_utils_strsplit_set (v, " \t");
 		for (iter = items; iter && *iter; iter++) {
 			if (!nm_setting_connection_add_secondary (s_con, *iter))
 				PARSE_WARNING ("secondary connection UUID '%s' already added", *iter);
@@ -562,7 +560,6 @@ make_connection_setting (const char *file,
 	return NM_SETTING (s_con);
 }
 
-/* Returns TRUE on missing address or valid address */
 static gboolean
 read_ip4_address (shvarFile *ifcfg,
                   const char *tag,
@@ -572,7 +569,7 @@ read_ip4_address (shvarFile *ifcfg,
 {
 	gs_free char *value_to_free = NULL;
 	const char *value;
-	guint32 a;
+	in_addr_t a;
 
 	nm_assert (ifcfg);
 	nm_assert (tag);
@@ -820,7 +817,7 @@ parse_route_line (const char *line,
                   NMIPRoute **out_route,
                   GError **error)
 {
-	nm_auto_ip_route_unref NMIPRoute *route = NULL;
+	nm_auto_unref_ip_route NMIPRoute *route = NULL;
 	gs_free const char **words_free = NULL;
 	const char *const*words;
 	const char *s;
@@ -889,7 +886,7 @@ parse_route_line (const char *line,
 	 * Maybe later we want to support some form of quotation here.
 	 * Which of course, would be incompatible with initscripts.
 	 */
-	words_free = nm_utils_strsplit_set (line, " \t\n", FALSE);
+	words_free = nm_utils_strsplit_set (line, " \t\n");
 
 	words = words_free ?: NM_PTRARRAY_EMPTY (const char *);
 
@@ -1284,7 +1281,7 @@ read_route_file (int addr_family,
 	for (line = strtok_r (contents, "\n", &contents_rest);
 	     line;
 	     line = strtok_r (NULL, "\n", &contents_rest)) {
-		nm_auto_ip_route_unref NMIPRoute *route = NULL;
+		nm_auto_unref_ip_route NMIPRoute *route = NULL;
 		gs_free_error GError *local = NULL;
 		int e;
 
@@ -1327,7 +1324,7 @@ parse_dns_options (NMSettingIPConfig *ip_config, const char *value)
 	if (!nm_setting_ip_config_has_dns_options (ip_config))
 		nm_setting_ip_config_clear_dns_options (ip_config, TRUE);
 
-	options = nm_utils_strsplit_set (value, " ", FALSE);
+	options = nm_utils_strsplit_set (value, " ");
 	if (options) {
 		for (item = options; *item; item++) {
 			if (!nm_setting_ip_config_add_dns_option (ip_config, *item))
@@ -1343,46 +1340,33 @@ parse_full_ip6_address (shvarFile *ifcfg,
                         NMIPAddress **out_address,
                         GError **error)
 {
-	char **list;
-	char *ip_val, *prefix_val;
+	NMIPAddress *addr;
+	NMIPAddr addr_bin;
 	int prefix;
-	gboolean success = FALSE;
 
-	g_return_val_if_fail (addr_str != NULL, FALSE);
-	g_return_val_if_fail (out_address != NULL, FALSE);
-	g_return_val_if_fail (*out_address == NULL, FALSE);
-	g_return_val_if_fail (!error || !*error, FALSE);
+	nm_assert (addr_str);
+	nm_assert (out_address && !*out_address);
+	nm_assert (!error || !*error);
 
-	/* Split the address and prefix */
-	list = g_strsplit_set (addr_str, "/", 2);
-	if (g_strv_length (list) < 1) {
+	if (!nm_utils_parse_inaddr_prefix_bin (AF_INET6,
+	                                       addr_str,
+	                                       NULL,
+	                                       &addr_bin,
+	                                       &prefix)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Invalid IP6 address '%s'", addr_str);
-		goto error;
+		return FALSE;
 	}
 
-	ip_val = list[0];
-
-	prefix_val = list[1];
-	if (prefix_val) {
-		prefix = _nm_utils_ascii_str_to_int64 (prefix_val, 10, 0, 128, -1);
-		if (prefix < 0) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid IP6 prefix '%s'", prefix_val);
-			goto error;
-		}
-	} else {
-		/* Missing prefix is treated as prefix of 64 */
+	if (prefix < 0)
 		prefix = 64;
-	}
 
-	*out_address = nm_ip_address_new (AF_INET6, ip_val, prefix, error);
-	if (*out_address)
-		success = TRUE;
+	addr = nm_ip_address_new_binary (AF_INET6, &addr_bin, prefix, error);
+	if (!addr)
+		return FALSE;
 
-error:
-	g_strfreev (list);
-	return success;
+	*out_address = addr;
+	return TRUE;
 }
 
 static NMSetting *
@@ -1443,13 +1427,12 @@ make_match_setting (shvarFile *ifcfg)
 	if (!v)
 		return NULL;
 
-	strv = nm_utils_strsplit_set (v, " \t", TRUE);
+	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,
-			                                     _nm_utils_unescape_spaces ((char *) strv[i]));
+			nm_setting_match_add_interface_name (s_match, strv[i]);
 		}
 	}
 
@@ -1722,7 +1705,7 @@ make_ip4_setting (shvarFile *ifcfg,
 		if (v) {
 			gs_free const char **searches = NULL;
 
-			searches = nm_utils_strsplit_set (v, " ", FALSE);
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
@@ -1783,7 +1766,7 @@ make_ip4_setting (shvarFile *ifcfg,
 		if (v) {
 			gs_free const char **searches = NULL;
 
-			searches = nm_utils_strsplit_set (v, " ", FALSE);
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
@@ -1809,7 +1792,8 @@ static void
 read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *filename)
 {
 	GDir *dir;
-	char *dirname, *base;
+	gs_free char *dirname  = NULL;
+	gs_free char *base  = NULL;
 	NMIPAddress *base_addr = NULL;
 	GError *err = NULL;
 
@@ -1820,9 +1804,9 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 		base_addr = nm_setting_ip_config_get_address (s_ip4, 0);
 
 	dirname = g_path_get_dirname (filename);
-	g_return_if_fail (dirname != NULL);
+	nm_assert (dirname != NULL);
 	base = g_path_get_basename (filename);
-	g_return_if_fail (base != NULL);
+	nm_assert (base != NULL);
 
 	dir = g_dir_open (dirname, 0, &err);
 	if (dir) {
@@ -1911,9 +1895,6 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 		PARSE_WARNING ("can not read directory '%s': %s", dirname, err->message);
 		g_error_free (err);
 	}
-
-	g_free (base);
-	g_free (dirname);
 }
 
 static NMSetting *
@@ -1922,10 +1903,9 @@ make_ip6_setting (shvarFile *ifcfg,
                   gboolean routes_read,
                   GError **error)
 {
-	NMSettingIPConfig *s_ip6 = NULL;
+	gs_unref_object NMSettingIPConfig *s_ip6 = NULL;
 	const char *v;
 	gs_free char *value = NULL;
-	char *route6_path = NULL;
 	gboolean ipv6init, ipv6forwarding, dhcp6 = FALSE;
 	char *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
 	const char *ipv6addr, *ipv6addr_secondaries;
@@ -2060,7 +2040,7 @@ make_ip6_setting (shvarFile *ifcfg,
 
 	/* Don't bother to read IP, DNS and routes when IPv6 is disabled */
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
-		return NM_SETTING (s_ip6);
+		return NM_SETTING (g_steal_pointer (&s_ip6));
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "DHCPV6_DUID", &value);
@@ -2083,7 +2063,7 @@ make_ip6_setting (shvarFile *ifcfg,
 		g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL);
 
 	g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
-		      svGetValueBoolean (ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), NULL);
+	              svGetValueBoolean (ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), NULL);
 
 	/* Read static IP addresses.
 	 * Read them even for AUTO and DHCP methods - in this case the addresses are
@@ -2099,12 +2079,12 @@ make_ip6_setting (shvarFile *ifcfg,
 	                   ipv6addr_secondaries ?: "",
 	                   NULL);
 
-	list = nm_utils_strsplit_set (value, " ", FALSE);
+	list = nm_utils_strsplit_set (value, " ");
 	for (iter = list, i = 0; iter && *iter; iter++, i++) {
 		NMIPAddress *addr = NULL;
 
 		if (!parse_full_ip6_address (ifcfg, *iter, i, &addr, error))
-			goto error;
+			return NULL;
 
 		if (!nm_setting_ip_config_add_address (s_ip6, addr))
 			PARSE_WARNING ("duplicate IP6 address");
@@ -2129,7 +2109,7 @@ make_ip6_setting (shvarFile *ifcfg,
 			if (!nm_utils_ipaddr_valid (AF_INET6, v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				             "Invalid IP6 address '%s'", v);
-				goto error;
+				return NULL;
 			}
 
 			g_object_set (s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, v, NULL);
@@ -2172,18 +2152,19 @@ make_ip6_setting (shvarFile *ifcfg,
 			/* Ignore IPv4 addresses */
 		} else {
 			PARSE_WARNING ("invalid DNS server address %s", v);
-			goto error;
+			return NULL;
 		}
 	}
 
 	if (!routes_read) {
 		/* NOP */
 	} else {
+		gs_free char *route6_path = NULL;
+
 		/* Read static routes from route6-<interface> file */
 		route6_path = utils_get_route6_path (svFileGetName (ifcfg));
 		if (!read_route_file (AF_INET6, route6_path, s_ip6, error))
-			goto error;
-		g_free (route6_path);
+			return NULL;
 	}
 
 	/* DNS searches */
@@ -2192,7 +2173,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	if (v) {
 		gs_free const char **searches = NULL;
 
-		searches = nm_utils_strsplit_set (v, " ", FALSE);
+		searches = nm_utils_strsplit_set (v, " ");
 		if (searches) {
 			for (iter = searches; *iter; iter++) {
 				if (!nm_setting_ip_config_add_dns_search (s_ip6, *iter))
@@ -2212,12 +2193,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	              priority,
 	              NULL);
 
-	return NM_SETTING (s_ip6);
-
-error:
-	g_free (route6_path);
-	g_object_unref (s_ip6);
-	return NULL;
+	return NM_SETTING (g_steal_pointer (&s_ip6));
 }
 
 static NMSetting *
@@ -2551,7 +2527,7 @@ read_dcb_percent_array (shvarFile *ifcfg,
 		return TRUE;
 	}
 
-	split = nm_utils_strsplit_set (val, ",", FALSE);
+	split = nm_utils_strsplit_set (val, ",");
 	if (NM_PTRARRAY_LEN (split) != 8) {
 		PARSE_WARNING ("invalid %s percentage list value '%s'", prop, val);
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -2588,10 +2564,9 @@ make_dcb_setting (shvarFile *ifcfg,
                   NMSetting **out_setting,
                   GError **error)
 {
-	NMSettingDcb *s_dcb = NULL;
+	gs_unref_object NMSettingDcb *s_dcb = NULL;
 	gboolean dcb_on;
 	NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE;
-	char *val;
 
 	g_return_val_if_fail (out_setting != NULL, FALSE);
 
@@ -2600,31 +2575,28 @@ make_dcb_setting (shvarFile *ifcfg,
 		return TRUE;
 
 	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
-	g_assert (s_dcb);
 
 	/* FCOE */
 	if (!read_dcb_app (ifcfg, s_dcb, "FCOE",
 	                   &dcb_flags_props[DCB_APP_FCOE_FLAGS],
 	                   NM_SETTING_DCB_APP_FCOE_PRIORITY,
 	                   error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 	if (nm_setting_dcb_get_app_fcoe_flags (s_dcb) & NM_SETTING_DCB_FLAG_ENABLE) {
+		gs_free char *val = NULL;
+
 		val = svGetValueStr_cp (ifcfg, KEY_DCB_APP_FCOE_MODE);
 		if (val) {
-			if (strcmp (val, NM_SETTING_DCB_FCOE_MODE_FABRIC) == 0 ||
-			    strcmp (val, NM_SETTING_DCB_FCOE_MODE_VN2VN) == 0)
+			if (NM_IN_STRSET (val, NM_SETTING_DCB_FCOE_MODE_FABRIC,
+			                       NM_SETTING_DCB_FCOE_MODE_VN2VN))
 				g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_FCOE_MODE, val, NULL);
 			else {
 				PARSE_WARNING ("invalid FCoE mode '%s'", val);
 				g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				                     "invalid FCoE mode");
-				g_free (val);
-				g_object_unref (s_dcb);
 				return FALSE;
 			}
-			g_free (val);
 		}
 	}
 
@@ -2633,7 +2605,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                   &dcb_flags_props[DCB_APP_ISCSI_FLAGS],
 	                   NM_SETTING_DCB_APP_ISCSI_PRIORITY,
 	                   error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2642,7 +2613,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                   &dcb_flags_props[DCB_APP_FIP_FLAGS],
 	                   NM_SETTING_DCB_APP_FIP_PRIORITY,
 	                   error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2657,7 +2627,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          "PFC",
 	                          nm_setting_dcb_set_priority_flow_control,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2673,7 +2642,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          TRUE,
 	                          nm_setting_dcb_set_priority_group_id,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2686,7 +2654,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                             TRUE,
 	                             nm_setting_dcb_set_priority_group_bandwidth,
 	                             error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2699,7 +2666,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                             FALSE,
 	                             nm_setting_dcb_set_priority_bandwidth,
 	                             error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2711,7 +2677,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          "STRICT",
 	                          nm_setting_dcb_set_priority_strict_bandwidth,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2723,11 +2688,10 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          FALSE,
 	                          nm_setting_dcb_set_priority_traffic_class,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
-	*out_setting = NM_SETTING (s_dcb);
+	*out_setting = NM_SETTING (g_steal_pointer (&s_dcb));
 	return TRUE;
 }
 
@@ -2834,7 +2798,7 @@ make_wep_setting (shvarFile *ifcfg,
                   GError **error)
 {
 	gs_unref_object NMSettingWirelessSecurity *s_wsec = NULL;
-	char *value;
+	gs_free char *value = NULL;
 	shvarFile *keys_ifcfg = NULL;
 	int default_key_idx = 0;
 	gboolean has_default_key = FALSE;
@@ -2849,13 +2813,12 @@ make_wep_setting (shvarFile *ifcfg,
 		if (default_key_idx == 0) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid default WEP key '%s'", value);
-			g_free (value);
 			return NULL;
 		}
 		has_default_key = TRUE;
 		default_key_idx--;  /* convert to [0...3] */
 		g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, (guint) default_key_idx, NULL);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	/* Read WEP key flags */
@@ -2902,23 +2865,21 @@ make_wep_setting (shvarFile *ifcfg,
 
 	value = svGetValueStr_cp (ifcfg, "SECURITYMODE");
 	if (value) {
-		char *lcase;
+		gs_free char *lcase = NULL;
 
 		lcase = g_ascii_strdown (value, -1);
-		g_free (value);
+		nm_clear_g_free (&value);
 
-		if (!strcmp (lcase, "open")) {
+		if (nm_streq (lcase, "open")) {
 			g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL);
-		} else if (!strcmp (lcase, "restricted")) {
+		} else if (nm_streq (lcase, "restricted")) {
 			g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", NULL);
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid WEP authentication algorithm '%s'",
 			             lcase);
-			g_free (lcase);
 			return NULL;
 		}
-		g_free (lcase);
 	}
 
 	/* If no WEP keys were given, and the keys are not agent-owned, and no
@@ -2963,7 +2924,7 @@ fill_wpa_ciphers (shvarFile *ifcfg,
 	if (!p)
 		return TRUE;
 
-	list = nm_utils_strsplit_set (p, " ", FALSE);
+	list = nm_utils_strsplit_set (p, " ");
 	for (iter = list; iter && *iter; iter++, i++) {
 		/* Ad-Hoc configurations cannot have pairwise ciphers, and can only
 		 * have one group cipher.  Ignore any additional group ciphers and
@@ -3233,7 +3194,7 @@ eap_peap_reader (const char *eap_method,
 	}
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (v, " ", FALSE);
+	list = nm_utils_strsplit_set (v, " ");
 	iter = list;
 	if (iter) {
 		if (NM_IN_STRSET (*iter, "MSCHAPV2",
@@ -3311,7 +3272,7 @@ eap_ttls_reader (const char *eap_method,
 	inner_auth = g_ascii_strdown (v, -1);
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (inner_auth, " ", FALSE);
+	list = nm_utils_strsplit_set (inner_auth, " ");
 	iter = list;
 	if (iter) {
 		if (NM_IN_STRSET (*iter, "mschapv2",
@@ -3372,7 +3333,7 @@ eap_fast_reader (const char *eap_method,
 	if (fast_provisioning) {
 		gs_free const char **list1 = NULL;
 
-		list1 = nm_utils_strsplit_set (fast_provisioning, " \t", FALSE);
+		list1 = nm_utils_strsplit_set (fast_provisioning, " \t");
 		for (iter = list1; iter && *iter; iter++) {
 			if (strcmp (*iter, "allow-unauth") == 0)
 				allow_unauth = TRUE;
@@ -3406,7 +3367,7 @@ eap_fast_reader (const char *eap_method,
 	}
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (inner_auth, " ", FALSE);
+	list = nm_utils_strsplit_set (inner_auth, " ");
 	iter = list;
 	if (iter) {
 		if (   !strcmp (*iter, "MSCHAPV2")
@@ -3486,7 +3447,7 @@ read_8021x_list_value (shvarFile *ifcfg,
 	if (!v)
 		return;
 
-	strv = nm_utils_strsplit_set (v, " \t", FALSE);
+	strv = nm_utils_strsplit_set (v, " \t");
 	if (strv)
 		g_object_set (setting, prop_name, strv, NULL);
 }
@@ -3515,7 +3476,7 @@ fill_8021x (shvarFile *ifcfg,
 		return NULL;
 	}
 
-	list = nm_utils_strsplit_set (v, " ", FALSE);
+	list = nm_utils_strsplit_set (v, " ");
 
 	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
 
@@ -3829,7 +3790,7 @@ transform_hwaddr_blacklist (const char *blacklist)
 	const char **strv;
 	gsize i, j;
 
-	strv = nm_utils_strsplit_set (blacklist, " \t", FALSE);
+	strv = nm_utils_strsplit_set (blacklist, " \t");
 	if (!strv)
 		return NULL;
 	for (i = 0, j = 0; strv[j]; j++) {
@@ -4147,7 +4108,7 @@ parse_ethtool_option (const char *value,
 	gs_free const char **words = NULL;
 	guint i;
 
-	words = nm_utils_strsplit_set (value, NULL, FALSE);
+	words = nm_utils_strsplit_set (value, " \t\n");
 	if (!words)
 		return;
 
@@ -4316,6 +4277,84 @@ parse_ethtool_option (const char *value,
 	}
 }
 
+static GPtrArray *
+read_routing_rules_parse (shvarFile *ifcfg,
+                          gboolean routes_read)
+{
+	gs_unref_ptrarray GPtrArray *arr = NULL;
+	gs_free const char **keys = NULL;
+	guint i, len;
+
+	keys = svGetKeysSorted (ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6, &len);
+	if (len == 0)
+		return NULL;
+
+	if (!routes_read) {
+		PARSE_WARNING ("'rule-' or 'rule6-' files are present; Policy routing rules (ROUTING_RULE*) settings are ignored");
+		return NULL;
+	}
+
+	arr = g_ptr_array_new_full (len, (GDestroyNotify) nm_ip_routing_rule_unref);
+	for (i = 0; i < len; i++) {
+		const char *key = keys[i];
+		nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+		gs_free_error GError *local = NULL;
+		gs_free char *value_to_free = NULL;
+		const char *value;
+		gboolean key_is_ipv4;
+
+		key_is_ipv4 = (key[NM_STRLEN ("ROUTING_RULE")] == '_');
+		nm_assert (  key_is_ipv4  == NM_STR_HAS_PREFIX (key, "ROUTING_RULE_"));
+		nm_assert ((!key_is_ipv4) == NM_STR_HAS_PREFIX (key, "ROUTING_RULE6_"));
+
+		value = svGetValueStr (ifcfg, key, &value_to_free);
+		if (!value)
+			continue;
+
+		rule = nm_ip_routing_rule_from_string (value,
+		                                       NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
+		                                       | (key_is_ipv4
+		                                          ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET
+		                                          : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6),
+		                                       NULL,
+		                                       &local);
+		if (!rule) {
+			PARSE_WARNING ("invalid routing rule %s=\"%s\": %s", key, value, local->message);
+			continue;
+		}
+
+		g_ptr_array_add (arr, g_steal_pointer (&rule));
+	}
+
+	if (arr->len == 0)
+		return NULL;
+
+	return g_steal_pointer (&arr);
+}
+
+static void
+read_routing_rules (shvarFile *ifcfg,
+                    gboolean routes_read,
+                    NMSettingIPConfig *s_ip4,
+                    NMSettingIPConfig *s_ip6)
+{
+	gs_unref_ptrarray GPtrArray *routing_rules = NULL;
+	guint i;
+
+	routing_rules = read_routing_rules_parse (ifcfg, routes_read);
+	if (!routing_rules)
+		return;
+
+	for (i = 0; i < routing_rules->len; i++) {
+		NMIPRoutingRule *rule = routing_rules->pdata[i];
+
+		nm_setting_ip_config_add_routing_rule (  (nm_ip_routing_rule_get_addr_family (rule) == AF_INET)
+		                                       ? s_ip4
+		                                       : s_ip6,
+		                                       rule);
+	}
+}
+
 static void
 parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 {
@@ -4340,7 +4379,7 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 			gs_free const char **opts = NULL;
 			const char *const *iter;
 
-			opts = nm_utils_strsplit_set (ethtool_opts, ";", FALSE);
+			opts = nm_utils_strsplit_set (ethtool_opts, ";");
 			for (iter = opts; iter && iter[0]; iter++) {
 				/* in case of repeated wol_passwords, parse_ethtool_option()
 				 * will do the right thing and clear wol_password before resetting. */
@@ -4436,7 +4475,7 @@ make_wired_setting (shvarFile *ifcfg,
 			gs_free const char **chans = NULL;
 			guint32 num_chans;
 
-			chans = nm_utils_strsplit_set (value, ",", FALSE);
+			chans = nm_utils_strsplit_set (value, ",");
 			num_chans = NM_PTRARRAY_LEN (chans);
 			if (num_chans < 2 || num_chans > 3) {
 				PARSE_WARNING ("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
@@ -4470,22 +4509,23 @@ make_wired_setting (shvarFile *ifcfg,
 
 	value = svGetValueStr_cp (ifcfg, "OPTIONS");
 	if (value) {
-		char **options, **iter;
+		gs_free const char **options = NULL;
+		gsize i;
 
-		iter = options = g_strsplit_set (value, " ", 0);
-		while (iter && *iter) {
-			char *equals = strchr (*iter, '=');
+		options = nm_utils_strsplit_set_with_empty (value, " ");
+		for (i = 0; options && options[i]; i++) {
+			const char *line = options[i];
+			const char *equals;
 			gboolean valid = FALSE;
 
+			equals = strchr (line, '=');
 			if (equals) {
-				*equals = '\0';
-				valid = nm_setting_wired_add_s390_option (s_wired, *iter, equals + 1);
+				((char *) equals)[0] = '\0';
+				valid = nm_setting_wired_add_s390_option (s_wired, line, equals + 1);
 			}
 			if (!valid)
-				PARSE_WARNING ("invalid s390 OPTION '%s'", *iter);
-			iter++;
+				PARSE_WARNING ("invalid s390 OPTION '%s'", line);
 		}
-		g_strfreev (options);
 		nm_clear_g_free (&value);
 	}
 
@@ -4759,18 +4799,18 @@ make_bond_setting (shvarFile *ifcfg,
 		gs_free const char **items = NULL;
 		const char *const *iter;
 
-		items = nm_utils_strsplit_set (v, " ", FALSE);
+		items = nm_utils_strsplit_set (v, " ");
 		for (iter = items; iter && *iter; iter++) {
-			gs_strfreev char **keys = NULL;
-			const char *key, *val;
-
-			keys = g_strsplit_set (*iter, "=", 2);
-			if (keys && *keys) {
-				key = *keys;
-				val = *(keys + 1);
-				if (val && key[0] && val[0])
-					handle_bond_option (s_bond, key, val);
-			}
+			gs_free char *key = NULL;
+			const char *val;
+
+			val = strchr (*iter, '=');
+			if (!val)
+				continue;
+			key = g_strndup (*iter, val - *iter);
+			val++;
+			if (key[0] && val[0])
+				handle_bond_option (s_bond, key, val);
 		}
 	}
 
@@ -4958,6 +4998,8 @@ handle_bridge_option (NMSetting *setting,
 		{ "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_snooping", NM_SETTING_BRIDGE_MULTICAST_SNOOPING, 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_fwd_mask",     NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, 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 },
@@ -5037,19 +5079,56 @@ handle_bridging_opts (NMSetting *setting,
 	gs_free const char **items = NULL;
 	const char *const *iter;
 
-	items = nm_utils_strsplit_set (value, " ", FALSE);
+	items = nm_utils_strsplit_set (value, " ");
 	for (iter = items; iter && *iter; iter++) {
-		gs_strfreev char **keys = NULL;
-		const char *key, *val;
-
-		keys = g_strsplit_set (*iter, "=", 2);
-		if (keys && *keys) {
-			key = *keys;
-			val = *(keys + 1);
-			if (val && key[0] && val[0])
-				func (setting, stp, key, val, opt_type);
+		gs_free char *key = NULL;
+		const char *val;
+
+		val = strchr (*iter, '=');
+		if (!val)
+			continue;
+		key = g_strndup (*iter, val - *iter);
+		val++;
+		if (key[0] && val[0])
+			func (setting, stp, key, val, opt_type);
+	}
+}
+
+static void
+read_bridge_vlans (shvarFile *ifcfg,
+                   const char *key,
+                   NMSetting *setting,
+                   const char *property)
+{
+	gs_unref_ptrarray GPtrArray *array = NULL;
+	gs_free char *value_to_free = NULL;
+	const char *value;
+
+	value = svGetValueStr (ifcfg, key, &value_to_free);
+	if (value) {
+		gs_free const char **strv = NULL;
+		const char *const *iter;
+		GError *local = NULL;
+		NMBridgeVlan *vlan;
+
+		array = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+
+		strv = nm_utils_escaped_tokens_split (value, ",");
+		if (strv) {
+			for (iter = strv; *iter; iter++) {
+				vlan = nm_bridge_vlan_from_str (*iter, &local);
+				if (!vlan) {
+					PARSE_WARNING ("invalid bridge VLAN: %s", local->message);
+					g_clear_error (&local);
+					continue;
+				}
+				g_ptr_array_add (array, vlan);
+			}
 		}
+		nm_clear_g_free (&value_to_free);
 	}
+
+	g_object_set (setting, property, array, NULL);
 }
 
 static NMSetting *
@@ -5110,6 +5189,11 @@ make_bridge_setting (shvarFile *ifcfg,
 		nm_clear_g_free (&value_to_free);
 	}
 
+	read_bridge_vlans (ifcfg,
+	                   "BRIDGE_VLANS",
+	                   NM_SETTING (s_bridge),
+	                   NM_SETTING_BRIDGE_VLANS);
+
 	return (NMSetting *) g_steal_pointer (&s_bridge);
 }
 
@@ -5179,6 +5263,11 @@ make_bridge_port_setting (shvarFile *ifcfg)
 			handle_bridging_opts (s_port, FALSE, value, handle_bridge_option, BRIDGE_OPT_TYPE_PORT_OPTION);
 			nm_clear_g_free (&value_to_free);
 		}
+
+		read_bridge_vlans (ifcfg,
+		                   "BRIDGE_PORT_VLANS",
+		                   s_port,
+		                   NM_SETTING_BRIDGE_PORT_VLANS);
 	}
 
 	return s_port;
@@ -5255,7 +5344,7 @@ parse_prio_map_list (NMSettingVlan *s_vlan,
 	v = svGetValueStr (ifcfg, key, &value);
 	if (!v)
 		return;
-	list = nm_utils_strsplit_set (v, ",", FALSE);
+	list = nm_utils_strsplit_set (v, ",");
 
 	for (iter = list; iter && *iter; iter++) {
 		if (!strchr (*iter, ':'))
@@ -5360,7 +5449,7 @@ make_vlan_setting (shvarFile *ifcfg,
 		gs_free const char **strv = NULL;
 		const char *const *ptr;
 
-		strv = nm_utils_strsplit_set (v, ", ", FALSE);
+		strv = nm_utils_strsplit_set (v, ", ");
 		for (ptr = strv; ptr && *ptr; ptr++) {
 			if (nm_streq (*ptr, "GVRP") && gvrp == -1)
 				vlan_flags |= NM_VLAN_FLAG_GVRP;
@@ -5502,7 +5591,7 @@ check_dns_search_domains (shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6)
 			gs_free const char **searches = NULL;
 			const char *const *item;
 
-			searches = nm_utils_strsplit_set (v, " ", FALSE);
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (NM_SETTING_IP_CONFIG (s_ip6), *item))
@@ -5768,8 +5857,7 @@ connection_from_file_full (const char *filename,
 	                          error);
 	if (!s_ip6)
 		return NULL;
-	else
-		nm_connection_add_setting (connection, s_ip6);
+	nm_connection_add_setting (connection, s_ip6);
 
 	s_ip4 = make_ip4_setting (main_ifcfg,
 	                          network_ifcfg,
@@ -5778,12 +5866,15 @@ connection_from_file_full (const char *filename,
 	                          error);
 	if (!s_ip4)
 		return NULL;
-	else {
-		read_aliases (NM_SETTING_IP_CONFIG (s_ip4),
-		              !has_ip4_defroute && !nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (s_ip4)),
-		              filename);
-		nm_connection_add_setting (connection, s_ip4);
-	}
+	read_aliases (NM_SETTING_IP_CONFIG (s_ip4),
+	              !has_ip4_defroute && !nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (s_ip4)),
+	              filename);
+	nm_connection_add_setting (connection, s_ip4);
+
+	read_routing_rules (main_ifcfg,
+	                    !has_complex_routes_v4 && !has_complex_routes_v6,
+	                    NM_SETTING_IP_CONFIG (s_ip4),
+	                    NM_SETTING_IP_CONFIG (s_ip6));
 
 	s_sriov = make_sriov_setting (main_ifcfg);
 	if (s_sriov)
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 84c22094..c7729df5 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -22,7 +22,7 @@
 #define _UTILS_H_
 
 #include "nm-connection.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "shvar.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 ee7fd161..80b1bffe 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -30,8 +30,8 @@
 #include <unistd.h>
 #include <stdio.h>
 
-#include "nm-utils/nm-enum-utils.h"
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-enum-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "nm-manager.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wired.h"
@@ -50,7 +50,7 @@
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-meta-setting.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "nms-ifcfg-rh-common.h"
 #include "nms-ifcfg-rh-reader.h"
@@ -1464,6 +1464,43 @@ get_setting_default_boolean (NMSetting *setting, const char *prop)
 }
 
 static gboolean
+write_bridge_vlans (NMSetting *setting,
+                    const char *property_name,
+                    shvarFile *ifcfg,
+                    const char *key,
+                    GError **error)
+{
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	NMBridgeVlan *vlan;
+	GString *string;
+	guint i;
+
+	g_object_get (setting, property_name, &vlans, NULL);
+
+	if (!vlans || !vlans->len) {
+		svUnsetValue (ifcfg, key);
+		return TRUE;
+	}
+
+	string = g_string_new ("");
+	for (i = 0; i < vlans->len; i++) {
+		gs_free char *vlan_str = NULL;
+
+		vlan = vlans->pdata[i];
+		vlan_str = nm_bridge_vlan_to_str (vlan, error);
+		if (!vlan_str)
+			return FALSE;
+		if (string->len > 0)
+			g_string_append (string, ",");
+		nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string);
+	}
+
+	svSetValueStr (ifcfg, key, string->str);
+	g_string_free (string, TRUE);
+	return TRUE;
+}
+
+static gboolean
 write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingBridge *s_bridge;
@@ -1534,10 +1571,31 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "multicast_snooping=%u", (guint32) b);
 	}
 
+	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 (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)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "default_pvid=%u", i);
+	}
+
 	if (opts->len)
 		svSetValueStr (ifcfg, "BRIDGING_OPTS", opts->str);
 	g_string_free (opts, TRUE);
 
+	if (!write_bridge_vlans ((NMSetting *) s_bridge,
+	                         NM_SETTING_BRIDGE_VLANS,
+	                         ifcfg,
+	                         "BRIDGE_VLANS",
+	                         error))
+		return FALSE;
+
 	svSetValueStr (ifcfg, "TYPE", TYPE_BRIDGE);
 
 	*wired = write_wired_for_virtual (connection, ifcfg);
@@ -1550,7 +1608,7 @@ write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **
 {
 	NMSettingBridgePort *s_port;
 	guint32 i;
-	GString *opts;
+	GString *string;
 
 	s_port = nm_connection_get_setting_bridge_port (connection);
 	if (!s_port)
@@ -1559,28 +1617,35 @@ write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **
 	svUnsetValue (ifcfg, "BRIDGING_OPTS");
 
 	/* Bridge options */
-	opts = g_string_sized_new (32);
+	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 (opts, "priority=%u", i);
+		g_string_append_printf (string, "priority=%u", i);
 
 	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)) {
-		if (opts->len)
-			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "path_cost=%u", i);
+		if (string->len)
+			g_string_append_c (string, ' ');
+		g_string_append_printf (string, "path_cost=%u", i);
 	}
 
 	if (nm_setting_bridge_port_get_hairpin_mode (s_port)) {
-		if (opts->len)
-			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "hairpin_mode=1");
+		if (string->len)
+			g_string_append_c (string, ' ');
+		g_string_append_printf (string, "hairpin_mode=1");
 	}
 
-	if (opts->len)
-		svSetValueStr (ifcfg, "BRIDGING_OPTS", opts->str);
-	g_string_free (opts, TRUE);
+	if (string->len)
+		svSetValueStr (ifcfg, "BRIDGING_OPTS", string->str);
+	g_string_free (string, TRUE);
+
+	if (!write_bridge_vlans ((NMSetting *) s_port,
+	                         NM_SETTING_BRIDGE_PORT_VLANS,
+	                         ifcfg,
+	                         "BRIDGE_PORT_VLANS",
+	                         error))
+		return FALSE;
 
 	return TRUE;
 }
@@ -2310,15 +2375,17 @@ write_match_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 
 	num = nm_setting_match_get_num_interface_names (s_match);
 	for (i = 0; i < num; i++) {
-		gs_free char *to_free = NULL;
 		const char *name;
 
-		if (i == 0)
+		name = nm_setting_match_get_interface_name (s_match, i);
+		if (!name || !name[0])
+			continue;
+
+		if (!str)
 			str = g_string_new ("");
 		else
 			g_string_append_c (str, ' ');
-		name = nm_setting_match_get_interface_name (s_match, i);
-		g_string_append (str, _nm_utils_escape_spaces (name, &to_free));
+		nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
 	}
 
 	if (str)
@@ -2889,6 +2956,52 @@ write_ip6_setting (NMConnection *connection,
 	return TRUE;
 }
 
+static void
+write_ip_routing_rules (NMConnection *connection,
+                        shvarFile *ifcfg,
+                        gboolean route_ignore)
+{
+	gsize idx;
+	int is_ipv4;
+
+	svUnsetAll (ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6);
+
+	if (route_ignore)
+		return;
+
+	idx = 0;
+
+	for (is_ipv4 = 1; is_ipv4 >= 0; is_ipv4--) {
+		const int addr_family = is_ipv4 ? AF_INET : AF_INET6;
+		NMSettingIPConfig *s_ip;
+		guint i, num;
+
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+		if (!s_ip)
+			continue;
+
+		num = nm_setting_ip_config_get_num_routing_rules (s_ip);
+		for (i = 0; i < num; i++) {
+			NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i);
+			gs_free const char *s = NULL;
+			char key[64];
+
+			s = nm_ip_routing_rule_to_string (rule,
+			                                  NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE,
+			                                  NULL,
+			                                  NULL);
+			if (!s)
+				continue;
+
+			if (is_ipv4)
+				numbered_tag (key, "ROUTING_RULE_", ++idx);
+			else
+				numbered_tag (key, "ROUTING_RULE6_", ++idx);
+			svSetValueStr (ifcfg, key, s);
+		}
+	}
+}
+
 static char *
 escape_id (const char *id)
 {
@@ -3111,6 +3224,15 @@ do_write_construct (NMConnection *connection,
 			             has_complex_routes_v4 ? "" : "6");
 			return FALSE;
 		}
+		if (   (   s_ip4
+		        && nm_setting_ip_config_get_num_routing_rules (s_ip4) > 0)
+		    || (   s_ip6
+		        && nm_setting_ip_config_get_num_routing_rules (s_ip6) > 0)) {
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+			             "Cannot configure routing rules on a connection that has an associated 'rule%s-' file",
+			             has_complex_routes_v4 ? "" : "6");
+			return FALSE;
+		}
 		route_ignore = TRUE;
 	} else
 		route_ignore = FALSE;
@@ -3128,6 +3250,10 @@ do_write_construct (NMConnection *connection,
 	                        error))
 		return FALSE;
 
+	write_ip_routing_rules (connection,
+	                        ifcfg,
+	                        route_ignore);
+
 	write_connection_setting (s_con, ifcfg);
 
 	NM_SET_OUT (out_ifcfg, g_steal_pointer (&ifcfg));
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index f3d58e26..b399a17f 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -36,8 +36,8 @@
 
 #include "nm-core-internal.h"
 #include "nm-core-utils.h"
-#include "nm-utils/nm-enum-utils.h"
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-enum-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "c-list/src/c-list.h"
 
 /*****************************************************************************/
@@ -879,10 +879,25 @@ _is_all_digits (const char *str)
 
 #define IS_NUMBERED_TAG(key, tab_name) \
 	({ \
+		const char *_key2 = (key); \
+		\
+		(   (strncmp (_key2, tab_name, NM_STRLEN (tab_name)) == 0) \
+		 && _is_all_digits (&_key2[NM_STRLEN (tab_name)])); \
+	})
+
+#define IS_NUMBERED_TAG_PARSE(key, tab_name, out_idx) \
+	({ \
 		const char *_key = (key); \
+		gint64 _idx; \
+		gboolean _good = FALSE; \
+		gint64 *_out_idx = (out_idx); \
 		\
-		(   (strncmp (_key, tab_name, NM_STRLEN (tab_name)) == 0) \
-		 && _is_all_digits (&_key[NM_STRLEN (tab_name)])); \
+		if (    IS_NUMBERED_TAG (_key, ""tab_name"") \
+		    && (_idx = _nm_utils_ascii_str_to_int64 (&_key[NM_STRLEN (tab_name)], 10, 0, G_MAXINT64, -1)) != -1) { \
+			NM_SET_OUT (_out_idx, _idx); \
+			_good = TRUE; \
+		} \
+		_good; \
 	})
 
 static gboolean
@@ -919,10 +934,30 @@ _svKeyMatchesType (const char *key, SvKeyType match_key_type)
 		if (IS_NUMBERED_TAG (key, "SRIOV_VF"))
 			return TRUE;
 	}
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) {
+		if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", NULL))
+			return TRUE;
+	}
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) {
+		if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", NULL))
+			return TRUE;
+	}
 
 	return FALSE;
 }
 
+gint64
+svNumberedParseKey (const char *key)
+{
+	gint64 idx;
+
+	if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", &idx))
+		return idx;
+	if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", &idx))
+		return idx;
+	return -1;
+}
+
 GHashTable *
 svGetKeys (shvarFile *s, SvKeyType match_key_type)
 {
@@ -947,6 +982,42 @@ svGetKeys (shvarFile *s, SvKeyType match_key_type)
 	return keys;
 }
 
+static int
+_get_keys_sorted_cmp (gconstpointer a,
+                      gconstpointer b,
+                      gpointer user_data)
+{
+	const char *k_a = *((const char *const*) a);
+	const char *k_b = *((const char *const*) b);
+	gint64 n_a;
+	gint64 n_b;
+
+	n_a = svNumberedParseKey (k_a);
+	n_b = svNumberedParseKey (k_b);
+	NM_CMP_DIRECT (n_a, n_b);
+	NM_CMP_RETURN (strcmp (k_a, k_b));
+	nm_assert_not_reached ();
+	return 0;
+}
+
+const char **
+svGetKeysSorted (shvarFile *s,
+                 SvKeyType match_key_type,
+                 guint *out_len)
+{
+	gs_unref_hashtable GHashTable *keys_hash = NULL;
+
+	keys_hash = svGetKeys (s, match_key_type);
+	if (!keys_hash) {
+		NM_SET_OUT (out_len, 0);
+		return NULL;
+	}
+	return (const char **) nm_utils_hash_keys_to_array (keys_hash,
+	                                                    _get_keys_sorted_cmp,
+	                                                    NULL,
+	                                                    out_len);
+}
+
 /*****************************************************************************/
 
 const char *
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index 622bb474..b38a8557 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -40,6 +40,8 @@ typedef enum {
 	SV_KEY_TYPE_TC                      = (1LL << 3),
 	SV_KEY_TYPE_USER                    = (1LL << 4),
 	SV_KEY_TYPE_SRIOV_VF                = (1LL << 5),
+	SV_KEY_TYPE_ROUTING_RULE4           = (1LL << 6),
+	SV_KEY_TYPE_ROUTING_RULE6           = (1LL << 7),
 } SvKeyType;
 
 const char *svFileGetName (const shvarFile *s);
@@ -67,8 +69,14 @@ char *svGetValueStr_cp (shvarFile *s, const char *key);
 
 int svParseBoolean (const char *value, int def);
 
+gint64 svNumberedParseKey (const char *key);
+
 GHashTable *svGetKeys (shvarFile *s, SvKeyType match_key_type);
 
+const char **svGetKeysSorted (shvarFile *s,
+                              SvKeyType match_key_type,
+                              guint *out_len);
+
 /* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
  * return FALSE if <key> resolves to any non-truth value (e.g. "no", "n", "false")
  * return <def> otherwise
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
index c478db38..a8ff8df3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
@@ -2,6 +2,7 @@ HWADDR=31:33:33:37:BE:CD
 MTU=1492
 TYPE=Ethernet
 BRIDGING_OPTS="priority=50 path_cost=33"
+BRIDGE_PORT_VLANS="1 untagged,2 pvid,4-4094 untagged"
 NAME="Test Write Bridge Component"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
new file mode 100644
index 00000000..0c2fa035
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
@@ -0,0 +1,19 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+ROUTING_RULE_1="priority 10 from 0.0.0.0/0 table 1"
+ROUTING_RULE_2="priority 10 to 192.167.8.0/24 table 2"
+ROUTING_RULE6_3="priority 10 from ::/0 table 10"
+ROUTING_RULE6_4="priority 10 to 1:2:3::5/24 table 22"
+ROUTING_RULE6_5="priority 10 to 1:3:3::5 table 55"
+NAME="Test Write Routing Rules"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
index 2bc987c2..1788efe2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
@@ -4,5 +4,5 @@ TYPE=Bridge
 BOOTPROTO=dhcp
 STP=on
 DELAY=2
-BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0 group_fwd_mask=24"
+BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0 group_fwd_mask=24 vlan_filtering=1 default_pvid=99"
 MACADDR=00:16:41:11:22:33
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index b352fbfc..49ab04d4 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -49,7 +49,7 @@
 #include "nm-setting-vlan.h"
 #include "nm-setting-dcb.h"
 #include "nm-core-internal.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "NetworkManagerUtils.h"
 
@@ -4465,6 +4465,101 @@ test_write_wired_dhcp (void)
 	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
 }
 
+static NMIPRoutingRule *
+_ip_routing_rule_new (int addr_family,
+                      const char *str)
+{
+	NMIPRoutingRuleAsStringFlags flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE;
+	gs_free_error GError *local = NULL;
+	NMIPRoutingRule *rule;
+
+	if (addr_family != AF_UNSPEC) {
+		if (addr_family == AF_INET)
+			flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET;
+		else {
+			g_assert (addr_family == AF_INET6);
+			flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6;
+		}
+	}
+
+	rule = nm_ip_routing_rule_from_string (str,
+	                                         NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
+	                                       | flags,
+	                                       NULL,
+	                                       nmtst_get_rand_bool () ? &local : NULL);
+	nmtst_assert_success (rule, local);
+
+	if (addr_family != AF_UNSPEC)
+		g_assert_cmpint (nm_ip_routing_rule_get_addr_family (rule), ==, addr_family);
+	return rule;
+}
+
+static void
+_ip_routing_rule_add_to_setting (NMSettingIPConfig *s_ip,
+                                 const char *str)
+{
+	nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+
+	rule = _ip_routing_rule_new (nm_setting_ip_config_get_addr_family (s_ip), str);
+	nm_setting_ip_config_add_routing_rule (s_ip, rule);
+}
+
+static void
+test_write_routing_rules (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWired *s_wired;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+
+	connection = nm_simple_connection_new ();
+
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Write Routing Rules",
+	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME,
+	              NULL);
+
+	s_wired = (NMSettingWired *) nm_setting_wired_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
+
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+	              NULL);
+
+	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+
+	g_object_set (s_ip6,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	              NULL);
+
+	_ip_routing_rule_add_to_setting (s_ip4, "pref 10 from 0.0.0.0/0 table 1");
+	_ip_routing_rule_add_to_setting (s_ip4, "priority 10 to 192.167.8.0/24 table 2");
+	_ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 table 10");
+	_ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 to 1:2:3::5/24 table 22");
+	_ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 to 1:3:3::5/128 table 55");
+
+	nmtst_assert_connection_verifies (connection);
+
+	_writer_new_connec_exp (connection,
+	                        TEST_SCRATCH_DIR,
+	                        TEST_IFCFG_DIR"/ifcfg-Test_Write_Routing_Rules.cexpected",
+	                        &testfile);
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
 static void
 test_write_wired_match (void)
 {
@@ -7507,6 +7602,8 @@ test_read_bridge_main (void)
 	g_assert_cmpuint (nm_setting_bridge_get_ageing_time (s_bridge), ==, 235352);
 	g_assert_cmpuint (nm_setting_bridge_get_group_forward_mask (s_bridge), ==, 24);
 	g_assert (!nm_setting_bridge_get_multicast_snooping (s_bridge));
+	g_assert_cmpint (nm_setting_bridge_get_vlan_filtering (s_bridge), ==, TRUE);
+	g_assert_cmpint (nm_setting_bridge_get_vlan_default_pvid (s_bridge), ==, 99);
 
 	/* MAC address */
 	s_wired = nm_connection_get_setting_wired (connection);
@@ -7531,6 +7628,8 @@ test_write_bridge_main (void)
 	NMIPAddress *addr;
 	static const char *mac = "31:33:33:37:be:cd";
 	GError *error = NULL;
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	NMBridgeVlan *vlan;
 
 	connection = nm_simple_connection_new ();
 	g_assert (connection);
@@ -7551,9 +7650,23 @@ test_write_bridge_main (void)
 	s_bridge = (NMSettingBridge *) nm_setting_bridge_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_bridge));
 
+	vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+	vlan = nm_bridge_vlan_new (10, 16);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (22, 22);
+	nm_bridge_vlan_set_pvid (vlan, TRUE);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (44, 0);
+	g_ptr_array_add (vlans, vlan);
+
 	g_object_set (s_bridge,
 	              NM_SETTING_BRIDGE_MAC_ADDRESS, mac,
 	              NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, 19008,
+	              NM_SETTING_BRIDGE_VLAN_FILTERING, TRUE,
+	              NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, 4000,
+	              NM_SETTING_BRIDGE_VLANS, vlans,
 	              NULL);
 
 	/* IP4 setting */
@@ -7631,6 +7744,8 @@ test_write_bridge_component (void)
 	NMSetting *s_port;
 	static const char *mac = "31:33:33:37:be:cd";
 	guint32 mtu = 1492;
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	NMBridgeVlan *vlan;
 
 	connection = nm_simple_connection_new ();
 	g_assert (connection);
@@ -7658,11 +7773,23 @@ test_write_bridge_component (void)
 	              NULL);
 
 	/* Bridge port */
+	vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+	vlan = nm_bridge_vlan_new (1, 0);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (4, 4094);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (2, 2);
+	nm_bridge_vlan_set_pvid (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+
 	s_port = nm_setting_bridge_port_new ();
 	nm_connection_add_setting (connection, s_port);
 	g_object_set (s_port,
 	              NM_SETTING_BRIDGE_PORT_PRIORITY, 50,
 	              NM_SETTING_BRIDGE_PORT_PATH_COST, 33,
+	              NM_SETTING_BRIDGE_PORT_VLANS, vlans,
 	              NULL);
 
 	nmtst_assert_connection_verifies (connection);
@@ -10168,6 +10295,7 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "wired/write-dhcp-plus-ip", test_write_wired_dhcp_plus_ip);
 	g_test_add_func (TPATH "wired/write/dhcp-8021x-peap-mschapv2", test_write_wired_dhcp_8021x_peap_mschapv2);
 	g_test_add_func (TPATH "wired/write/match", test_write_wired_match);
+	g_test_add_func (TPATH "wired/write/routing-rules", test_write_routing_rules);
 
 #define _add_test_write_wired_8021x_tls(testpath, scheme, flags) \
 	nmtst_add_test_func (testpath, test_write_wired_8021x_tls, GINT_TO_POINTER (scheme), GINT_TO_POINTER (flags))
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index fd5561ae..239f6415 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -419,17 +419,15 @@ update_wired_setting_from_if_block (NMConnection *connection,
 static void
 ifupdown_ip4_add_dns (NMSettingIPConfig *s_ip4, const char *dns)
 {
+	gs_free const char **list = NULL;
+	const char **iter;
 	guint32 addr;
-	gs_strfreev char **list = NULL;
-	char **iter;
 
 	if (dns == NULL)
 		return;
 
-	list = g_strsplit_set (dns, " \t", -1);
+	list = nm_utils_strsplit_set (dns, " \t");
 	for (iter = list; iter && *iter; iter++) {
-		if ((*iter)[0] == '\0')
-			continue;
 		if (!inet_pton (AF_INET, *iter, &addr)) {
 			_LOGW ("    ignoring invalid nameserver '%s'", *iter);
 			continue;
@@ -524,13 +522,11 @@ update_ip4_setting_from_if_block (NMConnection *connection,
 		/* DNS searches */
 		search_v = ifparser_getkey (block, "dns-search");
 		if (search_v) {
-			gs_strfreev char **list = NULL;
-			char **iter;
+			gs_free const char **list = NULL;
+			const char **iter;
 
-			list = g_strsplit_set (search_v, " \t", -1);
+			list = nm_utils_strsplit_set (search_v, " \t");
 			for (iter = list; iter && *iter; iter++) {
-				if ((*iter)[0] == '\0')
-					continue;
 				if (!nm_setting_ip_config_add_dns_search (s_ip4, *iter))
 					_LOGW ("    duplicate DNS domain '%s'", *iter);
 			}
@@ -546,17 +542,15 @@ update_ip4_setting_from_if_block (NMConnection *connection,
 static void
 ifupdown_ip6_add_dns (NMSettingIPConfig *s_ip6, const char *dns)
 {
+	gs_free const char **list = NULL;
+	const char **iter;
 	struct in6_addr addr;
-	gs_strfreev char **list = NULL;
-	char **iter;
 
 	if (dns == NULL)
 		return;
 
-	list = g_strsplit_set (dns, " \t", -1);
+	list = nm_utils_strsplit_set (dns, " \t");
 	for (iter = list; iter && *iter; iter++) {
-		if ((*iter)[0] == '\0')
-			continue;
 		if (!inet_pton (AF_INET6, *iter, &addr)) {
 			_LOGW ("    ignoring invalid nameserver '%s'", *iter);
 			continue;
@@ -640,13 +634,11 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 		/* DNS searches */
 		search_v = ifparser_getkey (block, "dns-search");
 		if (search_v) {
-			gs_strfreev char **list = NULL;
-			char **iter;
+			gs_free const char **list = NULL;
+			const char **iter;
 
-			list = g_strsplit_set (search_v, " \t", -1);
+			list = nm_utils_strsplit_set (search_v, " \t");
 			for (iter = list; iter && *iter; iter++) {
-				if ((*iter)[0] == '\0')
-					continue;
 				if (!nm_setting_ip_config_add_dns_search (s_ip6, *iter))
 					_LOGW ("    duplicate DNS domain '%s'", *iter);
 			}
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 99a59477..04281f00 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -175,14 +175,14 @@ initialize (NMSettingsPlugin *plugin)
 				const char *ports = ifparser_getkey (block, "bridge-ports");
 
 				if (ports) {
-					guint i;
 					int state = 0;
-					gs_strfreev char **port_ifaces = NULL;
+					gs_free const char **port_ifaces = NULL;
+					gsize i;
 
 					_LOGD ("parse: found bridge ports %s for %s", ports, block->name);
 
-					port_ifaces = g_strsplit_set (ports, " \t", -1);
-					for (i = 0; port_ifaces[i]; i++) {
+					port_ifaces = nm_utils_strsplit_set (ports, " \t");
+					for (i = 0; port_ifaces && port_ifaces[i]; i++) {
 						const char *token = port_ifaces[i];
 
 						/* Skip crazy stuff like regex or all */
@@ -200,7 +200,7 @@ initialize (NMSettingsPlugin *plugin)
 						}
 						if (nm_streq (token, "none"))
 							continue;
-						if (state == 0 && strlen (token) > 0) {
+						if (state == 0) {
 							conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
 							if (!conn) {
 								_LOGD ("parse: adding bridge port \"%s\"", token);
@@ -280,7 +280,7 @@ initialize (NMSettingsPlugin *plugin)
 		GHashTableIter iter;
 
 		g_hash_table_iter_init (&iter, priv->eni_ifaces);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) conn)) {
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &conn)) {
 			if (conn) {
 				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
 				                                                  NM_SETTINGS_CONNECTION (conn));
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 8c75d8c7..2e06aeb9 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -32,7 +32,7 @@
 #include "nms-keyfile-utils.h"
 #include "nms-keyfile-reader.h"
 
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 
 /*****************************************************************************/