summary refs log tree commit diff
path: root/libnm-core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
committerMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
commit7514efc2f38c9ace4557d4e69d68e7d380389030 (patch)
tree7fb00fda86cfcc2ca377f191633a7cfdbfea7ca3 /libnm-core
parentd6201f5d8daada3d64a0a3e0038e14eebec683ce (diff)
Imported Upstream version 1.4.0 upstream/1.4.0
Diffstat (limited to 'libnm-core')
-rw-r--r--libnm-core/Makefile.am7
-rw-r--r--libnm-core/Makefile.in54
-rw-r--r--libnm-core/Makefile.libnm-core4
-rw-r--r--libnm-core/crypto.h2
-rw-r--r--libnm-core/nm-connection.c101
-rw-r--r--libnm-core/nm-core-internal.h38
-rw-r--r--libnm-core/nm-dbus-interface.h35
-rw-r--r--libnm-core/nm-dbus-types.xml70
-rw-r--r--libnm-core/nm-keyfile-internal.h1
-rw-r--r--libnm-core/nm-keyfile-reader.c67
-rw-r--r--libnm-core/nm-keyfile-writer.c22
-rw-r--r--libnm-core/nm-property-compare.h2
-rw-r--r--libnm-core/nm-setting-8021x.c10
-rw-r--r--libnm-core/nm-setting-bond.c67
-rw-r--r--libnm-core/nm-setting-connection.c57
-rw-r--r--libnm-core/nm-setting-connection.h3
-rw-r--r--libnm-core/nm-setting-ip-config.c139
-rw-r--r--libnm-core/nm-setting-ip-config.h2
-rw-r--r--libnm-core/nm-setting-ip4-config.c11
-rw-r--r--libnm-core/nm-setting-ip6-config.c115
-rw-r--r--libnm-core/nm-setting-ip6-config.h4
-rw-r--r--libnm-core/nm-setting-private.h1
-rw-r--r--libnm-core/nm-setting-team-port.c56
-rw-r--r--libnm-core/nm-setting-team.c61
-rw-r--r--libnm-core/nm-setting-vpn.c20
-rw-r--r--libnm-core/nm-setting-wired.c183
-rw-r--r--libnm-core/nm-setting-wired.h4
-rw-r--r--libnm-core/nm-setting-wireless-security.c6
-rw-r--r--libnm-core/nm-setting-wireless.c220
-rw-r--r--libnm-core/nm-setting-wireless.h4
-rw-r--r--libnm-core/nm-utils-private.h26
-rw-r--r--libnm-core/nm-utils.c466
-rw-r--r--libnm-core/nm-version.h14
-rw-r--r--libnm-core/nm-vpn-dbus-interface.h6
-rw-r--r--libnm-core/nm-vpn-editor-plugin.c193
-rw-r--r--libnm-core/nm-vpn-editor-plugin.h29
-rw-r--r--libnm-core/nm-vpn-plugin-info.c396
-rw-r--r--libnm-core/nm-vpn-plugin-info.h22
-rw-r--r--libnm-core/tests/Makefile.am4
-rw-r--r--libnm-core/tests/Makefile.in17
-rw-r--r--libnm-core/tests/test-compare.c2
-rw-r--r--libnm-core/tests/test-crypto.c2
-rw-r--r--libnm-core/tests/test-general.c364
-rw-r--r--libnm-core/tests/test-keyfile.c2
-rw-r--r--libnm-core/tests/test-secrets.c4
-rw-r--r--libnm-core/tests/test-setting-8021x.c2
-rw-r--r--libnm-core/tests/test-setting-bond.c2
-rw-r--r--libnm-core/tests/test-setting-dcb.c2
-rw-r--r--libnm-core/tests/test-settings-defaults.c2
49 files changed, 2638 insertions, 283 deletions
diff --git a/libnm-core/Makefile.am b/libnm-core/Makefile.am
index 3335720a..695b6a3a 100644
--- a/libnm-core/Makefile.am
+++ b/libnm-core/Makefile.am
@@ -12,9 +12,11 @@ AM_CPPFLAGS = \
 	-DNMCONFDIR=\"$(nmconfdir)\" \
 	-DNMLIBDIR=\"$(nmlibdir)\" \
 	-DNMPLUGINDIR=\"$(pkglibdir)\" \
+	-DLIBEXECDIR=\"$(libexecdir)\" \
 	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIB \
 	$(GLIB_CFLAGS) \
-	$(CODE_COVERAGE_CFLAGS)
+	$(CODE_COVERAGE_CFLAGS) \
+	$(JANSSON_CFLAGS)
 
 noinst_LTLIBRARIES = libnm-core.la
 
@@ -36,7 +38,8 @@ GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM
 
 libnm_core_la_LIBADD =			\
 	$(GLIB_LIBS)			\
-	$(UUID_LIBS)
+	$(UUID_LIBS)			\
+	$(JANSSON_LIBS)
 
 libnm_core_la_LDFLAGS =			\
 	$(CODE_COVERAGE_LDFLAGS)
diff --git a/libnm-core/Makefile.in b/libnm-core/Makefile.in
index bdef6648..ca804e3c 100644
--- a/libnm-core/Makefile.in
+++ b/libnm-core/Makefile.in
@@ -101,8 +101,9 @@ host_triplet = @host@
 @WITH_NSS_TRUE@am__append_6 = $(NSS_LIBS)
 subdir = libnm-core
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -127,10 +128,10 @@ am__DEPENDENCIES_1 =
 @WITH_GNUTLS_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
 @WITH_NSS_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1)
 libnm_core_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
-	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \
-	$(am__DEPENDENCIES_3)
+	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+	$(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3)
 am__libnm_core_la_SOURCES_DIST =  \
-	$(top_builddir)/shared/nm-shared-utils.c \
+	$(top_builddir)/shared/nm-utils/nm-shared-utils.c \
 	$(core_build)/nm-core-enum-types.c $(core)/crypto.c \
 	$(core)/nm-connection.c $(core)/nm-dbus-utils.c \
 	$(core)/nm-errors.c $(core)/nm-keyfile-reader.c \
@@ -154,12 +155,12 @@ am__libnm_core_la_SOURCES_DIST =  \
 	$(core)/nm-setting-wireless.c $(core)/nm-setting.c \
 	$(core)/nm-simple-connection.c $(core)/nm-utils.c \
 	$(core)/nm-vpn-editor-plugin.c $(core)/nm-vpn-plugin-info.c \
-	$(top_builddir)/shared/nm-shared-utils.h $(core)/crypto.h \
-	$(core)/nm-connection-private.h $(core)/nm-core-internal.h \
-	$(core)/nm-core-types-internal.h $(core)/nm-keyfile-internal.h \
-	$(core)/nm-keyfile-utils.h $(core)/nm-property-compare.h \
-	$(core)/nm-setting-private.h $(core)/nm-utils-private.h \
-	crypto_gnutls.c crypto_nss.c
+	$(top_builddir)/shared/nm-utils/nm-shared-utils.h \
+	$(core)/crypto.h $(core)/nm-connection-private.h \
+	$(core)/nm-core-internal.h $(core)/nm-core-types-internal.h \
+	$(core)/nm-keyfile-internal.h $(core)/nm-keyfile-utils.h \
+	$(core)/nm-property-compare.h $(core)/nm-setting-private.h \
+	$(core)/nm-utils-private.h crypto_gnutls.c crypto_nss.c
 am__objects_1 = nm-shared-utils.lo nm-core-enum-types.lo crypto.lo \
 	nm-connection.lo nm-dbus-utils.lo nm-errors.lo \
 	nm-keyfile-reader.lo nm-keyfile-utils.lo nm-keyfile-writer.lo \
@@ -359,6 +360,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -415,13 +417,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -433,6 +436,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -493,6 +498,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -528,6 +535,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -593,10 +601,10 @@ SUBDIRS = . tests
 AM_CPPFLAGS = -I${top_srcdir}/shared -I${top_builddir}/shared \
 	-DG_LOG_DOMAIN=\""libnm"\" -DLOCALEDIR=\"$(datadir)/locale\" \
 	-DNMCONFDIR=\"$(nmconfdir)\" -DNMLIBDIR=\"$(nmlibdir)\" \
-	-DNMPLUGINDIR=\"$(pkglibdir)\" \
+	-DNMPLUGINDIR=\"$(pkglibdir)\" -DLIBEXECDIR=\"$(libexecdir)\" \
 	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_LIB \
-	$(GLIB_CFLAGS) $(CODE_COVERAGE_CFLAGS) $(am__append_1) \
-	$(am__append_4)
+	$(GLIB_CFLAGS) $(CODE_COVERAGE_CFLAGS) $(JANSSON_CFLAGS) \
+	$(am__append_1) $(am__append_4)
 noinst_LTLIBRARIES = libnm-core.la
 core = $(top_srcdir)/libnm-core
 core_build = $(top_builddir)/libnm-core
@@ -647,7 +655,7 @@ libnm_core_headers = \
 	$(core)/nm-vpn-plugin-info.h
 
 libnm_core_private_headers = \
-	$(top_builddir)/shared/nm-shared-utils.h \
+	$(top_builddir)/shared/nm-utils/nm-shared-utils.h \
 	$(core)/crypto.h			\
 	$(core)/nm-connection-private.h		\
 	$(core)/nm-core-internal.h		\
@@ -659,7 +667,7 @@ libnm_core_private_headers = \
 	$(core)/nm-utils-private.h
 
 libnm_core_sources = \
-	$(top_builddir)/shared/nm-shared-utils.c \
+	$(top_builddir)/shared/nm-utils/nm-shared-utils.c \
 	$(core_build)/nm-core-enum-types.c	\
 	$(core)/crypto.c			\
 	$(core)/nm-connection.c			\
@@ -717,8 +725,8 @@ GLIB_GENERATED = nm-core-enum-types.h nm-core-enum-types.c
 nm_core_enum_types_sources = $(filter-out %nm-core-enum-types.h,$(libnminclude_HEADERS))
 GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM
 GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM
-libnm_core_la_LIBADD = $(GLIB_LIBS) $(UUID_LIBS) $(am__append_3) \
-	$(am__append_6)
+libnm_core_la_LIBADD = $(GLIB_LIBS) $(UUID_LIBS) $(JANSSON_LIBS) \
+	$(am__append_3) $(am__append_6)
 libnm_core_la_LDFLAGS = \
 	$(CODE_COVERAGE_LDFLAGS)
 
@@ -850,12 +858,12 @@ distclean-compile:
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
 
-nm-shared-utils.lo: $(top_builddir)/shared/nm-shared-utils.c
-@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-shared-utils.lo -MD -MP -MF $(DEPDIR)/nm-shared-utils.Tpo -c -o nm-shared-utils.lo `test -f '$(top_builddir)/shared/nm-shared-utils.c' || echo '$(srcdir)/'`$(top_builddir)/shared/nm-shared-utils.c
+nm-shared-utils.lo: $(top_builddir)/shared/nm-utils/nm-shared-utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-shared-utils.lo -MD -MP -MF $(DEPDIR)/nm-shared-utils.Tpo -c -o nm-shared-utils.lo `test -f '$(top_builddir)/shared/nm-utils/nm-shared-utils.c' || echo '$(srcdir)/'`$(top_builddir)/shared/nm-utils/nm-shared-utils.c
 @am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/nm-shared-utils.Tpo $(DEPDIR)/nm-shared-utils.Plo
-@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(top_builddir)/shared/nm-shared-utils.c' object='nm-shared-utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(top_builddir)/shared/nm-utils/nm-shared-utils.c' object='nm-shared-utils.lo' libtool=yes @AMDEPBACKSLASH@
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
-@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-shared-utils.lo `test -f '$(top_builddir)/shared/nm-shared-utils.c' || echo '$(srcdir)/'`$(top_builddir)/shared/nm-shared-utils.c
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-shared-utils.lo `test -f '$(top_builddir)/shared/nm-utils/nm-shared-utils.c' || echo '$(srcdir)/'`$(top_builddir)/shared/nm-utils/nm-shared-utils.c
 
 nm-core-enum-types.lo: $(core_build)/nm-core-enum-types.c
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-core-enum-types.lo -MD -MP -MF $(DEPDIR)/nm-core-enum-types.Tpo -c -o nm-core-enum-types.lo `test -f '$(core_build)/nm-core-enum-types.c' || echo '$(srcdir)/'`$(core_build)/nm-core-enum-types.c
diff --git a/libnm-core/Makefile.libnm-core b/libnm-core/Makefile.libnm-core
index 0038ce23..52c71da5 100644
--- a/libnm-core/Makefile.libnm-core
+++ b/libnm-core/Makefile.libnm-core
@@ -52,7 +52,7 @@ libnm_core_headers =				\
 	$(core)/nm-vpn-plugin-info.h
 
 libnm_core_private_headers =			\
-	$(top_builddir)/shared/nm-shared-utils.h \
+	$(top_builddir)/shared/nm-utils/nm-shared-utils.h \
 	$(core)/crypto.h			\
 	$(core)/nm-connection-private.h		\
 	$(core)/nm-core-internal.h		\
@@ -64,7 +64,7 @@ libnm_core_private_headers =			\
 	$(core)/nm-utils-private.h
 
 libnm_core_sources =				\
-	$(top_builddir)/shared/nm-shared-utils.c \
+	$(top_builddir)/shared/nm-utils/nm-shared-utils.c \
 	$(core_build)/nm-core-enum-types.c	\
 	$(core)/crypto.c			\
 	$(core)/nm-connection.c			\
diff --git a/libnm-core/crypto.h b/libnm-core/crypto.h
index c77ada5a..8c4cfefe 100644
--- a/libnm-core/crypto.h
+++ b/libnm-core/crypto.h
@@ -24,8 +24,6 @@
 #ifndef __CRYPTO_H__
 #define __CRYPTO_H__
 
-#include "nm-default.h"
-
 #define MD5_HASH_LEN 20
 #define CIPHER_DES_EDE3_CBC "DES-EDE3-CBC"
 #define CIPHER_DES_CBC "DES-CBC"
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index 04385aae..f39d41c5 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -23,10 +23,12 @@
 #include "nm-default.h"
 
 #include <string.h>
+#include <arpa/inet.h>
 
 #include "nm-connection.h"
 #include "nm-connection-private.h"
 #include "nm-utils.h"
+#include "nm-utils-private.h"
 #include "nm-setting-private.h"
 #include "nm-core-internal.h"
 
