summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-06-29 22:15:58 +0200
committerMichael Biebl <biebl@debian.org>2020-06-29 22:15:58 +0200
commit10ae7d8cd706062742d0cdb1803d49909aef9e06 (patch)
treecb89e8b475cec18f22b1abfe45f1d63da7e3f440 /src
parenta54ac63bbf9b2c71026ac9028a8ffaf186cf3c82 (diff)
New upstream version 1.25.91 upstream/1.25.91
Diffstat (limited to 'src')
-rw-r--r--src/devices/nm-device.c89
-rw-r--r--src/initrd/nmi-cmdline-reader.c4
-rw-r--r--src/initrd/tests/test-cmdline-reader.c17
-rw-r--r--src/nm-core-utils.c166
-rw-r--r--src/nm-core-utils.h5
-rw-r--r--src/nm-ip4-config.c20
-rw-r--r--src/platform/nm-linux-platform.c10
-rw-r--r--src/platform/nm-platform.c24
-rw-r--r--src/platform/nm-platform.h40
-rw-r--r--src/platform/tests/test-route.c4
-rw-r--r--src/settings/nm-settings.c7
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c40
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c8
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes4
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c18
-rw-r--r--src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection4
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile-settings.c9
-rw-r--r--src/supplicant/nm-supplicant-interface.c1
-rw-r--r--src/tests/test-core.c56
20 files changed, 400 insertions, 127 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 63fa5a5c..90178b68 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -6353,85 +6353,13 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 			return FALSE;
 		}
 
-		{
-			const char *const*proc_cmdline;
-			gboolean pos_patterns = FALSE;
-			guint i;
-
-			patterns = nm_setting_match_get_kernel_command_lines (s_match, &num_patterns);
-			proc_cmdline = nm_utils_proc_cmdline_split ();
-
-			for (i = 0; i < num_patterns; i++) {
-				const char *patterns_i = patterns[i];
-				const char *const*proc_cmdline_i;
-				gboolean negative = FALSE;
-				gboolean found = FALSE;
-				const char *equal;
-
-				if (patterns_i[0] == '!') {
-					++patterns_i;
-					negative = TRUE;
-				} else
-					pos_patterns = TRUE;
-
-				equal = strchr (patterns_i, '=');
-
-				proc_cmdline_i = proc_cmdline;
-				while (*proc_cmdline_i) {
-					if (equal) {
-						/* if pattern contains = compare full key=value */
-						found = nm_streq (*proc_cmdline_i, patterns_i);
-					} else {
-						gsize l = strlen (patterns_i);
-
-						/* otherwise consider pattern as key only */
-						if (   strncmp (*proc_cmdline_i, patterns_i, l) == 0
-						    && NM_IN_SET ((*proc_cmdline_i)[l], '\0', '='))
-							found = TRUE;
-					}
-					if (   found
-					    && negative) {
-						/* first negative match */
-						nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-						                    "device does not satisfy match.kernel-command-line property %s",
-						                    patterns[i]);
-						return FALSE;
-					}
-					proc_cmdline_i++;
-				}
-
-				/* FIXME(release-blocker): match.interface-name and match.driver have the meaning,
-				 * that any of the matches may yield success. For match.kernel-command-line, we
-				 * do here that all must match. This inconsistency is undesired.
-				 *
-				 * 1) improve gtk-doc documentation explaining how these options match.
-				 *
-				 * 2) possibly unify the behavior so that kernel-command-line behaves like other
-				 *    matches (and ANY may match). Note that this would be contrary to systemd's
-				 *    Conditions, which by default requires that ALL conditions match (AND). We
-				 *    should be consistent within our match options, and not with systemd here.
-				 *
-				 * 2b) Note that systemd supports special token like "=|", to indicate that
-				 *    ANY behavior. If we want, we could also introduce two special prefixes
-				 *    "&..." and "|...", to support either. It's slightly complicated how
-				 *    these work in combinations with "!".
-				 *    Unless we fully decide what we do about this, NMSettingMatch.verify() should
-				 *    reject matches that start with '&' or '|', because these will be reserved for
-				 *    future use.
-				 *
-				 * 3) while fixing this, this code should move to a separate function so we
-				 *    can unit test the match of kernel command lines.
-				 */
-				if (   pos_patterns
-				    && !found) {
-					/* positive patterns configured but no match */
-					nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-					                    "device does not satisfy any match.kernel-command-line property %s...",
-					                    patterns[0]);
-					return FALSE;
-				}
-			}
-		}
+		patterns = nm_setting_match_get_kernel_command_lines (s_match, &num_patterns);
+		if (   num_patterns > 0
+		    && !nm_utils_kernel_cmdline_match_check (nm_utils_proc_cmdline_split (),
+		                                             patterns,
+		                                             num_patterns,
+		                                             error))
+			return FALSE;
 
 		device_driver = nm_device_get_driver (self);
 		patterns = nm_setting_match_get_drivers (s_match, &num_patterns);
