about summary refs log tree commit diff
path: root/src/dhcp/nm-dhcp-nettools.c
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2020-01-13 16:10:30 +0100
committerSebastien Bacher <seb128@ubuntu.com>2020-01-13 16:31:40 +0100
commit5a09f7759860f4f2a9bb01471ca8099cd705bc10 (patch)
tree3a9cffc9622d53c9196771a1c7924bceb754f118 /src/dhcp/nm-dhcp-nettools.c
parent25691220fd27093630cf244e219b2f4f1f4e749e (diff)
parentca847639aab94434daed120c874e1100c3d75dcf (diff)
Merge remote-tracking branch 'salsa/debian/master'
Diffstat (limited to 'src/dhcp/nm-dhcp-nettools.c')
-rw-r--r--src/dhcp/nm-dhcp-nettools.c451
1 files changed, 301 insertions, 150 deletions
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/dhcp/nm-dhcp-nettools.c
index 6140a916..1f76c64c 100644
--- a/src/dhcp/nm-dhcp-nettools.c
+++ b/src/dhcp/nm-dhcp-nettools.c
@@ -1,19 +1,6 @@
+// SPDX-License-Identifier: LGPL-2.1+
 /*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Library General Public License as
- * published by the Free Software Foundation; either version 2 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2014-2019 Red Hat, Inc.
+ * Copyright (C) 2014 - 2019 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -42,6 +29,7 @@
 #include "nm-dhcp-client-logging.h"
 #include "n-dhcp4/src/n-dhcp4.h"
 #include "systemd/nm-sd-utils-shared.h"
+#include "systemd/nm-sd-utils-dhcp.h"
 
 /*****************************************************************************/
 
@@ -55,8 +43,6 @@
 typedef struct _NMDhcpNettools NMDhcpNettools;
 typedef struct _NMDhcpNettoolsClass NMDhcpNettoolsClass;
 