@@ -780,6 +782,24 @@ _normalize_ip_config (NMConnection *self, GHashTable *parameters)
 			              NULL);
 			nm_connection_add_setting (self, setting);
 		} else {
+			const char *token;
+
+			token = nm_setting_ip6_config_get_token ((NMSettingIP6Config *) s_ip6);
+			if (   token
+			    && nm_setting_ip6_config_get_addr_gen_mode ((NMSettingIP6Config *) s_ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) {
+				struct in6_addr i6_token;
+				char normalized[NM_UTILS_INET_ADDRSTRLEN];
+
+				if (   inet_pton (AF_INET6, token, &i6_token) == 1
+				    && _nm_utils_inet6_is_token (&i6_token)) {
+					nm_utils_inet6_ntop (&i6_token, normalized);
+					if (g_strcmp0 (token, normalized)) {
+						g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_TOKEN, normalized, NULL);
+						changed = TRUE;
+					}
+				}
+			}
+
 			if (   nm_setting_ip_config_get_gateway (s_ip6)
 			    && nm_setting_ip_config_get_never_default (s_ip6)) {
 				g_object_set (s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, NULL, NULL);
@@ -842,6 +862,84 @@ _normalize_bond_mode (NMConnection *self, GHashTable *parameters)
 	return FALSE;
 }
 
+static gboolean
+_normalize_wireless_mac_address_randomization (NMConnection *self, GHashTable *parameters)
+{
+	NMSettingWireless *s_wifi = nm_connection_get_setting_wireless (self);
+	const char *cloned_mac_address;
+	NMSettingMacRandomization mac_address_randomization;
+
+	if (!s_wifi)
+		return FALSE;
+
+	mac_address_randomization = nm_setting_wireless_get_mac_address_randomization (s_wifi);
+	if (!NM_IN_SET (mac_address_randomization,
+	                NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
+	                NM_SETTING_MAC_RANDOMIZATION_NEVER,
+	                NM_SETTING_MAC_RANDOMIZATION_ALWAYS))
+		return FALSE;
+
+	cloned_mac_address = nm_setting_wireless_get_cloned_mac_address (s_wifi);
+	if (cloned_mac_address) {
+		if (nm_streq (cloned_mac_address, "random")) {
+			if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS)
+				return FALSE;
+			mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_ALWAYS;
+		} else if (nm_streq (cloned_mac_address, "permanent")) {
+			if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_NEVER)
+				return FALSE;
+			mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_NEVER;
+		} else {
+			if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT)
+				return FALSE;
+			mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT;
+		}
+		g_object_set (s_wifi, NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION, mac_address_randomization, NULL);
+		return TRUE;
+	}
+	if (mac_address_randomization != NM_SETTING_MAC_RANDOMIZATION_DEFAULT) {
+		g_object_set (s_wifi,
+		              NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+		              mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS
+		                  ? "random" : "permanent",
+		              NULL);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+_normalize_team_config (NMConnection *self, GHashTable *parameters)
+{
+	NMSettingTeam *s_team = nm_connection_get_setting_team (self);
+
+	if (s_team) {
+		const char *config = nm_setting_team_get_config (s_team);
+
+		if (config && !_nm_utils_check_valid_json (config, NULL)) {
+			g_object_set (s_team, NM_SETTING_TEAM_CONFIG, NULL, NULL);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static gboolean
+_normalize_team_port_config (NMConnection *self, GHashTable *parameters)
+{
+	NMSettingTeamPort *s_team_port = nm_connection_get_setting_team_port (self);
+
+	if (s_team_port) {
+		const char *config = nm_setting_team_port_get_config (s_team_port);
+
+		if (config && !_nm_utils_check_valid_json (config, NULL)) {
+			g_object_set (s_team_port, NM_SETTING_TEAM_PORT_CONFIG, NULL, NULL);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
 /**
  * nm_connection_verify:
  * @connection: the #NMConnection to verify
@@ -1084,6 +1182,9 @@ nm_connection_normalize (NMConnection *connection,
 	was_modified |= _normalize_ip_config (connection, parameters);
 	was_modified |= _normalize_infiniband_mtu (connection, parameters);
 	was_modified |= _normalize_bond_mode (connection, parameters);
+	was_modified |= _normalize_wireless_mac_address_randomization (connection, parameters);
+	was_modified |= _normalize_team_config (connection, parameters);
+	was_modified |= _normalize_team_port_config (connection, parameters);
 
 	/* Verify anew. */
 	success = _nm_connection_verify (connection, error);
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 2041a1d3..94e473d3 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -34,7 +34,6 @@
  **/
 
 
-#include "nm-default.h"
 #include "nm-connection.h"
 #include "nm-core-enum-types.h"
 #include "nm-setting-8021x.h"
@@ -124,6 +123,10 @@ guint32 _nm_setting_get_setting_priority (NMSetting *setting);
 
 gboolean _nm_setting_get_property (NMSetting *setting, const char *name, GValue *value);
 
+guint _nm_utils_hwaddr_length (const char *asc);
+
+char *_nm_utils_bin2str (gconstpointer addr, gsize length, gboolean upper_case);
+
 GSList *    _nm_utils_hash_values_to_slist (GHashTable *hash);
 
 GHashTable *_nm_utils_copy_strdict (GHashTable *strdict);
@@ -145,9 +148,6 @@ gssize _nm_utils_ptrarray_find_first (gpointer *list, gssize len, gconstpointer
 
 gssize _nm_utils_ptrarray_find_binary_search (gpointer *list, gsize len, gpointer needle, GCompareDataFunc cmpfcn, gpointer user_data);
 
-gboolean    _nm_utils_string_in_list   (const char *str,
-                                        const char **valid_strings);
-
 gssize      _nm_utils_strv_find_first (char **list, gssize len, const char *needle);
 
 char **_nm_utils_strv_cleanup (char **strv,
@@ -284,6 +284,16 @@ void     _nm_setting_vlan_get_priorities (NMSettingVlan *setting,
 
 /***********************************************************/
 
+struct ether_addr;
+
+gboolean _nm_utils_generate_mac_address_mask_parse (const char *value,
+                                                    struct ether_addr *out_mask,
+                                                    struct ether_addr **out_ouis,
+                                                    gsize *out_ouis_len,
+                                                    GError **error);
+
+/***********************************************************/
+
 typedef enum {
 	NM_BOND_OPTION_TYPE_INT,
 	NM_BOND_OPTION_TYPE_STRING,
@@ -296,4 +306,24 @@ typedef enum {
 NMBondOptionType
 _nm_setting_bond_get_option_type (NMSettingBond *setting, const char *name);
 
+/***********************************************************/
+
+typedef enum {
+	NM_BOND_MODE_UNKNOWN = 0,
+	NM_BOND_MODE_ROUNDROBIN,
+	NM_BOND_MODE_ACTIVEBACKUP,
+	NM_BOND_MODE_XOR,
+	NM_BOND_MODE_BROADCAST,
+	NM_BOND_MODE_8023AD,
+	NM_BOND_MODE_TLB,
+	NM_BOND_MODE_ALB,
+} NMBondMode;
+
+NMBondMode _nm_setting_bond_mode_from_string (const char *str);
+gboolean _nm_setting_bond_option_supported (const char *option, NMBondMode mode);
+
+/***********************************************************/
+
+gboolean _nm_utils_inet6_is_token (const struct in6_addr *in6addr);
+
 #endif
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index 1e0fbe68..e5b1b3d6 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -68,6 +68,7 @@
 #define NM_DBUS_INTERFACE_DEVICE_VXLAN      NM_DBUS_INTERFACE_DEVICE ".Vxlan"
 #define NM_DBUS_INTERFACE_DEVICE_GRE        NM_DBUS_INTERFACE_DEVICE ".Gre"
 #define NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL  NM_DBUS_INTERFACE_DEVICE ".IPTunnel"
+#define NM_DBUS_INTERFACE_DEVICE_STATISTICS NM_DBUS_INTERFACE_DEVICE ".Statistics"
 
 #define NM_DBUS_INTERFACE_SETTINGS        "org.freedesktop.NetworkManager.Settings"
 #define NM_DBUS_PATH_SETTINGS             "/org/freedesktop/NetworkManager/Settings"
@@ -689,4 +690,38 @@ typedef enum {
 	NM_IP_TUNNEL_MODE_VTI6        = 9,
 } NMIPTunnelMode;
 
+
+/**
+ * NMCheckpointCreateFlags:
+ * @NM_CHECKPOINT_CREATE_FLAG_NONE: no flags
+ * @NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL: when creating
+ *   a new checkpoint, destroy all existing ones.
+ *
+ * The flags for CheckpointCreate call
+ *
+ * Since: 1.4
+ */
+typedef enum { /*< skip >*/
+	NM_CHECKPOINT_CREATE_FLAG_NONE              = 0,
+	NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL       = 0x01,
+} NMCheckpointCreateFlags;
+
+/**
+ * NMRollbackResult:
+ * @NM_ROLLBACK_RESULT_OK: the rollback succeeded.
+ * @NM_ROLLBACK_RESULT_ERR_NO_DEVICE: the device no longer exists.
+ * @NM_ROLLBACK_RESULT_ERR_DEVICE_UNMANAGED: the device is now unmanaged.
+ * @NM_ROLLBACK_RESULT_ERR_FAILED: other errors during rollback.
+ *
+ * The result of a checkpoint Rollback() operation for a specific device.
+ *
+ * Since: 1.4
+ **/
+typedef enum { /*< skip >*/
+	NM_ROLLBACK_RESULT_OK                   = 0,
+	NM_ROLLBACK_RESULT_ERR_NO_DEVICE        = 1,
+	NM_ROLLBACK_RESULT_ERR_DEVICE_UNMANAGED = 2,
+	NM_ROLLBACK_RESULT_ERR_FAILED           = 3,
+} NMRollbackResult;
+
 #endif /* __NM_DBUS_INTERFACE_H__ */
diff --git a/libnm-core/nm-dbus-types.xml b/libnm-core/nm-dbus-types.xml
index 6d5d01bb..1fae76f6 100644
--- a/libnm-core/nm-dbus-types.xml
+++ b/libnm-core/nm-dbus-types.xml
@@ -1288,4 +1288,74 @@
     </refsect3>
   </refsect2>
 
+  <refsect2 id="NMCheckpointCreateFlags" role="enum">
+    <title>enum NMCheckpointCreateFlags</title>
+    <indexterm zone="NMCheckpointCreateFlags">
+      <primary>NMCheckpointCreateFlags</primary>
+    </indexterm>
+    <para><para>The flags for CheckpointCreate call</para><para>Since: 1.4</para><para></para></para>
+    <refsect3 role="enum_members">
+      <title>Values</title>
+      <informaltable role="enum_members_table" pgwide="1" frame="none">
+        <tgroup cols="4">
+          <colspec colname="enum_members_name" colwidth="300px" />
+          <colspec colname="enum_members_value" colwidth="100px"/>
+          <colspec colname="enum_members_description" />
+          <tbody>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CHECKPOINT_CREATE_FLAG_NONE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>no flags</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x01</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>when creating a new checkpoint, destroy all existing ones.</para><para></para></entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </informaltable>
+    </refsect3>
+  </refsect2>
+
+  <refsect2 id="NMRollbackResult" role="enum">
+    <title>enum NMRollbackResult</title>
+    <indexterm zone="NMRollbackResult">
+      <primary>NMRollbackResult</primary>
+    </indexterm>
+    <para><para>The result of a checkpoint Rollback() operation for a specific device.</para><para>Since: 1.4</para><para></para></para>
+    <refsect3 role="enum_members">
+      <title>Values</title>
+      <informaltable role="enum_members_table" pgwide="1" frame="none">
+        <tgroup cols="4">
+          <colspec colname="enum_members_name" colwidth="300px" />
+          <colspec colname="enum_members_value" colwidth="100px"/>
+          <colspec colname="enum_members_description" />
+          <tbody>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_ROLLBACK_RESULT_OK</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>the rollback succeeded.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_ROLLBACK_RESULT_ERR_NO_DEVICE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>1</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>the device no longer exists.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_ROLLBACK_RESULT_ERR_DEVICE_UNMANAGED</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>2</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>the device is now unmanaged.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_ROLLBACK_RESULT_ERR_FAILED</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>3</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>other errors during rollback.</para><para></para></entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </informaltable>
+    </refsect3>
+  </refsect2>
+
 </refentry>
diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h
index 7873aa7c..41bec0cf 100644
--- a/libnm-core/nm-keyfile-internal.h
+++ b/libnm-core/nm-keyfile-internal.h
@@ -24,7 +24,6 @@
 
 #include <sys/types.h>
 
-#include "nm-default.h"
 #include "nm-connection.h"
 #include "nm-setting-8021x.h"
 
diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c
index 4a17d2c4..54a694ef 100644
--- a/libnm-core/nm-keyfile-reader.c
+++ b/libnm-core/nm-keyfile-reader.c
@@ -31,6 +31,7 @@
 #include <arpa/inet.h>
 #include <string.h>
 
+#include "nm-common-macros.h"
 #include "nm-core-internal.h"
 #include "nm-keyfile-utils.h"
 
@@ -581,19 +582,28 @@ ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const cha
 }
 
 static void
-mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length)
+mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length, gboolean cloned_mac_addr)
 {
 	const char *setting_name = nm_setting_get_name (setting);
-	char *tmp_string = NULL, *p, *mac_str;
-	gint *tmp_list;
-	GByteArray *array = NULL;
+	gs_free char *tmp_string = NULL;
+	const char *p, *mac_str;
+	gs_free guint8 *buf_arr = NULL;
+	guint buf_len = 0;
 	gsize length;
 
-	p = tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+
+	if (   cloned_mac_addr
+	    && NM_CLONED_MAC_IS_SPECIAL (tmp_string)) {
+		mac_str = tmp_string;
+		goto out;
+	}
+
 	if (tmp_string && tmp_string[0]) {
 		/* Look for enough ':' characters to signify a MAC address */
 		guint i = 0;
 
+		p = tmp_string;
 		while (*p) {
 			if (*p == ':')
 				i++;
@@ -602,23 +612,24 @@ mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
 
 		if (enforce_length == 0 || enforce_length == i+1) {
 			/* If we found enough it's probably a string-format MAC address */
-			array = g_byte_array_sized_new (i+1);
-			g_byte_array_set_size (array, i+1);
-			if (!nm_utils_hwaddr_aton (tmp_string, array->data, array->len)) {
-				g_byte_array_unref (array);
-				array = NULL;
-			}
+			buf_len = i + 1;
+			buf_arr = g_new (guint8, buf_len);
+			if (!nm_utils_hwaddr_aton (tmp_string, buf_arr, buf_len))
+				g_clear_pointer (&buf_arr, g_free);
 		}
 	}
-	g_free (tmp_string);
+	g_clear_pointer (&tmp_string, g_free);
+
+	if (!buf_arr) {
+		gs_free int *tmp_list = NULL;
 
-	if (array == NULL) {
 		/* Old format; list of ints */
 		tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL);
 		if (length > 0 && (enforce_length == 0 || enforce_length == length)) {
 			gsize i;
 
-			array = g_byte_array_sized_new (length);
+			buf_len = length;
+			buf_arr = g_new (guint8, buf_len);
 			for (i = 0; i < length; i++) {
 				int val = tmp_list[i];
 				const guint8 v = (guint8) (val & 0xFF);
@@ -627,38 +638,42 @@ mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
 					handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
 					             _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
 					             val);
-					g_byte_array_free (array, TRUE);
-					g_free (tmp_list);
 					return;
 				}
-				g_byte_array_append (array, &v, 1);
+				buf_arr[i] = v;
 			}
 		}
-		g_free (tmp_list);
 	}
 
-	if (!array) {
+	if (!buf_arr) {
 		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
 		             _("ignoring invalid MAC address"));
 		return;
 	}
 
-	mac_str = nm_utils_hwaddr_ntoa (array->data, array->len);
+	tmp_string = nm_utils_hwaddr_ntoa (buf_arr, buf_len);
+	mac_str = tmp_string;
+
+out:
 	g_object_set (setting, key, mac_str, NULL);
-	g_free (mac_str);
-	g_byte_array_free (array, TRUE);
 }
 
 static void
 mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 {
-	mac_address_parser (info, setting, key, ETH_ALEN);
+	mac_address_parser (info, setting, key, ETH_ALEN, FALSE);
+}
+
+static void
+mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, ETH_ALEN, TRUE);
 }
 
 static void
 mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 {
-	mac_address_parser (info, setting, key, INFINIBAND_ALEN);
+	mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE);
 }
 
 static void
@@ -1209,7 +1224,7 @@ static KeyParser key_parsers[] = {
 	{ NM_SETTING_WIRED_SETTING_NAME,
 	  NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
 	  TRUE,
-	  mac_address_parser_ETHER },
+	  mac_address_parser_ETHER_cloned },
 	{ NM_SETTING_WIRELESS_SETTING_NAME,
 	  NM_SETTING_WIRELESS_MAC_ADDRESS,
 	  TRUE,
@@ -1217,7 +1232,7 @@ static KeyParser key_parsers[] = {
 	{ NM_SETTING_WIRELESS_SETTING_NAME,
 	  NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
 	  TRUE,
-	  mac_address_parser_ETHER },
+	  mac_address_parser_ETHER_cloned },
 	{ NM_SETTING_WIRELESS_SETTING_NAME,
 	  NM_SETTING_WIRELESS_BSSID,
 	  TRUE,
diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c
index 0ce7641c..e0e6ba4b 100644
--- a/libnm-core/nm-keyfile-writer.c
+++ b/libnm-core/nm-keyfile-writer.c
@@ -239,10 +239,11 @@ write_hash_of_string (GKeyFile *file,
                       const char *key,
                       const GValue *value)
 {
-	GHashTableIter iter;
-	const char *property = NULL, *data = NULL;
+	GHashTable *hash;
 	const char *group_name = nm_setting_get_name (setting);
 	gboolean vpn_secrets = FALSE;
+	gs_free const char **keys = NULL;
+	guint i, l;
 
 	/* Write VPN secrets out to a different group to keep them separate */
 	if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
@@ -250,10 +251,19 @@ write_hash_of_string (GKeyFile *file,
 		vpn_secrets = TRUE;
 	}
 
-	g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value));
-	while (g_hash_table_iter_next (&iter, (gpointer *) &property, (gpointer *) &data)) {
+	hash = g_value_get_boxed (value);
+	keys = (const char **) g_hash_table_get_keys_as_array (hash, &l);
+	if (!keys)
+		return;
+
+	g_qsort_with_data (keys, l, sizeof (const char *), nm_strcmp_p_with_data, NULL);
+
+	for (i = 0; keys[i]; i++) {
+		const char *property, *data;
 		gboolean write_item = TRUE;
 
+		property = keys[i];
+
 		/* Handle VPN secrets specially; they are nested in the property's hash;
 		 * we don't want to write them if the secret is not saved, not required,
 		 * or owned by a user's secret agent.
@@ -266,8 +276,10 @@ write_hash_of_string (GKeyFile *file,
 				write_item = FALSE;
 		}
 
-		if (write_item)
+		if (write_item) {
+			data = g_hash_table_lookup (hash, property);
 			nm_keyfile_plugin_kf_set_string (file, group_name, property, data);
+		}
 	}
 }
 
diff --git a/libnm-core/nm-property-compare.h b/libnm-core/nm-property-compare.h
index 3f105818..b859989f 100644
--- a/libnm-core/nm-property-compare.h
+++ b/libnm-core/nm-property-compare.h
@@ -23,8 +23,6 @@
 #ifndef __NM_PROPERTY_COMPARE_H__
 #define __NM_PROPERTY_COMPARE_H__
 
-#include "nm-default.h"
-
 int nm_property_compare (GVariant *value1, GVariant *value2);
 
 #endif /* __NM_PROPERTY_COMPARE_H__ */
diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c
index 4db420de..aa9d4619 100644
--- a/libnm-core/nm-setting-8021x.c
+++ b/libnm-core/nm-setting-8021x.c
@@ -2776,7 +2776,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
-	if (priv->phase1_peapver && !_nm_utils_string_in_list (priv->phase1_peapver, valid_phase1_peapver)) {
+	if (priv->phase1_peapver && !g_strv_contains (valid_phase1_peapver, priv->phase1_peapver)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -2786,7 +2786,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->phase1_peaplabel && !_nm_utils_string_in_list (priv->phase1_peaplabel, valid_phase1_peaplabel)) {
+	if (priv->phase1_peaplabel && !g_strv_contains (valid_phase1_peaplabel, priv->phase1_peaplabel)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -2796,7 +2796,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->phase1_fast_provisioning && !_nm_utils_string_in_list (priv->phase1_fast_provisioning, valid_phase1_fast_pac)) {
+	if (priv->phase1_fast_provisioning && !g_strv_contains (valid_phase1_fast_pac, priv->phase1_fast_provisioning)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -2806,7 +2806,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->phase2_auth && !_nm_utils_string_in_list (priv->phase2_auth, valid_phase2_auth)) {
+	if (priv->phase2_auth && !g_strv_contains (valid_phase2_auth, priv->phase2_auth)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -2816,7 +2816,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->phase2_autheap && !_nm_utils_string_in_list (priv->phase2_autheap, valid_phase2_autheap)) {
+	if (priv->phase2_autheap && !g_strv_contains (valid_phase2_autheap, priv->phase2_autheap)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
diff --git a/libnm-core/nm-setting-bond.c b/libnm-core/nm-setting-bond.c
index e008878d..a82d0d79 100644
--- a/libnm-core/nm-setting-bond.c
+++ b/libnm-core/nm-setting-bond.c
@@ -92,7 +92,7 @@ static const BondDefault defaults[] = {
 	  { "slow", "fast", NULL } },
 	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,     "",           NM_BOND_OPTION_TYPE_IFNAME },
 	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO,"65535",      NM_BOND_OPTION_TYPE_INT, 1, 65535 },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,  "",           NM_BOND_OPTION_TYPE_MAC },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,  NULL,         NM_BOND_OPTION_TYPE_MAC },
 	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, "0",          NM_BOND_OPTION_TYPE_INT, 0, 1023},
 	{ NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE,"0",          NM_BOND_OPTION_TYPE_INT, 0, 1},
 	{ NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS,  "any",        NM_BOND_OPTION_TYPE_BOTH, 0, 1, {"any", "all"}},
@@ -427,11 +427,22 @@ nm_setting_bond_get_valid_options  (NMSettingBond *setting)
 const char *
 nm_setting_bond_get_option_default (NMSettingBond *setting, const char *name)
 {
+	const char *mode;
 	guint i;
 
 	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), NULL);
 	g_return_val_if_fail (nm_setting_bond_validate_option (name, NULL), NULL);
 
+	if (nm_streq (name, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) {
+		/* The default value depends on the current mode */
+		mode = nm_setting_bond_get_option_by_name (setting, NM_SETTING_BOND_OPTION_MODE);
+		if (   nm_streq0 (mode, "4")
+		    || nm_streq0 (mode, "802.3ad"))
+			return "00:00:00:00:00:00";
+		else
+			return "";
+	}
+
 	for (i = 0; i < G_N_ELEMENTS (defaults); i++) {
 		if (g_strcmp0 (defaults[i].opt, name) == 0)
 			return defaults[i].val;
@@ -463,6 +474,60 @@ _nm_setting_bond_get_option_type (NMSettingBond *setting, const char *name)
 	g_assert_not_reached ();
 }
 
+NMBondMode
+_nm_setting_bond_mode_from_string (const char *str)
+{
+	g_return_val_if_fail (str, NM_BOND_MODE_UNKNOWN);
+
+	if (nm_streq (str, "balance-rr"))
+		return NM_BOND_MODE_ROUNDROBIN;
+	if (nm_streq (str, "active-backup"))
+		return NM_BOND_MODE_ACTIVEBACKUP;
+	if (nm_streq (str, "balance-xor"))
+		return NM_BOND_MODE_XOR;
+	if (nm_streq (str, "broadcast"))
+		return NM_BOND_MODE_BROADCAST;
+	if (nm_streq (str, "802.3ad"))
+		return NM_BOND_MODE_8023AD;
+	if (nm_streq (str, "balance-tlb"))
+		return NM_BOND_MODE_TLB;
+	if (nm_streq (str, "balance-alb"))
+		return NM_BOND_MODE_ALB;
+
+	return NM_BOND_MODE_UNKNOWN;
+}
+
+#define BIT(x) (1 << (x))
+
+const static struct {
+	const char *option;
+	NMBondMode unsupp_modes;
+} bond_unsupp_modes[] = {
+	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, ~(BIT (NM_BOND_MODE_ROUNDROBIN)) },
+	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,      BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
+	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,      BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
+	{ NM_SETTING_BOND_OPTION_LACP_RATE,         ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_PRIMARY,           ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,      ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,    ~(BIT (NM_BOND_MODE_TLB)) },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,   ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,  ~(BIT (NM_BOND_MODE_8023AD)) },
+};
+
+gboolean
+_nm_setting_bond_option_supported (const char *option, NMBondMode mode)
+{
+	guint i;
+
+	for (i = 0; i < G_N_ELEMENTS (bond_unsupp_modes); i++) {
+		if (nm_streq (option, bond_unsupp_modes[i].option))
+		    return !NM_FLAGS_HAS (bond_unsupp_modes[i].unsupp_modes, BIT (mode));
+	}
+
+	return TRUE;
+}
+
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index e1cd913a..f6b801c6 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -63,6 +63,7 @@ typedef struct {
 typedef struct {
 	char *id;
 	char *uuid;
+	char *stable_id;
 	char *interface_name;
 	char *type;
 	char *master;
@@ -99,6 +100,7 @@ enum {
 	PROP_GATEWAY_PING_TIMEOUT,
 	PROP_METERED,
 	PROP_LLDP,
+	PROP_STABLE_ID,
 
 	LAST_PROP
 };
@@ -231,6 +233,24 @@ nm_setting_connection_get_uuid (NMSettingConnection *setting)
 }
 
 /**
+ * nm_setting_connection_get_stable_id:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns the #NMSettingConnection:stable_id property of the connection.
+ *
+ * Returns: the stable-id for the connection
+ *
+ * Since: 1.4
+ **/
+const char *
+nm_setting_connection_get_stable_id (NMSettingConnection *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting), NULL);
+
+	return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->stable_id;
+}
+
+/**
  * nm_setting_connection_get_interface_name:
  * @setting: the #NMSettingConnection
  *
@@ -1128,6 +1148,7 @@ finalize (GObject *object)
 
 	g_free (priv->id);
 	g_free (priv->uuid);
+	g_free (priv->stable_id);
 	g_free (priv->interface_name);
 	g_free (priv->type);
 	g_free (priv->zone);
@@ -1174,6 +1195,10 @@ set_property (GObject *object, guint prop_id,
 		g_free (priv->uuid);
 		priv->uuid = g_value_dup_string (value);
 		break;
+	case PROP_STABLE_ID:
+		g_free (priv->stable_id);
+		priv->stable_id = g_value_dup_string (value);
+		break;
 	case PROP_INTERFACE_NAME:
 		g_free (priv->interface_name);
 		priv->interface_name = g_value_dup_string (value);
@@ -1260,6 +1285,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_UUID:
 		g_value_set_string (value, nm_setting_connection_get_uuid (setting));
 		break;
+	case PROP_STABLE_ID:
+		g_value_set_string (value, nm_setting_connection_get_stable_id (setting));
+		break;
 	case PROP_INTERFACE_NAME:
 		g_value_set_string (value, nm_setting_connection_get_interface_name (setting));
 		break;
@@ -1368,7 +1396,7 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class)
 	 * property: uuid
 	 * variable: UUID(+)
 	 * description: UUID for the connection profile. When missing, NetworkManager
-	 *   creates the UUID itself (by hashing the file).
+	 *   creates the UUID itself (by hashing the filename).
 	 * ---end---
 	 */
 	g_object_class_install_property
@@ -1380,6 +1408,33 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class)
 		                      G_PARAM_STATIC_STRINGS));
 
 	/**
+	 * NMSettingConnection:stable-id:
+	 *
+	 * This token to generate stable IDs for the connection. If unset,
+	 * the UUID will be used instead.
+	 *
+	 * The stable-id is used instead of the connection UUID for generating
+	 * IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy.
+	 * It is also used to seed the generated cloned MAC address for
+	 * ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable.
+	 *
+	 * Since: 1.4
+	 **/
+	/* ---ifcfg-rh---
+	 * property: stable-id
+	 * variable: STABLE_ID(+)
+	 * description: Token to generate stable IDs.
+	 * ---end---
+	 */
+	g_object_class_install_property
+		(object_class, PROP_STABLE_ID,
+		 g_param_spec_string (NM_SETTING_CONNECTION_STABLE_ID, "", "",
+		                      NULL,
+		                      G_PARAM_READWRITE |
+		                      NM_SETTING_PARAM_FUZZY_IGNORE |
+		                      G_PARAM_STATIC_STRINGS));
+
+	/**
 	 * NMSettingConnection:interface-name:
 	 *
 	 * The name of the network interface this connection is bound to. If not
diff --git a/libnm-core/nm-setting-connection.h b/libnm-core/nm-setting-connection.h
index 0d4966f0..67263d1f 100644
--- a/libnm-core/nm-setting-connection.h
+++ b/libnm-core/nm-setting-connection.h
@@ -46,6 +46,7 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_CONNECTION_ID             "id"
 #define NM_SETTING_CONNECTION_UUID           "uuid"
+#define NM_SETTING_CONNECTION_STABLE_ID      "stable-id"
 #define NM_SETTING_CONNECTION_INTERFACE_NAME "interface-name"
 #define NM_SETTING_CONNECTION_TYPE           "type"
 #define NM_SETTING_CONNECTION_AUTOCONNECT    "autoconnect"
@@ -116,6 +117,8 @@ GType nm_setting_connection_get_type (void);
 NMSetting * nm_setting_connection_new                  (void);
 const char *nm_setting_connection_get_id               (NMSettingConnection *setting);
 const char *nm_setting_connection_get_uuid             (NMSettingConnection *setting);
+NM_AVAILABLE_IN_1_4
+const char *nm_setting_connection_get_stable_id        (NMSettingConnection *setting);
 const char *nm_setting_connection_get_interface_name   (NMSettingConnection *setting);
 const char *nm_setting_connection_get_connection_type  (NMSettingConnection *setting);
 gboolean    nm_setting_connection_get_autoconnect      (NMSettingConnection *setting);
diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c
index bdcbc23b..55ad7bb0 100644
--- a/libnm-core/nm-setting-ip-config.c
+++ b/libnm-core/nm-setting-ip-config.c
@@ -300,17 +300,17 @@ nm_ip_address_unref (NMIPAddress *address)
 }
 
 /**
- * nm_ip_address_equal:
+ * _nm_ip_address_equal:
  * @address: the #NMIPAddress
  * @other: the #NMIPAddress to compare @address to.
+ * @consider_attributes: whether to check for equality of attributes too.
  *
- * Determines if two #NMIPAddress objects contain the same address and prefix
- * (attributes are not compared).
+ * Determines if two #NMIPAddress objects are equal.
  *
  * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
  **/
-gboolean
-nm_ip_address_equal (NMIPAddress *address, NMIPAddress *other)
+static gboolean
+_nm_ip_address_equal (NMIPAddress *address, NMIPAddress *other, gboolean consider_attributes)
 {
 	g_return_val_if_fail (address != NULL, FALSE);
 	g_return_val_if_fail (address->refcount > 0, FALSE);
@@ -322,10 +322,46 @@ nm_ip_address_equal (NMIPAddress *address, NMIPAddress *other)
 	    || address->prefix != other->prefix
 	    || strcmp (address->address, other->address) != 0)
 		return FALSE;
+	if (consider_attributes) {
+		GHashTableIter iter;
+		const char *key;
+		GVariant *value, *value2;
+		guint n;
+
+		n = address->attributes ? g_hash_table_size (address->attributes) : 0;
+		if (n != (other->attributes ? g_hash_table_size (other->attributes) : 0))
+			return FALSE;
+		if (n) {
+			g_hash_table_iter_init (&iter, address->attributes);
+			while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
+				value2 = g_hash_table_lookup (other->attributes, key);
+				if (!value2)
+					return FALSE;
+				if (!g_variant_equal (value, value2))
+					return FALSE;
+			}
+		}
+	}
 	return TRUE;
 }
 
 /**
+ * nm_ip_address_equal:
+ * @address: the #NMIPAddress
+ * @other: the #NMIPAddress to compare @address to.
+ *
+ * Determines if two #NMIPAddress objects contain the same address and prefix
+ * (attributes are not compared).
+ *
+ * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
+ **/
+gboolean
+nm_ip_address_equal (NMIPAddress *address, NMIPAddress *other)
+{
+	return _nm_ip_address_equal (address, other, FALSE);
+}
+
+/**
  * nm_ip_address_dup:
  * @address: the #NMIPAddress
  *
@@ -717,17 +753,18 @@ nm_ip_route_unref (NMIPRoute *route)
 }
 
 /**
- * nm_ip_route_equal:
+ * _nm_ip_route_equal:
  * @route: the #NMIPRoute
  * @other: the #NMIPRoute to compare @route to.
+ * @consider_attributes: whether to compare attributes too
  *
  * Determines if two #NMIPRoute objects contain the same destination, prefix,
- * next hop, and metric. (Attributes are not compared.)
+ * next hop, and metric.
  *
  * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
  **/
-gboolean
-nm_ip_route_equal (NMIPRoute *route, NMIPRoute *other)
+static gboolean
+_nm_ip_route_equal (NMIPRoute *route, NMIPRoute *other, gboolean consider_attributes)
 {
 	g_return_val_if_fail (route != NULL, FALSE);
 	g_return_val_if_fail (route->refcount > 0, FALSE);
@@ -740,10 +777,46 @@ nm_ip_route_equal (NMIPRoute *route, NMIPRoute *other)
 	    || strcmp (route->dest, other->dest) != 0
 	    || g_strcmp0 (route->next_hop, other->next_hop) != 0)
 		return FALSE;
+	if (consider_attributes) {
+		GHashTableIter iter;
+		const char *key;
+		GVariant *value, *value2;
+		guint n;
+
+		n = route->attributes ? g_hash_table_size (route->attributes) : 0;
+		if (n != (other->attributes ? g_hash_table_size (other->attributes) : 0))
+			return FALSE;
+		if (n) {
+			g_hash_table_iter_init (&iter, route->attributes);
+			while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
+				value2 = g_hash_table_lookup (other->attributes, key);
+				if (!value2)
+					return FALSE;
+				if (!g_variant_equal (value, value2))
+					return FALSE;
+			}
+		}
+	}
 	return TRUE;
 }
 
 /**
+ * nm_ip_route_equal:
+ * @route: the #NMIPRoute
+ * @other: the #NMIPRoute to compare @route to.
+ *
+ * Determines if two #NMIPRoute objects contain the same destination, prefix,
+ * next hop, and metric. (Attributes are not compared.)
+ *
+ * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
+ **/
+gboolean
+nm_ip_route_equal (NMIPRoute *route, NMIPRoute *other)
+{
+	return _nm_ip_route_equal (route, other, FALSE);
+}
+
+/**
  * nm_ip_route_dup:
  * @route: the #NMIPRoute
  *
@@ -1691,7 +1764,7 @@ nm_setting_ip_config_clear_dns_options (NMSettingIPConfig *setting, gboolean is_
  *
  * Returns: the priority of DNS servers
  *
- * Since: 1.2.4
+ * Since: 1.4
  **/
 gint
 nm_setting_ip_config_get_dns_priority (NMSettingIPConfig *setting)
