summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
committerMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
commit6518e361171f64bcaaa4bf868139362ed95cc2e0 (patch)
treed2d5b53faf80646a40ec2c0c7f2a42b3959612f5 /src/dhcp
parente126f3e804c35480c4f075777430419d6ece23da (diff)
New upstream version 1.14.2 upstream/1.14.2
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/meson.build1
-rw-r--r--src/dhcp/nm-dhcp-client.c18
-rw-r--r--src/dhcp/nm-dhcp-client.h16
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c31
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c196
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c90
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c66
-rw-r--r--src/dhcp/nm-dhcp-helper.c4
-rw-r--r--src/dhcp/nm-dhcp-manager.c61
-rw-r--r--src/dhcp/nm-dhcp-manager.h6
-rw-r--r--src/dhcp/nm-dhcp-systemd.c118
-rw-r--r--src/dhcp/nm-dhcp-utils.c2
-rw-r--r--src/dhcp/tests/meson.build1
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c157
14 files changed, 517 insertions, 250 deletions
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
index 289a16ca..76707bca 100644
--- a/src/dhcp/meson.build
+++ b/src/dhcp/meson.build
@@ -3,7 +3,6 @@ name = 'nm-dhcp-helper'
 cflags = [
   '-DG_LOG_DOMAIN="@0@"'.format(name),
   '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
-  '-DNMRUNDIR="@0@"'.format(nm_pkgrundir),
 ]
 
 executable(
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index e53c8d87..16db8306 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -445,7 +445,7 @@ transaction_timeout (gpointer user_data)
 }
 
 static void
-daemon_watch_cb (GPid pid, gint status, gpointer user_data)
+daemon_watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDhcpClient *self = NM_DHCP_CLIENT (user_data);
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
@@ -510,7 +510,8 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                           GBytes *client_id,
                           const char *dhcp_anycast_addr,
                           const char *hostname,
-                          const char *last_ip4_address)
+                          const char *last_ip4_address,
+                          GError **error)
 {
 	NMDhcpClientPrivate *priv;
 
@@ -531,7 +532,10 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 	g_clear_pointer (&priv->hostname, g_free);
 	priv->hostname = g_strdup (hostname);
 
-	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, dhcp_anycast_addr, last_ip4_address);
+	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self,
+	                                                   dhcp_anycast_addr,
+	                                                   last_ip4_address,
+	                                                   error);
 }
 
 static GBytes *
@@ -548,7 +552,8 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                           const struct in6_addr *ll_addr,
                           const char *hostname,
                           NMSettingIP6ConfigPrivacy privacy,
-                          guint needed_prefixes)
+                          guint needed_prefixes,
+                          GError **error)
 {
 	NMDhcpClientPrivate *priv;
 	gs_free char *str = NULL;
@@ -584,7 +589,8 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	                                                   ll_addr,
 	                                                   privacy,
 	                                                   priv->duid,
-	                                                   needed_prefixes);
+	                                                   needed_prefixes,
+	                                                   error);
 }
 
 void
@@ -734,7 +740,7 @@ maybe_add_option (NMDhcpClient *self,
 gboolean
 nm_dhcp_client_handle_event (gpointer unused,
                              const char *iface,
-                             gint pid,
+                             int pid,
                              GVariant *options,
                              const char *reason,
                              NMDhcpClient *self)
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 4c196045..86d60e38 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -76,18 +76,18 @@ typedef enum {
 typedef struct {
 	GObjectClass parent;
 
-	/* Methods */
-
 	gboolean (*ip4_start)     (NMDhcpClient *self,
 	                           const char *anycast_addr,
-	                           const char *last_ip4_address);
+	                           const char *last_ip4_address,
+	                           GError **error);
 
 	gboolean (*ip6_start)     (NMDhcpClient *self,
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
 	                           NMSettingIP6ConfigPrivacy privacy,
 	                           GBytes *duid,
-	                           guint needed_prefixes);
+	                           guint needed_prefixes,
+	                           GError **error);
 
 	void (*stop)              (NMDhcpClient *self,
 	                           gboolean release,
@@ -151,7 +151,8 @@ gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                                    GBytes *client_id,
                                    const char *dhcp_anycast_addr,
                                    const char *hostname,
-                                   const char *last_ip4_address);
+                                   const char *last_ip4_address,
+                                   GError **error);
 
 gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                                    GBytes *client_id,
@@ -160,7 +161,8 @@ gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                                    const struct in6_addr *ll_addr,
                                    const char *hostname,
                                    NMSettingIP6ConfigPrivacy privacy,
-                                   guint needed_prefixes);
+                                   guint needed_prefixes,
+                                   GError **error);
 
 void nm_dhcp_client_stop (NMDhcpClient *self, gboolean release);
 