@@ -10934,8 +10862,11 @@ act_stage3_ip_config_start (NMDevice *self,
 					platform = nm_device_get_platform (self);
 
 					if (ifindex > 0) {
+						gs_unref_object NMIP6Config *config = nm_device_ip6_config_new (self);
+
 						nm_platform_ip_route_flush (platform, AF_INET6, ifindex);
 						nm_platform_ip_address_flush (platform, AF_INET6, ifindex);
+						nm_device_set_ip_config (self, AF_INET6, (NMIPConfig *) config, FALSE, NULL);
 					}
 				} else {
 					gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index 7513b4c9..69e5e56d 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -464,7 +464,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 	if (NM_IN_STRSET (kind, "none", "off")) {
 		if (nm_setting_ip_config_get_num_addresses (s_ip6) == 0) {
 			g_object_set (s_ip6,
-			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
 			              NULL);
 		}
 		if (nm_setting_ip_config_get_num_addresses (s_ip4) == 0) {
@@ -479,7 +479,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 		              NULL);
 		if (nm_setting_ip_config_get_num_addresses (s_ip6) == 0) {
 			g_object_set (s_ip6,
-			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
 			              NULL);
 		}
 	} else if (nm_streq0 (kind, "dhcp6")) {
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index 5379c3a3..11077055 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -186,11 +186,12 @@ test_if_ip4_manual (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	const char *const*ARGV = NM_MAKE_STRV ("ip=192.0.2.2::192.0.2.1:255.255.255.0:"
-	                                       "hostname0.example.com:eth3::192.0.2.53",
+	                                       "hostname0.example.com:eth3:none:192.0.2.53",
 	                                       "ip=203.0.113.2::203.0.113.1:26:"
 	                                       "hostname1.example.com:eth4");
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
 	NMIPAddress *ip_addr;
 	gs_free char *hostname = NULL;
 
@@ -219,6 +220,11 @@ test_if_ip4_manual (void)
 	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "192.0.2.1");
 	g_assert_cmpstr (nm_setting_ip_config_get_dhcp_hostname (s_ip4), ==, "hostname0.example.com");
 
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip6);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+	g_assert (nm_setting_ip_config_get_may_fail (s_ip6));
+
 	connection = g_hash_table_lookup (connections, "eth4");
 	g_assert (connection);
 	nmtst_assert_connection_verifies_without_normalization (connection);
@@ -237,6 +243,11 @@ test_if_ip4_manual (void)
 	g_assert_cmpint (nm_ip_address_get_prefix (ip_addr), ==, 26);
 	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "203.0.113.1");
 	g_assert_cmpstr (nm_setting_ip_config_get_dhcp_hostname (s_ip4), ==, "hostname1.example.com");
+
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip6);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+	g_assert (nm_setting_ip_config_get_may_fail (s_ip6));
 }
 
 static void
@@ -1086,7 +1097,7 @@ test_bootif (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip6));
 }
 
@@ -1124,7 +1135,7 @@ test_bootif_hwtype (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip6));
 
 	connection = g_hash_table_lookup (connections, "bootif_connection");
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index 019d1e60..3950c3c3 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -1698,30 +1698,109 @@ nm_match_spec_join (GSList *specs)
 	return g_string_free (str, FALSE);
 }
 