@@ -1700,6 +1773,7 @@ nm_setting_ip_config_get_dns_priority (NMSettingIPConfig *setting)
 
 	return NM_SETTING_IP_CONFIG_GET_PRIVATE (setting)->dns_priority;
 }
+NM_BACKPORT_SYMBOL (libnm_1_2_4, gint, nm_setting_ip_config_get_dns_priority, (NMSettingIPConfig *setting), (setting));
 
 /**
  * nm_setting_ip_config_get_num_addresses:
@@ -2305,6 +2379,48 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	return TRUE;
 }
 
+static gboolean
+compare_property (NMSetting *setting,
+                  NMSetting *other,
+                  const GParamSpec *prop_spec,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingIPConfigPrivate *a_priv, *b_priv;
+	NMSettingClass *parent_class;
+	guint i;
+
+	if (nm_streq (prop_spec->name, NM_SETTING_IP_CONFIG_ADDRESSES)) {
+		a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+		b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (other);
+
+		if (a_priv->addresses->len != b_priv->addresses->len)
+			return FALSE;
+		for (i = 0; i < a_priv->addresses->len; i++) {
+			if (!_nm_ip_address_equal (a_priv->addresses->pdata[i], b_priv->addresses->pdata[i], TRUE))
+				return FALSE;
+		}
+		return TRUE;
+	}
+
+	if (nm_streq (prop_spec->name, NM_SETTING_IP_CONFIG_ROUTES)) {
+		a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
+		b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (other);
+
+		if (a_priv->routes->len != b_priv->routes->len)
+			return FALSE;
+		for (i = 0; i < a_priv->routes->len; i++) {
+			if (!_nm_ip_route_equal (a_priv->routes->pdata[i], b_priv->routes->pdata[i], TRUE))
+				return FALSE;
+		}
+		return TRUE;
+	}
+
+	/* Otherwise chain up to parent to handle generic compare */
+	parent_class = NM_SETTING_CLASS (nm_setting_ip_config_parent_class);
+	return parent_class->compare_property (setting, other, prop_spec, flags);
+}
+
+/*****************************************************************************/
 
 static void
 nm_setting_ip_config_init (NMSettingIPConfig *setting)
@@ -2535,6 +2651,7 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *setting_class)
 	object_class->get_property = get_property;
 	object_class->finalize     = finalize;
 	parent_class->verify       = verify;
+	parent_class->compare_property = compare_property;
 
 	/* Properties */
 
@@ -2627,7 +2744,7 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *setting_class)
 	 * priority, only DNS servers from configurations with the lowest priority
 	 * value will be used.
 	 *
-	 * Since: 1.2.4
+	 * Since: 1.4
 	 **/
 	g_object_class_install_property
 	    (object_class, PROP_DNS_PRIORITY,
diff --git a/libnm-core/nm-setting-ip-config.h b/libnm-core/nm-setting-ip-config.h
index a41c5466..b18d6a94 100644
--- a/libnm-core/nm-setting-ip-config.h
+++ b/libnm-core/nm-setting-ip-config.h
@@ -220,7 +220,7 @@ gboolean      nm_setting_ip_config_remove_dns_option_by_value (NMSettingIPConfig
                                                                const char        *dns_option);
 void          nm_setting_ip_config_clear_dns_options          (NMSettingIPConfig *setting, gboolean is_set);
 
-NM_AVAILABLE_IN_1_2_4
+NM_AVAILABLE_IN_1_4
 gint          nm_setting_ip_config_get_dns_priority (NMSettingIPConfig *setting);
 
 guint         nm_setting_ip_config_get_num_addresses          (NMSettingIPConfig *setting);
diff --git a/libnm-core/nm-setting-ip4-config.c b/libnm-core/nm-setting-ip4-config.c
index b3c8462e..70fab0e2 100644
--- a/libnm-core/nm-setting-ip4-config.c
+++ b/libnm-core/nm-setting-ip4-config.c
@@ -666,6 +666,17 @@ nm_setting_ip4_config_class_init (NMSettingIP4ConfigClass *ip4_class)
 	 * ---end---
 	 */
 
+	/* ---ifcfg-rh---
+	 * property: dns-priority
+	 * variable: IPV4_DNS_PRIORITY(+)
+	 * description: The priority for DNS servers of this connection. Lower values have higher priority.
+	 *    If zero, the default value will be used (50 for VPNs, 100 for other connections).
+	 *    A negative value prevents DNS from other connections with greater values to be used.
+	 * default: 0
+	 * example: IPV4_DNS_PRIORITY=20
+	 * ---end---
+	 */
+
 	/**
 	 * NMSettingIP4Config:dhcp-client-id:
 	 *
diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c
index 4d320e22..50b36543 100644
--- a/libnm-core/nm-setting-ip6-config.c
+++ b/libnm-core/nm-setting-ip6-config.c
@@ -24,6 +24,7 @@
 #include "nm-setting-ip6-config.h"
 
 #include <string.h>
+#include <arpa/inet.h>
 
 #include "nm-setting-private.h"
 #include "nm-core-enum-types.h"
@@ -59,6 +60,7 @@ NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_IP6_CONFIG)
 typedef struct {
 	NMSettingIP6ConfigPrivacy ip6_privacy;
 	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+	char *token;
 } NMSettingIP6ConfigPrivate;
 
 
@@ -66,6 +68,7 @@ enum {
 	PROP_0,
 	PROP_IP6_PRIVACY,
 	PROP_ADDR_GEN_MODE,
+	PROP_TOKEN,
 
 	LAST_PROP
 };
@@ -120,6 +123,25 @@ nm_setting_ip6_config_get_addr_gen_mode (NMSettingIP6Config *setting)
 	return NM_SETTING_IP6_CONFIG_GET_PRIVATE (setting)->addr_gen_mode;
 }
 
+/**
+ * nm_setting_ip6_config_get_token:
+ * @setting: the #NMSettingIP6Config
+ *
+ * Returns the value contained in the #NMSettingIP6Config:token
+ * property.
+ *
+ * Returns: A string.
+ *
+ * Since: 1.4
+ **/
+const char *
+nm_setting_ip6_config_get_token (NMSettingIP6Config *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_IP6_CONFIG (setting), NULL);
+
+	return NM_SETTING_IP6_CONFIG_GET_PRIVATE (setting)->token;
+}
+
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
@@ -127,6 +149,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting);
 	NMSettingVerifyResult ret;
 	const char *method;
+	gboolean token_needs_normalization = FALSE;
 
 	ret = NM_SETTING_CLASS (nm_setting_ip6_config_parent_class)->verify (setting, connection, error);
 	if (ret != NM_SETTING_VERIFY_SUCCESS)
@@ -201,6 +224,44 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
+	if (priv->token) {
+		if (priv->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) {
+			struct in6_addr i6_token;
+			char s_token[NM_UTILS_INET_ADDRSTRLEN];
+
+			if (   inet_pton (AF_INET6, priv->token, &i6_token) != 1
+			    || !_nm_utils_inet6_is_token (&i6_token)) {
+				g_set_error_literal (error,
+				                     NM_CONNECTION_ERROR,
+				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				                      _("value is not a valid token"));
+				g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP6_CONFIG_TOKEN);
+				return FALSE;
+			}
+
+			if (g_strcmp0 (priv->token, nm_utils_inet6_ntop (&i6_token, s_token)))
+				token_needs_normalization = TRUE;
+		} else {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                      _("only makes sense with EUI64 address generation mode"));
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP6_CONFIG_TOKEN);
+			return FALSE;
+		}
+	}
+
+	/* Failures from here on, are NORMALIZABLE_ERROR... */
+
+	if (token_needs_normalization) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("token is not in canonical form"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP6_CONFIG_TOKEN);
+		return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
+	}
+
 	/* Failures from here on are NORMALIZABLE... */
 
 	if (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
@@ -400,6 +461,10 @@ set_property (GObject *object, guint prop_id,
 	case PROP_ADDR_GEN_MODE:
 		priv->addr_gen_mode = g_value_get_int (value);
 		break;
+	case PROP_TOKEN:
+		g_free (priv->token);
+		priv->token = g_value_dup_string (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -419,6 +484,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_ADDR_GEN_MODE:
 		g_value_set_int (value, priv->addr_gen_mode);
 		break;
+	case PROP_TOKEN:
+		g_value_set_string (value, priv->token);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -426,6 +494,17 @@ get_property (GObject *object, guint prop_id,
 }
 
 static void
+finalize (GObject *object)
+{
+	NMSettingIP6Config *self = NM_SETTING_IP6_CONFIG (object);
+	NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE (self);
+
+	g_free (priv->token);
+
+	G_OBJECT_CLASS (nm_setting_ip6_config_parent_class)->finalize (object);
+}
+
+static void
 nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (ip6_class);
@@ -436,6 +515,7 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
 	/* virtual methods */
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
+	object_class->finalize = finalize;
 	setting_class->verify = verify;
 
 	/* Properties */
@@ -568,6 +648,17 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
 	 * ---end---
 	 */
 
+	/* ---ifcfg-rh---
+	 * property: dns-priority
+	 * variable: IPV6_DNS_PRIORITY(+)
+	 * description: The priority for DNS servers of this connection. Lower values have higher priority.
+	 *    If zero, the default value will be used (50 for VPNs, 100 for other connections).
+	 *    A negative value prevents DNS from other connections with greater values to be used.
+	 * default: 0
+	 * example: IPV6_DNS_PRIORITY=20
+	 * ---end---
+	 */
+
 	/**
 	 * NMSettingIP6Config:ip6-privacy:
 	 *
@@ -657,6 +748,30 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
 		                   G_PARAM_CONSTRUCT |
 		                   G_PARAM_STATIC_STRINGS));
 
+	/**
+	 * NMSettingIP6Config:token:
+	 *
+	 * Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02
+	 * IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode.
+	 *
+	 * Since: 1.4
+	 **/
+	/* ---ifcfg-rh---
+	 * property: token
+	 * variable: IPV6_TOKEN
+	 * description: The IPv6 tokenized interface identifier token
+	 * example: IPV6_TOKEN=::53
+	 * ---end---
+	 */
+        g_object_class_install_property
+                (object_class, PROP_TOKEN,
+                 g_param_spec_string (NM_SETTING_IP6_CONFIG_TOKEN, "", "",
+                                      NULL,
+                                      G_PARAM_READWRITE |
+                                      NM_SETTING_PARAM_INFERRABLE |
+                                      G_PARAM_STATIC_STRINGS));
+
+
 	/* IP6-specific property overrides */
 
 	/* ---dbus---
diff --git a/libnm-core/nm-setting-ip6-config.h b/libnm-core/nm-setting-ip6-config.h
index 2966e558..3b29aaa2 100644
--- a/libnm-core/nm-setting-ip6-config.h
+++ b/libnm-core/nm-setting-ip6-config.h
@@ -43,6 +43,8 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE "addr-gen-mode"
 
+#define NM_SETTING_IP6_CONFIG_TOKEN "token"
+
 /**
  * NM_SETTING_IP6_CONFIG_METHOD_IGNORE:
  *
@@ -156,6 +158,8 @@ NMSetting *nm_setting_ip6_config_new (void);
 NMSettingIP6ConfigPrivacy nm_setting_ip6_config_get_ip6_privacy (NMSettingIP6Config *setting);
 NM_AVAILABLE_IN_1_2
 NMSettingIP6ConfigAddrGenMode nm_setting_ip6_config_get_addr_gen_mode (NMSettingIP6Config *setting);
+NM_AVAILABLE_IN_1_4
+const char *nm_setting_ip6_config_get_token (NMSettingIP6Config *setting);
 
 G_END_DECLS
 
diff --git a/libnm-core/nm-setting-private.h b/libnm-core/nm-setting-private.h
index 6560c4a2..8d09e3d7 100644
--- a/libnm-core/nm-setting-private.h
+++ b/libnm-core/nm-setting-private.h
@@ -21,7 +21,6 @@
 #ifndef __NM_SETTING_PRIVATE_H__
 #define __NM_SETTING_PRIVATE_H__
 
-#include "nm-default.h"
 #include "nm-setting.h"
 #include "nm-connection.h"
 #include "nm-core-enum-types.h"
diff --git a/libnm-core/nm-setting-team-port.c b/libnm-core/nm-setting-team-port.c
index 9671f1d9..0d175d5e 100644
--- a/libnm-core/nm-setting-team-port.c
+++ b/libnm-core/nm-setting-team-port.c
@@ -85,6 +85,8 @@ nm_setting_team_port_get_config (NMSettingTeamPort *setting)
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
+	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+
 	if (connection) {
 		NMSettingConnection *s_con;
 		const char *slave_type;
@@ -113,9 +115,54 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			return FALSE;
 		}
 	}
+
+	if (priv->config) {
+		if (!_nm_utils_check_valid_json (priv->config, error)) {
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_TEAM_PORT_SETTING_NAME,
+			                NM_SETTING_TEAM_PORT_CONFIG);
+			/* for backward compatibility, we accept invalid json and normalize it */
+			if (!priv->config[0]) {
+				/* be more forgiving to "" and let it verify() as valid because
+				 * at least anaconda used to write such configs */
+				return NM_SETTING_VERIFY_NORMALIZABLE;
+			}
+			return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
+		}
+	}
+
+	/* NOTE: normalizable/normalizable-errors must appear at the end with decreasing severity.
+	 * Take care to properly order statements with priv->config above. */
+
 	return TRUE;
 }
 
+static gboolean
+compare_property (NMSetting *setting,
+                  NMSetting *other,
+                  const GParamSpec *prop_spec,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingClass *parent_class;
+
+	/* If we are trying to match a connection in order to assume it (and thus
+	 * @flags contains INFERRABLE), use the "relaxed" matching for team
+	 * configuration. Otherwise, for all other purposes (including connection
+	 * comparison before an update), resort to the default string comparison.
+	 */
+	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
+	    && nm_streq0 (prop_spec->name, NM_SETTING_TEAM_PORT_CONFIG)) {
+		return _nm_utils_team_config_equal (NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->config,
+		                                    NM_SETTING_TEAM_PORT_GET_PRIVATE (other)->config,
+		                                    TRUE);
+	}
+
+	/* Otherwise chain up to parent to handle generic compare */
+	parent_class = NM_SETTING_CLASS (nm_setting_team_port_parent_class);
+	return parent_class->compare_property (setting, other, prop_spec, flags);
+}
+
 static void
 nm_setting_team_port_init (NMSettingTeamPort *setting)
 {
@@ -173,10 +220,11 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *setting_class)
 	g_type_class_add_private (setting_class, sizeof (NMSettingTeamPortPrivate));
 
 	/* virtual methods */
-	object_class->set_property = set_property;
-	object_class->get_property = get_property;
-	object_class->finalize = finalize;
-	parent_class->verify       = verify;
+	object_class->set_property     = set_property;
+	object_class->get_property     = get_property;
+	object_class->finalize         = finalize;
+	parent_class->compare_property = compare_property;
+	parent_class->verify           = verify;
 
 	/* Properties */
 	/**
diff --git a/libnm-core/nm-setting-team.c b/libnm-core/nm-setting-team.c
index d47b1e72..a559e0db 100644
--- a/libnm-core/nm-setting-team.c
+++ b/libnm-core/nm-setting-team.c
@@ -27,6 +27,7 @@
 #include "nm-utils.h"
 #include "nm-utils-private.h"
 #include "nm-connection-private.h"
+#include "nm-utils-private.h"
 
 /**
  * SECTION:nm-setting-team
@@ -82,7 +83,56 @@ nm_setting_team_get_config (NMSettingTeam *setting)
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
-	return _nm_connection_verify_required_interface_name (connection, error);
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	if (!_nm_connection_verify_required_interface_name (connection, error))
+		return FALSE;
+
+	if (priv->config) {
+		if (!_nm_utils_check_valid_json (priv->config, error)) {
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_TEAM_SETTING_NAME,
+			                NM_SETTING_TEAM_CONFIG);
+			/* for backward compatibility, we accept invalid json and normalize it */
+			if (!priv->config[0]) {
+				/* be more forgiving to "" and let it verify() as valid because
+				 * at least anaconda used to write such configs */
+				return NM_SETTING_VERIFY_NORMALIZABLE;
+			}
+			return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
+		}
+	}
+
+	/* NOTE: normalizable/normalizable-errors must appear at the end with decreasing severity.
+	 * Take care to properly order statements with priv->config above. */
+
+	return TRUE;
+}
+
+static gboolean
+compare_property (NMSetting *setting,
+                  NMSetting *other,
+                  const GParamSpec *prop_spec,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingClass *parent_class;
+
+	/* If we are trying to match a connection in order to assume it (and thus
+	 * @flags contains INFERRABLE), use the "relaxed" matching for team
+	 * configuration. Otherwise, for all other purposes (including connection
+	 * comparison before an update), resort to the default string comparison.
+	 */
+	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
+	    && nm_streq0 (prop_spec->name, NM_SETTING_TEAM_CONFIG)) {
+		return _nm_utils_team_config_equal (NM_SETTING_TEAM_GET_PRIVATE (setting)->config,
+		                                    NM_SETTING_TEAM_GET_PRIVATE (other)->config,
+		                                    FALSE);
+	}
+
+	/* Otherwise chain up to parent to handle generic compare */
+	parent_class = NM_SETTING_CLASS (nm_setting_team_parent_class);
+	return parent_class->compare_property (setting, other, prop_spec, flags);
 }
 
 static void
@@ -142,10 +192,11 @@ nm_setting_team_class_init (NMSettingTeamClass *setting_class)
 	g_type_class_add_private (setting_class, sizeof (NMSettingTeamPrivate));
 
 	/* virtual methods */