@@ -180,7 +182,7 @@ void nm_dhcp_client_set_state (NMDhcpClient *self,
 
 gboolean nm_dhcp_client_handle_event (gpointer unused,
                                       const char *iface,
-                                      gint pid,
+                                      int pid,
                                       GVariant *options,
                                       const char *reason,
                                       NMDhcpClient *self);
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 3290dd65..a2c3bfb6 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -316,6 +316,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 	if (orig_contents) {
 		char **lines, **line;
+		int nest = 0;
 		gboolean in_alsoreq = FALSE;
 		gboolean in_req = FALSE;
 		char intf[IFNAMSIZ];
@@ -330,18 +331,24 @@ nm_dhcp_dhclient_create_config (const char *interface,
 			if (!strlen (g_strstrip (p)))
 				continue;
 
-			if (   !intf[0]
-			    && g_str_has_prefix (p, "interface")
-			    && !in_req) {
-				if (read_interface (p, intf, sizeof (intf)))
-					continue;
-			}
-
-			if (intf[0] && strchr (p, '}')) {
+			if (in_req) {
+				/* pass */
+			} else if (strchr (p, '{')) {
+				nest++;
+				if (   !intf[0]
+				    && g_str_has_prefix (p, "interface"))
+					if (read_interface (p, intf, sizeof (intf)))
+						continue;
+			} else if (strchr (p, '}')) {
+				if (nest)
+					nest--;
 				intf[0] = '\0';
 				continue;
 			}
 
+			if (nest && !intf[0])
+				continue;
+
 			if (intf[0] && !nm_streq (intf, interface))
 				continue;
 
@@ -437,6 +444,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		add_request (reqs, "static-routes");
 		add_request (reqs, "wpad");
 		add_request (reqs, "ntp-servers");
+		add_request (reqs, "root-path");
 	} else {
 		add_hostname6 (new_contents, hostname);
 		add_request (reqs, "dhcp6.name-servers");
@@ -523,6 +531,10 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 	guint i, len;
 	guint8 octal;
 
+	/* FIXME: it's wrong to have an "unescape-duid" function. dhclient
+	 * defines a file format with escaping. So we need a general unescape
+	 * function that can handle dhclient syntax. */
+
 	len = strlen (duid);
 	unescaped = g_byte_array_sized_new (len);
 	for (i = 0; i < len; i++) {
@@ -536,6 +548,9 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 				g_byte_array_append (unescaped, &octal, 1);
 				i += 2;
 			} else {
+				/* FIXME: don't warn on untrusted data. Either signal an error, or accept
+				 * it silently. */
+
 				/* One of ", ', $, `, \, |, or & */
 				g_warn_if_fail (p[i] == '"' || p[i] == '\'' || p[i] == '$' ||
 				                p[i] == '`' || p[i] == '\\' || p[i] == '|' ||
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index bf93c831..4a936178 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -41,6 +41,7 @@
 #include "nm-utils/nm-dedup-multi.h"
 
 #include "nm-utils.h"
+#include "nm-config.h"
 #include "nm-dhcp-dhclient-utils.h"
 #include "nm-dhcp-manager.h"
 #include "NetworkManagerUtils.h"
@@ -120,8 +121,20 @@ get_dhclient_leasefile (int addr_family,
                         const char *uuid,
                         char **out_preferred_path)
 {
+	char *rundir_path;
 	char *path;
 
+	/* First, see if the lease file is in /run */
+	rundir_path = g_strdup_printf (NMRUNDIR "/dhclient%s-%s-%s.lease",
+	                               _addr_family_to_path_part (addr_family),
+	                               uuid,
+	                               iface);
+
+	if (g_file_test (rundir_path, G_FILE_TEST_EXISTS)) {
+		NM_SET_OUT (out_preferred_path, g_strdup (rundir_path));
+		return rundir_path;
+	}
+
 	/* /var/lib/NetworkManager is the preferred leasefile path */
 	path = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s-%s.lease",
 	                        _addr_family_to_path_part (addr_family),
@@ -133,6 +146,12 @@ get_dhclient_leasefile (int addr_family,
 		return path;
 	}
 
+	if (nm_config_get_configure_and_quit (nm_config_get ()) == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) {
+		g_free (path);
+		path = rundir_path;
+	} else {
+		g_free (rundir_path);
+	}
 	NM_SET_OUT (out_preferred_path, g_steal_pointer (&path));
 
 	/* If the leasefile we're looking for doesn't exist yet in the new location
@@ -174,13 +193,14 @@ merge_dhclient_config (NMDhcpDhclient *self,
                        GBytes **out_new_client_id,
                        GError **error)
 {
-	char *orig = NULL, *new;
-	gboolean success = FALSE;
+	gs_free char *orig = NULL;
+	gs_free char *new = NULL;
 
-	g_return_val_if_fail (iface != NULL, FALSE);
-	g_return_val_if_fail (conf_file != NULL, FALSE);
+	g_return_val_if_fail (iface, FALSE);
+	g_return_val_if_fail (conf_file, FALSE);
 
-	if (orig_path && g_file_test (orig_path, G_FILE_TEST_EXISTS)) {
+	if (   orig_path
+	    && g_file_test (orig_path, G_FILE_TEST_EXISTS)) {
 		GError *read_error = NULL;
 
 		if (!g_file_get_contents (orig_path, &orig, NULL, &read_error)) {
@@ -190,14 +210,22 @@ merge_dhclient_config (NMDhcpDhclient *self,
 		}
 	}
 
-	new = nm_dhcp_dhclient_create_config (iface, addr_family, client_id, anycast_addr, hostname, timeout,
-	                                      use_fqdn, orig_path, orig, out_new_client_id);
+	new = nm_dhcp_dhclient_create_config (iface,
+	                                      addr_family,
+	                                      client_id,
+	                                      anycast_addr,
+	                                      hostname,
+	                                      timeout,
+	                                      use_fqdn,
+	                                      orig_path,
+	                                      orig,
+	                                      out_new_client_id);
 	g_assert (new);
-	success = g_file_set_contents (conf_file, new, -1, error);
-	g_free (new);
-	g_free (orig);
 
-	return success;
+	return g_file_set_contents (conf_file,
+	                            new,
+	                            -1,
+	                            error);
 }
 
 static char *
@@ -282,13 +310,14 @@ create_dhclient_config (NMDhcpDhclient *self,
                         gboolean use_fqdn,
                         GBytes **out_new_client_id)
 {
-	char *orig = NULL, *new = NULL;
+	gs_free char *orig = NULL;
+	char *new = NULL;
 	GError *error = NULL;
-	gboolean success = FALSE;
 
 	g_return_val_if_fail (iface != NULL, NULL);
 
 	new = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
+
 	_LOGD ("creating composite dhclient config %s", new);
 
 	orig = find_existing_config (self, addr_family, iface, uuid);
@@ -297,15 +326,12 @@ create_dhclient_config (NMDhcpDhclient *self,
 	else
 		_LOGD ("no existing dhclient configuration to merge");
 
-	error = NULL;
-	success = merge_dhclient_config (self, addr_family, iface, new, client_id, dhcp_anycast_addr,
-	                                 hostname, timeout, use_fqdn, orig, out_new_client_id, &error);
-	if (!success) {
+	if (!merge_dhclient_config (self, addr_family, iface, new, client_id, dhcp_anycast_addr,
+	                            hostname, timeout, use_fqdn, orig, out_new_client_id, &error)) {
 		_LOGW ("error creating dhclient configuration: %s", error->message);
-		g_error_free (error);
+		g_clear_error (&error);
 	}
 
-	g_free (orig);
 	return new;
 }
 
@@ -315,13 +341,14 @@ dhclient_start (NMDhcpClient *client,
                 GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
-                int prefixes)
+                int prefixes,
+                GError **error)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 	gs_unref_ptrarray GPtrArray *argv = NULL;
 	pid_t pid;
-	GError *error = NULL;
+	gs_free_error GError *local = NULL;
 	const char *iface;
 	const char *uuid;
 	const char *system_bus_address;
@@ -339,7 +366,7 @@ dhclient_start (NMDhcpClient *client,
 
 	dhclient_path = nm_dhcp_dhclient_get_path ();
 	if (!dhclient_path) {
-		_LOGW ("dhclient could not be found");
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhclient binary not found");
 		return FALSE;
 	}
 
@@ -371,11 +398,7 @@ dhclient_start (NMDhcpClient *client,
 		gs_unref_object GFile *dst = g_file_new_for_path (preferred_leasefile_path);
 
 		/* Try to copy the existing leasefile to the preferred location */
-		if (g_file_copy (src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
-			/* Success; use the preferred leasefile path */
-			g_free (priv->lease_file);
-			priv->lease_file = g_file_get_path (dst);
-		} else {
+		if (!g_file_copy (src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &local)) {
 			gs_free char *s_path = NULL;
 			gs_free char *d_path = NULL;
 
@@ -383,8 +406,12 @@ dhclient_start (NMDhcpClient *client,
 			_LOGW ("failed to copy leasefile %s to %s: %s",
 			       (s_path = g_file_get_path (src)),
 			       (d_path = g_file_get_path (dst)),
-			       error->message);
-			g_clear_error (&error);
+			       local->message);
+			g_clear_error (&local);
+		} else {
+			/* Success; use the preferred leasefile path */
+			g_free (priv->lease_file);
+			priv->lease_file = g_file_get_path (dst);
 		}
 	}
 
@@ -393,9 +420,12 @@ dhclient_start (NMDhcpClient *client,
 		gs_free char *escaped = NULL;
 
 		escaped = nm_dhcp_dhclient_escape_duid (duid);
-		if (!nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &error)) {
-			_LOGW ("failed to save DUID to %s: %s", priv->lease_file, error->message);
-			g_clear_error (&error);
+		if (!nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &local)) {
+			nm_utils_error_set (error,
+			                    NM_UTILS_ERROR_UNKNOWN,
+			                    "failed to save DUID to '%s': %s",
+			                    priv->lease_file,
+			                    local->message);
 			return FALSE;
 		}
 	}
@@ -451,13 +481,15 @@ dhclient_start (NMDhcpClient *client,
 	g_ptr_array_add (argv, NULL);
 
 	_LOGD ("running: %s",
-	       (cmd_str = g_strjoinv (" ", (gchar **) argv->pdata)));
+	       (cmd_str = g_strjoinv (" ", (char **) argv->pdata)));
 
 	if (!g_spawn_async (NULL, (char **) argv->pdata, NULL,
 	                    G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
-	                    nm_utils_setpgid, NULL, &pid, &error)) {
-		_LOGW ("dhclient failed to start: '%s'", error->message);
-		g_error_free (error);
+	                    nm_utils_setpgid, NULL, &pid, &local)) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "dhclient failed to start: %s",
+		                    local->message);
 		return FALSE;
 	}
 
@@ -473,36 +505,46 @@ dhclient_start (NMDhcpClient *client,
 }
 
 static gboolean
-ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+ip4_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const char *last_ip4_address,
+           GError **error)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 	GBytes *client_id;
 	gs_unref_bytes GBytes *new_client_id = NULL;
-	const char *iface, *uuid, *hostname;
-	guint32 timeout;
-	gboolean success = FALSE;
-	gboolean use_fqdn;
 
-	iface = nm_dhcp_client_get_iface (client);
-	uuid = nm_dhcp_client_get_uuid (client);
 	client_id = nm_dhcp_client_get_client_id (client);
-	hostname = nm_dhcp_client_get_hostname (client);
-	timeout = nm_dhcp_client_get_timeout (client);
-	use_fqdn = nm_dhcp_client_get_use_fqdn (client);
 
-	priv->conf_file = create_dhclient_config (self, AF_INET, iface, uuid, client_id, dhcp_anycast_addr,
-	                                          hostname, timeout, use_fqdn, &new_client_id);
-	if (priv->conf_file) {
-		if (new_client_id) {
-			nm_assert (!client_id);
-			nm_dhcp_client_set_client_id (client, new_client_id);
-		}
-		success = dhclient_start (client, NULL, NULL, FALSE, NULL, 0);
-	} else
-		_LOGW ("error creating dhclient configuration file");
+	priv->conf_file = create_dhclient_config (self,
+	                                          AF_INET,
+	                                          nm_dhcp_client_get_iface (client),
+	                                          nm_dhcp_client_get_uuid (client),
+	                                          client_id,
+	                                          dhcp_anycast_addr,
+	                                          nm_dhcp_client_get_hostname (client),
+	                                          nm_dhcp_client_get_timeout (client),
+	                                          nm_dhcp_client_get_use_fqdn (client),
+	                                          &new_client_id);
+	if (!priv->conf_file) {
+		nm_utils_error_set_literal (error,
+		                            NM_UTILS_ERROR_UNKNOWN,
+		                            "error creating dhclient configuration file");
+		return FALSE;
+	}
 
-	return success;
+	if (new_client_id) {
+		nm_assert (!client_id);
+		nm_dhcp_client_set_client_id (client, new_client_id);
+	}
+	return dhclient_start (client,
+	                       NULL,
+	                       NULL,
+	                       FALSE,
+	                       NULL,
+	                       0,
+	                       error);
 }
 
 static gboolean
@@ -511,22 +553,26 @@ ip6_start (NMDhcpClient *client,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
            GBytes *duid,
-           guint needed_prefixes)
+           guint needed_prefixes,
+           GError **error)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
-	const char *iface, *uuid, *hostname;
-	guint32 timeout;
-
-	iface = nm_dhcp_client_get_iface (client);
-	uuid = nm_dhcp_client_get_uuid (client);
-	hostname = nm_dhcp_client_get_hostname (client);
-	timeout = nm_dhcp_client_get_timeout (client);
 
-	priv->conf_file = create_dhclient_config (self, AF_INET6, iface, uuid, NULL, dhcp_anycast_addr,
-	                                          hostname, timeout, TRUE, NULL);
+	priv->conf_file = create_dhclient_config (self,
+	                                          AF_INET6,
+	                                          nm_dhcp_client_get_iface (client),
+	                                          nm_dhcp_client_get_uuid (client),
+	                                          NULL,
+	                                          dhcp_anycast_addr,
+	                                          nm_dhcp_client_get_hostname (client),
+	                                          nm_dhcp_client_get_timeout (client),
+	                                          TRUE,
+	                                          NULL);
 	if (!priv->conf_file) {
-		_LOGW ("error creating dhclient configuration file");
+		nm_utils_error_set_literal (error,
+		                            NM_UTILS_ERROR_UNKNOWN,
+		                            "error creating dhclient configuration file");
 		return FALSE;
 	}
 
@@ -534,7 +580,11 @@ ip6_start (NMDhcpClient *client,
 	                       nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self))
 	                         ? "-S"
 	                         : "-N",