-static GType nm_dhcp_nettools_get_type (void);
-
 /*****************************************************************************/
 
 typedef struct {
@@ -65,6 +51,7 @@ typedef struct {
 	NDhcp4ClientLease *lease;
 	GIOChannel *channel;
 	guint event_id;
+	char *lease_file;
 } NMDhcpNettoolsPrivate;
 
 struct _NMDhcpNettools {
@@ -85,13 +72,6 @@ G_DEFINE_TYPE (NMDhcpNettools, nm_dhcp_nettools, NM_TYPE_DHCP_CLIENT)
 #define DHCP_MAX_FQDN_LENGTH 255
 
 enum {
-	DHCP_FQDN_FLAG_S = (1 << 0),
-	DHCP_FQDN_FLAG_O = (1 << 1),
-	DHCP_FQDN_FLAG_E = (1 << 2),
-	DHCP_FQDN_FLAG_N = (1 << 3),
-};
-
-enum {
 	NM_IN_ADDR_CLASS_A,
 	NM_IN_ADDR_CLASS_B,
 	NM_IN_ADDR_CLASS_C,
@@ -148,6 +128,7 @@ lease_option_next_route (struct in_addr *destp,
 	uint8_t *data = *datap;
 	size_t n_data = *n_datap;
 	uint8_t plen;
+	uint8_t bytes;
 
 	if (classless) {
 		if (!lease_option_consume (&plen, sizeof (plen), &data, &n_data))
@@ -156,7 +137,9 @@ lease_option_next_route (struct in_addr *destp,
 		if (plen > 32)
 			return FALSE;
 
-		if (!lease_option_consume (&dest, plen / 8, &data, &n_data))
+		bytes = plen == 0 ? 0 : ((plen - 1) / 8) + 1;
+
+		if (!lease_option_consume (&dest, bytes, &data, &n_data))
 			return FALSE;
 	} else {
 		if (!lease_option_next_in_addr (&dest, &data, &n_data))
@@ -266,10 +249,10 @@ lease_option_print_domain_name (GString *str, uint8_t *cache, size_t *n_cachep,
 				return TRUE;
 			}
 
-			if (!first) {
+			if (!first)
 				g_string_append_c(str, '.');
+			else
 				first = FALSE;
-			}
 
 			if (!lease_option_print_label (str, n_label, domainp, n_domainp))
 				return FALSE;
@@ -354,27 +337,21 @@ lease_get_u16 (NDhcp4ClientLease *lease,
 	return TRUE;
 }
 
-#define LOG_LEASE(domain, ...) \
-    G_STMT_START { \
-        _LOG2I ((domain), (iface), "  "__VA_ARGS__); \
-    } G_STMT_END
-
 static gboolean
 lease_parse_address (NDhcp4ClientLease *lease,
-                     const char *iface,
                      NMIP4Config *ip4_config,
                      GHashTable *options,
                      GError **error)
 {
 	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
-	const gint64 ts = nm_utils_get_monotonic_timestamp_ns ();
-	const gint64 ts_clock_boottime = nm_utils_monotonic_timestamp_as_boottime (ts, 1);
 	struct in_addr a_address;
 	struct in_addr a_netmask;
+	struct in_addr a_next_server;
 	guint32 a_plen;
 	guint64 nettools_lifetime;
-	gint64 a_lifetime;
-	gint64 a_expiry;
+	guint32 a_lifetime;
+	guint32 a_timestamp;
+	guint64 a_expiry;
 
 	n_dhcp4_client_lease_get_yiaddr (lease, &a_address);
 	if (a_address.s_addr == INADDR_ANY) {
@@ -382,29 +359,47 @@ lease_parse_address (NDhcp4ClientLease *lease,
 		return FALSE;
 	}
 
-	/* n_dhcp4_client_lease_get_lifetime() never fails */
 	n_dhcp4_client_lease_get_lifetime (lease, &nettools_lifetime);
-	/* FIXME: n_dhcp4_client_lease_get_lifetime() returns the time in nsec of CLOCK_BOOTTIME.
-	 * We want to retrieve the original lifetime value in seconds, so we approximate it in a_lifetime.
-	 * Use a nettools API to retrieve the original value as passed by the server.
-	 */
+
 	if (nettools_lifetime == G_MAXUINT64) {
+		a_timestamp = 0;
 		a_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
-		a_expiry = NM_PLATFORM_LIFETIME_PERMANENT;
+		a_expiry = G_MAXUINT64;
 	} else {
-		gint64 ts_time = time (NULL);
-
-		a_lifetime = ((gint64) nettools_lifetime - ts_clock_boottime) / NM_UTILS_NS_PER_SECOND;
-		/* A lease time of 0 is allowed on some dhcp servers, so, let's accept it. */
-		if (a_lifetime < 0)
-			a_lifetime = 0;
-		else if (a_lifetime > NM_PLATFORM_LIFETIME_PERMANENT)
-			a_lifetime = NM_PLATFORM_LIFETIME_PERMANENT - 1;
-
-		if (ts_time > NM_PLATFORM_LIFETIME_PERMANENT - a_lifetime)
-			a_expiry = NM_PLATFORM_LIFETIME_PERMANENT - 1;
-		else
-			a_expiry = ts_time + a_lifetime;
+		guint64 nettools_basetime;
+		guint64 lifetime;
+		gint64 ts;
+
+		n_dhcp4_client_lease_get_basetime (lease, &nettools_basetime);
+
+		/* usually we shouldn't assert against external libraries like n-dhcp4.
+		 * Here we still do it... it seems safe enough. */
+		nm_assert (nettools_basetime > 0);
+		nm_assert (nettools_lifetime >= nettools_basetime);
+		nm_assert (((nettools_lifetime - nettools_basetime) % NM_UTILS_NS_PER_SECOND) == 0);
+		nm_assert ((nettools_lifetime - nettools_basetime) / NM_UTILS_NS_PER_SECOND <= G_MAXUINT32);
+
+		if (nettools_lifetime <= nettools_basetime) {
+			/* A lease time of 0 is allowed on some dhcp servers, so, let's accept it. */
+			lifetime = 0;
+		} else {
+			lifetime = nettools_lifetime - nettools_basetime;
+
+			/* we "ceil" the value to the next second. In practice, we don't expect any sub-second values
+			 * from n-dhcp4 anyway, so this should have no effect. */
+			lifetime += NM_UTILS_NS_PER_SECOND - 1;
+		}
+
+		ts = nm_utils_monotonic_timestamp_from_boottime (nettools_basetime, 1);
+
+		/* the timestamp must be positive, because we only started nettools DHCP client
+		 * after obtaining the first monotonic timestamp. Hence, the lease must have been
+		 * received afterwards. */
+		nm_assert (ts >= NM_UTILS_NS_PER_SECOND);
+
+		a_timestamp = ts / NM_UTILS_NS_PER_SECOND;
+		a_lifetime = NM_MIN (lifetime / NM_UTILS_NS_PER_SECOND, NM_PLATFORM_LIFETIME_PERMANENT - 1);
+		a_expiry = time (NULL) + ((lifetime - (nm_utils_clock_gettime_ns (CLOCK_BOOTTIME) - nettools_basetime)) / NM_UTILS_NS_PER_SECOND);
 	}
 
 	if (!lease_get_in_addr (lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) {
@@ -415,7 +410,6 @@ lease_parse_address (NDhcp4ClientLease *lease,
 	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
 	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
 
-	LOG_LEASE (LOGD_DHCP4, "address %s/%u", addr_str, a_plen);
 	nm_dhcp_option_add_option (options,
 	                           _nm_dhcp_option_dhcp4_options,
 	                           NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
@@ -425,20 +419,27 @@ lease_parse_address (NDhcp4ClientLease *lease,
 	                           NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
 	                           nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
 
-	LOG_LEASE (LOGD_DHCP4, "%s '%u' seconds (at %lld)",
-	           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options,
-	                                          NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME),
-	           (guint) a_lifetime,
-	           (long long) a_expiry);
 	nm_dhcp_option_add_option_u64 (options,
 	                               _nm_dhcp_option_dhcp4_options,
 	                               NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
 	                               (guint64) a_lifetime);
 
-	nm_dhcp_option_add_option_u64 (options,
-	                               _nm_dhcp_option_dhcp4_options,
-	                               NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
-	                               (guint64) a_expiry);
+	if (a_expiry != G_MAXUINT64) {
+		nm_dhcp_option_add_option_u64 (options,
+		                               _nm_dhcp_option_dhcp4_options,
+		                               NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
+		                               a_expiry);
+	}
+
+
+	n_dhcp4_client_lease_get_siaddr (lease, &a_next_server);
+	if (a_next_server.s_addr != INADDR_ANY) {
+		nm_utils_inet4_ntop (a_next_server.s_addr, addr_str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
+		                           addr_str);
+	}
 
 	nm_ip4_config_add_address (ip4_config,
 	                           &((const NMPlatformIP4Address) {
@@ -446,7 +447,7 @@ lease_parse_address (NDhcp4ClientLease *lease,
 	                                   .peer_address = a_address.s_addr,
 	                                   .plen         = a_plen,
 	                                   .addr_source  = NM_IP_CONFIG_SOURCE_DHCP,
-	                                   .timestamp    = ts / NM_UTILS_NS_PER_SECOND,
+	                                   .timestamp    = a_timestamp,
 	                                   .lifetime     = a_lifetime,
 	                                   .preferred    = a_lifetime,
 	                           }));
@@ -456,7 +457,6 @@ lease_parse_address (NDhcp4ClientLease *lease,
 
 static void
 lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
-                                 const char *iface,
                                  NMIP4Config *ip4_config,
                                  GHashTable *options)
 {
@@ -487,7 +487,6 @@ lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
 		nm_ip4_config_add_nameserver (ip4_config, addr.s_addr);
 	}
 
-	LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", str->str);
 	nm_dhcp_option_add_option (options,
 	                           _nm_dhcp_option_dhcp4_options,
 	                           NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,
@@ -496,7 +495,6 @@ lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
 
 static void
 lease_parse_routes (NDhcp4ClientLease *lease,
-                    const char *iface,
                     NMIP4Config *ip4_config,
                     GHashTable *options,
                     guint32 route_table,
@@ -526,11 +524,6 @@ lease_parse_routes (NDhcp4ClientLease *lease,
 			nm_utils_inet4_ntop (dest.s_addr, dest_str);
 			nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
 
-			LOG_LEASE (LOGD_DHCP4,
-			           "classless static route %s/%d gw %s",
-			           dest_str,
-			           (int) plen,
-			           gateway_str);
 			g_string_append_printf (nm_gstring_add_space_delimiter (str),
 			                        "%s/%d %s",
 			                        dest_str,
@@ -575,11 +568,6 @@ lease_parse_routes (NDhcp4ClientLease *lease,
 			nm_utils_inet4_ntop (dest.s_addr, dest_str);
 			nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
 
-			LOG_LEASE (LOGD_DHCP4,
-			           "static route %s/%d gw %s",
-			           dest_str,
-			           (int) plen,
-			           gateway_str);
 			g_string_append_printf (nm_gstring_add_space_delimiter (str),
 			                        "%s/%d %s",
 			                        dest_str,
@@ -656,7 +644,6 @@ lease_parse_routes (NDhcp4ClientLease *lease,
 			                         }),
 			                         NULL);
 		}
-		LOG_LEASE (LOGD_DHCP4, "router %s", str->str);
 		nm_dhcp_option_add_option (options,
 		                           _nm_dhcp_option_dhcp4_options,
 		                           NM_DHCP_OPTION_DHCP4_ROUTER,
@@ -666,7 +653,6 @@ lease_parse_routes (NDhcp4ClientLease *lease,
 
 static void
 lease_parse_mtu (NDhcp4ClientLease *lease,
-                 const char *iface,
                  NMIP4Config *ip4_config,
                  GHashTable *options)
 {
@@ -678,7 +664,6 @@ lease_parse_mtu (NDhcp4ClientLease *lease,
 	if (mtu < 68)
 		return;
 
-	LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
 	nm_dhcp_option_add_option_u64 (options,
 	                               _nm_dhcp_option_dhcp4_options,
 	                               NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
@@ -688,7 +673,6 @@ lease_parse_mtu (NDhcp4ClientLease *lease,
 
 static void
 lease_parse_metered (NDhcp4ClientLease *lease,
-                     const char *iface,
                      NMIP4Config *ip4_config,
                      GHashTable *options)
 {
@@ -704,13 +688,12 @@ lease_parse_metered (NDhcp4ClientLease *lease,
 		metered = !!memmem (data, n_data, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
 	}
 
-	LOG_LEASE (LOGD_DHCP4, "%s", metered ? "metered" : "unmetered");
+	/* TODO: expose the vendor specific option when present */
 	nm_ip4_config_set_metered (ip4_config, metered);
 }
 
 static void
 lease_parse_ntps (NDhcp4ClientLease *lease,
-                  const char *iface,
                   GHashTable *options)
 {
 	nm_auto_free_gstring GString *str = NULL;
@@ -731,13 +714,14 @@ lease_parse_ntps (NDhcp4ClientLease *lease,
 		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 	}
 
-	LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", str->str);
-	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NTP_SERVER, str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_NTP_SERVER,
+	                           str->str);
 }
 
 static void
 lease_parse_hostname (NDhcp4ClientLease *lease,
-                      const char *iface,
                       GHashTable *options)
 {
 	nm_auto_free_gstring GString *str = NULL;
@@ -754,13 +738,14 @@ lease_parse_hostname (NDhcp4ClientLease *lease,
 	if (is_localhost(str->str))
 		return;
 
-	LOG_LEASE (LOGD_DHCP4, "hostname '%s'", str->str);
-	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_HOST_NAME, str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_HOST_NAME,
+	                           str->str);
 }
 
 static void
 lease_parse_domainname (NDhcp4ClientLease *lease,
-                        const char *iface,
                         NMIP4Config *ip4_config,
                         GHashTable *options)
 {
@@ -787,41 +772,64 @@ lease_parse_domainname (NDhcp4ClientLease *lease,
 		g_string_append (nm_gstring_add_space_delimiter (str), *d);
 		nm_ip4_config_add_domain (ip4_config, *d);
 	}
-	LOG_LEASE (LOGD_DHCP4, "domain name '%s'", str->str);
-	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,
+	                           str->str);
+}
+
+char **
+nm_dhcp_parse_search_list (guint8 *data, size_t n_data)
+{
+	GPtrArray *array = NULL;
+	guint8 *cache = data;
+	size_t n_cache = 0;
+
+	for (;;) {
+		nm_auto_free_gstring GString *domain = NULL;
+
+		nm_gstring_prepare (&domain);
+
+		if (!lease_option_print_domain_name (domain, cache, &n_cache, &data, &n_data))
+			break;
+
+		if (!array)
+			array = g_ptr_array_new ();
+
+		g_ptr_array_add (array, g_string_free (domain, FALSE));
+		domain = NULL;
+	}
+
+	if (array) {
+		g_ptr_array_add (array, NULL);
+		return (char **) g_ptr_array_free (array, FALSE);
+	} else
+		return NULL;
 }
 
 static void
 lease_parse_search_domains (NDhcp4ClientLease *lease,
-                            const char *iface,
                             NMIP4Config *ip4_config,
                             GHashTable *options)
 {
 	nm_auto_free_gstring GString *str = NULL;
-	uint8_t *data, *cache;
-	size_t n_data, n_cache = 0;
+	uint8_t *data;
+	size_t n_data;
+	gs_strfreev char **domains = NULL;
+	guint i;
 	int r;
 
 	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &data, &n_data);
 	if (r)
 		return;
 
-	cache = data;
-
+	domains = nm_dhcp_parse_search_list (data, n_data);
 	nm_gstring_prepare (&str);
 
-	for (;;) {
-		nm_auto_free_gstring GString *domain = NULL;
-
-		nm_gstring_prepare (&domain);
-
-		if (!lease_option_print_domain_name (domain, cache, &n_cache, &data, &n_data))
-			break;
-
-		g_string_append (nm_gstring_add_space_delimiter (str), domain->str);
-		nm_ip4_config_add_search (ip4_config, domain->str);
+	for (i = 0; domains && domains[i]; i++) {
+		g_string_append (nm_gstring_add_space_delimiter (str), domains[i]);
+		nm_ip4_config_add_search (ip4_config, domains[i]);
 	}
-	LOG_LEASE (LOGD_DHCP4, "domain search '%s'", str->str);
 	nm_dhcp_option_add_option (options,
 	                           _nm_dhcp_option_dhcp4_options,
 	                           NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
@@ -830,7 +838,6 @@ lease_parse_search_domains (NDhcp4ClientLease *lease,
 
 static void
 lease_parse_root_path (NDhcp4ClientLease *lease,
-                       const char *iface,
                        GHashTable *options)
 {
 	nm_auto_free_gstring GString *str = NULL;
@@ -843,16 +850,17 @@ lease_parse_root_path (NDhcp4ClientLease *lease,
 		return;
 
 	str = g_string_new_len ((char *)data, n_data);
-	LOG_LEASE (LOGD_DHCP4, "root path '%s'", str->str);
-	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROOT_PATH, str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_ROOT_PATH,
+	                           str->str);
 }
 
 static void
 lease_parse_wpad (NDhcp4ClientLease *lease,
-                  const char *iface,
                   GHashTable *options)
 {
-	nm_auto_free_gstring GString *str = NULL;
+	gs_free char *wpad = NULL;
 	uint8_t *data;
 	size_t n_data;
 	int r;
@@ -861,12 +869,44 @@ lease_parse_wpad (NDhcp4ClientLease *lease,
 	if (r)
 		return;
 
-	str = g_string_new_len ((char *)data, n_data);
-	LOG_LEASE (LOGD_DHCP4, "wpad '%s'", str->str);
+	nm_utils_buf_utf8safe_escape ((char *)data, n_data, 0, &wpad);
+	if (wpad == NULL)
+		wpad = g_strndup ((char *)data, n_data);
+
 	nm_dhcp_option_add_option (options,
 	                           _nm_dhcp_option_dhcp4_options,
 	                           NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
-	                           str->str);
+	                           wpad);
+}
+
+static void
+lease_parse_private_options (NDhcp4ClientLease *lease,
+                             GHashTable *options)
+{
+	int i;
+
+	for (i = NM_DHCP_OPTION_DHCP4_PRIVATE_224; i <= NM_DHCP_OPTION_DHCP4_PRIVATE_254; i++) {
+		gs_free char *option_string = NULL;
+		guint8 *data;
+		gsize n_data;
+		int r;
+
+		/* We manage private options 249 (private classless static route) and 252 (wpad) in a special
+		 * way, so skip them as we here just manage all (the other) private options as raw data */
+		if (NM_IN_SET (i, NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE,
+		                  NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY))
+			continue;
+
+		r = n_dhcp4_client_lease_query (lease, i, &data, &n_data);
+		if (r)
+			continue;
+
+		option_string = nm_utils_bin2hexstr_full (data, n_data, ':', FALSE, NULL);
+		nm_dhcp_option_take_option (options,
+		                            _nm_dhcp_option_dhcp4_options,
+		                            i,
+		                            g_steal_pointer (&option_string));
+	}
 }
 
 static NMIP4Config *
@@ -885,22 +925,23 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	g_return_val_if_fail (lease != NULL, NULL);
 
 	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
-	options = out_options ? nm_dhcp_option_create_options_dict () : NULL;
+	options = nm_dhcp_option_create_options_dict ();
 
-	if (!lease_parse_address (lease, iface, ip4_config, options, error))
+	if (!lease_parse_address (lease, ip4_config, options, error))
 		return NULL;
 
-	lease_parse_routes (lease, iface, ip4_config, options, route_table, route_metric);
-	lease_parse_domain_name_servers (lease, iface, ip4_config, options);
-	lease_parse_domainname (lease, iface, ip4_config, options);
-	lease_parse_search_domains (lease, iface, ip4_config, options);
-	lease_parse_mtu (lease, iface, ip4_config, options);
-	lease_parse_metered (lease, iface, ip4_config, options);
+	lease_parse_routes (lease, ip4_config, options, route_table, route_metric);
+	lease_parse_domain_name_servers (lease, ip4_config, options);
+	lease_parse_domainname (lease, ip4_config, options);
+	lease_parse_search_domains (lease, ip4_config, options);
+	lease_parse_mtu (lease, ip4_config, options);
+	lease_parse_metered (lease, ip4_config, options);
 
-	lease_parse_hostname (lease, iface, options);
-	lease_parse_ntps (lease, iface, options);
-	lease_parse_root_path (lease, iface, options);
-	lease_parse_wpad (lease, iface, options);
+	lease_parse_hostname (lease, options);
+	lease_parse_ntps (lease, options);
+	lease_parse_root_path (lease, options);
+	lease_parse_wpad (lease, options);
+	lease_parse_private_options (lease, options);
 
 	NM_SET_OUT (out_options, g_steal_pointer (&options));
 	return g_steal_pointer (&ip4_config);
@@ -909,8 +950,36 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 /*****************************************************************************/
 
 static void
+lease_save (NMDhcpNettools *self, NDhcp4ClientLease *lease, const char *lease_file)
+{
+	struct in_addr a_address;
+	nm_auto_free_gstring GString *new_contents = NULL;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+	gs_free_error GError *error = NULL;
+
+	nm_assert (lease);
+	nm_assert (lease_file);
+
+	new_contents = g_string_new ("# This is private data. Do not parse.\n");
+
+	n_dhcp4_client_lease_get_yiaddr (lease, &a_address);
+	if (a_address.s_addr == INADDR_ANY)
+		return;
+
+	g_string_append_printf (new_contents,
+	                        "ADDRESS=%s\n", nm_utils_inet4_ntop (a_address.s_addr, sbuf));
+
+	if (!g_file_set_contents (lease_file,
+	                          new_contents->str,
+	                          -1,
+	                          &error))
+		_LOGW ("error saving lease to %s: %s", lease_file, error->message);
+}
+
+static void
 bound4_handle (NMDhcpNettools *self, NDhcp4ClientLease *lease)
 {
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
 	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
 	gs_unref_object NMIP4Config *ip4_config = NULL;
 	gs_unref_hashtable GHashTable *options = NULL;
@@ -934,6 +1003,7 @@ bound4_handle (NMDhcpNettools *self, NDhcp4ClientLease *lease)
 	}
 
 	nm_dhcp_option_add_requests_to_options (options, _nm_dhcp_option_dhcp4_options);
+	lease_save (self, lease, priv->lease_file);
 
 	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
 	                          NM_DHCP_STATE_BOUND,
@@ -1004,6 +1074,27 @@ dhcp4_event_cb (GIOChannel *source,
 	return G_SOURCE_CONTINUE;
 }
 
+G_GNUC_PRINTF (3, 4)
+static void
+nettools_log (int level, void *data, const char *fmt, ...)
+{
+	NMDhcpNettools *self = data;
+	NMLogLevel nm_level;
+	gs_free char *msg = NULL;
+	va_list ap;
+
+	nm_level = nm_log_level_from_syslog (level);
+	if (nm_logging_enabled (nm_level, LOGD_DHCP4)) {
+		va_start (ap, fmt);
+		msg = g_strdup_vprintf (fmt, ap);
+		va_end (ap);
+		nm_log (nm_level, LOGD_DHCP4, NULL , NULL,
+		        "dhcp4 (%s): %s",
+		        nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self)),
+		        msg);
+	}
+}
+
 static gboolean
 nettools_create (NMDhcpNettools *self,
                  const char *dhcp_anycast_addr,
@@ -1073,11 +1164,15 @@ nettools_create (NMDhcpNettools *self,
 		return FALSE;
 	}
 
+	n_dhcp4_client_config_set_log_level (config, nm_log_level_to_syslog (nm_logging_get_level (LOGD_DHCP4)));
+	n_dhcp4_client_config_set_log_func (config, nettools_log, self);
 	n_dhcp4_client_config_set_ifindex (config, nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)));
 	n_dhcp4_client_config_set_transport (config, transport);
 	n_dhcp4_client_config_set_mac (config, hwaddr_arr, hwaddr_len);
 	n_dhcp4_client_config_set_broadcast_mac (config, bcast_hwaddr_arr, bcast_hwaddr_len);
-	r = n_dhcp4_client_config_set_client_id (config, client_id_arr, client_id_len);
+	r = n_dhcp4_client_config_set_client_id (config,
+	                                         client_id_arr,
+	                                         NM_MIN (client_id_len, 1 + _NM_SD_MAX_CLIENT_ID_LEN));
 	if (r) {
 		nm_utils_error_set_errno (error, r, "failed to set client-id: %s");
 		return FALSE;
@@ -1146,6 +1241,22 @@ decline (NMDhcpClient *client,
 	return TRUE;
 }
 
+static guint8
+fqdn_flags_to_wire (NMDhcpHostnameFlags flags)
+{
+	guint r = 0;
+
+	/* RFC 4702 section 2.1 */
+	if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE)
+		r |= (1 << 0);
+	if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED)
+		r |= (1 << 2);
+	if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE)
+		r |= (1 << 3);
+
+	return r;
+}
+
 static gboolean
 ip4_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
@@ -1155,6 +1266,7 @@ ip4_start (NMDhcpClient *client,
 	nm_auto (n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *config = NULL;
 	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
 	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	gs_free char *lease_file = NULL;
 	struct in_addr last_addr = { 0 };
 	const char *hostname;
 	int r, i;
@@ -1176,9 +1288,32 @@ ip4_start (NMDhcpClient *client,
 	 */
 	n_dhcp4_client_probe_config_set_start_delay (config, 1);
 
-	if (last_ip4_address) {
+	nm_dhcp_utils_get_leasefile_path (AF_INET,
+	                                  "internal",
+	                                  nm_dhcp_client_get_iface (client),
+	                                  nm_dhcp_client_get_uuid (client),
+	                                  &lease_file);
+
+	if (last_ip4_address)
 		inet_pton (AF_INET, last_ip4_address, &last_addr);
+	else {
+		/*
+		 * TODO: we stick to the systemd-networkd lease file format. Quite easy for now to
+		 * just use the functions in systemd code. Anyway, as in the end we just use the
+		 * ip address from all the options found in the lease, write a function that parses
+		 * the lease file just for the assigned address and returns it in &last_address.
+		 * Then drop reference to systemd-networkd structures and functions.
+		 */
+		nm_auto (sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+
+		dhcp_lease_load (&lease, lease_file);
+		if (lease)
+			sd_dhcp_lease_get_address (lease, &last_addr);
+	}
+
+	if (last_addr.s_addr) {
 		n_dhcp4_client_probe_config_set_requested_ip (config, last_addr);
+		n_dhcp4_client_probe_config_set_init_reboot (config, TRUE);
 	}
 
 	/* Add requested options */
@@ -1193,26 +1328,38 @@ ip4_start (NMDhcpClient *client,
 	hostname = nm_dhcp_client_get_hostname (client);
 	if (hostname) {
 		if (nm_dhcp_client_get_use_fqdn (client)) {
-			uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
-
-			buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
-			            DHCP_FQDN_FLAG_E;  /* Canonical wire format */
-			buffer[1] = 0;                 /* RCODE1 (deprecated) */
-			buffer[2] = 0;                 /* RCODE2 (deprecated) */
-
-			r = nm_sd_dns_name_to_wire_format (hostname,
-			                                   buffer + 3,
-			                                   sizeof (buffer) - 3,
-			                                   FALSE);
-			if (r < 0) {
-				nm_utils_error_set_errno (error, r, "failed to convert DHCP FQDN: %s");
-				return FALSE;
+			uint8_t buffer[255];
+			NMDhcpHostnameFlags flags;
+			size_t fqdn_len;
+
+			flags = nm_dhcp_client_get_hostname_flags (client);
+			buffer[0] = fqdn_flags_to_wire (flags);
+			buffer[1] = 0;   /* RCODE1 (deprecated) */
+			buffer[2] = 0;   /* RCODE2 (deprecated) */
+
+			if (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED) {
+				r = nm_sd_dns_name_to_wire_format (hostname,
+				                                   buffer + 3,
+				                                   sizeof (buffer) - 3,
+				                                   FALSE);
+				if (r <= 0) {
+					nm_utils_error_set_errno (error, r, "failed to convert DHCP FQDN: %s");
+					return FALSE;
+				}
+				fqdn_len = r;
+			} else {
+				fqdn_len = strlen (hostname);
+				if (fqdn_len > sizeof (buffer) - 3) {
+					nm_utils_error_set (error, r, "failed to set DHCP FQDN: name too long");
+					return FALSE;
+				}
+				memcpy (buffer + 3, hostname, fqdn_len);
 			}
 
 			r = n_dhcp4_client_probe_config_append_option (config,
 			                                               NM_DHCP_OPTION_DHCP4_CLIENT_FQDN,
 			                                               buffer,
-			                                               3 + r);
+			                                               3 + fqdn_len);
 			if (r) {
 				nm_utils_error_set_errno (error, r, "failed to set DHCP FQDN: %s");
 				return FALSE;
@@ -1229,6 +1376,9 @@ ip4_start (NMDhcpClient *client,
 		}
 	}
 
+	g_free (priv->lease_file);
+	priv->lease_file = g_steal_pointer (&lease_file);
+
 	r = n_dhcp4_client_probe (priv->client, &priv->probe, config);
 	if (r) {
 		nm_utils_error_set_errno (error, r, "failed to start DHCP client: %s");
@@ -1268,6 +1418,7 @@ dispose (GObject *object)
 {
 	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE ((NMDhcpNettools *) object);
 
+	nm_clear_pointer (&priv->lease_file, g_free);
 	nm_clear_pointer (&priv->channel, g_io_channel_unref);
 	nm_clear_g_source (&priv->event_id);
 	nm_clear_pointer (&priv->lease, n_dhcp4_client_lease_unref);
@@ -1292,7 +1443,7 @@ nm_dhcp_nettools_class_init (NMDhcpNettoolsClass *class)
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_nettools = {
-	.name = "nettools",
-	.get_type = nm_dhcp_nettools_get_type,
-	.get_path = NULL,
+	.name         = "nettools",
+	.get_type     = nm_dhcp_nettools_get_type,
+	.experimental = TRUE,
 };