-	object_class->set_property = set_property;
-	object_class->get_property = get_property;
-	object_class->finalize     = finalize;
-	parent_class->verify       = verify;
+	object_class->set_property     = set_property;
+	object_class->get_property     = get_property;
+	object_class->finalize         = finalize;
+	parent_class->compare_property = compare_property;
+	parent_class->verify           = verify;
 
 	/* Properties */
 	/**
diff --git a/libnm-core/nm-setting-vpn.c b/libnm-core/nm-setting-vpn.c
index c9a1e20b..aa4ddc3a 100644
--- a/libnm-core/nm-setting-vpn.c
+++ b/libnm-core/nm-setting-vpn.c
@@ -566,8 +566,7 @@ get_secret_flags (NMSetting *setting,
                   GError **error)
 {
 	NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
-	gboolean success = FALSE;
-	char *flags_key;
+	gs_free char *flags_key = NULL;
 	gpointer val;
 	unsigned long tmp;
 	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
@@ -576,28 +575,21 @@ get_secret_flags (NMSetting *setting,
 	if (g_hash_table_lookup_extended (priv->data, flags_key, NULL, &val)) {
 		errno = 0;
 		tmp = strtoul ((const char *) val, NULL, 10);
-		if ((errno == 0) && (tmp <= NM_SETTING_SECRET_FLAGS_ALL)) {
-			flags = (NMSettingSecretFlags) tmp;
-			success = TRUE;
-		} else {
+		if ((errno != 0) || (tmp > NM_SETTING_SECRET_FLAGS_ALL)) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("failed to convert value '%s' to uint"),
 			             (const char *) val);
 			g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, flags_key);
+			return FALSE;
 		}
-	} else {
-		g_set_error_literal (error,
-		                     NM_CONNECTION_ERROR,
-		                     NM_CONNECTION_ERROR_MISSING_PROPERTY,
-		                     _("secret flags property not found"));
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, flags_key);
+		flags = (NMSettingSecretFlags) tmp;
 	}
-	g_free (flags_key);
+
 	if (out_flags)
 		*out_flags = flags;
-	return success;
+	return TRUE;
 }
 
 static gboolean
diff --git a/libnm-core/nm-setting-wired.c b/libnm-core/nm-setting-wired.c
index 132f8e05..fc5ab3e5 100644
--- a/libnm-core/nm-setting-wired.c
+++ b/libnm-core/nm-setting-wired.c
@@ -28,6 +28,7 @@
 #include <net/ethernet.h>
 
 #include "nm-utils.h"
+#include "nm-common-macros.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
 
@@ -52,6 +53,7 @@ typedef struct {
 	gboolean auto_negotiate;
 	char *device_mac_address;
 	char *cloned_mac_address;
+	char *generate_mac_address_mask;
 	GArray *mac_address_blacklist;
 	guint32 mtu;
 	char **s390_subchannels;
@@ -69,6 +71,7 @@ enum {
 	PROP_AUTO_NEGOTIATE,
 	PROP_MAC_ADDRESS,
 	PROP_CLONED_MAC_ADDRESS,
+	PROP_GENERATE_MAC_ADDRESS_MASK,
 	PROP_MAC_ADDRESS_BLACKLIST,
 	PROP_MTU,
 	PROP_S390_SUBCHANNELS,
@@ -188,6 +191,22 @@ nm_setting_wired_get_cloned_mac_address (NMSettingWired *setting)
 }
 
 /**
+ * nm_setting_wired_get_generate_mac_address_mask:
+ * @setting: the #NMSettingWired
+ *
+ * Returns: the #NMSettingWired:generate-mac-address-mask property of the setting
+ *
+ * Since: 1.4
+ **/
+const char *
+nm_setting_wired_get_generate_mac_address_mask (NMSettingWired *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), NULL);
+
+	return NM_SETTING_WIRED_GET_PRIVATE (setting)->generate_mac_address_mask;
+}
+
+/**
  * nm_setting_wired_get_mac_address_blacklist:
  * @setting: the #NMSettingWired
  *
@@ -504,7 +523,7 @@ nm_setting_wired_add_s390_option (NMSettingWired *setting,
 	g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), FALSE);
 	g_return_val_if_fail (key != NULL, FALSE);
 	g_return_val_if_fail (strlen (key), FALSE);
-	g_return_val_if_fail (_nm_utils_string_in_list (key, valid_s390_opts), FALSE);
+	g_return_val_if_fail (g_strv_contains (valid_s390_opts, key), FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
 
 	value_len = strlen (value);
@@ -611,8 +630,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	GHashTableIter iter;
 	const char *key, *value;
 	int i;
+	GError *local = NULL;
 
-	if (priv->port && !_nm_utils_string_in_list (priv->port, valid_ports)) {
+	if (priv->port && !g_strv_contains (valid_ports, priv->port)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -622,7 +642,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->duplex && !_nm_utils_string_in_list (priv->duplex, valid_duplex)) {
+	if (priv->duplex && !g_strv_contains (valid_duplex, priv->duplex)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -668,7 +688,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
-	if (priv->s390_nettype && !_nm_utils_string_in_list (priv->s390_nettype, valid_nettype)) {
+	if (priv->s390_nettype && !g_strv_contains (valid_nettype, priv->s390_nettype)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -679,7 +699,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 
 	g_hash_table_iter_init (&iter, priv->s390_options);
 	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) {
-		if (   !_nm_utils_string_in_list (key, valid_s390_opts)
+		if (   !g_strv_contains (valid_s390_opts, key)
 		    || !strlen (value)
 		    || (strlen (value) > 200)) {
 			g_set_error (error,
@@ -692,7 +712,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
-	if (priv->cloned_mac_address && !nm_utils_hwaddr_valid (priv->cloned_mac_address, ETH_ALEN)) {
+	if (   priv->cloned_mac_address
+	    && !NM_CLONED_MAC_IS_SPECIAL (priv->cloned_mac_address)
+	    && !nm_utils_hwaddr_valid (priv->cloned_mac_address, ETH_ALEN)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -701,6 +723,20 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
+	/* generate-mac-address-mask only makes sense with cloned-mac-address "random" or
+	 * "stable". Still, let's not be so strict about that and accept the value
+	 * even if it is unused. */
+	if (!_nm_utils_generate_mac_address_mask_parse (priv->generate_mac_address_mask,
+	                                                NULL, NULL, NULL, &local)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     local->message);
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK);
+		g_error_free (local);
+		return FALSE;
+	}
+
 	if (   NM_FLAGS_ANY (priv->wol, NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS)
 	    && !nm_utils_is_power_of_two (priv->wol)) {
 		g_set_error_literal (error,
@@ -732,6 +768,25 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	return TRUE;
 }
 
+static gboolean
+compare_property (NMSetting *setting,
+                  NMSetting *other,
+                  const GParamSpec *prop_spec,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingClass *parent_class;
+
+	if (nm_streq (prop_spec->name, NM_SETTING_WIRED_CLONED_MAC_ADDRESS)) {
+		return nm_streq0 (NM_SETTING_WIRED_GET_PRIVATE (setting)->cloned_mac_address,
+		                  NM_SETTING_WIRED_GET_PRIVATE (other)->cloned_mac_address);
+	}
+
+	parent_class = NM_SETTING_CLASS (nm_setting_wired_parent_class);
+	return parent_class->compare_property (setting, other, prop_spec, flags);
+}
+
+/*****************************************************************************/
+
 static void
 clear_blacklist_item (char **item_p)
 {
@@ -763,6 +818,7 @@ finalize (GObject *object)
 
 	g_free (priv->device_mac_address);
 	g_free (priv->cloned_mac_address);
+	g_free (priv->generate_mac_address_mask);
 	g_array_unref (priv->mac_address_blacklist);
 
 	if (priv->s390_subchannels)
@@ -807,6 +863,10 @@ set_property (GObject *object, guint prop_id,
 		priv->cloned_mac_address = _nm_utils_hwaddr_canonical_or_invalid (g_value_get_string (value),
 		                                                                  ETH_ALEN);
 		break;
+	case PROP_GENERATE_MAC_ADDRESS_MASK:
+		g_free (priv->generate_mac_address_mask);
+		priv->generate_mac_address_mask = g_value_dup_string (value);
+		break;
 	case PROP_MAC_ADDRESS_BLACKLIST:
 		blacklist = g_value_get_boxed (value);
 		g_array_set_size (priv->mac_address_blacklist, 0);
@@ -872,6 +932,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_CLONED_MAC_ADDRESS:
 		g_value_set_string (value, nm_setting_wired_get_cloned_mac_address (setting));
 		break;
+	case PROP_GENERATE_MAC_ADDRESS_MASK:
+		g_value_set_string (value, nm_setting_wired_get_generate_mac_address_mask (setting));
+		break;
 	case PROP_MAC_ADDRESS_BLACKLIST:
 		g_value_set_boxed (value, (char **) priv->mac_address_blacklist->data);
 		break;
@@ -900,18 +963,19 @@ get_property (GObject *object, guint prop_id,
 }
 
 static void
-nm_setting_wired_class_init (NMSettingWiredClass *setting_class)
+nm_setting_wired_class_init (NMSettingWiredClass *setting_wired_class)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (setting_class);
-	NMSettingClass *parent_class = NM_SETTING_CLASS (setting_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (setting_wired_class);
+	NMSettingClass *setting_class = NM_SETTING_CLASS (setting_wired_class);
 
-	g_type_class_add_private (setting_class, sizeof (NMSettingWiredPrivate));
+	g_type_class_add_private (setting_wired_class, sizeof (NMSettingWiredPrivate));
 
 	/* virtual methods */
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 	object_class->finalize     = finalize;
-	parent_class->verify       = verify;
+	setting_class->verify      = verify;
+	setting_class->compare_property = compare_property;
 
 	/* Properties */
 	/**
@@ -1023,7 +1087,7 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_class)
 		                      G_PARAM_READWRITE |
 		                      NM_SETTING_PARAM_INFERRABLE |
 		                      G_PARAM_STATIC_STRINGS));
-	_nm_setting_class_transform_property (parent_class, NM_SETTING_WIRED_MAC_ADDRESS,
+	_nm_setting_class_transform_property (setting_class, NM_SETTING_WIRED_MAC_ADDRESS,
 	                                      G_VARIANT_TYPE_BYTESTRING,
 	                                      _nm_utils_hwaddr_to_dbus,
 	                                      _nm_utils_hwaddr_from_dbus);
@@ -1033,6 +1097,20 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_class)
 	 *
 	 * If specified, request that the device use this MAC address instead of its
 	 * permanent MAC address.  This is known as MAC cloning or spoofing.
+	 *
+	 * Beside explicitly specifing a MAC address, the special values "preserve", "permanent",
+	 * "random" and "stable" are supported.
+	 * "preserve" means not to touch the MAC address on activation.
+	 * "permanent" means to use the permanent hardware address of the device.
+	 * "random" creates a random MAC address on each connect.
+	 * "stable" creates a hashed MAC address based on connection.stable-id (or
+	 * the connection's UUID) and a machine dependent key.
+	 *
+	 * If unspecified, the value can be overwritten via global defaults, see manual
+	 * of NetworkManager.conf. If still unspecified, it defaults to "permanent".
+	 *
+	 * On D-Bus, this field is expressed as "assigned-mac-address" or the deprecated
+	 * "cloned-mac-address".
 	 **/
 	/* ---keyfile---
 	 * property: cloned-mac-address
@@ -1047,6 +1125,12 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_class)
 	 * description: Cloned (spoofed) MAC address in traditional hex-digits-and-colons
 	 *    notation (e.g. 00:22:68:14:5A:99).
 	 * ---end---
+	 * ---dbus---
+	 * property: cloned-mac-address
+	 * format: byte array
+	 * description: This D-Bus field is deprecated in favor of "assigned-mac-address"
+	 *    which is more flexible and allows specifying special variants like "random".
+	 * ---end---
 	 */
 	g_object_class_install_property
 		(object_class, PROP_CLONED_MAC_ADDRESS,
@@ -1055,10 +1139,75 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_class)
 		                      G_PARAM_READWRITE |
 		                      NM_SETTING_PARAM_INFERRABLE |
 		                      G_PARAM_STATIC_STRINGS));
-	_nm_setting_class_transform_property (parent_class, NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
-	                                      G_VARIANT_TYPE_BYTESTRING,
-	                                      _nm_utils_hwaddr_to_dbus,
-	                                      _nm_utils_hwaddr_from_dbus);
+	_nm_setting_class_override_property (setting_class,
+	                                     NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+	                                     G_VARIANT_TYPE_BYTESTRING,
+	                                     _nm_utils_hwaddr_cloned_get,
+	                                     _nm_utils_hwaddr_cloned_set,
+	                                     _nm_utils_hwaddr_cloned_not_set);
+
+	/* ---dbus---
+	 * property: assigned-mac-address
+	 * format: string
+	 * description: The new field for the cloned MAC address. It can be either
+	 *   a hardware address in ASCII representation, or one of the special values
+	 *   "preserve", "permanent", "random" or "stable".
+	 *   This field replaces the deprecated "cloned-mac-address" on D-Bus, which
+	 *   can only contain explict hardware addresses.
+	 * ---end---
+	 */
+	_nm_setting_class_add_dbus_only_property (setting_class,
+	                                          "assigned-mac-address",
+	                                          G_VARIANT_TYPE_STRING,
+	                                          _nm_utils_hwaddr_cloned_data_synth,
+	                                          _nm_utils_hwaddr_cloned_data_set);
+
+	/**
+	 * NMSettingWired:generate-mac-address-mask:
+	 *
+	 * With #NMSettingWired:cloned-mac-address setting "random" or "stable",
+	 * by default all bits of the MAC address are scrambled and a locally-administered,
+	 * unicast MAC address is created. This property allows to specify that certain bits
+	 * are fixed. Note that the least significant bit of the first MAC address will
+	 * always be unset to create a unicast MAC address.
+	 *
+	 * If the property is %NULL, it is eligible to be overwritten by a default
+	 * connection setting. If the value is still %NULL or an empty string, the
+	 * default is to create a locally-administered, unicast MAC address.
+	 *
+	 * If the value contains one MAC address, this address is used as mask. The set
+	 * bits of the mask are to be filled with the current MAC address of the device,
+	 * while the unset bits are subject to randomization.
+	 * Setting "FE:FF:FF:00:00:00" means to preserve the OUI of the current MAC address
+	 * and only randomize the lower 3 bytes using the "random" or "stable" algorithm.
+	 *
+	 * If the value contains one additional MAC address after the mask,
+	 * this address is used instead of the current MAC address to fill the bits
+	 * that shall not be randomized. For example, a value of
+	 * "FE:FF:FF:00:00:00 68:F7:28:00:00:00" will set the OUI of the MAC address
+	 * to 68:F7:28, while the lower bits are randomized. A value of
+	 * "02:00:00:00:00:00 00:00:00:00:00:00" will create a fully scrambled
+	 * globally-administered, burned-in MAC address.
+	 *
+	 * If the value contains more then one additional MAC addresses, one of
+	 * them is chosen randomly. For example, "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00"
+	 * will create a fully scrambled MAC address, randomly locally or globally
+	 * administered.
+	 **/
+	/* ---ifcfg-rh---
+	 * property: generate-mac-address-mask
+	 * variable: GENERATE_MAC_ADDRESS_MASK
+	 * description: the MAC address mask for generating randomized and stable
+	 *   cloned-mac-address.
+	 * ---end---
+	 */
+	g_object_class_install_property
+	    (object_class, PROP_GENERATE_MAC_ADDRESS_MASK,
+	     g_param_spec_string (NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, "", "",
+	                          NULL,
+	                          G_PARAM_READWRITE |
+	                          NM_SETTING_PARAM_FUZZY_IGNORE |
+	                          G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * NMSettingWired:mac-address-blacklist:
@@ -1183,7 +1332,7 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_class)
 		                     G_PARAM_READWRITE |
 		                     NM_SETTING_PARAM_INFERRABLE |
 		                     G_PARAM_STATIC_STRINGS));
-	_nm_setting_class_transform_property (parent_class, NM_SETTING_WIRED_S390_OPTIONS,
+	_nm_setting_class_transform_property (setting_class, NM_SETTING_WIRED_S390_OPTIONS,
 	                                      G_VARIANT_TYPE ("a{ss}"),
 	                                      _nm_utils_strdict_to_dbus,
 	                                      _nm_utils_strdict_from_dbus);
diff --git a/libnm-core/nm-setting-wired.h b/libnm-core/nm-setting-wired.h
index 6bc66782..30b0b6c2 100644
--- a/libnm-core/nm-setting-wired.h
+++ b/libnm-core/nm-setting-wired.h
@@ -85,6 +85,7 @@ typedef enum { /*< flags >*/
 #define NM_SETTING_WIRED_AUTO_NEGOTIATE "auto-negotiate"
 #define NM_SETTING_WIRED_MAC_ADDRESS "mac-address"
 #define NM_SETTING_WIRED_CLONED_MAC_ADDRESS "cloned-mac-address"
+#define NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK "generate-mac-address-mask"
 #define NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST "mac-address-blacklist"
 #define NM_SETTING_WIRED_MTU "mtu"
 #define NM_SETTING_WIRED_S390_SUBCHANNELS "s390-subchannels"
@@ -117,6 +118,9 @@ gboolean          nm_setting_wired_get_auto_negotiate   (NMSettingWired *setting
 const char *      nm_setting_wired_get_mac_address      (NMSettingWired *setting);
 const char *      nm_setting_wired_get_cloned_mac_address (NMSettingWired *setting);
 
+NM_AVAILABLE_IN_1_4
+const char *      nm_setting_wired_get_generate_mac_address_mask (NMSettingWired *setting);
+
 const char * const *nm_setting_wired_get_mac_address_blacklist   (NMSettingWired *setting);
 guint32           nm_setting_wired_get_num_mac_blacklist_items (NMSettingWired *setting);
 const char *      nm_setting_wired_get_mac_blacklist_item      (NMSettingWired *setting,
diff --git a/libnm-core/nm-setting-wireless-security.c b/libnm-core/nm-setting-wireless-security.c
index 64e2aa45..f98e9e57 100644
--- a/libnm-core/nm-setting-wireless-security.c
+++ b/libnm-core/nm-setting-wireless-security.c
@@ -863,7 +863,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (!_nm_utils_string_in_list (priv->key_mgmt, valid_key_mgmt)) {
+	if (!g_strv_contains (valid_key_mgmt, priv->key_mgmt)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -936,7 +936,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->auth_alg && !_nm_utils_string_in_list (priv->auth_alg, valid_auth_algs)) {
+	if (priv->auth_alg && !g_strv_contains (valid_auth_algs, priv->auth_alg)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -958,7 +958,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		const char *wpa_none[] = { "wpa-none", NULL };
 
 		/* For ad-hoc connections, pairwise must be "none" */
-		if (_nm_utils_string_in_list (priv->key_mgmt, wpa_none)) {
+		if (g_strv_contains (wpa_none, priv->key_mgmt)) {
 			GSList *iter;
 			gboolean found = FALSE;
 
diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c
index ee4ac6f2..a7f58fe5 100644
--- a/libnm-core/nm-setting-wireless.c
+++ b/libnm-core/nm-setting-wireless.c
@@ -22,11 +22,13 @@
 
 #include "nm-default.h"
 
+#include "nm-setting-wireless.h"
+
 #include <string.h>
 #include <net/ethernet.h>
 
-#include "nm-setting-wireless.h"
 #include "nm-utils.h"
+#include "nm-common-macros.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
 
@@ -54,6 +56,7 @@ typedef struct {
 	guint32 tx_power;
 	char *device_mac_address;
 	char *cloned_mac_address;
+	char *generate_mac_address_mask;
 	GArray *mac_address_blacklist;
 	guint32 mtu;
 	GSList *seen_bssids;
@@ -73,6 +76,7 @@ enum {
 	PROP_TX_POWER,
 	PROP_MAC_ADDRESS,
 	PROP_CLONED_MAC_ADDRESS,
+	PROP_GENERATE_MAC_ADDRESS_MASK,
 	PROP_MAC_ADDRESS_BLACKLIST,
 	PROP_MTU,
 	PROP_SEEN_BSSIDS,
@@ -420,6 +424,22 @@ nm_setting_wireless_get_cloned_mac_address (NMSettingWireless *setting)
 }
 
 /**
+ * nm_setting_wireless_get_generate_mac_address_mask:
+ * @setting: the #NMSettingWireless
+ *
+ * Returns: the #NMSettingWireless:generate-mac-address-mask property of the setting
+ *
+ * Since: 1.4
+ **/
+const char *
+nm_setting_wireless_get_generate_mac_address_mask (NMSettingWireless *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIRELESS (setting), NULL);
+
+	return NM_SETTING_WIRELESS_GET_PRIVATE (setting)->generate_mac_address_mask;
+}
+
+/**
  * nm_setting_wireless_get_mac_address_blacklist:
  * @setting: the #NMSettingWireless
  *
@@ -721,6 +741,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	GSList *iter;
 	int i;
 	gsize length;
+	GError *local = NULL;
 
 	if (!priv->ssid) {
 		g_set_error_literal (error,
@@ -741,7 +762,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->mode && !_nm_utils_string_in_list (priv->mode, valid_modes)) {
+	if (priv->mode && !g_strv_contains (valid_modes, priv->mode)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -751,7 +772,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->band && !_nm_utils_string_in_list (priv->band, valid_bands)) {
+	if (priv->band && !g_strv_contains (valid_bands, priv->band)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -801,7 +822,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->cloned_mac_address && !nm_utils_hwaddr_valid (priv->cloned_mac_address, ETH_ALEN)) {
+	if (   priv->cloned_mac_address
+	    && !NM_CLONED_MAC_IS_SPECIAL (priv->cloned_mac_address)
+	    && !nm_utils_hwaddr_valid (priv->cloned_mac_address, ETH_ALEN)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -810,6 +833,20 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
+	/* generate-mac-address-mask only makes sense with cloned-mac-address "random" or
+	 * "stable". Still, let's not be so strict about that and accept the value
+	 * even if it is unused. */
+	if (!_nm_utils_generate_mac_address_mask_parse (priv->generate_mac_address_mask,
+	                                                NULL, NULL, NULL, &local)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     local->message);
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK);
+		g_error_free (local);
+		return FALSE;
+	}
+
 	for (i = 0; i < priv->mac_address_blacklist->len; i++) {
 		const char *mac = g_array_index (priv->mac_address_blacklist, const char *, i);
 
@@ -836,9 +873,61 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
+	if (!NM_IN_SET (priv->mac_address_randomization,
+	                NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
+	                NM_SETTING_MAC_RANDOMIZATION_NEVER,
+	                NM_SETTING_MAC_RANDOMIZATION_ALWAYS)) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("invalid value"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION);
+		return FALSE;
+	}
+
+	/* from here on, check for NM_SETTING_VERIFY_NORMALIZABLE conditions. */
+
+	if (priv->cloned_mac_address) {
+		if (   priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS
+		    && nm_streq (priv->cloned_mac_address, "random"))
+			goto mac_addr_rand_ok;
+		if (   priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_NEVER
+		    && nm_streq (priv->cloned_mac_address, "permanent"))
+			goto mac_addr_rand_ok;
+		if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT)
+			goto mac_addr_rand_ok;
+	} else if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT)
+		goto mac_addr_rand_ok;
+	g_set_error (error,
+	             NM_CONNECTION_ERROR,
+	             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+	             _("conflicting value of mac-address-randomization and cloned-mac-address"));
+	g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS);
+	return NM_SETTING_VERIFY_NORMALIZABLE;
+mac_addr_rand_ok:
+
 	return TRUE;
 }
 
+static gboolean
+compare_property (NMSetting *setting,
+                  NMSetting *other,
+                  const GParamSpec *prop_spec,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingClass *parent_class;
+
+	if (nm_streq (prop_spec->name, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS)) {
+		return nm_streq0 (NM_SETTING_WIRELESS_GET_PRIVATE (setting)->cloned_mac_address,
+		                  NM_SETTING_WIRELESS_GET_PRIVATE (other)->cloned_mac_address);
+	}
+
+	parent_class = NM_SETTING_CLASS (nm_setting_wireless_parent_class);
+	return parent_class->compare_property (setting, other, prop_spec, flags);
+}
+
+/*****************************************************************************/
+
 static GVariant *
 nm_setting_wireless_get_security (NMSetting    *setting,
                                   NMConnection *connection,
@@ -879,6 +968,7 @@ finalize (GObject *object)
 	g_free (priv->bssid);
 	g_free (priv->device_mac_address);
 	g_free (priv->cloned_mac_address);
+	g_free (priv->generate_mac_address_mask);
 	g_array_unref (priv->mac_address_blacklist);
 	g_slist_free_full (priv->seen_bssids, g_free);
 
@@ -931,6 +1021,10 @@ set_property (GObject *object, guint prop_id,
 		priv->cloned_mac_address = _nm_utils_hwaddr_canonical_or_invalid (g_value_get_string (value),
 		                                                                  ETH_ALEN);
 		break;
+	case PROP_GENERATE_MAC_ADDRESS_MASK:
+		g_free (priv->generate_mac_address_mask);
+		priv->generate_mac_address_mask = g_value_dup_string (value);
+		break;
 	case PROP_MAC_ADDRESS_BLACKLIST:
 		blacklist = g_value_get_boxed (value);
 		g_array_set_size (priv->mac_address_blacklist, 0);
@@ -998,6 +1092,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_CLONED_MAC_ADDRESS:
 		g_value_set_string (value, nm_setting_wireless_get_cloned_mac_address (setting));
 		break;
+	case PROP_GENERATE_MAC_ADDRESS_MASK:
+		g_value_set_string (value, nm_setting_wireless_get_generate_mac_address_mask (setting));
+		break;
 	case PROP_MAC_ADDRESS_BLACKLIST:
 		g_value_set_boxed (value, (char **) priv->mac_address_blacklist->data);
 		break;
@@ -1023,18 +1120,19 @@ get_property (GObject *object, guint prop_id,
 }
 
 static void
-nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
+nm_setting_wireless_class_init (NMSettingWirelessClass *setting_wireless_class)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (setting_class);
-	NMSettingClass *parent_class = NM_SETTING_CLASS (setting_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (setting_wireless_class);
+	NMSettingClass *setting_class = NM_SETTING_CLASS (setting_wireless_class);
 
-	g_type_class_add_private (setting_class, sizeof (NMSettingWirelessPrivate));
+	g_type_class_add_private (setting_wireless_class, sizeof (NMSettingWirelessPrivate));
 
 	/* virtual methods */
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 	object_class->finalize     = finalize;
-	parent_class->verify       = verify;
+	setting_class->verify      = verify;
+	setting_class->compare_property = compare_property;
 
 	/* Properties */
 	/**
@@ -1154,7 +1252,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
 		                      NULL,
 		                      G_PARAM_READWRITE |
 		                      G_PARAM_STATIC_STRINGS));
-	_nm_setting_class_transform_property (parent_class, NM_SETTING_WIRELESS_BSSID,
+	_nm_setting_class_transform_property (setting_class, NM_SETTING_WIRELESS_BSSID,
 	                                      G_VARIANT_TYPE_BYTESTRING,
 	                                      _nm_utils_hwaddr_to_dbus,
 	                                      _nm_utils_hwaddr_from_dbus);
@@ -1231,7 +1329,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
 		                      NULL,
 		                      G_PARAM_READWRITE |
 		                      G_PARAM_STATIC_STRINGS));
-	_nm_setting_class_transform_property (parent_class, NM_SETTING_WIRELESS_MAC_ADDRESS,
+	_nm_setting_class_transform_property (setting_class, NM_SETTING_WIRELESS_MAC_ADDRESS,
 	                                      G_VARIANT_TYPE_BYTESTRING,
 	                                      _nm_utils_hwaddr_to_dbus,
 	                                      _nm_utils_hwaddr_from_dbus);
@@ -1239,8 +1337,22 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
 	/**
 	 * NMSettingWireless:cloned-mac-address:
 	 *
-	 * If specified, request that the Wi-Fi device use this MAC address instead
-	 * of its permanent MAC address.  This is known as MAC cloning or spoofing.
+	 * If specified, request that the device use this MAC address instead of its
+	 * permanent MAC address.  This is known as MAC cloning or spoofing.
+	 *
+	 * Beside explicitly specifing a MAC address, the special values "preserve", "permanent",
+	 * "random" and "stable" are supported.
+	 * "preserve" means not to touch the MAC address on activation.
+	 * "permanent" means to use the permanent hardware address of the device.
+	 * "random" creates a random MAC address on each connect.
+	 * "stable" creates a hashed MAC address based on connection.stable-id (or
+	 * the connection's UUID) and a machine dependent key.
+	 *
+	 * If unspecified, the value can be overwritten via global defaults, see manual
+	 * of NetworkManager.conf. If still unspecified, it defaults to "permanent".
+	 *
+	 * On D-Bus, this field is expressed as "assigned-mac-address" or the deprecated
+	 * "cloned-mac-address".
 	 **/
 	/* ---keyfile---
 	 * property: cloned-mac-address
@@ -1255,6 +1367,12 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
 	 * description: Cloned (spoofed) MAC address in traditional hex-digits-and-colons
 	 *    notation (e.g. 00:22:68:14:5A:99).
 	 * ---end---
+	 * ---dbus---
+	 * property: cloned-mac-address
+	 * format: byte array
+	 * description: This D-Bus field is deprecated in favor of "assigned-mac-address"
+	 *    which is more flexible and allows specifying special variants like "random".
+	 * ---end---
 	 */
 	g_object_class_install_property
 		(object_class, PROP_CLONED_MAC_ADDRESS,
@@ -1262,10 +1380,75 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
 		                      NULL,
 		                      G_PARAM_READWRITE |
 		                      G_PARAM_STATIC_STRINGS));
-	_nm_setting_class_transform_property (parent_class, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
-	                                      G_VARIANT_TYPE_BYTESTRING,
-	                                      _nm_utils_hwaddr_to_dbus,
-	                                      _nm_utils_hwaddr_from_dbus);
+	_nm_setting_class_override_property (setting_class,
+	                                     NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+	                                     G_VARIANT_TYPE_BYTESTRING,
+	                                     _nm_utils_hwaddr_cloned_get,
+	                                     _nm_utils_hwaddr_cloned_set,
+	                                     _nm_utils_hwaddr_cloned_not_set);
+
+	/* ---dbus---
+	 * property: assigned-mac-address
+	 * format: string
+	 * description: The new field for the cloned MAC address. It can be either
+	 *   a hardware address in ASCII representation, or one of the special values
+	 *   "preserve", "permanent", "random" or "stable".
+	 *   This field replaces the deprecated "cloned-mac-address" on D-Bus, which
+	 *   can only contain explict hardware addresses.
+	 * ---end---
+	 */
+	_nm_setting_class_add_dbus_only_property (setting_class,
+	                                          "assigned-mac-address",
+	                                          G_VARIANT_TYPE_STRING,
+	                                          _nm_utils_hwaddr_cloned_data_synth,
+	                                          _nm_utils_hwaddr_cloned_data_set);
+
+	/**
+	 * NMSettingWireless:generate-mac-address-mask:
+	 *
+	 * With #NMSettingWireless:cloned-mac-address setting "random" or "stable",
+	 * by default all bits of the MAC address are scrambled and a locally-administered,
+	 * unicast MAC address is created. This property allows to specify that certain bits
+	 * are fixed. Note that the least significant bit of the first MAC address will
+	 * always be unset to create a unicast MAC address.
+	 *
+	 * If the property is %NULL, it is eligible to be overwritten by a default
+	 * connection setting. If the value is still %NULL or an empty string, the
+	 * default is to create a locally-administered, unicast MAC address.
+	 *
+	 * If the value contains one MAC address, this address is used as mask. The set
+	 * bits of the mask are to be filled with the current MAC address of the device,
+	 * while the unset bits are subject to randomization.
+	 * Setting "FE:FF:FF:00:00:00" means to preserve the OUI of the current MAC address
+	 * and only randomize the lower 3 bytes using the "random" or "stable" algorithm.
+	 *
+	 * If the value contains one additional MAC address after the mask,
+	 * this address is used instead of the current MAC address to fill the bits
+	 * that shall not be randomized. For example, a value of
+	 * "FE:FF:FF:00:00:00 68:F7:28:00:00:00" will set the OUI of the MAC address
+	 * to 68:F7:28, while the lower bits are randomized. A value of
+	 * "02:00:00:00:00:00 00:00:00:00:00:00" will create a fully scrambled
+	 * globally-administered, burned-in MAC address.
+	 *
+	 * If the value contains more then one additional MAC addresses, one of
+	 * them is chosen randomly. For example, "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00"
+	 * will create a fully scrambled MAC address, randomly locally or globally
+	 * administered.
+	 **/
+	/* ---ifcfg-rh---
+	 * property: generate-mac-address-mask
+	 * variable: GENERATE_MAC_ADDRESS_MASK
+	 * description: the MAC address mask for generating randomized and stable
+	 *   cloned-mac-address.
+	 * ---end---
+	 */
+	g_object_class_install_property
+	    (object_class, PROP_GENERATE_MAC_ADDRESS_MASK,
+	     g_param_spec_string (NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK, "", "",
+	                          NULL,
+	                          G_PARAM_READWRITE |
+	                          NM_SETTING_PARAM_FUZZY_IGNORE |
+	                          G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * NMSettingWireless:mac-address-blacklist:
@@ -1398,6 +1581,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
 	 * (always randomize the MAC address).
 	 *
 	 * Since: 1.2
+	 * Deprecated: 1.4: Deprecated by NMSettingWireless:cloned-mac-address property
 	 **/
 	/* ---ifcfg-rh---
 	 * property: mac-address-randomization
@@ -1428,7 +1612,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_class)
 	 *   NetworkManager daemons.
 	 * ---end---
 	 */
-	_nm_setting_class_add_dbus_only_property (parent_class, "security",
+	_nm_setting_class_add_dbus_only_property (setting_class, "security",
 	                                          G_VARIANT_TYPE_STRING,
 	                                          nm_setting_wireless_get_security, NULL);
 }
diff --git a/libnm-core/nm-setting-wireless.h b/libnm-core/nm-setting-wireless.h
index 574cee54..35fa79c1 100644
--- a/libnm-core/nm-setting-wireless.h
+++ b/libnm-core/nm-setting-wireless.h
@@ -50,6 +50,7 @@ G_BEGIN_DECLS
 #define NM_SETTING_WIRELESS_TX_POWER    "tx-power"
 #define NM_SETTING_WIRELESS_MAC_ADDRESS "mac-address"
 #define NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS "cloned-mac-address"
+#define NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK "generate-mac-address-mask"
 #define NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST "mac-address-blacklist"
 #define NM_SETTING_WIRELESS_MTU         "mtu"
 #define NM_SETTING_WIRELESS_SEEN_BSSIDS "seen-bssids"
@@ -126,6 +127,9 @@ guint32           nm_setting_wireless_get_tx_power           (NMSettingWireless
 const char       *nm_setting_wireless_get_mac_address        (NMSettingWireless *setting);
 const char       *nm_setting_wireless_get_cloned_mac_address (NMSettingWireless *setting);
 
+NM_AVAILABLE_IN_1_4
+const char       *nm_setting_wireless_get_generate_mac_address_mask (NMSettingWireless *setting);
+
 const char * const *nm_setting_wireless_get_mac_address_blacklist   (NMSettingWireless *setting);
 guint32           nm_setting_wireless_get_num_mac_blacklist_items (NMSettingWireless *setting);
 const char *      nm_setting_wireless_get_mac_blacklist_item      (NMSettingWireless *setting,
diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h
index 68aaaa1c..bd54756a 100644
--- a/libnm-core/nm-utils-private.h
+++ b/libnm-core/nm-utils-private.h
@@ -31,8 +31,34 @@
 gboolean    _nm_utils_string_slist_validate (GSList *list,
                                              const char **valid_values);
 
+gboolean    _nm_utils_check_valid_json  (const char *json, GError **error);
+gboolean    _nm_utils_team_config_equal (const char *conf1, const char *conf2, gboolean port);
+
 /* D-Bus transform funcs */
 
+GVariant   *_nm_utils_hwaddr_cloned_get (NMSetting     *setting,
+                                         const char    *property);
+gboolean    _nm_utils_hwaddr_cloned_set (NMSetting     *setting,
+                                         GVariant      *connection_dict,
+                                         const char    *property,
+                                         GVariant      *value,
+                                         NMSettingParseFlags parse_flags,
+                                         GError       **error);
+gboolean    _nm_utils_hwaddr_cloned_not_set (NMSetting *setting,
+                                             GVariant      *connection_dict,
+                                             const char    *property,
+                                             NMSettingParseFlags parse_flags,
+                                             GError       **error);
+GVariant *  _nm_utils_hwaddr_cloned_data_synth (NMSetting *setting,
+                                                NMConnection *connection,
+                                                const char *property);
+gboolean    _nm_utils_hwaddr_cloned_data_set (NMSetting *setting,
+                                              GVariant *connection_dict,
+                                              const char *property,
+                                              GVariant *value,
+                                              NMSettingParseFlags parse_flags,
+                                              GError **error);
+
 GVariant *  _nm_utils_hwaddr_to_dbus   (const GValue *prop_value);
 void        _nm_utils_hwaddr_from_dbus (GVariant *dbus_value,
                                         GValue *prop_value);
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 1080d9ea..a31a9190 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -33,6 +33,11 @@
 #include <gmodule.h>
 #include <sys/stat.h>
 
+#if WITH_JANSSON
+#include <jansson.h>
+#endif
+
+#include "nm-common-macros.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
 #include "crypto.h"
@@ -438,12 +443,6 @@ nm_utils_same_ssid (const guint8 *ssid1, gsize len1,
 	return memcmp (ssid1, ssid2, len1) == 0 ? TRUE : FALSE;
 }
 
-gboolean
-_nm_utils_string_in_list (const char *str, const char **valid_strings)
-{
-	return _nm_utils_strv_find_first ((char **) valid_strings, -1, str) >= 0;
-}
-
 /**
  * _nm_utils_strv_find_first:
  * @list: the strv list to search
@@ -526,7 +525,7 @@ _nm_utils_string_slist_validate (GSList *list, const char **valid_values)
 	GSList *iter;
 
 	for (iter = list; iter; iter = iter->next) {
-		if (!_nm_utils_string_in_list ((char *) iter->data, valid_values))
+		if (!g_strv_contains (valid_values, (char *) iter->data))
 			return FALSE;
 	}
 
@@ -3068,24 +3067,15 @@ nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize length)
 		return NULL;
 }
 
-/**
- * nm_utils_hwaddr_ntoa:
- * @addr: (type guint8) (array length=length): a binary hardware address
- * @length: the length of @addr
- *
- * Converts @addr to textual form.
- *
- * Return value: (transfer full): the textual form of @addr
- */
-char *
-nm_utils_hwaddr_ntoa (gconstpointer addr, gsize length)
+static char *
+_bin2str (gconstpointer addr, gsize length, gboolean upper_case)
 {
 	const guint8 *in = addr;
 	char *out, *result;
-	const char *LOOKUP = "0123456789ABCDEF";
+	const char *LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef";
 
 	g_return_val_if_fail (addr != NULL, g_strdup (""));
-	g_return_val_if_fail (length > 0 && length <= NM_UTILS_HWADDR_LEN_MAX, g_strdup (""));
+	g_return_val_if_fail (length > 0, g_strdup (""));
 
 	result = out = g_malloc (length * 3);
 	while (length--) {
@@ -3101,6 +3091,37 @@ nm_utils_hwaddr_ntoa (gconstpointer addr, gsize length)
 	return result;
 }
 
+/**
+ * nm_utils_hwaddr_ntoa:
+ * @addr: (type guint8) (array length=length): a binary hardware address
+ * @length: the length of @addr
+ *
+ * Converts @addr to textual form.
+ *
+ * Return value: (transfer full): the textual form of @addr
+ */
+char *
+nm_utils_hwaddr_ntoa (gconstpointer addr, gsize length)
+{
+	return _bin2str (addr, length, TRUE);
+}
+
+/**
+ * _nm_utils_bin2str:
+ * @addr: (type guint8) (array length=length): a binary hardware address
+ * @length: the length of @addr
+ * @upper_case: the case for the hexadecimal digits.
+ *
+ * Converts @addr to textual form.
+ *
+ * Return value: (transfer full): the textual form of @addr
+ */
+char *
+_nm_utils_bin2str (gconstpointer addr, gsize length, gboolean upper_case)
+{
+	return _bin2str (addr, length, upper_case);
+}
+
 static int
 hwaddr_binary_len (const char *asc)
 {
@@ -3117,6 +3138,33 @@ hwaddr_binary_len (const char *asc)
 }
 
 /**
+ * _nm_utils_hwaddr_length:
+ * @asc: the ASCII representation of the hardware address
+ *
+ * Validates that @asc is a valid representation of a hardware
+ * address up to (including) %NM_UTILS_HWADDR_LEN_MAX bytes.
+ *
+ * Returns: binary length of the hardware address @asc or
+ *   0 on error.
+ */
+guint
+_nm_utils_hwaddr_length (const char *asc)
+{
+	int l;
+
+	if (!asc)
+		return 0;
+
+	l = hwaddr_binary_len (asc);
+	if (l <= 0 || l > NM_UTILS_HWADDR_LEN_MAX)
+		return 0;
+
+	if (!nm_utils_hwaddr_valid (asc, l))
+		return 0;
+	return l;
+}
+
+/**
  * nm_utils_hwaddr_valid:
  * @asc: the ASCII representation of a hardware address
  * @length: the length of address that @asc is expected to convert to
@@ -3288,24 +3336,124 @@ nm_utils_hwaddr_matches (gconstpointer hwaddr1,
 	return !memcmp (hwaddr1, hwaddr2, hwaddr1_len);
 }
 
-GVariant *
-_nm_utils_hwaddr_to_dbus (const GValue *prop_value)
+/*****************************************************************************/
+
+static GVariant *
+_nm_utils_hwaddr_to_dbus_impl (const char *str)
 {
-	const char *str = g_value_get_string (prop_value);
 	guint8 buf[NM_UTILS_HWADDR_LEN_MAX];
 	int len;
 
-	if (str) {
-		len = hwaddr_binary_len (str);
-		g_return_val_if_fail (len > 0 && len <= NM_UTILS_HWADDR_LEN_MAX, NULL);
-		if (!nm_utils_hwaddr_aton (str, buf, len))
-			len = 0;
-	} else
-		len = 0;
+	if (!str)
+		return NULL;
+
+	len = _nm_utils_hwaddr_length (str);
+	if (len == 0)
+		return NULL;
+
+	if (!nm_utils_hwaddr_aton (str, buf, len))
+		return NULL;
 
 	return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, buf, len, 1);
 }
 
+GVariant *
+_nm_utils_hwaddr_cloned_get (NMSetting     *setting,
+                             const char    *property)
+{
+	gs_free char *addr = NULL;
+
+	nm_assert (nm_streq0 (property, "cloned-mac-address"));
+
+	g_object_get (setting, "cloned-mac-address", &addr, NULL);
+	return _nm_utils_hwaddr_to_dbus_impl (addr);
+}
+
+gboolean
+_nm_utils_hwaddr_cloned_set (NMSetting     *setting,
+                             GVariant      *connection_dict,
+                             const char    *property,
+                             GVariant      *value,
+                             NMSettingParseFlags parse_flags,
+                             GError       **error)
+{
+	gsize length;
+	const guint8 *array;
+	char *str;
+
+	nm_assert (nm_streq0 (property, "cloned-mac-address"));
+
+	if (!_nm_setting_use_legacy_property (setting, connection_dict, "cloned-mac-address", "assigned-mac-address"))
+		return TRUE;
+
+	length = 0;
+	array = g_variant_get_fixed_array (value, &length, 1);
+
+	if (!length)
+		return TRUE;
+
+	str = nm_utils_hwaddr_ntoa (array, length);
+	g_object_set (setting,
+	              "cloned-mac-address",
+	              str,
+	              NULL);
+	g_free (str);
+	return TRUE;
+}
+
+gboolean
+_nm_utils_hwaddr_cloned_not_set (NMSetting *setting,
+                                 GVariant      *connection_dict,
+                                 const char    *property,
+                                 NMSettingParseFlags parse_flags,
+                                 GError       **error)
+{
+	nm_assert (nm_streq0 (property, "cloned-mac-address"));
+	return TRUE;
+}
+
+GVariant *
+_nm_utils_hwaddr_cloned_data_synth (NMSetting *setting,
+                                    NMConnection *connection,
+                                    const char *property)
+{
+	gs_free char *addr = NULL;
+
+	nm_assert (nm_streq0 (property, "assigned-mac-address"));
+
+	g_object_get (setting,
+	              "cloned-mac-address",
+	              &addr,
+	              NULL);
+	return addr ? g_variant_new_string (addr) : NULL;
+}
+
+gboolean
+_nm_utils_hwaddr_cloned_data_set (NMSetting *setting,
+                                  GVariant *connection_dict,
+                                  const char *property,
+                                  GVariant *value,
+                                  NMSettingParseFlags parse_flags,
+                                  GError **error)
+{
+	nm_assert (nm_streq0 (property, "assigned-mac-address"));
+
+	if (_nm_setting_use_legacy_property (setting, connection_dict, "cloned-mac-address", "assigned-mac-address"))
+		return TRUE;
+
+	g_object_set (setting,
+	              "cloned-mac-address",
+	              g_variant_get_string (value, NULL),
+	              NULL);
+	return TRUE;
+}
+
+GVariant *
+_nm_utils_hwaddr_to_dbus (const GValue *prop_value)
+{
+	return _nm_utils_hwaddr_to_dbus_impl (g_value_get_string (prop_value));
+}
+
 void
 _nm_utils_hwaddr_from_dbus (GVariant *dbus_value,
                             GValue *prop_value)
@@ -3318,6 +3466,93 @@ _nm_utils_hwaddr_from_dbus (GVariant *dbus_value,
 	g_value_take_string (prop_value, str);
 }
 
+/*****************************************************************************/
+
+static char *
+_split_word (char *s)
+{
+	/* takes @s and truncates the string on the first white-space.
+	 * then it returns the first word afterwards (again seeking
+	 * over leading white-space). */
+	for (; s[0]; s++) {
+		if (g_ascii_isspace (s[0])) {
+			s[0] = '\0';
+			s++;
+			while (g_ascii_isspace (s[0]))
+				s++;
+			return s;
+		}
+	}
+	return s;
+}
+
+gboolean
+_nm_utils_generate_mac_address_mask_parse (const char *value,
+                                           struct ether_addr *out_mask,
+                                           struct ether_addr **out_ouis,
+                                           gsize *out_ouis_len,
+                                           GError **error)
+{
+	gs_free char *s_free = NULL;
+	char *s, *s_next;
+	struct ether_addr mask;
+	gs_unref_array GArray *ouis = NULL;
+
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	if (!value || !*value)  {
+		/* NULL and "" are valid values and both mean the default
+		 * "q */
+		if (out_mask) {
+			memset (out_mask, 0, sizeof (*out_mask));
+			out_mask->ether_addr_octet[0] |= 0x02;
+		}
+		NM_SET_OUT (out_ouis, NULL);
+		NM_SET_OUT (out_ouis_len, 0);
+		return TRUE;
+	}
+
+	s_free = g_strdup (value);
+	s = s_free;
+
+	/* skip over leading whitespace */
+	while (g_ascii_isspace (s[0]))
+		s++;
+
+	/* parse the first mask */
+	s_next = _split_word (s);
+	if (!nm_utils_hwaddr_aton (s, &mask, ETH_ALEN)) {
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             _("not a valid ethernet MAC address for mask at position %lld"),
+		             (long long) (s - s_free));
+		return FALSE;
+	}
+
+	if (s_next[0]) {
+		ouis = g_array_sized_new (FALSE, FALSE, sizeof (struct ether_addr), 4);
+
+		do {
+			s = s_next;
+			s_next = _split_word (s);
+
+			g_array_set_size (ouis, ouis->len + 1);
+			if (!nm_utils_hwaddr_aton (s, &g_array_index (ouis, struct ether_addr, ouis->len - 1), ETH_ALEN)) {
+				g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+				             _("not a valid ethernet MAC address #%u at position %lld"),
+				             ouis->len, (long long) (s - s_free));
+				return FALSE;
+			}
+		} while (s_next[0]);
+	}
+
+	NM_SET_OUT (out_mask, mask);
+	NM_SET_OUT (out_ouis_len, ouis ? ouis->len : 0);
+	NM_SET_OUT (out_ouis, ouis ? ((struct ether_addr *) g_array_free (g_steal_pointer (&ouis), FALSE)) : NULL);
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_utils_bin2hexstr:
  * @src: (type guint8) (array length=len): an array of bytes
@@ -3562,6 +3797,40 @@ nm_utils_ipaddr_valid (int family, const char *ip)
 }
 
 /**
+ * nm_utils_iinet6_is_token:
+ * @in6addr: the AF_INET6 address structure
+ *
+ * Checks if only the bottom 64bits of the address are set.
+ *
+ * Return value: %TRUE or %FALSE
+ */
+gboolean
+_nm_utils_inet6_is_token (const struct in6_addr *in6addr)
+{
+	if (   in6addr->s6_addr[0]
+	    || in6addr->s6_addr[1]
+	    || in6addr->s6_addr[2]
+	    || in6addr->s6_addr[3]
+	    || in6addr->s6_addr[4]
+	    || in6addr->s6_addr[5]
+	    || in6addr->s6_addr[6]
+	    || in6addr->s6_addr[7])
+		return FALSE;
+
+	if (   in6addr->s6_addr[8]
+	    || in6addr->s6_addr[9]
+	    || in6addr->s6_addr[10]
+	    || in6addr->s6_addr[11]
+	    || in6addr->s6_addr[12]
+	    || in6addr->s6_addr[13]
+	    || in6addr->s6_addr[14]
+	    || in6addr->s6_addr[15])
+		return TRUE;
+
+	return FALSE;
+}
+
+/**
  * nm_utils_check_virtual_device_compatibility:
  * @virtual_type: a virtual connection type
  * @other_type: a connection type to test against @virtual_type
@@ -4090,3 +4359,140 @@ const char **nm_utils_enum_get_values (GType type, gint from, gint to)
 	return (const char **) g_ptr_array_free (array, FALSE);
 }
 
+#if WITH_JANSSON
+gboolean
+_nm_utils_check_valid_json (const char *str, GError **error)
+{
+	json_t *json;
+	json_error_t jerror;
+
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	if (!str || !str[0]) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     "value is NULL or empty");
+		return FALSE;
+	}
+
+	json = json_loads (str, 0, &jerror);
+	if (!json) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             "%s at position %d",
+		             jerror.text,
+		             jerror.position);
+		return FALSE;
+	}
+
+	json_decref (json);
+	return TRUE;
+}
+
+/* json_object_foreach_safe() is only available since Jansson 2.8,
+ * reimplement it */
+#define _json_object_foreach_safe(object, n, key, value) \
+    for (key = json_object_iter_key (json_object_iter (object)), \
+         n = json_object_iter_next (object, json_object_iter_at (object, key)); \
+         key && (value = json_object_iter_value (json_object_iter_at (object, key))); \
+         key = json_object_iter_key (n), \
+         n = json_object_iter_next (object, json_object_iter_at (object, key)))
+
+gboolean
+_nm_utils_team_config_equal (const char *conf1,
+                             const char *conf2,
+                             gboolean port_config)
+{
+	json_t *json1 = NULL, *json2 = NULL, *json;
+	gs_free char *dump1 = NULL, *dump2 = NULL;
+	json_t *value, *property;
+	json_error_t jerror;
+	const char *key;
+	gboolean ret;
+	void *tmp;
+	int i;
+
+	if (nm_streq0 (conf1, conf2))
+		return TRUE;
+
+	/* A NULL configuration is equivalent to default value '{}' */
+	json1 = json_loads (conf1 ?: "{}", 0, &jerror);
+	if (json1)
+		json2 = json_loads (conf2 ?: "{}", 0, &jerror);
+
+	if (!json1 || !json2) {
+		ret = FALSE;
+		goto out;
+	}
+
+	/* Some properties are added by teamd when missing from the initial
+	 * configuration.  Add them with the default value if necessary, depending
+	 * on the configuration type.
+	 */
+	for (i = 0, json = json1; i < 2; i++, json = json2) {
+		if  (port_config) {
+			property = json_object_get (json, "link_watch");
+			if (!property) {
+				property = json_object ();
+				json_object_set_new (property, "name", json_string ("ethtool"));
+				json_object_set_new (json, "link_watch", property);
+			}
+		} else {
+			property = json_object_get (json, "runner");
+			if (!property) {
+				property = json_object ();
+				json_object_set_new (property, "name", json_string ("roundrobin"));
+				json_object_set_new (json, "runner", property);
+			}
+		}
+	}
+
+	/* Only consider a given subset of nodes, others can change depending on
+	 * current state */
+	for (i = 0, json = json1; i < 2; i++, json = json2) {
+		_json_object_foreach_safe (json, tmp, key, value) {
+			if (!NM_IN_STRSET (key, "runner", "link_watch"))
+				json_object_del (json, key);
+		}
+	}
+
+	dump1 = json_dumps (json1, JSON_INDENT(0) | JSON_ENSURE_ASCII | JSON_SORT_KEYS);
+	dump2 = json_dumps (json2, JSON_INDENT(0) | JSON_ENSURE_ASCII | JSON_SORT_KEYS);
+
+	ret = nm_streq0 (dump1, dump2);
+out:
+
+	if (json1)
+		json_decref (json1);
+	if (json2)
+		json_decref (json2);
+
+	return ret;
+}
+
+#else /* WITH_JANSSON */
+
+gboolean
+_nm_utils_check_valid_json (const char *str, GError **error)
+{
+	if (!str || !str[0]) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     "value is NULL or empty");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+_nm_utils_team_config_equal (const char *conf1,
+                             const char *conf2,
+                             gboolean port_config)
+{
+	return nm_streq0 (conf1, conf2);
+}
+#endif
diff --git a/libnm-core/nm-version.h b/libnm-core/nm-version.h
index 3b448819..d7f112bf 100644
--- a/libnm-core/nm-version.h
+++ b/libnm-core/nm-version.h
@@ -90,10 +90,18 @@
 # define NM_AVAILABLE_IN_1_2
 #endif
 
-#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_2_4
-# define NM_AVAILABLE_IN_1_2_4          G_UNAVAILABLE(1.2,4)
+#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_4
+# define NM_DEPRECATED_IN_1_4           G_DEPRECATED
+# define NM_DEPRECATED_IN_1_4_FOR(f)    G_DEPRECATED_FOR(f)
 #else
-# define NM_AVAILABLE_IN_1_2_4
+# define NM_DEPRECATED_IN_1_4
+# define NM_DEPRECATED_IN_1_4_FOR(f)
+#endif
+
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_4
+# define NM_AVAILABLE_IN_1_4            G_UNAVAILABLE(1,4)
+#else
+# define NM_AVAILABLE_IN_1_4
 #endif
 
 #endif  /* NM_VERSION_H */
diff --git a/libnm-core/nm-vpn-dbus-interface.h b/libnm-core/nm-vpn-dbus-interface.h
index 9226458d..3295a1ae 100644
--- a/libnm-core/nm-vpn-dbus-interface.h
+++ b/libnm-core/nm-vpn-dbus-interface.h
@@ -242,6 +242,9 @@ typedef enum {
  */
 #define NM_VPN_PLUGIN_IP4_CONFIG_ROUTES      "routes"
 
+/* whether the previous IP4 routing configuration should be preserved. */
+#define NM_VPN_PLUGIN_IP4_CONFIG_PRESERVE_ROUTES "preserve-routes"
+
 /* boolean: prevent this VPN connection from ever getting the default route */
 #define NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT "never-default"
 
@@ -293,6 +296,9 @@ typedef enum {
  */
 #define NM_VPN_PLUGIN_IP6_CONFIG_ROUTES      "routes"
 
+/* whether the previous IP6 routing configuration should be preserved. */
+#define NM_VPN_PLUGIN_IP6_CONFIG_PRESERVE_ROUTES "preserve-routes"
+
 /* boolean: prevent this VPN connection from ever getting the default route */
 #define NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT "never-default"
 
diff --git a/libnm-core/nm-vpn-editor-plugin.c b/libnm-core/nm-vpn-editor-plugin.c
index 6c78c209..7cb02abd 100644
--- a/libnm-core/nm-vpn-editor-plugin.c
+++ b/libnm-core/nm-vpn-editor-plugin.c
@@ -25,6 +25,7 @@
 #include "nm-vpn-editor-plugin.h"
 
 #include <dlfcn.h>
+#include <gmodule.h>
 
 #include "nm-core-internal.h"
 
@@ -73,6 +74,159 @@ nm_vpn_editor_plugin_default_init (NMVpnEditorPluginInterface *iface)
 
 /*********************************************************************/
 
+typedef struct {
+	NMVpnPluginInfo *plugin_info;
+} NMVpnEditorPluginPrivate;
+
+static void
+_private_destroy (gpointer data)
+{
+	NMVpnEditorPluginPrivate *priv = data;
+
+	if (priv->plugin_info)
+		g_object_remove_weak_pointer ((GObject *) priv->plugin_info, (gpointer *) &priv->plugin_info);
+
+	g_slice_free (NMVpnEditorPluginPrivate, priv);
+}
+
+static NMVpnEditorPluginPrivate *
+_private_get (NMVpnEditorPlugin *plugin, gboolean create)
+{
+	static GQuark quark = 0;
+	NMVpnEditorPluginPrivate *priv;
+
+	nm_assert (NM_IS_VPN_EDITOR_PLUGIN (plugin));
+
+	if (G_UNLIKELY (quark == 0))
+		quark = g_quark_from_string ("nm-vpn-editor-plugin-private");
+
+	priv = g_object_get_qdata ((GObject *) plugin, quark);
+	if (G_LIKELY (priv))
+		return priv;
+	if (!create)
+		return NULL;
+	priv = g_slice_new0 (NMVpnEditorPluginPrivate);
+	g_object_set_qdata_full ((GObject *) plugin, quark, priv, _private_destroy);
+	return priv;
+}
+
+#define NM_VPN_EDITOR_PLUGIN_GET_PRIVATE(plugin)     _private_get (plugin, TRUE)
+#define NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE(plugin) _private_get (plugin, FALSE)
+
+/*********************************************************************/
+
+/**
+ * nm_vpn_editor_plugin_get_plugin_info:
+ * @plugin: the #NMVpnEditorPlugin instance
+ *
+ * Returns: (transfer none): if set, return the #NMVpnPluginInfo instance.
+ *
+ * Since: 1.4
+ */
+NMVpnPluginInfo *
+nm_vpn_editor_plugin_get_plugin_info (NMVpnEditorPlugin *plugin)
+{
+	NMVpnEditorPluginPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), NULL);
+
+	priv = NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE (plugin);
+	return priv ? priv->plugin_info : NULL;
+}
+
+/**
+ * nm_vpn_editor_plugin_set_plugin_info:
+ * @plugin: the #NMVpnEditorPlugin instance
+ * @plugin_info: (allow-none): a #NMVpnPluginInfo instance or %NULL
+ *
+ * Set or clear the plugin-info instance.
+ * This takes a weak reference on @plugin_info, to avoid circular
+ * reference as the plugin-info might also reference the editor-plugin.
+ *
+ * Since: 1.4
+ */
+void
+nm_vpn_editor_plugin_set_plugin_info (NMVpnEditorPlugin *plugin, NMVpnPluginInfo *plugin_info)
+{
+	NMVpnEditorPluginInterface *interface;
+	NMVpnEditorPluginPrivate *priv;
+
+	g_return_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin));
+
+	if (!plugin_info) {
+		priv = NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE (plugin);
+		if (!priv)
+			return;
+	} else {
+		g_return_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info));
+		priv = NM_VPN_EDITOR_PLUGIN_GET_PRIVATE (plugin);
+	}
+
+	if (priv->plugin_info == plugin_info)
+		return;
+	if (priv->plugin_info)
+		g_object_remove_weak_pointer ((GObject *) priv->plugin_info, (gpointer *) &priv->plugin_info);
+	priv->plugin_info = plugin_info;
+	if (priv->plugin_info)
+		g_object_add_weak_pointer ((GObject *) priv->plugin_info, (gpointer *) &priv->plugin_info);
+
+	if (plugin_info) {
+		interface = NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin);
+		if (interface->notify_plugin_info_set)
+			interface->notify_plugin_info_set (plugin, plugin_info);
+	}
+
+}
+
+/*********************************************************************/
+
+/**
+ * nm_vpn_editor_plugin_get_vt:
+ * @plugin: the #NMVpnEditorPlugin
+ * @vt: (out): buffer to be filled with the VT table of the plugin
+ * @vt_size: the size of the buffer. Can be 0 to only query the
+ *   size of plugin's VT.
+ *
+ * Returns an opaque VT function table for the plugin to extend
+ * functionality. The actual meaning of NMVpnEditorPluginVT is not
+ * defined in public API of libnm, instead it must be agreed by
+ * both the plugin and the caller. See the header-only file
+ * 'nm-vpn-editor-plugin-call.h' which defines the meaning.
+ *
+ * Returns: the actual size of the @plugin's virtual function table.
+ *
+ * Since: 1.4
+ **/
+gsize
+nm_vpn_editor_plugin_get_vt (NMVpnEditorPlugin *plugin,
+                             NMVpnEditorPluginVT *vt,
+                             gsize vt_size)
+{
+	const NMVpnEditorPluginVT *p_vt = NULL;
+	gsize p_vt_size = 0;
+	NMVpnEditorPluginInterface *interface;
+
+	g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), 0);
+
+	if (vt_size) {
+		g_return_val_if_fail (vt, 0);
+		memset (vt, 0, vt_size);
+	}
+
+	interface = NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin);
+	if (interface->get_vt) {
+		p_vt = interface->get_vt (plugin, &p_vt_size);
+		if (!p_vt)
+			p_vt_size = 0;
+		g_return_val_if_fail (p_vt_size, 0);
+		memcpy (vt, p_vt, MIN (vt_size, p_vt_size));
+	}
+
+	return p_vt_size;
+}
+
+/*********************************************************************/
+
 static NMVpnEditorPlugin *
 _nm_vpn_editor_plugin_load (const char *plugin_name,
                             gboolean do_file_checks,
@@ -232,7 +386,8 @@ _nm_vpn_editor_plugin_load (const char *plugin_name,
  * If @plugin_name is not an absolute path name, it assumes the file
  * is in the plugin directory of NetworkManager. In any case, the call
  * will do certain checks on the file before passing it to dlopen.
- * A consequence for that is, that you cannot omit the ".so" suffix.
+ * A consequence for that is, that you cannot omit the ".so" suffix
+ * as you could for nm_vpn_editor_plugin_load().
  *
  * Returns: (transfer full): a new plugin instance or %NULL on error.
  *
@@ -255,6 +410,42 @@ nm_vpn_editor_plugin_load_from_file  (const char *plugin_name,
 	                                   error);
 }
 
+/**
+ * nm_vpn_editor_plugin_load:
+ * @plugin_name: The name of the shared library to load.
+ *  This path will be directly passed to dlopen() without
+ *  further checks.
+ * @check_service: if not-null, check that the loaded plugin advertises
+ *  the given service.
+ * @error: on failure the error reason.
+ *
+ * Load the shared libary @plugin_name and create a new
+ * #NMVpnEditorPlugin instace via the #NMVpnEditorPluginFactory
+ * function.
+ *
+ * This is similar to nm_vpn_editor_plugin_load_from_file(), but
+ * it does no validation of the plugin name, instead passes it directly
+ * to dlopen(). If you have the full path to a plugin file,
+ * nm_vpn_editor_plugin_load_from_file() is preferred.
+ *
+ * Returns: (transfer full): a new plugin instance or %NULL on error.
+ *
+ * Since: 1.4
+ */
+NMVpnEditorPlugin *
+nm_vpn_editor_plugin_load (const char *plugin_name,
+                           const char *check_service,
+                           GError **error)
+{
+	return _nm_vpn_editor_plugin_load (plugin_name,
+	                                   FALSE,
+	                                   check_service,
+	                                   -1,
+	                                   NULL,
+	                                   NULL,
+	                                   error);
+}
+
 /*********************************************************************/
 
 /**
diff --git a/libnm-core/nm-vpn-editor-plugin.h b/libnm-core/nm-vpn-editor-plugin.h
index 9ff23a81..fb51f7e6 100644
--- a/libnm-core/nm-vpn-editor-plugin.h
+++ b/libnm-core/nm-vpn-editor-plugin.h
@@ -34,6 +34,8 @@
 
 G_BEGIN_DECLS
 
+struct _NMVpnPluginInfo;
+
 typedef struct _NMVpnEditorPlugin NMVpnEditorPlugin;
 typedef struct _NMVpnEditor NMVpnEditor;
 
@@ -80,6 +82,8 @@ typedef enum /*< flags >*/ {
 /* D-Bus service name of the plugin's VPN service */
 #define NM_VPN_EDITOR_PLUGIN_SERVICE "service"
 
+typedef struct _NMVpnEditorPluginVT NMVpnEditorPluginVT;
+
 /**
  * NMVpnEditorPluginInterface:
  * @g_iface: the parent interface
@@ -97,6 +101,8 @@ typedef enum /*< flags >*/ {
  * @get_suggested_filename: For a given connection, return a suggested file
  *   name.  Returned value will be %NULL or a suggested file name to be freed by
  *   the caller.
+ * @get_vt: return a virtual function table to implement further functions in
+ *   the plugin, without requiring to update libnm. Used by nm_vpn_editor_plugin_get_vt().
  *
  * Interface for VPN editor plugins.
  */
@@ -119,6 +125,12 @@ typedef struct {
 	                            GError **error);
 
 	char * (*get_suggested_filename) (NMVpnEditorPlugin *plugin, NMConnection *connection);
+
+	void (*notify_plugin_info_set) (NMVpnEditorPlugin *plugin,
+	                                struct _NMVpnPluginInfo *plugin_info);
+
+	const NMVpnEditorPluginVT *(*get_vt) (NMVpnEditorPlugin *plugin,
+	                                      gsize *out_vt_size);
 } NMVpnEditorPluginInterface;
 
 GType nm_vpn_editor_plugin_get_type (void);
@@ -129,6 +141,11 @@ NMVpnEditor *nm_vpn_editor_plugin_get_editor (NMVpnEditorPlugin *plugin,
 
 NMVpnEditorPluginCapability nm_vpn_editor_plugin_get_capabilities (NMVpnEditorPlugin *plugin);
 
+NM_AVAILABLE_IN_1_4
+gsize nm_vpn_editor_plugin_get_vt (NMVpnEditorPlugin *plugin,
+                                   NMVpnEditorPluginVT *vt,
+                                   gsize vt_size);
+
 NMConnection *nm_vpn_editor_plugin_import                 (NMVpnEditorPlugin *plugin,
                                                            const char *path,
                                                            GError **error);
@@ -147,6 +164,18 @@ NMVpnEditorPlugin *nm_vpn_editor_plugin_load_from_file  (const char *plugin_name
                                                          gpointer user_data,
                                                          GError **error);
 
+NM_AVAILABLE_IN_1_4
+NMVpnEditorPlugin *nm_vpn_editor_plugin_load (const char *plugin_name,
+                                              const char *check_service,
+                                              GError **error);
+
+NM_AVAILABLE_IN_1_4
+struct _NMVpnPluginInfo *nm_vpn_editor_plugin_get_plugin_info (NMVpnEditorPlugin *plugin);
+NM_AVAILABLE_IN_1_4
+void             nm_vpn_editor_plugin_set_plugin_info (NMVpnEditorPlugin *plugin, struct _NMVpnPluginInfo *plugin_info);
+
+#include "nm-vpn-plugin-info.h"
+
 G_END_DECLS
 
 #endif	/* __NM_VPN_EDITOR_PLUGIN_H__ */
diff --git a/libnm-core/nm-vpn-plugin-info.c b/libnm-core/nm-vpn-plugin-info.c
index 2e6275e3..a4727619 100644
--- a/libnm-core/nm-vpn-plugin-info.c
+++ b/libnm-core/nm-vpn-plugin-info.c
@@ -45,6 +45,7 @@ typedef struct {
 	char *filename;
 	char *name;
 	char *service;
+	char *auth_dialog;
 	char **aliases;
 	GKeyFile *keyfile;
 
@@ -188,6 +189,23 @@ _sort_files (LoadDirInfo *a, LoadDirInfo *b)
 	                  nm_vpn_plugin_info_get_filename (b->plugin_info));
 }
 
+#define DEFINE_DEFAULT_DIR_LIST(dir) \
+	const char *dir[] = { \
+		/* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with
+		 * preference to the former.
+		 *
+		 * load user directory with highest priority. */ \
+		_nm_vpn_plugin_info_get_default_dir_user (), \
+		\
+		/* lib directory has higher priority then etc. The reason is that
+		 * etc is deprecated and used by old plugins. We expect newer plugins
+		 * to install their file in lib, where they have higher priority.
+		 *
+		 * Optimally, there are no duplicates anyway, so it doesn't really matter. */ \
+		_nm_vpn_plugin_info_get_default_dir_lib (), \
+		_nm_vpn_plugin_info_get_default_dir_etc (), \
+	}
+
 /**
  * _nm_vpn_plugin_info_get_default_dir_etc:
  *
@@ -253,7 +271,10 @@ _nm_vpn_plugin_info_list_load_dir (const char *dirname,
 	GSList *res = NULL;
 	guint i;
 
-	g_return_val_if_fail (dirname && dirname[0], NULL);
+	g_return_val_if_fail (dirname, NULL);
+
+	if (!dirname[0])
+		return NULL;
 
 	dir = g_dir_open (dirname, 0, NULL);
 	if (!dir)
@@ -312,21 +333,7 @@ nm_vpn_plugin_info_list_load ()
 	gint64 uid;
 	GSList *list = NULL;
 	GSList *infos, *info;
-	const char *dir[] = {
-		/* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with
-		 * preference to the former.
-		 *
-		 * load user directory with highest priority. */
-		_nm_vpn_plugin_info_get_default_dir_user (),
-
-		/* lib directory has higher priority then etc. The reason is that
-		 * etc is deprecated and used by old plugins. We expect newer plugins
-		 * to install their file in lib, where they have higher priority.
-		 *
-		 * Optimally, there are no duplicates anyway, so it doesn't really matter. */
-		_nm_vpn_plugin_info_get_default_dir_lib (),
-		_nm_vpn_plugin_info_get_default_dir_etc (),
-	};
+	DEFINE_DEFAULT_DIR_LIST (dir);
 
 	uid = getuid ();
 
@@ -345,6 +352,66 @@ nm_vpn_plugin_info_list_load ()
 	return list;
 }
 