-	                       duid, FALSE, NULL, needed_prefixes);
+	                       duid,
+	                       FALSE,
+	                       NULL,
+	                       needed_prefixes,
+	                       error);
 }
 
 static void
@@ -560,7 +610,13 @@ stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 	if (release) {
 		pid_t rpid = -1;
 
-		if (dhclient_start (client, NULL, duid, TRUE, &rpid, 0)) {
+		if (dhclient_start (client,
+		                    NULL,
+		                    duid,
+		                    TRUE,
+		                    &rpid,
+		                    0,
+		                    NULL)) {
 			/* Wait a few seconds for the release to happen */
 			nm_dhcp_client_stop_pid (rpid, nm_dhcp_client_get_iface (client));
 		}
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index 12aa57aa..de403020 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -82,28 +82,36 @@ dhcpcanon_start (NMDhcpClient *client,
                 GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
-                int prefixes)
+                guint needed_prefixes,
+                GError **error)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
-	GPtrArray *argv = NULL;
+	gs_unref_ptrarray GPtrArray *argv = NULL;
 	pid_t pid;
-	GError *error = NULL;
-	const char *iface, *system_bus_address, *dhcpcanon_path = NULL;
-	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
+	gs_free_error GError *local = NULL;
+	const char *iface;
+	const char *system_bus_address;
+	const char *dhcpcanon_path;
+	gs_free char *binary_name = NULL;
+	gs_free char *pid_file = NULL;
+	gs_free char *system_bus_address_env = NULL;
 	int addr_family;
 
-	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
+	g_return_val_if_fail (!priv->pid_file, FALSE);
 
 	iface = nm_dhcp_client_get_iface (client);
+
 	addr_family = nm_dhcp_client_get_addr_family (client);
+
 	dhcpcanon_path = nm_dhcp_dhcpcanon_get_path ();
-	_LOGD ("dhcpcanon_path: %s", dhcpcanon_path);
 	if (!dhcpcanon_path) {
-		_LOGW ("dhcpcanon could not be found");
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcanon binary not found");
 		return FALSE;
 	}
 
+	_LOGD ("dhcpcanon_path: %s", dhcpcanon_path);
+
 	pid_file = g_strdup_printf (RUNSTATEDIR "/dhcpcanon%c-%s.pid",
 	                            nm_utils_addr_family_to_char (addr_family),
 	                            iface);
@@ -112,7 +120,6 @@ dhcpcanon_start (NMDhcpClient *client,
 	/* Kill any existing dhcpcanon from the pidfile */
 	binary_name = g_path_get_basename (dhcpcanon_path);
 	nm_dhcp_client_stop_existing (pid_file, binary_name);
-	g_free (binary_name);
 
 	argv = g_ptr_array_new ();
 	g_ptr_array_add (argv, (gpointer) dhcpcanon_path);
@@ -120,10 +127,8 @@ dhcpcanon_start (NMDhcpClient *client,
 	g_ptr_array_add (argv, (gpointer) "-sf"); /* Set script file */
 	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
 
-	if (pid_file) {
-		g_ptr_array_add (argv, (gpointer) "-pf"); /* Set pid file */
-		g_ptr_array_add (argv, (gpointer) pid_file);
-	}
+	g_ptr_array_add (argv, (gpointer) "-pf"); /* Set pid file */
+	g_ptr_array_add (argv, (gpointer) pid_file);
 
 	if (priv->conf_file) {
 		g_ptr_array_add (argv, (gpointer) "-cf"); /* Set interface config file */
@@ -144,33 +149,43 @@ dhcpcanon_start (NMDhcpClient *client,
 	g_ptr_array_add (argv, (gpointer) iface);
 	g_ptr_array_add (argv, NULL);
 
-	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
-	g_free (cmd_str);
-
-	if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
-	                   G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
-	                   nm_utils_setpgid, NULL, &pid, &error)) {
-		 g_assert (pid > 0);
-		_LOGI ("dhcpcanon started with pid %d", pid);
-		nm_dhcp_client_watch_child (client, pid);
-		priv->pid_file = pid_file;
-	} else {
-		_LOGW ("dhcpcanon failed to start: '%s'", error->message);
-		g_error_free (error);
-		g_free (pid_file);
+	if (!g_spawn_async (NULL,
+	                   (char **) argv->pdata,
+	                   NULL,
+	                     G_SPAWN_DO_NOT_REAP_CHILD
+	                   | G_SPAWN_STDOUT_TO_DEV_NULL
+	                   | G_SPAWN_STDERR_TO_DEV_NULL,
+	                   nm_utils_setpgid,
+	                   NULL,
+	                   &pid,
+	                   &local)) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "dhcpcanon failed to start: %s",
+		                    local->message);
+		return FALSE;
 	}
 
-	g_ptr_array_free (argv, TRUE);
-	g_free (system_bus_address_env);
-	return pid > 0 ? TRUE : FALSE;
+	nm_assert (pid > 0);
+	_LOGI ("dhcpcanon started with pid %d", pid);
+	nm_dhcp_client_watch_child (client, pid);
+	priv->pid_file = g_steal_pointer (&pid_file);
+	return TRUE;
 }
 
 static gboolean
-ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+ip4_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const char *last_ip4_address,
+           GError **error)
 {
-	gboolean success = FALSE;
-	success = dhcpcanon_start (client, NULL, NULL, FALSE, NULL, 0);
-	return success;
+	return dhcpcanon_start (client,
+	                        NULL,
+	                        NULL,
+	                        FALSE,
+	                        NULL,
+	                        0,
+	                        error);
 }
 
 static gboolean