+static void
+_pattern_parse (const char *input,
+                const char **out_pattern,
+                gboolean *out_is_inverted,
+                gboolean *out_is_mandatory)
+{
+	gboolean is_inverted = FALSE;
+	gboolean is_mandatory = FALSE;
+
+	if (input[0] == '&') {
+		input++;
+		is_mandatory = TRUE;
+		if (input[0] == '!') {
+			input++;
+			is_inverted = TRUE;
+		}
+		goto out;
+	}
+
+	if (input[0] == '|') {
+		input++;
+		if (input[0] == '!') {
+			input++;
+			is_inverted = TRUE;
+		}
+		goto out;
+	}
+
+	if (input[0] == '!') {
+		input++;
+		is_inverted = TRUE;
+		is_mandatory = TRUE;
+		goto out;
+	}
+
+out:
+	if (input[0] == '\\')
+		input++;
+
+	*out_pattern = input;
+	*out_is_inverted = is_inverted;
+	*out_is_mandatory = is_mandatory;
+}
+
 gboolean
 nm_wildcard_match_check (const char *str,
                          const char *const *patterns,
                          guint num_patterns)
 {
-	gsize i, neg = 0;
+	gboolean has_optional = FALSE;
+	gboolean has_any_optional = FALSE;
+	guint i;
 
 	for (i = 0; i < num_patterns; i++) {
-		if (patterns[i][0] == '!') {
-			neg++;
-			if (!str)
-				continue;
-			if (!fnmatch (patterns[i] + 1, str, 0))
+		gboolean is_inverted;
+		gboolean is_mandatory;
+		gboolean match;
+		const char *p;
+
+		_pattern_parse (patterns[i], &p, &is_inverted, &is_mandatory);
+
+		match = (fnmatch (p, str, 0) == 0);
+		if (is_inverted)
+			match = !match;
+
+		if (is_mandatory) {
+			if (!match)
 				return FALSE;
+		} else {
+			has_any_optional = TRUE;
+			if (match)
+				has_optional = TRUE;
 		}
 	}
 
-	if (neg == num_patterns)
-		return TRUE;
+	return    has_optional
+	       || !has_any_optional;
+}
+
+/*****************************************************************************/
 
-	if (str) {
-		for (i = 0; i < num_patterns; i++) {
-			if (   patterns[i][0] != '!'
-			    && !fnmatch (patterns[i], str, 0))
+static gboolean
+_kernel_cmdline_match (const char *const*proc_cmdline,
+                       const char *pattern)
+{
+
+	if (proc_cmdline) {
+		gboolean has_equal = (!!strchr (pattern, '='));
+		gsize pattern_len = strlen (pattern);
+
+		for (; proc_cmdline[0]; proc_cmdline++) {
+			const char *c = proc_cmdline[0];
+
+			if (has_equal) {
+				/* if pattern contains '=' compare full key=value */
+				if (nm_streq (c, pattern))
+					return TRUE;
+				continue;
+			}
+
+			/* otherwise consider pattern as key only */
+			if (   strncmp (c, pattern, pattern_len) == 0
+			    && NM_IN_SET (c[pattern_len], '\0', '='))
 				return TRUE;
 		}
 	}
@@ -1729,6 +1808,53 @@ nm_wildcard_match_check (const char *str,
 	return FALSE;
 }
 
+gboolean
+nm_utils_kernel_cmdline_match_check (const char *const*proc_cmdline,
+                                     const char *const*patterns,
+                                     guint num_patterns,
+                                     GError **error)
+{
+	gboolean has_optional = FALSE;
+	gboolean has_any_optional = FALSE;
+	guint i;
+
+	for (i = 0; i < num_patterns; i++) {
+		const char *element = patterns[i];
+		gboolean is_inverted = FALSE;
+		gboolean is_mandatory = FALSE;
+		gboolean match;
+		const char *p;
+
+		_pattern_parse (element, &p, &is_inverted, &is_mandatory);
+
+		match = _kernel_cmdline_match (proc_cmdline, p);
+		if (is_inverted)
+			match = !match;
+
+		if (is_mandatory) {
+			if (!match) {
+				nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                    "device does not satisfy match.kernel-command-line property %s",
+				                    patterns[i]);
+				return FALSE;
+			}
+		} else {
+			has_any_optional = TRUE;
+			if (match)
+				has_optional = TRUE;
+		}
+	}
+
+	if (   !has_optional
+	    && has_any_optional) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                    "device does not satisfy any match.kernel-command-line property");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 char *
@@ -2778,19 +2904,13 @@ nm_utils_proc_cmdline_split (void)
 again:
 	proc_cmdline = g_atomic_pointer_get (&proc_cmdline_cached);
 	if (G_UNLIKELY (!proc_cmdline)) {
-		gs_free const char **split = NULL;
-
-		/* FIXME(release-blocker): support quotation, like systemd's proc_cmdline_extract_first().
-		 * For that, add a new NMUtilsStrsplitSetFlags flag. */
-		split = nm_utils_strsplit_set_full (nm_utils_proc_cmdline (),
-		                                    NM_ASCII_WHITESPACES,
-		                                    NM_UTILS_STRSPLIT_SET_FLAGS_NONE);
-		proc_cmdline =    split
-		               ?: NM_PTRARRAY_EMPTY (const char *);
-		if (!g_atomic_pointer_compare_and_exchange (&proc_cmdline_cached, NULL, proc_cmdline))
+		gs_strfreev char **split = NULL;
+
+		split = nm_utils_strsplit_quoted (nm_utils_proc_cmdline ());
+		if (!g_atomic_pointer_compare_and_exchange (&proc_cmdline_cached, NULL, (gpointer) split))
 			goto again;
 
-		g_steal_pointer (&split);
+		proc_cmdline = (const char *const*) g_steal_pointer (&split);
 	}
 
 	return proc_cmdline;
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index e30d7b36..fae7adbf 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -230,6 +230,11 @@ gboolean nm_wildcard_match_check (const char *str,
                                   const char *const *patterns,
                                   guint num_patterns);
 
+gboolean nm_utils_kernel_cmdline_match_check (const char *const*proc_cmdline,
+                                              const char *const*patterns,
+                                              guint num_patterns,
+                                              GError **error);
+
 /*****************************************************************************/
 
 gboolean nm_utils_connection_has_default_route (NMConnection *connection,
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 0feb7b2c..62b41478 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -860,8 +860,26 @@ _nm_ip_config_merge_route_attributes (int addr_family,
 			(dst) = (dflt); \
 	} G_STMT_END
 
+	if (   (variant = nm_ip_route_get_attribute (s_route, NM_IP_ROUTE_ATTRIBUTE_TYPE))
+	    && g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
+		guint8 type;
+
+		type = nm_utils_route_type_by_name (g_variant_get_string (variant, NULL));
+		nm_assert (NM_IN_SET (type,
+		                      RTN_UNICAST,
+		                      RTN_LOCAL));
+
+		r->type_coerced = nm_platform_route_type_coerce (type);
+	} else
+		r->type_coerced = nm_platform_route_type_coerce (RTN_UNICAST);
+
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TABLE, table, UINT32, uint32, 0);
-	r->table_coerced = nm_platform_route_table_coerce (table ?: (route_table ?: RT_TABLE_MAIN));
+
+	if (   !table
+	    && r->type_coerced == nm_platform_route_type_coerce (RTN_LOCAL))
+		r->table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL);
+	else
+		r->table_coerced = nm_platform_route_table_coerce (table ?: (route_table ?: RT_TABLE_MAIN));
 
 	if (addr_family == AF_INET) {
 		guint8 scope;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index e2c45c88..710a6f91 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -3257,14 +3257,16 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	rtm = nlmsg_data (nlh);
 
 	/*****************************************************************
-	 * only handle ~normal~ routes.
+	 * only handle ~supported~ routes.
 	 *****************************************************************/
 
 	if (!NM_IN_SET (rtm->rtm_family, AF_INET, AF_INET6))
 		return NULL;
 
-	if (rtm->rtm_type != RTN_UNICAST)
-		return NULL;
+	if (!NM_IN_SET (rtm->rtm_type,
+	                RTN_UNICAST,
+	                RTN_LOCAL))
+	    return NULL;
 
 	if (nlmsg_parse_arr (nlh,
 	                     sizeof (struct rtmsg),
@@ -4491,7 +4493,7 @@ _nl_msg_new_route (int nlmsg_type,
 		.rtm_scope = is_v4
 		             ? nm_platform_route_scope_inv (obj->ip4_route.scope_inv)
 		             : RT_SCOPE_NOWHERE,
-		.rtm_type = RTN_UNICAST,
+		.rtm_type = nm_platform_route_type_uncoerce (NMP_OBJECT_CAST_IP_ROUTE (obj)->type_coerced),
 		.rtm_flags = obj->ip_route.r_rtm_flags & ((unsigned) (RTNH_F_ONLINK)),
 		.rtm_dst_len = obj->ip_route.plen,
 		.rtm_src_len = is_v4
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index c023f435..c8b8c6e8 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -4518,8 +4518,12 @@ _ip_route_scope_inv_get_normalized (const NMPlatformIP4Route *route)
 	 * so that the default equals zero (~(RT_SCOPE_NOWHERE)).
 	 **/
 	if (route->scope_inv == 0) {
-		return nm_platform_route_scope_inv (!route->gateway
-		                                    ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE);
+		if (route->type_coerced == nm_platform_route_type_coerce (RTN_LOCAL))
+			return nm_platform_route_scope_inv (RT_SCOPE_HOST);
+		else {
+			return nm_platform_route_scope_inv (!route->gateway
+			                                    ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE);
+		}
 	}
 	return route->scope_inv;
 }
@@ -6079,6 +6083,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN];
 	char s_pref_src[INET_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char str_type[30];
 	char str_table[30];
 	char str_scope[30], s_source[50];
 	char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
@@ -6093,6 +6098,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
 	g_snprintf (buf, len,
+	            "%s" /* type */
 	            "%s" /* table */
 	            "%s/%d"
 	            " via %s"
@@ -6110,6 +6116,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            "%s" /* initrwnd */
 	            "%s" /* mtu */
 	            "",
+	            route->type_coerced ? nm_sprintf_buf (str_type, "type %s ", nm_utils_route_type2str (nm_platform_route_type_uncoerce (route->type_coerced), NULL, 0)) : "",
 	            route->table_coerced ? nm_sprintf_buf (str_table, "table %u ", nm_platform_route_table_uncoerce (route->table_coerced, FALSE)) : "",
 	            s_network,
 	            route->plen,
@@ -6152,6 +6159,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	char s_pref_src[INET6_ADDRSTRLEN];
 	char s_src_all[INET6_ADDRSTRLEN + 40];
 	char s_src[INET6_ADDRSTRLEN];
+	char str_type[30];
 	char str_table[30];
 	char str_pref[40];
 	char str_pref2[30];
@@ -6178,6 +6186,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
 	g_snprintf (buf, len,
+	            "%s" /* type */
 	            "%s" /* table */
 	            "%s/%d"
 	            " via %s"
@@ -6195,6 +6204,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            "%s" /* mtu */
 	            "%s" /* pref */
 	            "",
+	            route->type_coerced ? nm_sprintf_buf (str_type, "type %s ", nm_utils_route_type2str (nm_platform_route_type_uncoerce (route->type_coerced), NULL, 0)) : "",
 	            route->table_coerced ? nm_sprintf_buf (str_table, "table %u ", nm_platform_route_table_uncoerce (route->table_coerced, FALSE)) : "",
 	            s_network,
 	            route->plen,
@@ -7274,6 +7284,7 @@ nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRo
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
 		nm_hash_update_vals (h,
+		                     obj->type_coerced,
 		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
 		                     nm_utils_ip4_address_clear_host_address (obj->network, obj->plen),
 		                     obj->plen,
@@ -7301,6 +7312,7 @@ nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRo
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
 		nm_hash_update_vals (h,
+		                     obj->type_coerced,
 		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
 		                     obj->ifindex,
 		                     nm_utils_ip4_address_clear_host_address (obj->network, obj->plen),
@@ -7327,6 +7339,7 @@ nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRo
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
 		nm_hash_update_vals (h,
+		                     obj->type_coerced,
 		                     obj->table_coerced,
 		                     obj->ifindex,
 		                     obj->network,
@@ -7369,6 +7382,7 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 		NM_CMP_FIELD (a, b, tos);
 		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
 			NM_CMP_FIELD (a, b, ifindex);
+			NM_CMP_FIELD (a, b, type_coerced);
 			NM_CMP_DIRECT (nmp_utils_ip_config_source_round_trip_rtprot (a->rt_source),
 			               nmp_utils_ip_config_source_round_trip_rtprot (b->rt_source));
 			NM_CMP_DIRECT (_ip_route_scope_inv_get_normalized (a),
@@ -7392,6 +7406,7 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
+		NM_CMP_FIELD (a, b, type_coerced);
 		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
 			NM_CMP_DIRECT (nm_platform_route_table_uncoerce (a->table_coerced, TRUE),
 			               nm_platform_route_table_uncoerce (b->table_coerced, TRUE));
@@ -7454,6 +7469,7 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
 		nm_hash_update_vals (h,
+		                     obj->type_coerced,
 		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
 		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
 		                     obj->plen,
@@ -7466,6 +7482,7 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
 		nm_hash_update_vals (h,
+		                     obj->type_coerced,
 		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
 		                     obj->ifindex,
 		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
@@ -7493,6 +7510,7 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
 		nm_hash_update_vals (h,
+		                     obj->type_coerced,
 		                     obj->table_coerced,
 		                     obj->ifindex,
 		                     obj->network,
@@ -7537,11 +7555,13 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 		NM_CMP_FIELD (a, b, src_plen);
 		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
 			NM_CMP_FIELD (a, b, ifindex);
+			NM_CMP_FIELD (a, b, type_coerced);
 			NM_CMP_FIELD_IN6ADDR (a, b, gateway);
 		}
 		break;
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
 	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
+		NM_CMP_FIELD (a, b, type_coerced);
 		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
 			NM_CMP_DIRECT (nm_platform_route_table_uncoerce (a->table_coerced, TRUE),
 			               nm_platform_route_table_uncoerce (b->table_coerced, TRUE));
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index aa3551a6..3e6ef84c 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -468,6 +468,13 @@ typedef union {
 	 * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */ \
 	guint32 table_coerced; \
 	\
+	/* rtm_type.
+	 *
+	 * This is not the original type, if type_coerced is 0 then
+	 * it means RTN_UNSPEC otherwise the type value is preserved.
+	 * */ \
+	guint8 type_coerced; \
+	\
 	/*end*/
 
 typedef struct {
@@ -1285,6 +1292,39 @@ _nm_platform_uint8_inv (guint8 scope)
 	return (guint8) ~scope;
 }
 
+/**
+ * nm_platform_route_type_coerce:
+ * @table: the route type, in its original value.
+ *
+ * Returns: returns the coerced type, that can be stored in
+ *   NMPlatformIPRoute.type_coerced.
+ */
+static inline guint8
+nm_platform_route_type_coerce (guint8 type)
+{
+	switch (type) {
+	case 0 /* RTN_UNSPEC */:
+		return 1;
+	case 1 /* RTN_UNICAST */:
+		return 0;
+	default:
+		return type;
+	}
+}
+
+/**
+ * nm_platform_route_type_uncoerce:
+ * @table: the type table, in its coerced value
+ *
+ * Returns: reverts the coerced type in NMPlatformIPRoute.type_coerced
+ *   to the original value as kernel understands it.
+ */
+static inline guint8
+nm_platform_route_type_uncoerce (guint8 type_coerced)
+{
+	return nm_platform_route_type_coerce (type_coerced);
+}
+
 gboolean nm_platform_get_use_udev (NMPlatform *self);
 gboolean nm_platform_get_log_with_ptr (NMPlatform *self);
 
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index f074ca69..19debfb5 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -328,13 +328,13 @@ test_ip6_route (void)
 
 	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, pref_src, 128, in6addr_any,
 	                                       NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0));
-	accept_signals (route_added, 0, 1);
+	accept_signals (route_added, 0, 2);
 
 	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 200, ifindex, 1, &pref_src);
 
 	/* Add route to gateway */
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway, 128, in6addr_any, in6addr_any, metric, mss);
-	accept_signal (route_added);
+	accept_signals (route_added, 0, 3);
 
 	/* Add route */
 	g_assert (!nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0));
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 5899b079..0a1e7b47 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -1996,11 +1996,10 @@ nm_settings_update_connection (NMSettings *self,
 		 */
 		device = nm_settings_connection_default_wired_get_device (sett_conn);
 		if (device) {
+
 			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_EXTERNAL));
+			nm_assert (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
+			                         NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED));
 
 			default_wired_clear_tag (self, device, sett_conn, FALSE);
 
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 d65ae555..1a9df92c 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -800,6 +800,7 @@ typedef struct {
 	union {
 		guint8 uint8;
 		guint32 uint32;
+		const char *str;
 		struct {
 			guint32 uint32;
 			bool lock:1;
@@ -815,6 +816,7 @@ typedef struct {
 
 enum {
 	/* route attributes */
+	PARSE_LINE_ATTR_ROUTE_TYPE,
 	PARSE_LINE_ATTR_ROUTE_TABLE,
 	PARSE_LINE_ATTR_ROUTE_SRC,
 	PARSE_LINE_ATTR_ROUTE_FROM,
@@ -844,6 +846,7 @@ enum {
 #define PARSE_LINE_TYPE_IFNAME            'i'
 #define PARSE_LINE_TYPE_FLAG              'f'
 #define PARSE_LINE_TYPE_ROUTE_SCOPE       'S'
+#define PARSE_LINE_TYPE_STRING            's'
 
 /**
  * parse_route_line:
@@ -875,6 +878,8 @@ parse_route_line (const char *line,
                   GError **error)
 {
 	static const ParseLineInfo parse_infos[] = {
+		[PARSE_LINE_ATTR_ROUTE_TYPE]      = { .key = NM_IP_ROUTE_ATTRIBUTE_TYPE,
+		                                      .type = PARSE_LINE_TYPE_STRING, },
 		[PARSE_LINE_ATTR_ROUTE_TABLE]     = { .key = NM_IP_ROUTE_ATTRIBUTE_TABLE,
 		                                      .type = PARSE_LINE_TYPE_UINT32, },
 		[PARSE_LINE_ATTR_ROUTE_SRC]       = { .key = NM_IP_ROUTE_ATTRIBUTE_SRC,
@@ -1010,6 +1015,23 @@ parse_route_line (const char *line,
 			}
 		}
 
+		p_info = &parse_infos[PARSE_LINE_ATTR_ROUTE_TYPE];
+		p_data = &parse_datas[PARSE_LINE_ATTR_ROUTE_TYPE];
+		if (   !p_data->has
+		    && NM_IN_STRSET (w,
+		                     "local",
+		                     "unicast",
+		                     "broadcast"
+		                     "multicast",
+		                     "throw",
+		                     "unreachable",
+		                     "prohibit",
+		                     "blackhole",
+		                     "nat")) {
+			p_data->has = TRUE;
+			goto parse_line_type_string;
+		}
+
 		/* "to" is also accepted unqualified... (once) */
 		p_info = &parse_infos[PARSE_LINE_ATTR_ROUTE_TO];
 		p_data = &parse_datas[PARSE_LINE_ATTR_ROUTE_TO];
@@ -1160,6 +1182,15 @@ parse_line_type_addr_with_prefix:
 		i_words++;
 		goto next;
 
+parse_line_type_string:
+		s = words[i_words];
+		if (!s)
+			goto err_word_missing_argument;
+
+		p_data->v.str = s;
+		i_words++;
+		goto next;
+
 err_word_missing_argument:
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Missing argument for \"%s\"", w);
@@ -1253,13 +1284,18 @@ next:
 			                           p_info->key,
 			                           g_variant_new_boolean (TRUE));
 			break;
+		case PARSE_LINE_TYPE_STRING:
+			nm_ip_route_set_attribute (route,
+			                           p_info->key,
+			                           g_variant_new_string (p_data->v.str));
+			break;
 		default:
 			nm_assert_not_reached ();
 			break;
 		}
 	}
 
-	nm_assert (_nm_ip_route_attribute_validate_all (route));
+	nm_assert (_nm_ip_route_attribute_validate_all (route, NULL));
 
 	NM_SET_OUT (out_route, g_steal_pointer (&route));
 	return 0;
@@ -2479,7 +2515,7 @@ make_tc_setting (shvarFile *ifcfg)
 		NMTCTfilter *tfilter = NULL;
 		gs_free char *value_to_free = NULL;
 		const char *value = NULL;
-		GError *local = NULL;
+		gs_free_error GError *local = NULL;
 
 		value = svGetValueStr (ifcfg, numbered_tag (tag, "FILTER", i), &value_to_free);
 		if (!value)
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 199e8e4e..e6526944 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -2099,7 +2099,15 @@ get_route_attributes_string (NMIPRoute *route, int family)
 
 	str = g_string_new ("");
 
+	attr = nm_ip_route_get_attribute (route, NM_IP_ROUTE_ATTRIBUTE_TYPE);
+	if (   attr
+	    && nm_ip_route_attribute_validate (NM_IP_ROUTE_ATTRIBUTE_TYPE, attr, family, NULL, NULL))
+		g_string_append_printf (str, "%s ", g_variant_get_string (attr, NULL));
+
 	for (i = 0; i < len; i++) {
+		if (nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_TYPE))
+			continue;
+
 		attr = nm_ip_route_get_attribute (route, names[i]);
 
 		if (!nm_ip_route_attribute_validate (names[i], attr, family, NULL, NULL))
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes b/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
index 5d02c62e..9c05417e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
@@ -13,3 +13,7 @@ NETMASK2=255.255.255.255
 GATEWAY2=192.168.1.8
 METRIC2=3
 OPTIONS2="mtu lock 9000 cwnd 12 src 1.1.1.1 tos 0x28 onlink window 30000 initcwnd lock 13 initrwnd 14 scope link"
+
+ADDRESS3=1.2.3.4
+NETMASK3=255.255.255.255
+OPTIONS3="local scope host"
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy b/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
index faa247d8..1fef7e97 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
@@ -6,3 +6,4 @@
  43.53.0.0/16 metric 3 via 7.7.7.7 dev eth2 cwnd 14 mtu lock 9000  initrwnd 20 window lock 10000 initcwnd lock 42 src 1.2.3.4
 
 7.7.7.8/32 via (null) metric 18
+local 1.2.3.4
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index a5025f3b..40d1bb8c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -1323,7 +1323,7 @@ test_read_wired_static_routes (void)
 	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
 
 	/* Routes */
-	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 3);
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 4);
 
 	ip4_route = nm_setting_ip_config_get_route (s_ip4, 0);
 	g_assert (ip4_route);
@@ -1367,6 +1367,13 @@ test_read_wired_static_routes (void)
 	nmtst_assert_route_attribute_boolean (ip4_route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, TRUE);
 	nmtst_assert_route_attribute_byte (ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 253);
 
+	ip4_route = nm_setting_ip_config_get_route (s_ip4, 3);
+	g_assert (ip4_route);
+	g_assert_cmpstr (nm_ip_route_get_dest (ip4_route), ==, "1.2.3.4");
+	g_assert_cmpint (nm_ip_route_get_prefix (ip4_route), ==, 32);
+	nmtst_assert_route_attribute_string (ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
+	nmtst_assert_route_attribute_byte (ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 254);
+
 	g_object_unref (connection);
 }
 
@@ -1402,7 +1409,7 @@ test_read_wired_static_routes_legacy (void)
 	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
 
 	/* Routes */
-	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 4);
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 5);
 
 	/* Route #1 */
 	ip4_route = nm_setting_ip_config_get_route (s_ip4, 0);
@@ -1443,6 +1450,13 @@ test_read_wired_static_routes_legacy (void)
 	g_assert_cmpstr (nm_ip_route_get_next_hop (ip4_route), ==, NULL);
 	g_assert_cmpint (nm_ip_route_get_metric (ip4_route), ==, 18);
 
+
+	/* Route #5 */
+	ip4_route = nm_setting_ip_config_get_route (s_ip4, 4);
+	g_assert (ip4_route != NULL);
+	g_assert_cmpstr (nm_ip_route_get_dest (ip4_route), ==, "1.2.3.4");
+	nmtst_assert_route_attribute_string (ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
+
 	g_object_unref (connection);
 }
 
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
index 1e62f4b3..f9ccc003 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
+++ b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
@@ -36,7 +36,9 @@ routes9=1.1.1.9/19,0.0.0.0,0
 route10=1.1.1.10/21,,0
 routes10=1.1.1.10/20,,0
 routes11=1.1.1.11/21,,21
-routes11_options=cwnd=10,lock-cwnd=true,mtu=1430,src=7.7.7.7
+routes11_options=cwnd=10,lock-cwnd=true,mtu=1430,src=7.7.7.7,type=unicast
+routes12=1.2.3.4/32
+routes12_options=type=local
 address30=1.2.3.30/24
 addresses30=1.2.3.30/25
 addresses31=1.2.3.31/25
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
index 1a9482e5..98820272 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -274,7 +274,7 @@ test_read_valid_wired_connection (void)
 	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "2.3.4.6");
 
 	/* IPv4 routes */
-	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 13);
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 14);
 	check_ip_route (s_ip4, 0, "5.6.7.8", 32, NULL, -1);
 	check_ip_route (s_ip4, 1, "1.2.3.0", 24, "2.3.4.8", 99);
 	check_ip_route (s_ip4, 2, "1.1.1.2", 12, NULL, -1);
@@ -288,6 +288,7 @@ test_read_valid_wired_connection (void)
 	check_ip_route (s_ip4, 10, "1.1.1.10", 21, NULL, 0);
 	check_ip_route (s_ip4, 11, "1.1.1.10", 20, NULL, 0);
 	check_ip_route (s_ip4, 12, "1.1.1.11", 21, NULL, 21);
+	check_ip_route (s_ip4, 13, "1.2.3.4", 32, NULL, -1);
 
 	/* Route attributes */
 	route = nm_setting_ip_config_get_route (s_ip4, 12);
@@ -297,6 +298,12 @@ test_read_valid_wired_connection (void)
 	nmtst_assert_route_attribute_uint32  (route, NM_IP_ROUTE_ATTRIBUTE_MTU, 1430);
 	nmtst_assert_route_attribute_boolean (route, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, TRUE);
 	nmtst_assert_route_attribute_string  (route, NM_IP_ROUTE_ATTRIBUTE_SRC, "7.7.7.7");
+	nmtst_assert_route_attribute_string  (route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "unicast");
+
+	route = nm_setting_ip_config_get_route (s_ip4, 13);
+	g_assert (route);
+
+	nmtst_assert_route_attribute_string  (route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 6bbeb1eb..3dda0fbf 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -3292,6 +3292,7 @@ dispose (GObject *object)
 	g_clear_object (&priv->supplicant_manager);
 	g_clear_object (&priv->dbus_connection);
 	nm_clear_g_free (&priv->ifname);
+	nm_clear_g_free (&priv->driver);
 	nm_assert (!priv->net_path);
 }
 
diff --git a/src/tests/test-core.c b/src/tests/test-core.c
index b90a8fac..099786ef 100644
--- a/src/tests/test-core.c
+++ b/src/tests/test-core.c
@@ -968,10 +968,17 @@ test_wildcard_match (void)
 	do_test_wildcard_match ("b",      TRUE,  "!!a");
 	do_test_wildcard_match ("!a",     FALSE, "!!a");
 
-	do_test_wildcard_match ("\\",     TRUE,  "\\\\");
+	do_test_wildcard_match ("\\",     TRUE,  "\\\\\\");
 	do_test_wildcard_match ("\\\\",   FALSE, "\\\\");
 	do_test_wildcard_match ("",       FALSE, "\\\\");
 
+	do_test_wildcard_match ("\\a",    TRUE, "\\\\\\a");
+	do_test_wildcard_match ("b",      TRUE, "&!a");
+	do_test_wildcard_match ("a",      FALSE, "&!a");
+	do_test_wildcard_match ("!a",     TRUE, "&\\!a");
+	do_test_wildcard_match ("!a",     TRUE, "|\\!a");
+	do_test_wildcard_match ("!a",     TRUE, "\\!a");
+
 	do_test_wildcard_match ("name",   FALSE, "name[123]");
 	do_test_wildcard_match ("name1",  TRUE,  "name[123]");
 	do_test_wildcard_match ("name2",  TRUE,  "name[123]");
@@ -979,6 +986,12 @@ test_wildcard_match (void)
 	do_test_wildcard_match ("name4",  FALSE, "name[123]");
 
 	do_test_wildcard_match ("[a]",    TRUE,  "\\[a\\]");
+
+	do_test_wildcard_match ("aa",     FALSE, "!a*");
+	do_test_wildcard_match ("aa",     FALSE, "&!a*");
+	do_test_wildcard_match ("aa",     FALSE, "|!a*");
+	do_test_wildcard_match ("aa",     FALSE, "&!a*", "aa");
+	do_test_wildcard_match ("aa",     TRUE, "|!a*", "aa");
 }
 
 static NMConnection *
@@ -2118,6 +2131,46 @@ test_nm_utils_dhcp_client_id_systemd_node_specific (gconstpointer test_data)
 /*****************************************************************************/
 
 static void
+_kernel_cmdline_match (gboolean expected_match,
+                       const char *const*proc_cmdline,
+                       const char *const*patterns)
+{
+	gs_free_error GError *error = NULL;
+	GError **p_error = nmtst_get_rand_bool () ? &error : NULL;
+	gboolean match;
+
+	nm_assert (proc_cmdline);
+	nm_assert (patterns);
+
+	match = nm_utils_kernel_cmdline_match_check (proc_cmdline, patterns, NM_PTRARRAY_LEN (patterns), p_error);
+	if (expected_match)
+		nmtst_assert_success (match, error);
+	else {
+		g_assert (!p_error || error);
+		g_assert (!match);
+	}
+}
+
+static void
+test_kernel_cmdline_match_check (void)
+{
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV (""), NM_MAKE_STRV (""));
+	_kernel_cmdline_match (FALSE, NM_MAKE_STRV (""), NM_MAKE_STRV ("a"));
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a"), NM_MAKE_STRV ("a"));
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b"), NM_MAKE_STRV ("a"));
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("a", "b"));
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("&a", "&b"));
+	_kernel_cmdline_match (FALSE, NM_MAKE_STRV ("a=b", "bc"), NM_MAKE_STRV ("&a", "&b"));
+	_kernel_cmdline_match (FALSE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("&a", "&b", "c"));
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("&a", "&b", "b", "c"));
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b", "c=dd"), NM_MAKE_STRV ("&a", "&b", "c"));
+	_kernel_cmdline_match (FALSE, NM_MAKE_STRV ("a", "b"), NM_MAKE_STRV ("a", "&c"));
+	_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a", "b"), NM_MAKE_STRV ("a", "|\\c"));
+}
+
+/*****************************************************************************/
+
+static void
 test_connectivity_state_cmp (void)
 {
 	NMConnectivityState a;
@@ -2229,6 +2282,7 @@ main (int argc, char **argv)
 	g_test_add_data_func ("/general/nm_utils_dhcp_client_id_systemd_node_specific/1", GINT_TO_POINTER (1), test_nm_utils_dhcp_client_id_systemd_node_specific);
 
 	g_test_add_func ("/core/general/test_connectivity_state_cmp", test_connectivity_state_cmp);
+	g_test_add_func ("/core/general/test_kernel_cmdline_match_check", test_kernel_cmdline_match_check);
 
 	return g_test_run ();
 }