+/**
+ * nm_vpn_plugin_info_new_search_file:
+ * @name: (allow-none): the name to search for. Either @name or @service
+ *   must be present.
+ * @service: (allow-none): the service to search for. Either @name  or
+ *   @service must be present.
+ *
+ * This has the same effect as doing a full nm_vpn_plugin_info_list_load()
+ * followed by a search for the first matching VPN plugin info that has the
+ * given @name and/or @service.
+ *
+ * Returns: (transfer full): a newly created instance of plugin info
+ *   or %NULL if no matching value was found.
+ *
+ * Since: 1.4
+ */
+NMVpnPluginInfo *
+nm_vpn_plugin_info_new_search_file (const char *name, const char *service)
+{
+	int i;
+	gint64 uid;
+	NMVpnPluginInfo *plugin_info = NULL;
+	GSList *infos, *info;
+	DEFINE_DEFAULT_DIR_LIST (dir);
+
+	if (!name && !service)
+		g_return_val_if_reached (NULL);
+
+	uid = getuid ();
+
+	for (i = 0; !plugin_info && i < G_N_ELEMENTS (dir); i++) {
+		if (   !dir[i]
+		    || _nm_utils_strv_find_first ((char **) dir, i, dir[i]) >= 0)
+			continue;
+
+		/* We still must load the entire directory while searching for the matching
+		 * plugin-info. The reason is that reading the directory has no stable
+		 * order and we can only sort them after reading the entire directory --
+		 * which _nm_vpn_plugin_info_list_load_dir() does. */
+		infos = _nm_vpn_plugin_info_list_load_dir (dir[i], TRUE, uid, NULL, NULL);
+
+		for (info = infos; info; info = info->next) {
+			NMVpnPluginInfo *p = info->data;
+
+			if (name && !nm_streq (nm_vpn_plugin_info_get_name (p), name))
+				continue;
+			if (   service
+			    && !nm_streq (nm_vpn_plugin_info_get_service (p), service)
+			    && (_nm_utils_strv_find_first (NM_VPN_PLUGIN_INFO_GET_PRIVATE (p)->aliases,
+			                                   -1, service) < 0))
+				continue;
+			plugin_info = g_object_ref (p);
+			break;
+		}
+
+		g_slist_free_full (infos, g_object_unref);
+	}
+	return plugin_info;
+}
+
 /*********************************************************************/
 
 static gboolean