@@ -179,11 +194,10 @@ ip6_start (NMDhcpClient *client,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
            GBytes *duid,
-           guint needed_prefixes)
+           guint needed_prefixes,
+           GError **error)
 {
-	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
-
-	_LOGW ("the dhcpcd backend does not support IPv6");
+	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcanon plugin does not support IPv6");
 	return FALSE;
 }
 static void
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index c4bcb084..98ab5342 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -81,15 +81,21 @@ nm_dhcp_dhcpcd_get_path (void)
 }
 
 static gboolean
-ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+ip4_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const char *last_ip4_address,
+           GError **error)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
 	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
-	GPtrArray *argv = NULL;
+	gs_unref_ptrarray GPtrArray *argv = NULL;
 	pid_t pid = -1;
-	GError *error = NULL;
-	char *pid_contents = NULL, *binary_name, *cmd_str;
-	const char *iface, *dhcpcd_path, *hostname;
+	GError *local = NULL;
+	gs_free char *cmd_str = NULL;
+	gs_free char *binary_name = NULL;
+	const char *iface;
+	const char *dhcpcd_path;
+	const char *hostname;
 
 	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
 
@@ -102,14 +108,13 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 
 	dhcpcd_path = nm_dhcp_dhcpcd_get_path ();
 	if (!dhcpcd_path) {
-		_LOGW ("dhcpcd could not be found");
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcd binary not found");
 		return FALSE;
 	}
 
 	/* Kill any existing dhcpcd from the pidfile */
 	binary_name = g_path_get_basename (dhcpcd_path);
 	nm_dhcp_client_stop_existing (priv->pid_file, binary_name);
-	g_free (binary_name);
 
 	argv = g_ptr_array_new ();
 	g_ptr_array_add (argv, (gpointer) dhcpcd_path);
@@ -128,13 +133,11 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	g_ptr_array_add (argv, (gpointer) "-c");    /* Set script file */
 	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
 
-#ifdef DHCPCD_SUPPORTS_IPV6
 	/* IPv4-only for now.  NetworkManager knows better than dhcpcd when to
 	 * run IPv6, and dhcpcd's automatic Router Solicitations cause problems
 	 * with devices that don't expect them.
 	 */
 	g_ptr_array_add (argv, (gpointer) "-4");
