summary refs log tree commit diff
path: root/src/nm-core-utils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-06-04 00:07:45 +0200
committerMichael Biebl <biebl@debian.org>2018-06-04 00:07:45 +0200
commit04bc9e1cd3544445d883ad29ea108c1645c8e7b7 (patch)
treed10c354b1b980ca8a7b9e48ec9019e8ed88bde2b /src/nm-core-utils.c
parentee9c73a923909e23a649407be77e25235d769e25 (diff)
New upstream version 1.11.4 upstream/1.11.4
Diffstat (limited to 'src/nm-core-utils.c')
-rw-r--r--src/nm-core-utils.c456
1 files changed, 184 insertions, 272 deletions
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index f6b33a14..b1a4cc25 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -452,83 +452,6 @@ nm_utils_modprobe (GError **error, gboolean suppress_error_logging, const char *
 	return exit_status;
 }
 
-/**
- * nm_utils_get_start_time_for_pid:
- * @pid: the process identifier
- * @out_state: return the state character, like R, S, Z. See `man 5 proc`.
- * @out_ppid: parent process id
- *
- * Originally copied from polkit source (src/polkit/polkitunixprocess.c)
- * and adjusted.
- *
- * Returns: the timestamp when the process started (by parsing /proc/$PID/stat).
- * If an error occurs (e.g. the process does not exist), 0 is returned.
- *
- * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds)
- **/
-guint64
-nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
-{
-	guint64 start_time;
-	char filename[256];
-	gs_free gchar *contents = NULL;
-	size_t length;
-	gs_strfreev gchar **tokens = NULL;
-	guint num_tokens;
-	gchar *p;
-	char state = ' ';
-	gint64 ppid = 0;
-
-	start_time = 0;
-	contents = NULL;
-
-	g_return_val_if_fail (pid > 0, 0);
-
-	nm_sprintf_buf (filename, "/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid);
-
-	if (!g_file_get_contents (filename, &contents, &length, NULL))
-		goto fail;
-
-	/* start time is the token at index 19 after the '(process name)' entry - since only this
-	 * field can contain the ')' character, search backwards for this to avoid malicious
-	 * processes trying to fool us
-	 */
-	p = strrchr (contents, ')');
-	if (p == NULL)
-		goto fail;
-	p += 2; /* skip ') ' */
-	if (p - contents >= (int) length)
-		goto fail;
-
-	state = p[0];
-
-	tokens = g_strsplit (p, " ", 0);
-
-	num_tokens = g_strv_length (tokens);
-
-	if (num_tokens < 20)
-		goto fail;
-
-	if (out_ppid) {
-		ppid = _nm_utils_ascii_str_to_int64 (tokens[1], 10, 1, G_MAXINT, 0);
-		if (ppid == 0)
-			goto fail;
-	}
-
-	start_time = _nm_utils_ascii_str_to_int64 (tokens[19], 10, 1, G_MAXINT64, 0);
-	if (start_time == 0)
-		goto fail;
-
-	NM_SET_OUT (out_state, state);
-	NM_SET_OUT (out_ppid, ppid);
-	return start_time;
-
-fail:
-	NM_SET_OUT (out_state, ' ');
-	NM_SET_OUT (out_ppid, 0);
-	return 0;
-}
-
 /*****************************************************************************/
 
 typedef struct {
@@ -1246,8 +1169,8 @@ typedef struct {
 static gboolean
 match_device_s390_subchannels_parse (const char *s390_subchannels, guint32 *out_a, guint32 *out_b, guint32 *out_c)
 {
-	const int BUFSIZE = 30;
-	char buf[BUFSIZE + 1];
+	char buf[30 + 1];
+	const int BUFSIZE = G_N_ELEMENTS (buf) - 1;
 	guint i = 0;
 	char *pa = NULL, *pb = NULL, *pc = NULL;
 	gint64 a, b, c;
@@ -1877,110 +1800,6 @@ nm_utils_new_infiniband_name (char *name, const char *parent_name, int p_key)
 
 /*****************************************************************************/
 
-gboolean
-nm_utils_resolve_conf_parse (int addr_family,
-                             const char *rc_contents,
-                             GArray *nameservers,
-                             GPtrArray *dns_options)
-{
-	guint i;
-	gboolean changed = FALSE;
-	gs_free const char **lines = NULL;
-	gsize l;
-
-	g_return_val_if_fail (rc_contents, FALSE);
-	g_return_val_if_fail (nameservers, FALSE);
-	g_return_val_if_fail (   (   addr_family == AF_INET
-	                          && g_array_get_element_size (nameservers) == sizeof (in_addr_t))
-	                      || (   addr_family == AF_INET6
-	                          && g_array_get_element_size (nameservers) == sizeof (struct in6_addr)), FALSE);
-
-	lines = nm_utils_strsplit_set (rc_contents, "\r\n");
-	if (!lines)
-		return FALSE;
-
-/* like glibc's MATCH() macro in resolv/res_init.c. */
-#define RC_MATCH(line, option, out_arg) \
-    ({ \
-        const char *const _line = (line); \
-        gboolean _match = FALSE; \
-        \
-        if (   (strncmp (_line, option, NM_STRLEN (option)) == 0) \
-            && (NM_IN_SET (_line[NM_STRLEN (option)], ' ', '\t'))) { \
-            _match = TRUE;\
-            (out_arg) = &_line[NM_STRLEN (option) + 1]; \
-        } \
-        _match; \
-    })
-
-	for (l = 0; lines[l]; l++) {
-		const char *const line = lines[l];
-		const char *s = NULL;
-
-		if (RC_MATCH (line, "nameserver", s)) {
-			gs_free char *s_cpy = NULL;
-			NMIPAddr ns;
-
-			s = nm_strstrip_avoid_copy (s, &s_cpy);
-			if (inet_pton (addr_family, s, &ns) != 1)
-				continue;
-
-			if (addr_family == AF_INET) {
-				if (!ns.addr4)
-					continue;
-				for (i = 0; i < nameservers->len; i++) {
-					if (g_array_index (nameservers, guint32, i) == ns.addr4)
-						break;
-				}
-			} else {
-				if (IN6_IS_ADDR_UNSPECIFIED (&ns.addr6))
-					continue;
-				for (i = 0; i < nameservers->len; i++) {
-					struct in6_addr *t = &g_array_index (nameservers, struct in6_addr, i);
-
-					if (IN6_ARE_ADDR_EQUAL (t, &ns.addr6))
-						break;
-				}
-			}
-
-			if (i == nameservers->len) {
-				g_array_append_val (nameservers, ns);
-				changed = TRUE;
-			}
-			continue;
-		}
-
-		if (RC_MATCH (line, "options", s)) {
-			if (!dns_options)
-				continue;
-
-			s = nm_str_skip_leading_spaces (s);
-			if (s[0]) {
-				gs_free const char **tokens = NULL;
-				gsize i_tokens;
-
-				tokens = nm_utils_strsplit_set (s, " \t");
-				for (i_tokens = 0; tokens && tokens[i_tokens]; i_tokens++) {
-					gs_free char *t = g_strstrip (g_strdup (tokens[i_tokens]));
-
-					if (   _nm_utils_dns_option_validate (t, NULL, NULL,
-					                                      addr_family == AF_INET6,
-					                                      _nm_utils_dns_option_descs)
-					    && _nm_utils_dns_option_find_idx (dns_options, t) < 0) {
-						g_ptr_array_add (dns_options, g_steal_pointer (&t));
-						changed = TRUE;
-					}
-				}
-			}
-			continue;
-		}
-	}
-
-	return changed;
-}
-
-/*****************************************************************************/
-
 /**
  * nm_utils_cmp_connection_by_autoconnect_priority:
  * @a:
@@ -2225,8 +2044,8 @@ _log_connection_sort_hashes_fcn (gconstpointer a, gconstpointer b)
 	NMSettingPriority p1, p2;
 	NMSetting *s1, *s2;
 
-	s1 = v1->setting ? v1->setting : v1->diff_base_setting;
-	s2 = v2->setting ? v2->setting : v2->diff_base_setting;
+	s1 = v1->setting ?: v1->diff_base_setting;
+	s2 = v2->setting ?: v2->diff_base_setting;
 
 	g_assert (s1 && s2);
 
@@ -2338,7 +2157,13 @@ _log_connection_sort_names (LogConnectionSettingData *setting_data, GArray *sort
 }
 
 void
-nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix)
+nm_utils_log_connection_diff (NMConnection *connection,
+                              NMConnection *diff_base,
+                              guint32 level,
+                              guint64 domain,
+                              const char *name,
+                              const char *prefix,
+                              const char *dbus_path)
 {
 	GHashTable *connection_diff = NULL;
 	GArray *sorted_hashes;
@@ -2414,7 +2239,6 @@ nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base,
 
 			if (print_header) {
 				GError *err_verify = NULL;
-				const char *path = nm_connection_get_path (connection);
 				const char *t1, *t2;
 
 				t1 = nm_connection_get_connection_type (connection);
@@ -2424,12 +2248,12 @@ nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base,
 					        prefix, name,
 					        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1),
 					        diff_base, G_OBJECT_TYPE_NAME (diff_base), NM_PRINT_FMT_QUOTE_STRING (t2),
-					        NM_PRINT_FMT_QUOTED (path, " [", path, "]", ""));
+					        NM_PRINT_FMT_QUOTED (dbus_path, " [", dbus_path, "]", ""));
 				} else {
 					nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s/%s%s%s):%s%s%s",
 					        prefix, name,
 					        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1),
-					        NM_PRINT_FMT_QUOTED (path, " [", path, "]", ""));
+					        NM_PRINT_FMT_QUOTED (dbus_path, " [", dbus_path, "]", ""));
 				}
 				print_header = FALSE;
 
@@ -2455,13 +2279,13 @@ nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base,
 			g_string_printf (str1, "%s.%s", setting_data->name, item->item_name);
 			switch (item->diff_result & (NM_SETTING_DIFF_RESULT_IN_A | NM_SETTING_DIFF_RESULT_IN_B)) {
 				case NM_SETTING_DIFF_RESULT_IN_B:
-					nm_log (level, domain, NULL, NULL, "%s%"_NM_LOG_ALIGN"s < %s", prefix, str1->str, str_diff ? str_diff : "NULL");
+					nm_log (level, domain, NULL, NULL, "%s%"_NM_LOG_ALIGN"s < %s", prefix, str1->str, str_diff ?: "NULL");
 					break;
 				case NM_SETTING_DIFF_RESULT_IN_A:
-					nm_log (level, domain, NULL, NULL, "%s%"_NM_LOG_ALIGN"s = %s", prefix, str1->str, str_conn ? str_conn : "NULL");
+					nm_log (level, domain, NULL, NULL, "%s%"_NM_LOG_ALIGN"s = %s", prefix, str1->str, str_conn ?: "NULL");
 					break;
 				default:
-					nm_log (level, domain, NULL, NULL, "%s%"_NM_LOG_ALIGN"s = %s < %s", prefix, str1->str, str_conn ? str_conn : "NULL", str_diff ? str_diff : "NULL");
+					nm_log (level, domain, NULL, NULL, "%s%"_NM_LOG_ALIGN"s = %s < %s", prefix, str1->str, str_conn ?: "NULL", str_diff ?: "NULL");
 					break;
 #undef _NM_LOG_ALIGN
 			}
@@ -2520,7 +2344,6 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_
 	return timestamp - offset;
 }
 
-
 #define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/"
 #define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/"
 G_STATIC_ASSERT (sizeof (IPV4_PROPERTY_DIR) == sizeof (IPV6_PROPERTY_DIR));
@@ -2747,7 +2570,7 @@ _get_contents_error (GError **error, int errsv, const char *format, ...)
 /**
  * nm_utils_fd_get_contents:
  * @fd: open file descriptor to read. The fd will not be closed,
- *   but don't rely on it's state afterwards.
+ *   but don't rely on its state afterwards.
  * @close_fd: if %TRUE, @fd will be closed by the function.
  *  Passing %TRUE here might safe a syscall for dup().
  * @max_length: allocate at most @max_length bytes. If the
@@ -2974,53 +2797,103 @@ nm_utils_file_get_contents (int dirfd,
 
 /*****************************************************************************/
 
-guint8 *
-nm_utils_secret_key_read (gsize *out_key_len, GError **error)
+static gboolean
+_secret_key_read (guint8 **out_secret_key,
+                  gsize *out_key_len)
 {
-	guint8 *secret_key = NULL;
+	guint8 *secret_key;
+	gboolean success = TRUE;
 	gsize key_len;
-
-	/* out_key_len is not optional, because without it you cannot safely
-	 * access the returned memory. */
-	*out_key_len = 0;
+	gs_free_error GError *error = NULL;
 
 	/* Let's try to load a saved secret key first. */
-	if (g_file_get_contents (NMSTATEDIR "/secret_key", (char **) &secret_key, &key_len, NULL)) {
-		if (key_len < 16) {
-			g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			                     "Key is too short to be usable");
-			key_len = 0;
-		}
+	if (g_file_get_contents (NMSTATEDIR "/secret_key", (char **) &secret_key, &key_len, &error)) {
+		if (key_len >= 16)
+			goto out;
+
+		/* the secret key is borked. Log a warning, but proceed below to generate
+		 * a new one. */
+		nm_log_warn (LOGD_CORE, "secret-key: too short secret key in \"%s\" (generate new key)", NMSTATEDIR "/secret_key");
+		nm_clear_g_free (&secret_key);
 	} else {
-		mode_t key_mask;
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+			nm_log_warn (LOGD_CORE, "secret-key: failure reading secret key in \"%s\": %s (generate new key)",
+			             NMSTATEDIR "/secret_key", error->message);
+		}
+		g_clear_error (&error);
+	}
 
-		/* RFC7217 mandates the key SHOULD be at least 128 bits.
-		 * Let's use twice as much. */
-		key_len = 32;
-		secret_key = g_malloc (key_len);
+	/* RFC7217 mandates the key SHOULD be at least 128 bits.
+	 * Let's use twice as much. */
+	key_len = 32;
+	secret_key = g_malloc (key_len + 1);
 
-		if (!nm_utils_random_bytes (secret_key, key_len)) {
-			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "Can't get random data to generate secret key");
-			key_len = 0;
-			goto out;
-		}
+	/* the secret-key is binary. Still, ensure that it's NULL terminated, just like
+	 * g_file_set_contents() does. */
+	secret_key[32] = '\0';
 
-		key_mask = umask (0077);
-		if (!g_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, error)) {
-			g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key: ");
-			key_len = 0;
-		}
-		umask (key_mask);
+	if (!nm_utils_random_bytes (secret_key, key_len)) {
+		nm_log_warn (LOGD_CORE, "secret-key: failure to generate good random data for secret-key (use non-persistent key)");
+		success = FALSE;
+		goto out;
+	}
+
+	if (!nm_utils_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, 0077, &error)) {
+		nm_log_warn (LOGD_CORE, "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)",
+		             NMSTATEDIR "/secret_key", error->message);
+		success = FALSE;
+		goto out;
 	}
 
 out:
-	if (key_len) {
-		*out_key_len = key_len;
-		return secret_key;
+	/* regardless of success or failue, we always return a secret-key. The
+	 * caller may choose to ignore the error and proceed. */
+	*out_key_len = key_len;
+	*out_secret_key = secret_key;
+	return success;
+}
+
+typedef struct {
+	const guint8 *secret_key;
+	gsize key_len;
+	bool is_good:1;
+} SecretKeyData;
+
+gboolean
+nm_utils_secret_key_get (const guint8 **out_secret_key,
+                         gsize *out_key_len)
+{
+	static volatile const SecretKeyData *secret_key_static;
+	const SecretKeyData *secret_key;
+
+	secret_key = g_atomic_pointer_get (&secret_key_static);
+	if (G_UNLIKELY (!secret_key)) {
+		static gsize init_value = 0;
+		static SecretKeyData secret_key_data;
+		gboolean tmp_success;
+		gs_free guint8 *tmp_secret_key = NULL;
+		gsize tmp_key_len;
+
+		tmp_success = _secret_key_read (&tmp_secret_key, &tmp_key_len);
+		if (g_once_init_enter (&init_value)) {
+			secret_key_data.secret_key = tmp_secret_key;
+			secret_key_data.key_len = tmp_key_len;
+			secret_key_data.is_good = tmp_success;
+
+			if (g_atomic_pointer_compare_and_exchange (&secret_key_static, NULL, &secret_key_data)) {
+				g_steal_pointer (&tmp_secret_key);
+				secret_key = &secret_key_data;
+			}
+
+			g_once_init_leave (&init_value, 1);
+		}
+		if (!secret_key)
+			secret_key = g_atomic_pointer_get (&secret_key_static);
 	}
-	g_free (secret_key);
-	return NULL;
+
+	*out_secret_key = secret_key->secret_key;
+	*out_key_len = secret_key->key_len;
+	return secret_key->is_good;
 }
 
 /*****************************************************************************/
@@ -3308,8 +3181,9 @@ _stable_id_append (GString *str,
 
 NMUtilsStableType
 nm_utils_stable_id_parse (const char *stable_id,
-                          const char *uuid,
+                          const char *deviceid,
                           const char *bootid,
+                          const char *uuid,
                           char **out_generated)
 {
 	gsize i, idx_start;
@@ -3384,6 +3258,8 @@ nm_utils_stable_id_parse (const char *stable_id,
 			_stable_id_append (str, uuid);
 		else if (CHECK_PREFIX ("${BOOT}"))
 			_stable_id_append (str, bootid ?: nm_utils_get_boot_id ());
+		else if (CHECK_PREFIX ("${DEVICE}"))
+			_stable_id_append (str, deviceid);
 		else if (g_str_has_prefix (&stable_id[i], "${RANDOM}")) {
 			/* RANDOM makes not so much sense for cloned-mac-address
 			 * as the result is simmilar to specyifing "cloned-mac-address=random".
@@ -3460,7 +3336,7 @@ _set_stable_privacy (NMUtilsStableType stable_type,
                      const char *ifname,
                      const char *network_id,
                      guint32 dad_counter,
-                     guint8 *secret_key,
+                     const guint8 *secret_key,
                      gsize key_len,
                      GError **error)
 {
@@ -3559,8 +3435,8 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type,
                                        guint32 dad_counter,
                                        GError **error)
 {
-	gs_free guint8 *secret_key = NULL;
-	gsize key_len = 0;
+	const guint8 *secret_key;
+	gsize key_len;
 
 	g_return_val_if_fail (network_id, FALSE);
 
@@ -3570,9 +3446,7 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type,
 		return FALSE;
 	}
 
-	secret_key = nm_utils_secret_key_read (&key_len, error);
-	if (!secret_key)
-		return FALSE;
+	nm_utils_secret_key_get (&secret_key, &key_len);
 
 	return _set_stable_privacy (stable_type, addr, ifname, network_id, dad_counter,
 	                            secret_key, key_len, error);
@@ -3708,14 +3582,12 @@ nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
                                  const char *current_mac_address,
                                  const char *generate_mac_address_mask)
 {
-	gs_free guint8 *secret_key = NULL;
-	gsize key_len = 0;
+	const guint8 *secret_key;
+	gsize key_len;
 
 	g_return_val_if_fail (stable_id, NULL);
 
-	secret_key = nm_utils_secret_key_read (&key_len, NULL);
-	if (!secret_key)
-		return NULL;
+	nm_utils_secret_key_get (&secret_key, &key_len);
 
 	return _hw_addr_gen_stable_eth (stable_type,
 	                                stable_id,
@@ -3749,7 +3621,8 @@ nm_utils_setpgid (gpointer unused G_GNUC_UNUSED)
 /**
  * nm_utils_g_value_set_strv:
  * @value: a #GValue, initialized to store a #G_TYPE_STRV
- * @strings: a #GPtrArray of strings
+ * @strings: a #GPtrArray of strings. %NULL values are not
+ *   allowed.
  *
  * Converts @strings to a #GStrv and stores it in @value.
  */
@@ -3757,11 +3630,13 @@ void
 nm_utils_g_value_set_strv (GValue *value, GPtrArray *strings)
 {
 	char **strv;
-	int i;
+	guint i;
 
 	strv = g_new (char *, strings->len + 1);
-	for (i = 0; i < strings->len; i++)
+	for (i = 0; i < strings->len; i++) {
+		nm_assert (strings->pdata[i]);
 		strv[i] = g_strdup (strings->pdata[i]);
+	}
 	strv[i] = NULL;
 
 	g_value_take_boxed (value, strv);
@@ -3891,12 +3766,11 @@ nm_utils_lifetime_rebase_relative_time_on_now (guint32 timestamp,
 	return t;
 }
 
-gboolean
+guint32
 nm_utils_lifetime_get (guint32 timestamp,
                        guint32 lifetime,
                        guint32 preferred,
                        gint32 now,
-                       guint32 *out_lifetime,
                        guint32 *out_preferred)
 {
 	guint32 t_lifetime, t_preferred;
@@ -3904,38 +3778,39 @@ nm_utils_lifetime_get (guint32 timestamp,
 	nm_assert (now >= 0);
 
 	if (timestamp == 0 && lifetime == 0) {
-		/* We treat lifetime==0 && timestamp == 0 addresses as permanent addresses to allow easy
+		/* We treat lifetime==0 && timestamp==0 addresses as permanent addresses to allow easy
 		 * creation of such addresses (without requiring to set the lifetime fields to
 		 * NM_PLATFORM_LIFETIME_PERMANENT). The real lifetime==0 addresses (E.g. DHCP6 telling us
 		 * to drop an address will have timestamp set.
 		 */
-		*out_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
-		*out_preferred = NM_PLATFORM_LIFETIME_PERMANENT;
-		g_return_val_if_fail (preferred == 0, TRUE);
-	} else {
-		if (now <= 0)
-			now = nm_utils_get_monotonic_timestamp_s ();
-		t_lifetime = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now);
-		if (!t_lifetime) {
-			*out_lifetime = 0;
-			*out_preferred = 0;
-			return FALSE;
-		}
-		t_preferred = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, preferred, now);
+		NM_SET_OUT (out_preferred, NM_PLATFORM_LIFETIME_PERMANENT);
+		g_return_val_if_fail (preferred == 0, NM_PLATFORM_LIFETIME_PERMANENT);
+		return NM_PLATFORM_LIFETIME_PERMANENT;
+	}
 
-		*out_lifetime = t_lifetime;
-		*out_preferred = MIN (t_preferred, t_lifetime);
+	if (now <= 0)
+		now = nm_utils_get_monotonic_timestamp_s ();
 
-		/* Assert that non-permanent addresses have a (positive) @timestamp. nm_utils_lifetime_rebase_relative_time_on_now()
-		 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
-		 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
-		 */
-		g_return_val_if_fail (   timestamp != 0
-		                      || (   lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
-		                          && preferred == NM_PLATFORM_LIFETIME_PERMANENT), TRUE);
-		g_return_val_if_fail (t_preferred <= t_lifetime, TRUE);
+	t_lifetime = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now);
+	if (!t_lifetime) {
+		NM_SET_OUT (out_preferred, 0);
+		return 0;
 	}
-	return TRUE;
+
+	t_preferred = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, preferred, now);
+
+	NM_SET_OUT (out_preferred, MIN (t_preferred, t_lifetime));
+
+	/* Assert that non-permanent addresses have a (positive) @timestamp. nm_utils_lifetime_rebase_relative_time_on_now()
+	 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
+	 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
+	 */
+	g_return_val_if_fail (   timestamp != 0
+	                      || (   lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
+	                          && preferred == NM_PLATFORM_LIFETIME_PERMANENT), t_lifetime);
+	g_return_val_if_fail (t_preferred <= t_lifetime, t_lifetime);
+
+	return t_lifetime;
 }
 
 const char *
@@ -4260,7 +4135,7 @@ nm_utils_read_plugin_paths (const char *dirname, const char *prefix)
 
 		if (!g_str_has_prefix (item, prefix))
 			continue;
-		if (g_str_has_suffix (item, ".la"))
+		if (!g_str_has_suffix (item, ".so"))
 			continue;
 
 		data.path = g_build_filename (dirname, item, NULL);
@@ -4329,6 +4204,43 @@ nm_utils_format_con_diff_for_audit (GHashTable *diff)
 	return g_string_free (str, FALSE);
 }
 
+const char *
+nm_utils_parse_dns_domain (const char *domain, gboolean *is_routing)
+{
+	g_return_val_if_fail (domain, NULL);
+	g_return_val_if_fail (domain[0], NULL);
+
+	if (domain[0] == '~') {
+		domain++;
+		NM_SET_OUT (is_routing, TRUE);
+	} else
+		NM_SET_OUT (is_routing, FALSE);
+
+	return domain;
+}
+
+/*****************************************************************************/
+
+GVariant *
+nm_utils_strdict_to_variant (GHashTable *options)
+{
+	GVariantBuilder builder;
+	gs_free const char **keys = NULL;
+	guint i;
+	guint nkeys;
+
+	keys = nm_utils_strdict_get_keys (options, TRUE, &nkeys);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+	for (i = 0; i < nkeys; i++) {
+		g_variant_builder_add (&builder,
+		                       "{sv}",
+		                       keys[i],
+		                       g_variant_new_string (g_hash_table_lookup (options, keys[i])));
+	}
+	return g_variant_builder_end (&builder);
+}
+
 /*****************************************************************************/
 
 NM_UTILS_ENUM2STR_DEFINE (nm_icmpv6_router_pref_to_string, NMIcmpv6RouterPref,