@@ -424,7 +491,7 @@ nm_vpn_plugin_info_list_add (GSList **list, NMVpnPluginInfo *plugin_info, GError
 		}
 
 		/* the plugin must have unique values for certain properties. E.g. two different
-		 * plugins cannot share the same service name. */
+		 * plugins cannot share the same service type. */
 		if (!_check_no_conflict (plugin_info, iter->data, error))
 			return FALSE;
 	}
@@ -506,10 +573,24 @@ nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename)
 	return NULL;
 }
 
+static NMVpnPluginInfo *
+_list_find_by_service (GSList *list, const char *service)
+{
+	for (; list; list = list->next) {
+		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (list->data);
+
+		if (   nm_streq (priv->service, service)
+		    || _nm_utils_strv_find_first (priv->aliases, -1, service) >= 0)
+			return list->data;
+	}
+	return NULL;
+}
+
 /**
  * nm_vpn_plugin_info_list_find_by_service:
  * @list: (element-type NMVpnPluginInfo): list of plugins
- * @service: service to search
+ * @service: service to search. This can be the main service-type
+ *   or one of the provided aliases.
  *
  * Returns: (transfer none): the first plugin with a matching @service (or %NULL).
  *
@@ -518,27 +599,180 @@ nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename)
 NMVpnPluginInfo *
 nm_vpn_plugin_info_list_find_by_service (GSList *list, const char *service)
 {
+	if (!service)
+		g_return_val_if_reached (NULL);
+	return _list_find_by_service (list, service);
+}
+
+/* known_names are well known short names for the service-type. They all implicitly
+ * have a prefix "org.freedesktop.NetworkManager." + known_name. */
+static const char *known_names[] = {
+	"openvpn",
+	"vpnc",
+	"pptp",
+	"openconnect",
+	"openswan",
+	"libreswan",
+	"strongswan",
+	"ssh",
+	"l2tp",
+	"iodine",
+	"fortisslvpn",
+};
+
+/**
+ * nm_vpn_plugin_info_list_find_service_type:
+ * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo instances
+ * @name: a name to lookup the service-type.
+ *
+ * A VPN plugin provides one or several service-types, like org.freedesktop.NetworkManager.libreswan
+ * Certain plugins provide more then one service type, via aliases (org.freedesktop.NetworkManager.openswan).
+ * This function looks up a service-type (or an alias) based on a name.
+ *
+ * Preferably, the name can be a full service-type/alias of an installed
+ * plugin. Otherwise, it can be the name of a VPN plugin (in which case, the
+ * primary, non-aliased service-type is returned). Otherwise, it can be
+ * one of several well known short-names (which is a hard-coded list of
+ * types in libnm). On success, this returns a full qualified service-type
+ * (or an alias). It doesn't say, that such an plugin is actually available,
+ * but it could be retrieved via nm_vpn_plugin_info_list_find_by_service().
+ *
+ * Returns: (transfer full): the resolved service-type or %NULL on failure.
+ *
+ * Since: 1.4
+ */
+char *
+nm_vpn_plugin_info_list_find_service_type (GSList *list, const char *name)
+{
 	GSList *iter;
+	char *n;
 
-	if (!service)
+	if (!name)
 		g_return_val_if_reached (NULL);
+	if (!*name)
+		return NULL;
+
+	/* First, try to interpret @name as a full service-type (or alias). */
+	if (_list_find_by_service (list, name))
+		return g_strdup (name);
 
-	/* First, consider the primary service name. */
+	/* try to interpret @name as plugin name, in which case we return
+	 * the main service-type (not an alias). */
 	for (iter = list; iter; iter = iter->next) {
-		if (strcmp (NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data)->service, service) == 0)
-			return iter->data;
+		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data);
+
+		if (nm_streq (priv->name, name))
+			return g_strdup (priv->service);
 	}
 