-#endif
 
 	hostname = nm_dhcp_client_get_hostname (client);
 
@@ -153,24 +156,30 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	g_ptr_array_add (argv, (gpointer) iface);
 	g_ptr_array_add (argv, NULL);
 
-	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
-	_LOGD ("running: %s", cmd_str);
-	g_free (cmd_str);
-
-	if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
-	                   G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
-	                   nm_utils_setpgid, NULL, &pid, &error)) {
-		g_assert (pid > 0);
-		_LOGI ("dhcpcd started with pid %d", pid);
-		nm_dhcp_client_watch_child (client, pid);
-	} else {
-		_LOGW ("dhcpcd failed to start, error: '%s'", error->message);
-		g_error_free (error);
+	_LOGD ("running: %s",
+	       (cmd_str = g_strjoinv (" ", (char **) argv->pdata)));
+
+	if (!g_spawn_async (NULL,
+	                    (char **) argv->pdata, NULL,
+	                      G_SPAWN_DO_NOT_REAP_CHILD
+	                    | G_SPAWN_STDOUT_TO_DEV_NULL
+	                    | G_SPAWN_STDERR_TO_DEV_NULL,
+	                    nm_utils_setpgid,
+	                    NULL,
+	                    &pid,
+	                    &local)) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "dhcpcd failed to start: %s",
+		                    local->message);
+		g_error_free (local);
+		return FALSE;
 	}
 
-	g_free (pid_contents);
-	g_ptr_array_free (argv, TRUE);
-	return pid > 0 ? TRUE : FALSE;
+	nm_assert (pid > 0);
+	_LOGI ("dhcpcd started with pid %d", pid);
+	nm_dhcp_client_watch_child (client, pid);
+	return TRUE;
 }
 
 static gboolean
@@ -179,11 +188,10 @@ ip6_start (NMDhcpClient *client,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
            GBytes *duid,
-           guint needed_prefixes)
+           guint needed_prefixes,
+           GError **error)
 {
-	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
-
-	_LOGW ("the dhcpcd backend does not support IPv6");
+	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcd plugin does not support IPv6");
 	return FALSE;
 }
 
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 8ea55061..7f1d2a7b 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -31,7 +31,7 @@
 
 /*****************************************************************************/
 
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 #define _NMLOG_ENABLED(level) TRUE
 #else
 #define _NMLOG_ENABLED(level) ((level) <= LOG_ERR)
@@ -118,7 +118,7 @@ kill_pid (void)
 	if (pid_str)
 		pid = strtol (pid_str, NULL, 10);
 	if (pid) {
-		_LOGI ("a fatal error occured, kill dhclient instance with pid %d\n", pid);
+		_LOGI ("a fatal error occurred, kill dhclient instance with pid %d", pid);
 		kill (pid, SIGTERM);
 	}
 }
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index 67254323..5ae16d72 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -172,22 +172,39 @@ client_start (NMDhcpManager *self,
               gboolean info_only,
               NMSettingIP6ConfigPrivacy privacy,
               const char *last_ip4_address,
-              guint needed_prefixes)
+              guint needed_prefixes,
+              GError **error)
 {
 	NMDhcpManagerPrivate *priv;
 	NMDhcpClient *client;
 	gboolean success = FALSE;
+	gsize hwaddr_len;
 
-	g_return_val_if_fail (self, NULL);
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
 	g_return_val_if_fail (ifindex > 0, NULL);
 	g_return_val_if_fail (uuid != NULL, NULL);
 	g_return_val_if_fail (!dhcp_client_id || g_bytes_get_size (dhcp_client_id) >= 2, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	if (!hwaddr) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "missing MAC address");
+		return NULL;
+	}
+
+	hwaddr_len = g_bytes_get_size (hwaddr);
+	if (   hwaddr_len == 0
+	    || hwaddr_len > NM_UTILS_HWADDR_LEN_MAX) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "invalid MAC address");
+		g_return_val_if_reached (NULL) ;
+	}
 
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 
-	if (!priv->client_factory)
-		return NULL;
+	nm_assert (priv->client_factory);
 
 	/* Kill any old client instance */
 	client = get_client_for_ifindex (self, addr_family, ifindex);
@@ -216,10 +233,24 @@ client_start (NMDhcpManager *self,
 	c_list_link_tail (&priv->dhcp_client_lst_head, &client->dhcp_client_lst);
 	g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self);
 
-	if (addr_family == AF_INET)
-		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, last_ip4_address);
-	else
-		success = nm_dhcp_client_start_ip6 (client, dhcp_client_id, enforce_duid, dhcp_anycast_addr, ipv6_ll_addr, hostname, privacy, needed_prefixes);
+	if (addr_family == AF_INET) {
+		success = nm_dhcp_client_start_ip4 (client,
+		                                    dhcp_client_id,
+		                                    dhcp_anycast_addr,
+		                                    hostname,
+		                                    last_ip4_address,
+		                                    error);
+	} else {
+		success = nm_dhcp_client_start_ip6 (client,
+		                                    dhcp_client_id,
+		                                    enforce_duid,
+		                                    dhcp_anycast_addr,
+		                                    ipv6_ll_addr,
+		                                    hostname,
+		                                    privacy,
+		                                    needed_prefixes,
+		                                    error);
+	}
 
 	if (!success) {
 		remove_client_unref (self, client);
@@ -245,7 +276,8 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            GBytes *dhcp_client_id,
                            guint32 timeout,
                            const char *dhcp_anycast_addr,
-                           const char *last_ip_address)
+                           const char *last_ip_address,
+                           GError **error)
 {
 	NMDhcpManagerPrivate *priv;
 	const char *hostname = NULL;
@@ -282,7 +314,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 	return client_start (self, AF_INET, multi_idx, iface, ifindex, hwaddr, uuid,
 	                     route_table, route_metric, NULL,
 	                     dhcp_client_id, 0, timeout, dhcp_anycast_addr, hostname,
-	                     use_fqdn, FALSE, 0, last_ip_address, 0);
+	                     use_fqdn, FALSE, 0, last_ip_address, 0, error);
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -304,7 +336,8 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            const char *dhcp_anycast_addr,
                            gboolean info_only,
                            NMSettingIP6ConfigPrivacy privacy,
-                           guint needed_prefixes)
+                           guint needed_prefixes,
+                           GError **error)
 {
 	NMDhcpManagerPrivate *priv;
 	const char *hostname = NULL;
@@ -319,7 +352,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 	return client_start (self, AF_INET6, multi_idx, iface, ifindex, hwaddr, uuid,
 	                     route_table, route_metric, ll_addr, duid, enforce_duid,
 	                     timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
-	                     privacy, NULL, needed_prefixes);
+	                     privacy, NULL, needed_prefixes, error);
 }
 
 void