-	/* Then look into the aliases. */
+	/* check the hard-coded list of short-names. They all have have the same
+	 * well-known prefix org.freedesktop.NetworkManager and the name. */
+	if (_nm_utils_strv_find_first ((char **) known_names, G_N_ELEMENTS (known_names), name) >= 0)
+		return g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, name);
+
+	/* try, if there exists a plugin with @name under org.freedesktop.NetworkManager.
+	 * Allow this to be a valid abbreviation. */
+	n = g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, name);
+	if (_list_find_by_service (list, n))
+		return n;
+	g_free (n);
+
+	/* currently, VPN plugins have no way to define a short-name for their
+	 * alias name, unless the alias name is prefixed by org.freedesktop.NetworkManager. */
+
+	return NULL;
+}
+
+static const char *
+_service_type_get_default_abbreviation (const char *service_type)
+{
+	if (!g_str_has_prefix (service_type, NM_DBUS_INTERFACE))
+		return NULL;
+	service_type += NM_STRLEN (NM_DBUS_INTERFACE);
+	if (service_type[0] != '.')
+		return NULL;
+	service_type++;
+	if (!service_type[0])
+		return NULL;
+	return service_type;
+}
+
+/**
+ * nm_vpn_plugin_info_list_get_service_types:
+ * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo
+ * @only_existing: only include results that are actually in @list.
+ *   Otherwise, the result is extended with a hard-code list or
+ *   well-known plugins
+ * @with_abbreviations: if %FALSE, only full service types are returned.
+ *   Otherwise, this also includes abbreviated names that can be used
+ *   with nm_vpn_plugin_info_list_find_service_type().
+ *
+ * Returns: (transfer full): a %NULL terminated strv list of strings.
+ *   The list itself and the values must be freed with g_strfreev().
+ *
+ * Since: 1.4
+ */
+char **
+nm_vpn_plugin_info_list_get_service_types (GSList *list,
+                                           gboolean only_existing,
+                                           gboolean with_abbreviations)
+{
+	GSList *iter;
+	GPtrArray *l;
+	guint i, j;
+	const char *n;
+
+	l = g_ptr_array_sized_new (20);
+
 	for (iter = list; iter; iter = iter->next) {
-		char **aliases = (NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data))->aliases;
+		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data);
 
-		if (!aliases)
-			continue;
-		if (_nm_utils_strv_find_first (aliases, -1, service) >= 0)
-			return iter->data;
+		g_ptr_array_add (l, g_strdup (priv->service));
+		if (priv->aliases) {
+			for (i = 0; priv->aliases[i]; i++)
+				g_ptr_array_add (l, g_strdup (priv->aliases[i]));
+		}
+
+		if (with_abbreviations) {
+			g_ptr_array_add (l, g_strdup (priv->name));
+			n = _service_type_get_default_abbreviation (priv->service);
+			if (n)
+				g_ptr_array_add (l, g_strdup (n));
+			for (i = 0; priv->aliases[i]; i++) {
+				n = _service_type_get_default_abbreviation (priv->aliases[i]);
+				if (n)
+					g_ptr_array_add (l, g_strdup (n));
+			}
+		}
 	}
-	return NULL;
+
+	if (!only_existing) {
+		for (i = 0; i < G_N_ELEMENTS (known_names); i++) {
+			g_ptr_array_add (l, g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, known_names[i]));
+			if (with_abbreviations)
+				g_ptr_array_add (l, g_strdup (known_names[i]));
+		}
+	}
+
+	if (l->len <= 0) {
+		g_ptr_array_free (l, TRUE);
+		return g_new0 (char *, 1);
+	}
+
+	/* sort the result and remove duplicates. */
+	g_ptr_array_sort (l, nm_strcmp_p);
+	for (i = 1, j = 1; i < l->len; i++) {
+		if (nm_streq (l->pdata[j-1], l->pdata[i]))
+			g_free (l->pdata[i]);
+		else
+			l->pdata[j++] = l->pdata[i];
+	}
+
+	if (j == l->len)
+		g_ptr_array_add (l, NULL);
+	else
+		l->pdata[j] = NULL;
+	return (char **) g_ptr_array_free (l, FALSE);
 }
 
 /*********************************************************************/
@@ -576,6 +810,77 @@ nm_vpn_plugin_info_get_name (NMVpnPluginInfo *self)
 }
 
 /**
+ * nm_vpn_plugin_info_get_service:
+ * @self: plugin info instance
+ *
+ * Returns: (transfer none): the service. Cannot be %NULL.
+ *
+ * Since: 1.4
+ */
+const char *
+nm_vpn_plugin_info_get_service (NMVpnPluginInfo *self)
+{
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
+
+	return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->service;
+}
+
+/**
+ * nm_vpn_plugin_info_get_auth_dialog:
+ * @self: plugin info instance
+ *
+ * Returns: the absolute path to the auth-dialog helper or %NULL.
+ *
+ * Since: 1.4
+ **/
+const char *
+nm_vpn_plugin_info_get_auth_dialog (NMVpnPluginInfo *self)
+{
+	NMVpnPluginInfoPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
+
+	priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (priv->auth_dialog == NULL)) {
+		const char *s;
+
+		s = g_hash_table_lookup (priv->keys, _nm_utils_strstrdictkey_static (NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "auth-dialog"));
+		if (!s || !s[0])
+			priv->auth_dialog = g_strdup ("");
+		else if (g_path_is_absolute (s))
+			priv->auth_dialog = g_strdup (s);
+		else {
+			/* for relative paths, we take the basename and assume it's in LIBEXECDIR. */
+			gs_free char *prog_basename = g_path_get_basename (s);
+
+			priv->auth_dialog = g_build_filename (LIBEXECDIR, prog_basename, NULL);
+		}
+	}
+
+	return priv->auth_dialog[0] ? priv->auth_dialog : NULL;
+}
+
+/**
+ * nm_vpn_plugin_info_supports_hints:
+ * @self: plugin info instance
+ *
+ * Returns: %TRUE if the supports hints for secret requests, otherwise %FALSE
+ *
+ * Since: 1.4
+ */
+gboolean
+nm_vpn_plugin_info_supports_hints (NMVpnPluginInfo *self)
+{
+	const char *s;
+
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), FALSE);
+
+	s = nm_vpn_plugin_info_lookup_property (self, NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "supports-hints");
+	return _nm_utils_ascii_str_to_bool (s, FALSE);
+}
+
+/**
  * nm_vpn_plugin_info_get_plugin:
  * @self: plugin info instance
  *
@@ -630,6 +935,32 @@ nm_vpn_plugin_info_supports_multiple (NMVpnPluginInfo *self)
 
 
 /**
+ * nm_vpn_plugin_info_get_aliases:
+ * @self: plugin info instance
+ *
+ * Returns: (array zero-terminated=1) (element-type utf8) (transfer none):
+ *   the aliases from the name-file.
+ *
+ * Since: 1.4
+ */
+const char *const*
+nm_vpn_plugin_info_get_aliases (NMVpnPluginInfo *self)
+{
+	NMVpnPluginInfoPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
+
+	priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
+	if (priv->aliases)
+		return (const char *const*) priv->aliases;
+
+	/* For convenience, we always want to return non-NULL, even for empty
+	 * aliases. Hack around that, by making a NULL terminated array using
+	 * the NULL of priv->aliases. */
+	return (const char *const*) &priv->aliases;
+}
+
+/**
  * nm_vpn_plugin_info_lookup_property:
  * @self: plugin info instance
  * @group: group name
@@ -753,11 +1084,13 @@ nm_vpn_plugin_info_load_editor_plugin (NMVpnPluginInfo *self, GError **error)
 
 	priv->editor_plugin_loaded = TRUE;
 	priv->editor_plugin = nm_vpn_editor_plugin_load_from_file (plugin_filename,
-	                                                           priv->service,
+	                                                           nm_vpn_plugin_info_get_service (self),
 	                                                           getuid (),
 	                                                           NULL,
 	                                                           NULL,
 	                                                           error);
+	if (priv->editor_plugin)
+		nm_vpn_editor_plugin_set_plugin_info (priv->editor_plugin, self);
 	return priv->editor_plugin;
 }
 
@@ -865,6 +1198,8 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 	}
 
 	priv->aliases = g_key_file_get_string_list (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "aliases", NULL, NULL);
+	if (priv->aliases && !priv->aliases[0])
+		g_clear_pointer (&priv->aliases, g_free);
 
 	priv->keys = g_hash_table_new_full (_nm_utils_strstrdictkey_hash,
 	                                    _nm_utils_strstrdictkey_equal,
@@ -947,6 +1282,7 @@ finalize (GObject *object)
 
 	g_free (priv->name);
 	g_free (priv->service);
+	g_free (priv->auth_dialog);
 	g_strfreev (priv->aliases);
 	g_free (priv->filename);
 	g_hash_table_unref (priv->keys);
diff --git a/libnm-core/nm-vpn-plugin-info.h b/libnm-core/nm-vpn-plugin-info.h
index c7fef857..908ee482 100644
--- a/libnm-core/nm-vpn-plugin-info.h
+++ b/libnm-core/nm-vpn-plugin-info.h
@@ -44,10 +44,12 @@ G_BEGIN_DECLS
 #define NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM        "libnm"
 #define NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME        "GNOME"
 
+struct _NMVpnPluginInfo;
+
 /**
  * NMVpnPluginInfo:
  */
-typedef struct {
+typedef struct _NMVpnPluginInfo {
 	NM_AVAILABLE_IN_1_2
 	GObject parent;
 } NMVpnPluginInfo;
@@ -73,16 +75,28 @@ NMVpnPluginInfo *nm_vpn_plugin_info_new_with_data (const char *filename,
                                                    GKeyFile *keyfile,
                                                    GError **error);
 
+NM_AVAILABLE_IN_1_4
+NMVpnPluginInfo *nm_vpn_plugin_info_new_search_file (const char *name,
+                                                     const char *service);
+
 NM_AVAILABLE_IN_1_2
 const char *nm_vpn_plugin_info_get_name        (NMVpnPluginInfo *self);
 NM_AVAILABLE_IN_1_2
 const char *nm_vpn_plugin_info_get_filename    (NMVpnPluginInfo *self);
+NM_AVAILABLE_IN_1_4
+const char *nm_vpn_plugin_info_get_service     (NMVpnPluginInfo *self);
 NM_AVAILABLE_IN_1_2
 const char *nm_vpn_plugin_info_get_plugin      (NMVpnPluginInfo *self);
 NM_AVAILABLE_IN_1_2
 const char *nm_vpn_plugin_info_get_program     (NMVpnPluginInfo *self);
+NM_AVAILABLE_IN_1_4
+const char *nm_vpn_plugin_info_get_auth_dialog (NMVpnPluginInfo *self);
+NM_AVAILABLE_IN_1_4
+gboolean nm_vpn_plugin_info_supports_hints     (NMVpnPluginInfo *self);
 NM_AVAILABLE_IN_1_2
 gboolean nm_vpn_plugin_info_supports_multiple  (NMVpnPluginInfo *self);
+NM_AVAILABLE_IN_1_4
+const char *const*nm_vpn_plugin_info_get_aliases (NMVpnPluginInfo *self);
 NM_AVAILABLE_IN_1_2
 const char *nm_vpn_plugin_info_lookup_property (NMVpnPluginInfo *self, const char *group, const char *key);
 
@@ -102,6 +116,12 @@ NMVpnPluginInfo *nm_vpn_plugin_info_list_find_by_filename (GSList *list, const c
 NM_AVAILABLE_IN_1_2
 NMVpnPluginInfo *nm_vpn_plugin_info_list_find_by_service  (GSList *list, const char *service);
 
+NM_AVAILABLE_IN_1_4
+char *nm_vpn_plugin_info_list_find_service_type (GSList *list, const char *name);
+NM_AVAILABLE_IN_1_4
+char **nm_vpn_plugin_info_list_get_service_types (GSList *list,
+                                                  gboolean only_existing,
+                                                  gboolean with_abbreviations);
 
 NM_AVAILABLE_IN_1_2
 NMVpnEditorPlugin *nm_vpn_plugin_info_get_editor_plugin  (NMVpnPluginInfo *self);
diff --git a/libnm-core/tests/Makefile.am b/libnm-core/tests/Makefile.am
index 1926c86f..485fd141 100644
--- a/libnm-core/tests/Makefile.am
+++ b/libnm-core/tests/Makefile.am
@@ -9,8 +9,6 @@ BUILT_SOURCES = $(GLIB_GENERATED)
 
 if ENABLE_TESTS
 
-certsdir = $(srcdir)/certs
-
 AM_CPPFLAGS = \
 	-I${top_srcdir}/shared \
 	-I${top_builddir}/shared \
@@ -18,7 +16,7 @@ AM_CPPFLAGS = \
 	-I$(top_builddir)/libnm-core \
 	-DNETWORKMANAGER_COMPILATION \
 	$(GLIB_CFLAGS) \
-	-DTEST_CERT_DIR=\"$(certsdir)\"
+	-DTEST_CERT_DIR=\"$(abs_srcdir)/certs\"
 
 noinst_PROGRAMS =		\
 	test-compare		\
diff --git a/libnm-core/tests/Makefile.in b/libnm-core/tests/Makefile.in
index 1ffcabc7..75ccb08e 100644
--- a/libnm-core/tests/Makefile.in
+++ b/libnm-core/tests/Makefile.in
@@ -97,8 +97,9 @@ host_triplet = @host@
 @ENABLE_TESTS_TRUE@	test-settings-defaults$(EXEEXT)
 subdir = libnm-core/tests
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -487,6 +488,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -543,13 +545,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -561,6 +564,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -621,6 +626,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -656,6 +663,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -722,7 +730,6 @@ nm_core_tests_enum_types_sources = test-general-enums.h
 GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM
 GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM
 BUILT_SOURCES = $(GLIB_GENERATED)
-@ENABLE_TESTS_TRUE@certsdir = $(srcdir)/certs
 @ENABLE_TESTS_TRUE@AM_CPPFLAGS = \
 @ENABLE_TESTS_TRUE@	-I${top_srcdir}/shared \
 @ENABLE_TESTS_TRUE@	-I${top_builddir}/shared \
@@ -730,7 +737,7 @@ BUILT_SOURCES = $(GLIB_GENERATED)
 @ENABLE_TESTS_TRUE@	-I$(top_builddir)/libnm-core \
 @ENABLE_TESTS_TRUE@	-DNETWORKMANAGER_COMPILATION \
 @ENABLE_TESTS_TRUE@	$(GLIB_CFLAGS) \
-@ENABLE_TESTS_TRUE@	-DTEST_CERT_DIR=\"$(certsdir)\"
+@ENABLE_TESTS_TRUE@	-DTEST_CERT_DIR=\"$(abs_srcdir)/certs\"
 
 @ENABLE_TESTS_TRUE@LDADD = \
 @ENABLE_TESTS_TRUE@	$(top_builddir)/libnm-core/libnm-core.la \
diff --git a/libnm-core/tests/test-compare.c b/libnm-core/tests/test-compare.c
index dca5c7d3..7f2ba980 100644
--- a/libnm-core/tests/test-compare.c
+++ b/libnm-core/tests/test-compare.c
@@ -26,7 +26,7 @@
 
 #include "nm-property-compare.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 static void
 compare_ints (void)
diff --git a/libnm-core/tests/test-crypto.c b/libnm-core/tests/test-crypto.c
index bd87c710..9bab985f 100644
--- a/libnm-core/tests/test-crypto.c
+++ b/libnm-core/tests/test-crypto.c
@@ -33,7 +33,7 @@
 #include "nm-errors.h"
 #include "nm-core-internal.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 #if 0
 static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----";
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index c71e2d26..6aee2527 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -62,7 +62,7 @@
 
 #include "test-general-enums.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 /* When passing a "bool" typed argument to a variadic function that
  * expects a gboolean, the compiler will promote the integer type
@@ -145,54 +145,54 @@ test_setting_vpn_items (void)
 	nm_setting_vpn_remove_secret (s_vpn, "foobar4");
 
 	/* Try to add some blank values and make sure they are rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*key != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (key != NULL));
 	nm_setting_vpn_add_data_item (s_vpn, NULL, NULL);
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strlen (key) > 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strlen (key) > 0));
 	nm_setting_vpn_add_data_item (s_vpn, "", "");
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*item != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (item != NULL));
 	nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL);
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strlen (item) > 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strlen (item) > 0));
 	nm_setting_vpn_add_data_item (s_vpn, "foobar1", "");
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*key != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (key != NULL));
 	nm_setting_vpn_add_data_item (s_vpn, NULL, "blahblah1");
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strlen (key) > 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strlen (key) > 0));
 	nm_setting_vpn_add_data_item (s_vpn, "", "blahblah1");
 	g_test_assert_expected_messages ();
 
 	nm_setting_vpn_foreach_data_item (s_vpn, vpn_check_empty_func, NULL);
 
 	/* Try to add some blank secrets and make sure they are rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*key != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (key != NULL));
 	nm_setting_vpn_add_secret (s_vpn, NULL, NULL);
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strlen (key) > 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strlen (key) > 0));
 	nm_setting_vpn_add_secret (s_vpn, "", "");
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*secret != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (secret != NULL));
 	nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL);
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strlen (secret) > 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strlen (secret) > 0));
 	nm_setting_vpn_add_secret (s_vpn, "foobar1", "");
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*key != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (key != NULL));
 	nm_setting_vpn_add_secret (s_vpn, NULL, "blahblah1");
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strlen (key) > 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strlen (key) > 0));
 	nm_setting_vpn_add_secret (s_vpn, "", "blahblah1");
 	g_test_assert_expected_messages ();
 
@@ -1621,47 +1621,47 @@ test_setting_connection_permissions_helpers (void)
 	s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
 
 	/* Ensure a bad [type] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strcmp (ptype, \"user\") == 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strcmp (ptype, "user") == 0));
 	success = nm_setting_connection_add_permission (s_con, "foobar", "blah", NULL);
 	g_test_assert_expected_messages ();
 	g_assert (!success);
 
 	/* Ensure a bad [type] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*ptype*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (ptype));
 	success = nm_setting_connection_add_permission (s_con, NULL, "blah", NULL);
 	g_test_assert_expected_messages ();
 	g_assert (!success);
 
 	/* Ensure a bad [item] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*uname*");
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*p != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (uname));
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (p != NULL));
 	success = nm_setting_connection_add_permission (s_con, "user", NULL, NULL);
 	g_test_assert_expected_messages ();
 	g_assert (!success);
 
 	/* Ensure a bad [item] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*uname[0] != '\\0'*");
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*p != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (uname[0] != '\0'));
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (p != NULL));
 	success = nm_setting_connection_add_permission (s_con, "user", "", NULL);
 	g_test_assert_expected_messages ();
 	g_assert (!success);
 
 	/* Ensure an [item] with ':' is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strchr (uname, ':')*");
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*p != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strchr (uname, ':') == NULL));
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (p != NULL));
 	success = nm_setting_connection_add_permission (s_con, "user", "ad:asdf", NULL);
 	g_test_assert_expected_messages ();
 	g_assert (!success);
 
 	/* Ensure a non-UTF-8 [item] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*g_utf8_validate (uname, -1, NULL)*");
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*p != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (g_utf8_validate (uname, -1, NULL) == TRUE));
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (p != NULL));
 	success = nm_setting_connection_add_permission (s_con, "user", buf, NULL);
 	g_test_assert_expected_messages ();
 	g_assert (!success);
 
 	/* Ensure a non-NULL [detail] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*detail == NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (detail == NULL));
 	success = nm_setting_connection_add_permission (s_con, "user", "dafasdf", "asdf");
 	g_test_assert_expected_messages ();
 	g_assert (!success);
@@ -1732,49 +1732,49 @@ test_setting_connection_permissions_property (void)
 	s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
 
 	/* Ensure a bad [type] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strncmp (str, PERM_USER_PREFIX, strlen (PERM_USER_PREFIX)) == 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strncmp (str, PERM_USER_PREFIX, strlen (PERM_USER_PREFIX)) == 0));
 	add_permission_property (s_con, "foobar", "blah", -1, NULL);
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
 
 	/* Ensure a bad [type] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*strncmp (str, PERM_USER_PREFIX, strlen (PERM_USER_PREFIX)) == 0*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (strncmp (str, PERM_USER_PREFIX, strlen (PERM_USER_PREFIX)) == 0));
 	add_permission_property (s_con, NULL, "blah", -1, NULL);
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
 
 	/* Ensure a bad [item] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*last_colon > str*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (last_colon > str));
 	add_permission_property (s_con, "user", NULL, -1, NULL);
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
 
 	/* Ensure a bad [item] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*last_colon > str*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (last_colon > str));
 	add_permission_property (s_con, "user", "", -1, NULL);
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
 
 	/* Ensure an [item] with ':' in the middle is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*str[i] != ':'*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (str[i] != ':'));
 	add_permission_property (s_con, "user", "ad:asdf", -1, NULL);
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
 
 	/* Ensure an [item] with ':' at the end is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*str[i] != ':'*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (str[i] != ':'));
 	add_permission_property (s_con, "user", "adasdfaf:", -1, NULL);
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
 
 	/* Ensure a non-UTF-8 [item] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*g_utf8_validate (str, -1, NULL)*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (g_utf8_validate (str, -1, NULL) == TRUE));
 	add_permission_property (s_con, "user", buf, (int) sizeof (buf), NULL);
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
 
 	/* Ensure a non-NULL [detail] is rejected */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*(last_colon + 1) == '\\0'*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (*(last_colon + 1) == '\0'));
 	add_permission_property (s_con, "user", "dafasdf", -1, "asdf");
 	g_test_assert_expected_messages ();
 	g_assert_cmpint (nm_setting_connection_get_num_permissions (s_con), ==, 0);
@@ -1916,6 +1916,7 @@ test_connection_diff_a_only (void)
 		{ NM_SETTING_CONNECTION_SETTING_NAME, {
 			{ NM_SETTING_CONNECTION_ID,                   NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_UUID,                 NM_SETTING_DIFF_RESULT_IN_A },
+			{ NM_SETTING_CONNECTION_STABLE_ID,            NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_INTERFACE_NAME,       NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_TYPE,                 NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_TIMESTAMP,            NM_SETTING_DIFF_RESULT_IN_A },
@@ -1940,6 +1941,7 @@ test_connection_diff_a_only (void)
 			{ NM_SETTING_WIRED_AUTO_NEGOTIATE,        NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_WIRED_MAC_ADDRESS,           NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_WIRED_CLONED_MAC_ADDRESS,    NM_SETTING_DIFF_RESULT_IN_A },
+			{ NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_WIRED_MTU,                   NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_WIRED_S390_SUBCHANNELS,      NM_SETTING_DIFF_RESULT_IN_A },
@@ -2385,6 +2387,154 @@ test_setting_compare_id (void)
 }
 
 static void
+test_setting_compare_addresses (void)
+{
+	gs_unref_object NMSetting *s1 = NULL, *s2 = NULL;
+	gboolean success;
+	NMIPAddress *a;
+	GHashTable *result = NULL;
+
+	s1 = nm_setting_ip4_config_new ();
+	s2 = nm_setting_ip4_config_new ();
+
+	a = nm_ip_address_new (AF_INET, "192.168.7.5", 24, NULL);
+
+	nm_ip_address_set_attribute (a, "label", g_variant_new_string ("xoxoxo"));
+	nm_setting_ip_config_add_address ((NMSettingIPConfig *) s1, a);
+
+	nm_ip_address_set_attribute (a, "label", g_variant_new_string ("hello"));
+	nm_setting_ip_config_add_address ((NMSettingIPConfig *) s2, a);
+
+	nm_ip_address_unref (a);
+
+	if (nmtst_get_rand_int () % 2)
+		NMTST_SWAP (s1, s2);
+
+	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
+	g_assert (!success);
+
+	success = nm_setting_diff (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result);
+	g_assert (!success);
+	g_clear_pointer (&result, g_hash_table_unref);
+}
+
+static void
+test_setting_compare_routes (void)
+{
+	gs_unref_object NMSetting *s1 = NULL, *s2 = NULL;
+	gboolean success;
+	NMIPRoute *r;
+	GHashTable *result = NULL;
+
+	s1 = nm_setting_ip4_config_new ();
+	s2 = nm_setting_ip4_config_new ();
+
+	r = nm_ip_route_new (AF_INET, "192.168.12.0", 24, "192.168.11.1", 473, NULL);
+
+	nm_ip_route_set_attribute (r, "label", g_variant_new_string ("xoxoxo"));
+	nm_setting_ip_config_add_route ((NMSettingIPConfig *) s1, r);
+
+	nm_ip_route_set_attribute (r, "label", g_variant_new_string ("hello"));
+	nm_setting_ip_config_add_route ((NMSettingIPConfig *) s2, r);
+
+	nm_ip_route_unref (r);
+
+	if (nmtst_get_rand_int () % 2)
+		NMTST_SWAP (s1, s2);
+
+	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
+	g_assert (!success);
+
+	success = nm_setting_diff (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result);
+	g_assert (!success);
+	g_clear_pointer (&result, g_hash_table_unref);
+}
+
+static void
+test_setting_compare_wired_cloned_mac_address (void)
+{
+	gs_unref_object NMSetting *old = NULL, *new = NULL;
+	gboolean success;
+	gs_free char *str1 = NULL;
+
+	old = nm_setting_wired_new ();
+	g_object_set (old,
+	              NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "stable",
+	              NULL);
+
+	g_assert_cmpstr ("stable", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) old));
+	g_object_get (old, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
+	g_assert_cmpstr ("stable", ==, str1);
+	g_clear_pointer (&str1, g_free);
+
+	new = nm_setting_duplicate (old);
+	g_object_set (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL);
+
+	g_assert_cmpstr ("11:22:33:44:55:66", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) new));
+	g_object_get (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
+	g_assert_cmpstr ("11:22:33:44:55:66", ==, str1);
+	g_clear_pointer (&str1, g_free);
+
+	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
+	g_assert (!success);
+	g_clear_object (&new);
+
+	new = nm_setting_duplicate (old);
+	g_object_set (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "stable-bia", NULL);
+
+	g_assert_cmpstr ("stable-bia", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) new));
+	g_object_get (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
+	g_assert_cmpstr ("stable-bia", ==, str1);
+	g_clear_pointer (&str1, g_free);
+
+	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
+	g_assert (!success);
+	g_clear_object (&new);
+}
+
+static void
+test_setting_compare_wireless_cloned_mac_address (void)
+{
+	gs_unref_object NMSetting *old = NULL, *new = NULL;
+	gboolean success;
+	gs_free char *str1 = NULL;
+
+	old = nm_setting_wireless_new ();
+	g_object_set (old,
+	              NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "stable",
+	              NULL);
+
+	g_assert_cmpstr ("stable", ==, nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) old));
+	g_object_get (old, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL);
+	g_assert_cmpstr ("stable", ==, str1);
+	g_clear_pointer (&str1, g_free);
+
+	new = nm_setting_duplicate (old);
+	g_object_set (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL);
+
+	g_assert_cmpstr ("11:22:33:44:55:66", ==, nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) new));
+	g_object_get (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL);
+	g_assert_cmpstr ("11:22:33:44:55:66", ==, str1);
+	g_clear_pointer (&str1, g_free);
+
+	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
+	g_assert (!success);
+	g_clear_object (&new);
+
+	new = nm_setting_duplicate (old);
+	g_object_set (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "stable-bia", NULL);
+
+	g_assert_cmpstr ("stable-bia", ==, nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) new));
+	g_object_get (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL);
+	g_assert_cmpstr ("stable-bia", ==, str1);
+	g_clear_pointer (&str1, g_free);
+
+	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
+	g_assert (!success);
+	g_clear_object (&new);
+}
+
+static void
 test_setting_compare_timestamp (void)
 {
 	gs_unref_object NMSetting *old = NULL, *new = NULL;
@@ -2755,7 +2905,7 @@ test_setting_connection_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_connection_add_permission (s_con, "user", "billsmith", NULL));
 	ASSERT_CHANGED (nm_setting_connection_remove_permission (s_con, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*iter != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (iter != NULL));
 	ASSERT_UNCHANGED (nm_setting_connection_remove_permission (s_con, 1));
 	g_test_assert_expected_messages ();
 
@@ -2763,7 +2913,7 @@ test_setting_connection_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_connection_add_secondary (s_con, uuid));
 	ASSERT_CHANGED (nm_setting_connection_remove_secondary (s_con, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*elt != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (elt != NULL));
 	ASSERT_UNCHANGED (nm_setting_connection_remove_secondary (s_con, 1));
 	g_test_assert_expected_messages ();
 
@@ -2815,7 +2965,7 @@ test_setting_ip4_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_dns (s_ip4, "11.22.0.0"));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_dns (s_ip4, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->dns->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->dns->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_dns (s_ip4, 1));
 	g_test_assert_expected_messages ();
 