@@ -380,7 +413,7 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 	                                        NM_CONFIG_KEYFILE_KEY_MAIN_DHCP,
 	                                        NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
 	client = client_free;
-	if (nm_config_get_configure_and_quit (config)) {
+	if (nm_config_get_configure_and_quit (config) == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) {
 		client_factory = &_nm_dhcp_client_factory_internal;
 		if (client && !nm_streq (client, client_factory->name))
 			nm_log_info (LOGD_DHCP, "dhcp-init: Using internal DHCP client since configure-and-quit is set.");
@@ -409,7 +442,7 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 		}
 	}
 
-	nm_assert (client_factory);
+	g_return_if_fail (client_factory);
 
 	nm_log_info (LOGD_DHCP, "dhcp-init: Using DHCP client '%s'", client_factory->name);
 
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index 7eb32c37..1d9e5c21 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -59,7 +59,8 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               GBytes *dhcp_client_id,
                                               guint32 timeout,
                                               const char *dhcp_anycast_addr,
-                                              const char *last_ip_address);
+                                              const char *last_ip_address,
+                                              GError **error);
 
 NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               struct _NMDedupMultiIndex *multi_idx,
@@ -78,7 +79,8 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               const char *dhcp_anycast_addr,
                                               gboolean info_only,
                                               NMSettingIP6ConfigPrivacy privacy,
-                                              guint needed_prefixes);
+                                              guint needed_prefixes,
+                                              GError **error);
 
 /* For testing only */
 extern const char* nm_dhcp_helper_path;
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 2d0202bb..5b7b5fbe 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -32,6 +32,7 @@
 #include "nm-utils/unaligned.h"
 
 #include "nm-utils.h"
+#include "nm-config.h"
 #include "nm-dhcp-utils.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
@@ -121,6 +122,7 @@ static const ReqOption dhcp4_requests[] = {
 	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         REQPREFIX "rfc3442_classless_static_routes", TRUE },
 	{ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, REQPREFIX "ms_classless_static_routes",      TRUE },
 	{ SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    REQPREFIX "wpad",                            TRUE },
+	{ SD_DHCP_OPTION_ROOT_PATH,                      REQPREFIX "root_path",                       TRUE },
 
 	/* Internal values */
 	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          REQPREFIX "expiry",                          FALSE },
@@ -432,6 +434,13 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
 	}
 
+	/* Root path */
+	r = sd_dhcp_lease_get_root_path (lease, &s);
+	if (r >= 0) {
+		LOG_LEASE (LOGD_DHCP4, "root path '%s'", s);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROOT_PATH, s);
+	}
+
 	r = sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len);
 	if (r >= 0)
 		metered = !!memmem (data, data_len, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
@@ -445,10 +454,30 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 static char *
 get_leasefile_path (int addr_family, const char *iface, const char *uuid)
 {
-	return g_strdup_printf (NMSTATEDIR "/internal%s-%s-%s.lease",
-	                        addr_family == AF_INET6 ? "6" : "",
-	                        uuid,
-	                        iface);
+	char *rundir_path;
+	char *statedir_path;
+
+	rundir_path = g_strdup_printf (NMRUNDIR "/internal%s-%s-%s.lease",
+	                               addr_family == AF_INET6 ? "6" : "",
+	                               uuid,
+	                               iface);
+
+	if (g_file_test (rundir_path, G_FILE_TEST_EXISTS))
+		return rundir_path;
+
+	statedir_path = g_strdup_printf (NMSTATEDIR "/internal%s-%s-%s.lease",
+	                                 addr_family == AF_INET6 ? "6" : "",
+	                                 uuid,
+	                                 iface);
+
+	if (   g_file_test (statedir_path, G_FILE_TEST_EXISTS)
+	    || nm_config_get_configure_and_quit (nm_config_get ()) != NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) {
+		g_free (rundir_path);
+		return statedir_path;
+	} else {
+		g_free (statedir_path);
+		return rundir_path;
+	}
 }
 
 /*****************************************************************************/
@@ -567,7 +596,10 @@ get_arp_type (GBytes *hwaddr)
 }
 
 static gboolean
-ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+ip4_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const char *last_ip4_address,
+           GError **error)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
@@ -590,7 +622,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 
 	r = sd_dhcp_client_new (&priv->client4, FALSE);
 	if (r < 0) {
-		_LOGW ("failed to create client (%d)", r);
+		nm_utils_error_set_errno (error, r, "failed to create dhcp-client: %s");
 		return FALSE;
 	}
 
@@ -598,8 +630,8 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 
 	r = sd_dhcp_client_attach_event (priv->client4, NULL, 0);
 	if (r < 0) {
-		_LOGW ("failed to attach event (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to attach event: %s");
+		goto errout;
 	}
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
@@ -613,21 +645,21 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 		                            len,
 		                            get_arp_type (hwaddr));
 		if (r < 0) {
-			_LOGW ("failed to set MAC address (%d)", r);
-			goto error;
+			nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
+			goto errout;
 		}
 	}
 
 	r = sd_dhcp_client_set_ifindex (priv->client4, nm_dhcp_client_get_ifindex (client));
 	if (r < 0) {
-		_LOGW ("failed to set ififindex (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
+		goto errout;
 	}
 
 	r = sd_dhcp_client_set_callback (priv->client4, dhcp_event_cb, client);
 	if (r < 0) {
-		_LOGW ("failed to set callback (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
+		goto errout;
 	}
 
 	dhcp_lease_load (&lease, priv->lease_file);
@@ -640,8 +672,8 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	if (last_addr.s_addr) {
 		r = sd_dhcp_client_set_request_address (priv->client4, &last_addr);
 		if (r < 0) {
-			_LOGW ("failed to set last IPv4 address (%d)", r);
-			goto error;
+			nm_utils_error_set_errno (error, r, "failed to set last IPv4 address: %s");
+			goto errout;
 		}
 	}
 
@@ -681,25 +713,25 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 		 */
 		r = sd_dhcp_client_set_hostname (priv->client4, hostname);
 		if (r < 0) {
-			_LOGW ("failed to set DHCP hostname to '%s' (%d)", hostname, r);
-			goto error;
+			nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
+			goto errout;
 		}
 	}
 
 	r = sd_dhcp_client_start (priv->client4);
 	if (r < 0) {
-		_LOGW ("failed to start client (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to start DHCP client: %s");
+		goto errout;
 	}
 
 	nm_dhcp_client_start_timeout (client);
 
 	success = TRUE;
 
-error:
+errout:
 	sd_dhcp_lease_unref (lease);
 	if (!success)
-		priv->client4 = sd_dhcp_client_unref (priv->client4);
+		sd_dhcp_client_unref (g_steal_pointer (&priv->client4));
 	return success;
 }
 
@@ -864,7 +896,8 @@ ip6_start (NMDhcpClient *client,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
            GBytes *duid,
-           guint needed_prefixes)
+           guint needed_prefixes,
+           GError **error)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
@@ -888,12 +921,12 @@ ip6_start (NMDhcpClient *client,
 
 	r = sd_dhcp6_client_new (&priv->client6);
 	if (r < 0) {
-		_LOGW ("failed to create client (%d)", r);
+		nm_utils_error_set_errno (error, r, "failed to create dhcp-client: %s");
 		return FALSE;
 	}
 
 	if (needed_prefixes > 0) {
-		_LOGW ("dhcp-client6: prefix delegation not yet supported, won't supply %d prefixes\n",
+		_LOGW ("dhcp-client6: prefix delegation not yet supported, won't supply %d prefixes",
 		       needed_prefixes);
 	}
 
@@ -907,14 +940,14 @@ ip6_start (NMDhcpClient *client,
 	                              &duid_arr[2],
 	                              duid_len - 2);
 	if (r < 0) {
-		_LOGW ("failed to set DUID (%d)", r);
+		nm_utils_error_set_errno (error, r, "failed to set DUID: %s");
 		return FALSE;
 	}
 
 	r = sd_dhcp6_client_attach_event (priv->client6, NULL, 0);
 	if (r < 0) {
-		_LOGW ("failed to attach event (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to attach event: %s");
+		goto errout;
 	}
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
@@ -928,21 +961,21 @@ ip6_start (NMDhcpClient *client,
 		                             len,
 		                             get_arp_type (hwaddr));
 		if (r < 0) {
-			_LOGW ("failed to set MAC address (%d)", r);
-			goto error;
+			nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
+			goto errout;
 		}
 	}
 
 	r = sd_dhcp6_client_set_ifindex (priv->client6, nm_dhcp_client_get_ifindex (client));
 	if (r < 0) {
-		_LOGW ("failed to set ifindex (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
+		goto errout;
 	}
 
 	r = sd_dhcp6_client_set_callback (priv->client6, dhcp6_event_cb, client);
 	if (r < 0) {
-		_LOGW ("failed to set callback (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
+		goto errout;
 	}
 
 	/* Add requested options */
@@ -953,30 +986,29 @@ ip6_start (NMDhcpClient *client,
 
 	r = sd_dhcp6_client_set_local_address (priv->client6, ll_addr);
 	if (r < 0) {
-		_LOGW ("failed to set local address (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to set local address: %s");
+		goto errout;
 	}
 
 	hostname = nm_dhcp_client_get_hostname (client);
 	r = sd_dhcp6_client_set_fqdn (priv->client6, hostname);
 	if (r < 0) {
-		_LOGW ("failed to set DHCP hostname to '%s' (%d)", hostname, r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
+		goto errout;
 	}
 
 	r = sd_dhcp6_client_start (priv->client6);
 	if (r < 0) {
-		_LOGW ("failed to start client (%d)", r);
-		goto error;
+		nm_utils_error_set_errno (error, r, "failed to start client: %s");
+		goto errout;
 	}
 
 	nm_dhcp_client_start_timeout (client);
 
 	return TRUE;
 
-error:
-	sd_dhcp6_client_unref (priv->client6);
-	priv->client6 = NULL;
+errout:
+	sd_dhcp6_client_unref (g_steal_pointer (&priv->client6));
 	return FALSE;
 }
 
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 9185a135..6bbc670b 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -594,7 +594,7 @@ ip6_add_domain_search (gpointer data, gpointer user_data)
 NMPlatformIP6Address
 nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options)
 {
-	gs_strfreev gchar **split_addr = NULL;
+	gs_strfreev char **split_addr = NULL;
 	NMPlatformIP6Address address = { 0, };
 	struct in6_addr tmp_addr;
 	char *str = NULL;
diff --git a/src/dhcp/tests/meson.build b/src/dhcp/tests/meson.build
index 9d8be427..0fee26b2 100644
--- a/src/dhcp/tests/meson.build
+++ b/src/dhcp/tests/meson.build
@@ -8,7 +8,6 @@ foreach test_unit: test_units
     test_unit,
     test_unit + '.c',
     dependencies: test_nm_dep,
-    c_args: nm_build_cflags,
   )
 
   test(
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 2f369aac..edac4257 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -86,7 +86,7 @@ test_config (const char *orig,
 	if (expected_new_client_id) {
 		g_assert (new_client_id);
 		g_assert (g_bytes_equal (new_client_id, expected_new_client_id));
-	 } else
+	} else
 		g_assert (new_client_id == NULL);
 }
 
@@ -104,6 +104,7 @@ static const char *orig_missing_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -132,6 +133,7 @@ static const char *override_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -161,6 +163,7 @@ static const char *quote_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -190,6 +193,7 @@ static const char *quote_client_id_expected_2 = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -219,6 +223,7 @@ static const char *hex_zero_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -248,6 +253,7 @@ static const char *ascii_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -277,6 +283,7 @@ static const char *hex_single_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -310,6 +317,7 @@ static const char *existing_hex_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -347,6 +355,7 @@ static const char *existing_escaped_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -385,6 +394,7 @@ static const char *existing_ascii_client_id_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -419,7 +429,8 @@ static const char *fqdn_expected = \
 	"also request ms-classless-static-routes;\n"
 	"also request static-routes;\n"
 	"also request wpad;\n"
-	"also request ntp-servers;\n\n";
+	"also request ntp-servers;\n"
+	"also request root-path;\n\n";
 
 static void
 test_fqdn (void)
@@ -455,6 +466,7 @@ static const char *fqdn_options_override_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n"
 	"# FQDN options from /path/to/dhclient.conf\n"
 	"send fqdn.encoded off;\n"
@@ -492,6 +504,7 @@ static const char *override_hostname_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -580,6 +593,7 @@ static const char *existing_alsoreq_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -618,6 +632,7 @@ static const char *existing_req_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -657,6 +672,7 @@ static const char *existing_multiline_alsoreq_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -675,27 +691,18 @@ test_existing_multiline_alsoreq (void)
 static void
 test_one_duid (const char *escaped, const guint8 *unescaped, guint len)
 {
-	GBytes *t;
-	char *w;
-	gsize t_len;
-	gconstpointer t_arr;
-
-	t = nm_dhcp_dhclient_unescape_duid (escaped);
-	g_assert (t);
-	t_arr = g_bytes_get_data (t, &t_len);
-	g_assert (t_arr);
-	g_assert_cmpint (t_len, ==, len);
-	g_assert_cmpint (memcmp (t_arr, unescaped, len), ==, 0);
-	g_bytes_unref (t);
-
-	t = g_bytes_new_static (unescaped, len);
-	w = nm_dhcp_dhclient_escape_duid (t);
+	gs_unref_bytes GBytes *t1 = NULL;
+	gs_unref_bytes GBytes *t2 = NULL;
+	gs_free char *w = NULL;
+
+	t1 = nm_dhcp_dhclient_unescape_duid (escaped);
+	g_assert (t1);
+	g_assert (nm_utils_gbytes_equal_mem (t1, unescaped, len));
+
+	t2 = g_bytes_new (unescaped, len);
+	w = nm_dhcp_dhclient_escape_duid (t2);
 	g_assert (w);
-	g_assert_cmpint (strlen (escaped), ==, strlen (w));
 	g_assert_cmpstr (escaped, ==, w);
-
-	g_bytes_unref (t);
-	g_free (w);
 }
 
 static void
@@ -735,15 +742,11 @@ test_read_duid_from_leasefile (void)
 	                            0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70 };
 	gs_unref_bytes GBytes *duid = NULL;
 	GError *error = NULL;
-	gconstpointer duid_arr;
-	gsize duid_len;
 
 	duid = nm_dhcp_dhclient_read_duid (TEST_DIR"/test-dhclient-duid.leases", &error);
-	g_assert_no_error (error);
-	g_assert (duid);
-	duid_arr = g_bytes_get_data (duid, &duid_len);
-	g_assert_cmpint (duid_len, ==, sizeof (expected));
-	g_assert_cmpint (memcmp (duid_arr, expected, duid_len), ==, 0);
+	nmtst_assert_success (duid, error);
+
+	g_assert (nm_utils_gbytes_equal_mem (duid, expected, G_N_ELEMENTS (expected)));
 }
 
 static void
@@ -903,6 +906,7 @@ static const char *interface1_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -948,6 +952,7 @@ static const char *interface2_expected = \
 	"also request static-routes;\n"
 	"also request wpad;\n"
 	"also request ntp-servers;\n"
+	"also request root-path;\n"
 	"\n";
 
 static void
@@ -962,6 +967,100 @@ test_interface2 (void)
 }
 
 static void