@@ -2825,7 +2975,7 @@ test_setting_ip4_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_dns_search (s_ip4, "foobar.com"));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_dns_search (s_ip4, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->dns_search->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->dns_search->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_dns_search (s_ip4, 1));
 	g_test_assert_expected_messages ();
 
@@ -2837,7 +2987,7 @@ test_setting_ip4_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_address (s_ip4, addr));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_address (s_ip4, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->addresses->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->addresses->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_address (s_ip4, 1));
 	g_test_assert_expected_messages ();
 
@@ -2850,7 +3000,7 @@ test_setting_ip4_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_route (s_ip4, route));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_route (s_ip4, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->routes->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->routes->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_route (s_ip4, 1));
 	g_test_assert_expected_messages ();
 
@@ -2860,7 +3010,7 @@ test_setting_ip4_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_dns_option (s_ip4, "debug"));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_dns_option (s_ip4, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->dns_options->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->dns_options->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_dns_option (s_ip4, 1));
 	g_test_assert_expected_messages ();
 
@@ -2891,7 +3041,7 @@ test_setting_ip6_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_dns (s_ip6, "1:2:3::4:5:6"));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_dns (s_ip6, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->dns->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->dns->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_dns (s_ip6, 1));
 	g_test_assert_expected_messages ();
 
@@ -2901,7 +3051,7 @@ test_setting_ip6_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_dns_search (s_ip6, "foobar.com"));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_dns_search (s_ip6, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->dns_search->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->dns_search->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_dns_search (s_ip6, 1));
 	g_test_assert_expected_messages ();
 
@@ -2914,7 +3064,7 @@ test_setting_ip6_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_address (s_ip6, addr));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_address (s_ip6, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->addresses->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->addresses->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_address (s_ip6, 1));
 	g_test_assert_expected_messages ();
 
@@ -2927,7 +3077,7 @@ test_setting_ip6_changed_signal (void)
 	ASSERT_CHANGED (nm_setting_ip_config_add_route (s_ip6, route));
 	ASSERT_CHANGED (nm_setting_ip_config_remove_route (s_ip6, 0));
 
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < priv->routes->len*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < priv->routes->len));
 	ASSERT_UNCHANGED (nm_setting_ip_config_remove_route (s_ip6, 1));
 	g_test_assert_expected_messages ();
 
@@ -2957,7 +3107,7 @@ test_setting_vlan_changed_signal (void)
 
 	ASSERT_CHANGED (nm_setting_vlan_add_priority (s_vlan, NM_VLAN_INGRESS_MAP, 1, 3));
 	ASSERT_CHANGED (nm_setting_vlan_remove_priority (s_vlan, NM_VLAN_INGRESS_MAP, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < g_slist_length (list)*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < g_slist_length (list)));
 	ASSERT_UNCHANGED (nm_setting_vlan_remove_priority (s_vlan, NM_VLAN_INGRESS_MAP, 1));
 	g_test_assert_expected_messages ();
 	ASSERT_CHANGED (nm_setting_vlan_add_priority_str (s_vlan, NM_VLAN_INGRESS_MAP, "1:3"));
@@ -2965,7 +3115,7 @@ test_setting_vlan_changed_signal (void)
 
 	ASSERT_CHANGED (nm_setting_vlan_add_priority (s_vlan, NM_VLAN_EGRESS_MAP, 1, 3));
 	ASSERT_CHANGED (nm_setting_vlan_remove_priority (s_vlan, NM_VLAN_EGRESS_MAP, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*idx < g_slist_length (list)*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (idx < g_slist_length (list)));
 	ASSERT_UNCHANGED (nm_setting_vlan_remove_priority (s_vlan, NM_VLAN_EGRESS_MAP, 1));
 	g_test_assert_expected_messages ();
 	ASSERT_CHANGED (nm_setting_vlan_add_priority_str (s_vlan, NM_VLAN_EGRESS_MAP, "1:3"));
@@ -3064,7 +3214,7 @@ test_setting_wireless_security_changed_signal (void)
 	/* Protos */
 	ASSERT_CHANGED (nm_setting_wireless_security_add_proto (s_wsec, "wpa"));
 	ASSERT_CHANGED (nm_setting_wireless_security_remove_proto (s_wsec, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*elt != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (elt != NULL));
 	ASSERT_UNCHANGED (nm_setting_wireless_security_remove_proto (s_wsec, 1));
 	g_test_assert_expected_messages ();
 
@@ -3074,7 +3224,7 @@ test_setting_wireless_security_changed_signal (void)
 	/* Pairwise ciphers */
 	ASSERT_CHANGED (nm_setting_wireless_security_add_pairwise (s_wsec, "tkip"));
 	ASSERT_CHANGED (nm_setting_wireless_security_remove_pairwise (s_wsec, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*elt != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (elt != NULL));
 	ASSERT_UNCHANGED (nm_setting_wireless_security_remove_pairwise (s_wsec, 1));
 	g_test_assert_expected_messages ();
 
@@ -3084,7 +3234,7 @@ test_setting_wireless_security_changed_signal (void)
 	/* Group ciphers */
 	ASSERT_CHANGED (nm_setting_wireless_security_add_group (s_wsec, "ccmp"));
 	ASSERT_CHANGED (nm_setting_wireless_security_remove_group (s_wsec, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*elt != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (elt != NULL));
 	ASSERT_UNCHANGED (nm_setting_wireless_security_remove_group (s_wsec, 1));
 	g_test_assert_expected_messages ();
 
@@ -3119,7 +3269,7 @@ test_setting_802_1x_changed_signal (void)
 	/* EAP methods */
 	ASSERT_CHANGED (nm_setting_802_1x_add_eap_method (s_8021x, "tls"));
 	ASSERT_CHANGED (nm_setting_802_1x_remove_eap_method (s_8021x, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*elt != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (elt != NULL));
 	ASSERT_UNCHANGED (nm_setting_802_1x_remove_eap_method (s_8021x, 1));
 	g_test_assert_expected_messages ();
 
@@ -3129,7 +3279,7 @@ test_setting_802_1x_changed_signal (void)
 	/* alternate subject matches */
 	ASSERT_CHANGED (nm_setting_802_1x_add_altsubject_match (s_8021x, "EMAIL:server@example.com"));
 	ASSERT_CHANGED (nm_setting_802_1x_remove_altsubject_match (s_8021x, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*elt != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (elt != NULL));
 	ASSERT_UNCHANGED (nm_setting_802_1x_remove_altsubject_match (s_8021x, 1));
 	g_test_assert_expected_messages ();
 
@@ -3139,7 +3289,7 @@ test_setting_802_1x_changed_signal (void)
 	/* phase2 alternate subject matches */
 	ASSERT_CHANGED (nm_setting_802_1x_add_phase2_altsubject_match (s_8021x, "EMAIL:server@example.com"));
 	ASSERT_CHANGED (nm_setting_802_1x_remove_phase2_altsubject_match (s_8021x, 0));
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, "*elt != NULL*");
+	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL, NMTST_G_RETURN_MSG (elt != NULL));
 	ASSERT_UNCHANGED (nm_setting_802_1x_remove_phase2_altsubject_match (s_8021x, 1));
 	g_test_assert_expected_messages ();
 
@@ -3773,7 +3923,6 @@ test_connection_normalize_may_fail (void)
 {
 	gs_unref_object NMConnection *con = NULL;
 	NMSettingIPConfig *s_ip4, *s_ip6;
-	gs_free_error GError *error = NULL;
 
 	con = nmtst_create_minimal_connection ("test2", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
 	nmtst_assert_connection_verifies_and_normalizable (con);
@@ -4477,6 +4626,110 @@ test_nm_utils_dns_option_find_idx (void)
 
 /******************************************************************************/
 
+static void
+_json_config_check_valid (const char *conf, gboolean expected)
+{
+	gs_free_error GError *error = NULL;
+	gboolean res;
+
+	res = _nm_utils_check_valid_json (conf, &error);
+	g_assert_cmpint (res, ==, expected);
+	g_assert (res || error);
+}
+
+static void
+test_nm_utils_check_valid_json (void)
+{
+	_json_config_check_valid (NULL, FALSE);
+	_json_config_check_valid ("", FALSE);
+#if WITH_JANSSON
+	_json_config_check_valid ("{ }", TRUE);
+	_json_config_check_valid ("{ \"a\" : 1 }", TRUE);
+	_json_config_check_valid ("{ \"a\" : }", FALSE);
+#else
+	/* Without JSON library everything except empty string is considered valid */
+	_json_config_check_valid ("{ }", TRUE);
+	_json_config_check_valid ("{'%!-a1", TRUE);
+#endif
+}
+
+static void
+_team_config_equal_check (const char *conf1,
+                          const char *conf2,
+                          gboolean port_config,
+                          gboolean expected)
+{
+	g_assert_cmpint (_nm_utils_team_config_equal (conf1, conf2, port_config), ==, expected);
+}
+
+static void
+test_nm_utils_team_config_equal (void)
+{
+#if WITH_JANSSON
+	_team_config_equal_check ("", "", TRUE, TRUE);
+	_team_config_equal_check ("{}",
+	                          "{ }",
+	                          TRUE,
+	                          TRUE);
+	_team_config_equal_check ("{}",
+	                          "{",
+	                          TRUE,
+	                          FALSE);
+
+	/* team config */
+	_team_config_equal_check ("{ }",
+	                          "{ \"runner\" :  { \"name\" : \"roundrobin\"} }",
+	                          FALSE,
+	                          TRUE);
+	_team_config_equal_check ("{ }",
+	                          "{ \"runner\" :  { \"name\" : \"random\"} }",
+	                          FALSE,
+	                          FALSE);
+	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"roundrobin\"} }",
+	                          "{ \"runner\" :  { \"name\" : \"random\"} }",
+	                          FALSE,
+	                          FALSE);
+	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"random\"} }",
+	                          "{ \"runner\" :  { \"name\" : \"random\"} }",
+	                          FALSE,
+	                          TRUE);
+	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"random\"}, \"ports\" : { \"eth0\" : {} } }",
+	                          "{ \"runner\" :  { \"name\" : \"random\"}, \"ports\" : { \"eth1\" : {} } }",
+	                          FALSE,
+	                          TRUE);
+
+	/* team port config */
+	_team_config_equal_check ("{ }",
+	                          "{ \"link_watch\" :  { \"name\" : \"ethtool\"} }",
+	                          TRUE,
+	                          TRUE);
+	_team_config_equal_check ("{ }",
+	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
+	                          TRUE,
+	                          FALSE);
+	_team_config_equal_check ("{ \"link_watch\" :  { \"name\" : \"ethtool\"} }",
+	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
+	                          TRUE,
+	                          FALSE);
+	_team_config_equal_check ("{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
+	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
+	                          TRUE,
+	                          TRUE);
+	_team_config_equal_check ("{ \"link_watch\" :  { \"name\" : \"arp_ping\"}, \"ports\" : { \"eth0\" : {} } }",
+	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"}, \"ports\" : { \"eth1\" : {} } }",
+	                          TRUE,
+	                          TRUE);
+#else
+	/* Without JSON library, strings are compared for equality */
+	_team_config_equal_check ("", "", TRUE, TRUE);
+	_team_config_equal_check ("", " ", TRUE, FALSE);
+	_team_config_equal_check ("{ \"a\": 1 }", "{ \"a\": 1 }", TRUE, TRUE);
+	_team_config_equal_check ("{ \"a\": 1 }", "{ \"a\":   1 }", TRUE, FALSE);
+#endif
+}
+
+/******************************************************************************/
+
 enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED {
 	_DUMMY_1 = -1,
 };
@@ -5074,6 +5327,10 @@ int main (int argc, char **argv)
 	g_test_add_func ("/core/general/test_setting_to_dbus_transform", test_setting_to_dbus_transform);
 	g_test_add_func ("/core/general/test_setting_to_dbus_enum", test_setting_to_dbus_enum);
 	g_test_add_func ("/core/general/test_setting_compare_id", test_setting_compare_id);
+	g_test_add_func ("/core/general/test_setting_compare_addresses", test_setting_compare_addresses);
+	g_test_add_func ("/core/general/test_setting_compare_routes", test_setting_compare_routes);
+	g_test_add_func ("/core/general/test_setting_compare_wired_cloned_mac_address", test_setting_compare_wired_cloned_mac_address);
+	g_test_add_func ("/core/general/test_setting_compare_wirless_cloned_mac_address", test_setting_compare_wireless_cloned_mac_address);
 	g_test_add_func ("/core/general/test_setting_compare_timestamp", test_setting_compare_timestamp);
 #define ADD_FUNC(name, func, secret_flags, comp_flags, remove_secret) \
 	g_test_add_data_func_full ("/core/general/" G_STRINGIFY (func) "_" name, \
@@ -5163,7 +5420,8 @@ int main (int argc, char **argv)
 
 	g_test_add_func ("/core/general/_nm_utils_dns_option_validate", test_nm_utils_dns_option_validate);
 	g_test_add_func ("/core/general/_nm_utils_dns_option_find_idx", test_nm_utils_dns_option_find_idx);
-
+	g_test_add_func ("/core/general/_nm_utils_validate_json", test_nm_utils_check_valid_json);
+	g_test_add_func ("/core/general/_nm_utils_team_config_equal", test_nm_utils_team_config_equal);
 	g_test_add_func ("/core/general/test_nm_utils_enum", test_nm_utils_enum);
 
 	return g_test_run ();
diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c
index 92b6c1f5..ecb7cd5a 100644
--- a/libnm-core/tests/test-keyfile.c
+++ b/libnm-core/tests/test-keyfile.c
@@ -28,7 +28,7 @@
 #include "nm-setting-wired.h"
 #include "nm-setting-8021x.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 
 #define TEST_WIRED_TLS_CA_CERT     TEST_CERT_DIR"/test-ca-cert.pem"
diff --git a/libnm-core/tests/test-secrets.c b/libnm-core/tests/test-secrets.c
index a32ed4f1..0149348d 100644
--- a/libnm-core/tests/test-secrets.c
+++ b/libnm-core/tests/test-secrets.c
@@ -38,7 +38,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 #define TEST_NEED_SECRETS_EAP_TLS_CA_CERT TEST_CERT_DIR "/test_ca_cert.pem"
 #define TEST_NEED_SECRETS_EAP_TLS_CLIENT_CERT TEST_CERT_DIR "/test_key_and_cert.pem"
@@ -639,7 +639,7 @@ test_update_secrets_null_setting_name_with_setting_hash (void)
 	secrets = build_wep_secrets (wepkey);
 
 	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL,
-	                       "*nm_connection_update_secrets*setting_name != NULL || full_connection*");
+	                       NMTST_G_RETURN_MSG (setting_name != NULL || full_connection));
 	success = nm_connection_update_secrets (connection, NULL, secrets, &error);
 	g_test_assert_expected_messages ();
 	g_assert_no_error (error);
diff --git a/libnm-core/tests/test-setting-8021x.c b/libnm-core/tests/test-setting-8021x.c
index 56563a6f..268a9f52 100644
--- a/libnm-core/tests/test-setting-8021x.c
+++ b/libnm-core/tests/test-setting-8021x.c
@@ -27,7 +27,7 @@
 #include "nm-setting-connection.h"
 #include "nm-setting-8021x.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 static void
 compare_blob_data (const char *test,
diff --git a/libnm-core/tests/test-setting-bond.c b/libnm-core/tests/test-setting-bond.c
index a6afa1c8..91a81997 100644
--- a/libnm-core/tests/test-setting-bond.c
+++ b/libnm-core/tests/test-setting-bond.c
@@ -26,7 +26,7 @@
 #include "nm-setting-connection.h"
 #include "nm-errors.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 static void
 create_bond_connection (NMConnection **con, NMSettingBond **s_bond)
diff --git a/libnm-core/tests/test-setting-dcb.c b/libnm-core/tests/test-setting-dcb.c
index 09cf8d98..55c1c516 100644
--- a/libnm-core/tests/test-setting-dcb.c
+++ b/libnm-core/tests/test-setting-dcb.c
@@ -28,7 +28,7 @@
 #include "nm-connection.h"
 #include "nm-errors.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 #define DCB_FLAGS_ALL (NM_SETTING_DCB_FLAG_ENABLE | \
                        NM_SETTING_DCB_FLAG_ADVERTISE | \
diff --git a/libnm-core/tests/test-settings-defaults.c b/libnm-core/tests/test-settings-defaults.c
index 366910ac..95570b03 100644
--- a/libnm-core/tests/test-settings-defaults.c
+++ b/libnm-core/tests/test-settings-defaults.c
@@ -38,7 +38,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 
-#include "nm-test-utils.h"
+#include "nm-utils/nm-test-utils.h"
 
 static void
 test_defaults (GType type, const char *name)