+test_structured (void)
+{
+	gs_unref_bytes GBytes *new_client_id = NULL;
+	const guint8 bytes[] = "sad-and-useless";
+
+	static const char *const orig = \
+		"interface \"eth0\"   {  \n"
+		"    send host-name \"useless.example.com\";\n"
+		"    hardware ethernet de:ad:80:86:ba:be;\n"
+		"    send dhcp-client-identifier \"sad-and-useless\";\n"
+		"    script \"/bin/useless\";\n"
+		"    send dhcp-lease-time 8086;\n"
+		"    request subnet-mask, broadcast-address, time-offset, routers,\n"
+		"        domain-search, domain-name, host-name;\n"
+		"    require subnet-mask;\n"
+		"}  \n"
+		"\n"
+		"    interface \"eth1\"   {  \n"
+		"    send host-name \"sad.example.com\";\n"
+		"    hardware ethernet de:ca:f6:66:ca:fe;\n"
+		"    send dhcp-client-identifier \"useless-and-miserable\";\n"
+		"    script \"/bin/miserable\";\n"
+		"    send dhcp-lease-time 1337;\n"
+		"    request subnet-mask, broadcast-address, time-offset, routers,\n"
+		"        domain-search, domain-name, domain-name-servers, host-name;\n"
+		"    require subnet-mask, domain-name-servers;\n"
+		"    }  \n"
+		"\n"
+		"pseudo \"secondary\" \"eth0\"   {  \n"
+		"    send dhcp-client-identifier \"sad-useless-and-secondary\";\n"
+		"    script \"/bin/secondary\";\n"
+		"    send host-name \"secondary.useless.example.com\";\n"
+		"    send dhcp-lease-time 666;\n"
+		"    request routers;\n"
+		"    require routers;\n"
+		"    }  \n"
+		"\n"
+		"    pseudo \"tertiary\" \"eth0\"   {  \n"
+		"   send dhcp-client-identifier \"sad-useless-and-tertiary\";\n"
+		"  script \"/bin/tertiary\";\n"
+		" send host-name \"tertiary.useless.example.com\";\n"
+		"}  \n"
+		"\n"
+		"  alias{  \n"
+		"    interface \"eth0\";\n"
+		"    fixed-address 192.0.2.1;\n"
+		"    option subnet-mask 255.255.255.0;\n"
+		"  }  \n"
+		"  lease   {  \n"
+		"    interface \"eth0\";\n"
+		"    fixed-address 192.0.2.2;\n"
+		"    option subnet-mask 255.255.255.0;\n"
+		"  }  \n";
+
+	static const char *const expected = \
+		"# Created by NetworkManager\n"
+		"# Merged from /path/to/dhclient.conf\n"
+		"\n"
+		"send host-name \"useless.example.com\";\n"
+		"hardware ethernet de:ad:80:86:ba:be;\n"
+		"send dhcp-client-identifier \"sad-and-useless\";\n"
+		"send dhcp-lease-time 8086;\n"
+		"require subnet-mask;\n"
+		"\n"
+		"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+		"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+		"option wpad code 252 = string;\n"
+		"\n"
+		"request; # override dhclient defaults\n"
+		"also request subnet-mask;\n"
+		"also request broadcast-address;\n"
+		"also request time-offset;\n"
+		"also request routers;\n"
+		"also request domain-search;\n"
+		"also request domain-name;\n"
+		"also request host-name;\n"
+		"also request rfc3442-classless-static-routes;\n"
+		"also request ms-classless-static-routes;\n"
+		"also request static-routes;\n"
+		"also request wpad;\n"
+		"also request ntp-servers;\n"
+		"also request root-path;\n"
+		"\n";
+
+	new_client_id = g_bytes_new (bytes, sizeof (bytes) - 1);
+	test_config (orig, expected,
+	             AF_INET, NULL, 0, FALSE,
+	             NULL,
+	             new_client_id,
+	             "eth0",
+	             NULL);
+}
+
+static void
 test_config_req_intf (void)
 {
 	static const char *const orig = \
@@ -1005,6 +1104,7 @@ test_config_req_intf (void)
 		"also request ms-classless-static-routes;\n"
 		"also request static-routes;\n"
 		"also request wpad;\n"
+		"also request root-path;\n"
 		"\n";
 
 	test_config (orig, expected,
@@ -1046,6 +1146,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/dhcp/dhclient/interface/1", test_interface1);
 	g_test_add_func ("/dhcp/dhclient/interface/2", test_interface2);
 	g_test_add_func ("/dhcp/dhclient/config/req_intf", test_config_req_intf);
+	g_test_add_func ("/dhcp/dhclient/structured", test_structured);
 
 	g_test_add_func ("/dhcp/dhclient/read_duid_from_leasefile", test_read_duid_from_leasefile);
 	g_test_add_func ("/dhcp/dhclient/read_commented_duid_from_leasefile", test_read_commented_duid_from_leasefile);