about summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/nm-dhcp-client-logging.h1
-rw-r--r--src/dhcp/nm-dhcp-client.c108
-rw-r--r--src/dhcp/nm-dhcp-client.h41
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c1
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h1
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c3
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c14
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c18
-rw-r--r--src/dhcp/nm-dhcp-helper-api.h1
-rw-r--r--src/dhcp/nm-dhcp-helper.c1
-rw-r--r--src/dhcp/nm-dhcp-listener.c17
-rw-r--r--src/dhcp/nm-dhcp-listener.h1
-rw-r--r--src/dhcp/nm-dhcp-manager.c73
-rw-r--r--src/dhcp/nm-dhcp-manager.h5
-rw-r--r--src/dhcp/nm-dhcp-nettools.c1278
-rw-r--r--src/dhcp/nm-dhcp-options.c278
-rw-r--r--src/dhcp/nm-dhcp-options.h204
-rw-r--r--src/dhcp/nm-dhcp-systemd.c425
-rw-r--r--src/dhcp/nm-dhcp-utils.c1
-rw-r--r--src/dhcp/nm-dhcp-utils.h1
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c1
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c1
22 files changed, 2183 insertions, 291 deletions
diff --git a/src/dhcp/nm-dhcp-client-logging.h b/src/dhcp/nm-dhcp-client-logging.h
index 1ed47170..d9b53f58 100644
--- a/src/dhcp/nm-dhcp-client-logging.h
+++ b/src/dhcp/nm-dhcp-client-logging.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index d494eff0..9f585c5d 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
@@ -52,6 +51,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpClient,
 	PROP_ADDR_FAMILY,
 	PROP_FLAGS,
 	PROP_HWADDR,
+	PROP_BROADCAST_HWADDR,
 	PROP_IFACE,
 	PROP_IFINDEX,
 	PROP_MULTI_IDX,
@@ -66,6 +66,7 @@ typedef struct _NMDhcpClientPrivate {
 	NMDedupMultiIndex *multi_idx;
 	char *       iface;
 	GBytes *     hwaddr;
+	GBytes *     bcast_hwaddr;
 	char *       uuid;
 	GBytes *     client_id;
 	char *       hostname;
@@ -144,6 +145,14 @@ nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->hwaddr;
 }
 
+GBytes *
+nm_dhcp_client_get_broadcast_hw_addr (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->bcast_hwaddr;
+}
+
 guint32
 nm_dhcp_client_get_route_table (NMDhcpClient *self)
 {
@@ -476,8 +485,9 @@ nm_dhcp_client_start_timeout (NMDhcpClient *self)
 {
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
 
+	g_return_if_fail (priv->timeout_id == 0);
+
 	/* Set up a timeout on the transaction to kill it after the timeout */
-	g_assert (priv->timeout_id == 0);
 
 	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
 		return;
@@ -530,6 +540,36 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 	                                                   error);
 }
 
+gboolean
+nm_dhcp_client_accept (NMDhcpClient *self,
+                       GError **error)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	if (NM_DHCP_CLIENT_GET_CLASS (self)->accept) {
+		return NM_DHCP_CLIENT_GET_CLASS (self)->accept (self,
+		                                                error);
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_dhcp_client_decline (NMDhcpClient *self,
+                        const char *error_message,
+                        GError **error)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	if (NM_DHCP_CLIENT_GET_CLASS (self)->decline) {
+		return NM_DHCP_CLIENT_GET_CLASS (self)->decline (self,
+		                                                 error_message,
+		                                                 error);
+	}
+
+	return TRUE;
+}
+
 static GBytes *
 get_duid (NMDhcpClient *self)
 {
@@ -690,6 +730,23 @@ bytearray_variant_to_string (NMDhcpClient *self, GVariant *value, const char *ke
 	return converted;
 }
 
+static int
+label_is_unknown_xyz (const char *label)
+{
+	if (!NM_STR_HAS_PREFIX (label, "unknown_"))
+		return -EINVAL;
+
+	label += NM_STRLEN  ("unknown_");
+	if (   label[0] != '2'
+	    || !g_ascii_isdigit (label[1])
+	    || !g_ascii_isdigit (label[2])
+	    || label[3] != '\0')
+		return -EINVAL;
+
+	return _nm_utils_ascii_str_to_int64 (label, 10, 224, 254, -EINVAL);
+}
+
+
 #define OLD_TAG "old_"
 #define NEW_TAG "new_"
 
@@ -713,14 +770,41 @@ maybe_add_option (NMDhcpClient *self,
 	                       "dhcp_message_type"))
 		return;
 
-	if (g_str_has_prefix (key, NEW_TAG))
+	if (NM_STR_HAS_PREFIX (key, NEW_TAG))
 		key += NM_STRLEN (NEW_TAG);
-	if (!key[0])
+	if (NM_STR_HAS_PREFIX (key, "private_") || !key[0])
 		return;
 
 	str_value = bytearray_variant_to_string (self, value, key);
-	if (str_value)
+	if (str_value) {
+		int priv_opt_num;
+
 		g_hash_table_insert (hash, g_strdup (key), str_value);
+
+		/* dhclient has no special labels for private dhcp options: it uses "unknown_xyz"
+		 * labels for that. We need to identify those to alias them to our "private_xyz"
+		 * format unsed in the internal dchp plugins.
+		 */
+		if ((priv_opt_num = label_is_unknown_xyz (key)) > 0) {
+			gs_free guint8 *check_val = NULL;
+			char *hex_str = NULL;
+			gsize len;
+
+			/* dhclient passes values from dhcp private options in its own "string" format:
+			 * if the raw values are printable as ascii strings, it will pass the string
+			 * representation; if the values are not printable as an ascii string, it will
+			 * pass a string displaying the hex values (hex string). Try to enforce passing
+			 * always an hex string, converting string representation if needed.
+			 */
+			check_val = nm_utils_hexstr2bin_alloc (str_value, FALSE, TRUE, ":", 0, &len);
+			hex_str = nm_utils_bin2hexstr_full (check_val ?: (guint8 *) str_value,
+			                                    check_val ? len : strlen (str_value),
+			                                    ':', FALSE, NULL);
+			g_hash_table_insert (hash,
+			                     g_strdup_printf ("private_%d", priv_opt_num),
+			                     hex_str);
+		}
+	}
 }
 
 gboolean
@@ -839,6 +923,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_HWADDR:
 		g_value_set_boxed (value, priv->hwaddr);
 		break;
+	case PROP_BROADCAST_HWADDR:
+		g_value_set_boxed (value, priv->bcast_hwaddr);
+		break;
 	case PROP_ADDR_FAMILY:
 		g_value_set_int (value, priv->addr_family);
 		break;
@@ -900,6 +987,10 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->hwaddr = g_value_dup_boxed (value);
 		break;
+	case PROP_BROADCAST_HWADDR:
+		/* construct-only */
+		priv->bcast_hwaddr = g_value_dup_boxed (value);
+		break;
 	case PROP_ADDR_FAMILY:
 		/* construct-only */
 		priv->addr_family = g_value_get_int (value);
@@ -966,6 +1057,7 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->uuid, g_free);
 	g_clear_pointer (&priv->client_id, g_bytes_unref);
 	g_clear_pointer (&priv->hwaddr, g_bytes_unref);
+	g_clear_pointer (&priv->bcast_hwaddr, g_bytes_unref);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 
@@ -1010,6 +1102,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_BROADCAST_HWADDR] =
+	    g_param_spec_boxed (NM_DHCP_CLIENT_BROADCAST_HWADDR, "", "",
+	                        G_TYPE_BYTES,
+	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                        G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_ADDR_FAMILY] =
 	    g_param_spec_int (NM_DHCP_CLIENT_ADDR_FAMILY, "", "",
 	                      0, G_MAXINT, AF_UNSPEC,
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 1db7eac6..9eb76f33 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
@@ -35,17 +34,18 @@
 #define NM_IS_DHCP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_CLIENT))
 #define NM_DHCP_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
 
-#define NM_DHCP_CLIENT_ADDR_FAMILY  "addr-family"
-#define NM_DHCP_CLIENT_FLAGS        "flags"
-#define NM_DHCP_CLIENT_HWADDR       "hwaddr"
-#define NM_DHCP_CLIENT_IFINDEX      "ifindex"
-#define NM_DHCP_CLIENT_INTERFACE    "iface"
-#define NM_DHCP_CLIENT_MULTI_IDX    "multi-idx"
-#define NM_DHCP_CLIENT_HOSTNAME     "hostname"
-#define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
-#define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
-#define NM_DHCP_CLIENT_TIMEOUT      "timeout"
-#define NM_DHCP_CLIENT_UUID         "uuid"
+#define NM_DHCP_CLIENT_ADDR_FAMILY      "addr-family"
+#define NM_DHCP_CLIENT_FLAGS            "flags"
+#define NM_DHCP_CLIENT_HWADDR           "hwaddr"
+#define NM_DHCP_CLIENT_BROADCAST_HWADDR "broadcast-hwaddr"
+#define NM_DHCP_CLIENT_IFINDEX          "ifindex"
+#define NM_DHCP_CLIENT_INTERFACE        "iface"
+#define NM_DHCP_CLIENT_MULTI_IDX        "multi-idx"
+#define NM_DHCP_CLIENT_HOSTNAME         "hostname"
+#define NM_DHCP_CLIENT_ROUTE_METRIC     "route-metric"
+#define NM_DHCP_CLIENT_ROUTE_TABLE      "route-table"
+#define NM_DHCP_CLIENT_TIMEOUT          "timeout"
+#define NM_DHCP_CLIENT_UUID             "uuid"
 
 #define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed"
 #define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
@@ -83,6 +83,13 @@ typedef struct {
 	                           const char *last_ip4_address,
 	                           GError **error);
 
+	gboolean (*accept)        (NMDhcpClient *self,
+	                           GError **error);
+
+	gboolean (*decline)       (NMDhcpClient *self,
+	                           const char *error_message,
+	                           GError **error);
+
 	gboolean (*ip6_start)     (NMDhcpClient *self,
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
@@ -123,6 +130,8 @@ GBytes *nm_dhcp_client_get_duid (NMDhcpClient *self);
 
 GBytes *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
+GBytes *nm_dhcp_client_get_broadcast_hw_addr (NMDhcpClient *self);
+
 guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
 
 void nm_dhcp_client_set_route_table (NMDhcpClient *self, guint32 route_table);
@@ -156,6 +165,13 @@ gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                                    guint needed_prefixes,
                                    GError **error);
 
+gboolean nm_dhcp_client_accept (NMDhcpClient *self,
+                                GError **error);
+
+gboolean nm_dhcp_client_decline (NMDhcpClient *self,
+                                 const char *error_message,
+                                 GError **error);
+
 void nm_dhcp_client_stop (NMDhcpClient *self, gboolean release);
 
 /* Backend helpers for subclasses */
@@ -200,5 +216,6 @@ extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_internal;
+extern const NMDhcpClientFactory _nm_dhcp_client_factory_nettools;
 
 #endif /* __NETWORKMANAGER_DHCP_CLIENT_H__ */
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 85ca3704..bf3df399 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index 57a711db..8ca893c3 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index b655a1eb..54b50479 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-dhclient.c - dhclient specific hooks for NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -376,7 +375,7 @@ dhclient_start (NMDhcpClient *client,
 	iface = nm_dhcp_client_get_iface (client);
 	uuid = nm_dhcp_client_get_uuid (client);
 
-	pid_file = g_strdup_printf (RUNSTATEDIR "/dhclient%s-%s.pid",
+	pid_file = g_strdup_printf (NMRUNDIR "/dhclient%s-%s.pid",
 	                            _addr_family_to_path_part (addr_family),
 	                            iface);
 
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index 868cc9dd..2d2113cc 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-dhcpcanon.c - dhcpcanon specific hooks for NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -186,18 +185,6 @@ ip4_start (NMDhcpClient *client,
 	                        error);
 }
 
-static gboolean
-ip6_start (NMDhcpClient *client,
-           const char *dhcp_anycast_addr,
-           const struct in6_addr *ll_addr,
-           NMSettingIP6ConfigPrivacy privacy,
-           guint needed_prefixes,
-           GError **error)
-{
-	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcanon plugin does not support IPv6");
-	return FALSE;
-}
-
 static void
 stop (NMDhcpClient *client, gboolean release)
 {
@@ -257,7 +244,6 @@ nm_dhcp_dhcpcanon_class_init (NMDhcpDhcpcanonClass *dhcpcanon_class)
 	object_class->dispose = dispose;
 
 	client_class->ip4_start = ip4_start;
-	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
 }
 
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index 2a7482b1..c300bbe2 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-dhcpcd.c - dhcpcd specific hooks for NetworkManager
  *
  * Copyright (C) 2008 Roy Marples
@@ -100,9 +99,9 @@ ip4_start (NMDhcpClient *client,
 	iface = nm_dhcp_client_get_iface (client);
 
 	/* dhcpcd does not allow custom pidfiles; the pidfile is always
-	 * RUNDIR "dhcpcd-<ifname>.pid".
+	 * RUNSTATEDIR "dhcpcd-<ifname>.pid".
 	 */
-	priv->pid_file = g_strdup_printf (RUNDIR "/dhcpcd-%s.pid", iface);
+	priv->pid_file = g_strdup_printf (RUNSTATEDIR "/dhcpcd-%s.pid", iface);
 
 	dhcpcd_path = nm_dhcp_dhcpcd_get_path ();
 	if (!dhcpcd_path) {
@@ -180,18 +179,6 @@ ip4_start (NMDhcpClient *client,
 	return TRUE;
 }
 
-static gboolean
-ip6_start (NMDhcpClient *client,
-           const char *dhcp_anycast_addr,
-           const struct in6_addr *ll_addr,
-           NMSettingIP6ConfigPrivacy privacy,
-           guint needed_prefixes,
-           GError **error)
-{
-	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcd plugin does not support IPv6");
-	return FALSE;
-}
-
 static void
 stop (NMDhcpClient *client, gboolean release)
 {
@@ -251,7 +238,6 @@ nm_dhcp_dhcpcd_class_init (NMDhcpDhcpcdClass *dhcpcd_class)
 	object_class->dispose = dispose;
 
 	client_class->ip4_start = ip4_start;
-	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
 }
 
diff --git a/src/dhcp/nm-dhcp-helper-api.h b/src/dhcp/nm-dhcp-helper-api.h
index 58344215..c1a3c71c 100644
--- a/src/dhcp/nm-dhcp-helper-api.h
+++ b/src/dhcp/nm-dhcp-helper-api.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 8f753a61..9acc4045 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index 049c4e55..88aafeb0 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
@@ -39,7 +38,7 @@
 
 /*****************************************************************************/
 
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5] = {
 	/* the order here matters, as we will try the plugins in this order to find
 	 * the first available plugin. */
 
@@ -53,15 +52,16 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 	&_nm_dhcp_client_factory_dhcpcd,
 #endif
 	&_nm_dhcp_client_factory_internal,
+	&_nm_dhcp_client_factory_nettools,
 };
 
 /*****************************************************************************/
 
 typedef struct {
-	NMDBusManager *      dbus_mgr;
-	gulong              new_conn_id;
-	gulong              dis_conn_id;
-	GHashTable *        connections;
+	NMDBusManager *dbus_mgr;
+	gulong         new_conn_id;
+	gulong         dis_conn_id;
+	GHashTable    *connections;
 } NMDhcpListenerPrivate;
 
 struct _NMDhcpListener {
@@ -282,7 +282,7 @@ nm_dhcp_listener_init (NMDhcpListener *self)
 	/* Maps GDBusConnection :: signal-id */
 	priv->connections = g_hash_table_new (nm_direct_hash, NULL);
 
-	priv->dbus_mgr = nm_dbus_manager_get ();
+	priv->dbus_mgr = g_object_ref (nm_dbus_manager_get ());
 
 	/* Register the socket our DHCP clients will return lease info on */
 	nm_dbus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
@@ -303,10 +303,11 @@ dispose (GObject *object)
 
 	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->new_conn_id);
 	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->dis_conn_id);
-	priv->dbus_mgr = NULL;
 
 	g_clear_pointer (&priv->connections, g_hash_table_destroy);
 
+	g_clear_object (&priv->dbus_mgr);
+
 	G_OBJECT_CLASS (nm_dhcp_listener_parent_class)->dispose (object);
 }
 
diff --git a/src/dhcp/nm-dhcp-listener.h b/src/dhcp/nm-dhcp-listener.h
index 3018b97a..d9724062 100644
--- a/src/dhcp/nm-dhcp-listener.h
+++ b/src/dhcp/nm-dhcp-listener.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index fe843a2c..304a7b99 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -90,6 +89,54 @@ _client_factory_available (const NMDhcpClientFactory *client_factory)
 	return NULL;
 }
 
+static const NMDhcpClientFactory *
+_client_factory_get_effective (const NMDhcpClientFactory *client_factory,
+                               int addr_family)
+{
+	nm_auto_unref_gtypeclass NMDhcpClientClass *klass = NULL;
+
+	nm_assert (client_factory);
+	nm_assert_addr_family (addr_family);
+
+	/* currently, the chosen DHCP plugin for IPv4 and IPv6 is configured in NetworkManager.conf
+	 * and cannot be reloaded. It would be nice to configure the plugin per address family
+	 * or to be able to reload it.
+	 *
+	 * Note that certain options in NetworkManager.conf depend on the chosen DHCP plugin.
+	 * See "dhcp-plugin:" in "Device List Format" (`man NetworkManager.conf`).
+	 * Supporting reloading the plugin would also require to re-evalate the decisions from
+	 * the "Device List Format". Likewise, having per-address family plugins would make the
+	 * "main.dhcp" setting and "dhcp-plugin:" match non-sensical because these configurations
+	 * currently are address family independet.
+	 *
+	 * So actually, we don't want that complexity. We want to phase out all plugins in favor
+	 * of the internal plugin.
+	 * However, certain existing plugins are well known to not support an address family.
+	 * In those cases, we should just silently fallback to the internal plugin.
+	 *
+	 * This could be a problem with forward compatibility if we ever intended to add IPv6 support
+	 * to those plugins. But we don't intend to do so. The internal plugin is the way forward and
+	 * not extending other plugins. */
+
+	if (client_factory == &_nm_dhcp_client_factory_internal) {
+		/* already using internal plugin. Nothing to do. */
+		return client_factory;
+	}
+
+	klass = g_type_class_ref (client_factory->get_type ());
+
+	nm_assert (NM_IS_DHCP_CLIENT_CLASS (klass));
+
+	if (addr_family == AF_INET6) {
+		return   klass->ip6_start
+		       ? client_factory
+		       : &_nm_dhcp_client_factory_internal;
+	}
+	return   klass->ip4_start
+	       ? client_factory
+	       : &_nm_dhcp_client_factory_internal;
+}
+
 /*****************************************************************************/
 
 static NMDhcpClient *
@@ -157,6 +204,7 @@ client_start (NMDhcpManager *self,
               const char *iface,
               int ifindex,
               GBytes *hwaddr,
+              GBytes *bcast_hwaddr,
               const char *uuid,
               guint32 route_table,
               guint32 route_metric,
@@ -177,6 +225,7 @@ client_start (NMDhcpManager *self,
 	NMDhcpClient *client;
 	gboolean success = FALSE;
 	gsize hwaddr_len;
+	const NMDhcpClientFactory *client_factory;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
 	g_return_val_if_fail (iface, NULL);
@@ -185,10 +234,11 @@ client_start (NMDhcpManager *self,
 	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) {
+	if (!hwaddr || !bcast_hwaddr) {
 		nm_utils_error_set (error,
 		                    NM_UTILS_ERROR_UNKNOWN,
-		                    "missing MAC address");
+		                    "missing %s address",
+		                    hwaddr ? "broadcast" : "MAC");
 		return NULL;
 	}
 
@@ -201,24 +251,33 @@ client_start (NMDhcpManager *self,
 		g_return_val_if_reached (NULL) ;
 	}
 
+	nm_assert (g_bytes_get_size (hwaddr) == g_bytes_get_size (bcast_hwaddr));
+
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 
-	nm_assert (priv->client_factory);
+	client_factory = _client_factory_get_effective (priv->client_factory, addr_family);
 
 	/* Kill any old client instance */
 	client = get_client_for_ifindex (self, addr_family, ifindex);
 	if (client) {
+		/* FIXME: we cannot just call synchronously "stop()" and forget about the client.
+		 * We need to wait for the client to be fully stopped because most/all clients
+		 * cannot quit right away.
+		 *
+		 * FIXME(shutdown): also fix this during shutdown, to wait for all DHCP clients
+		 * to be fully stopped. */
 		remove_client (self, client);
 		nm_dhcp_client_stop (client, FALSE);
 		g_object_unref (client);
 	}
 
-	client = g_object_new (priv->client_factory->get_type (),
+	client = g_object_new (client_factory->get_type (),
 	                       NM_DHCP_CLIENT_MULTI_IDX, multi_idx,
 	                       NM_DHCP_CLIENT_ADDR_FAMILY, addr_family,
 	                       NM_DHCP_CLIENT_INTERFACE, iface,
 	                       NM_DHCP_CLIENT_IFINDEX, ifindex,
 	                       NM_DHCP_CLIENT_HWADDR, hwaddr,
+	                       NM_DHCP_CLIENT_BROADCAST_HWADDR, bcast_hwaddr,
 	                       NM_DHCP_CLIENT_UUID, uuid,
 	                       NM_DHCP_CLIENT_HOSTNAME, hostname,
 	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
@@ -291,6 +350,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            const char *iface,
                            int ifindex,
                            GBytes *hwaddr,
+                           GBytes *bcast_hwaddr,
                            const char *uuid,
                            guint32 route_table,
                            guint32 route_metric,
@@ -341,6 +401,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 	                     iface,
 	                     ifindex,
 	                     hwaddr,
+	                     bcast_hwaddr,
 	                     uuid,
 	                     route_table,
 	                     route_metric,
@@ -365,6 +426,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            const char *iface,
                            int ifindex,
                            GBytes *hwaddr,
+                           GBytes *bcast_hwaddr,
                            const struct in6_addr *ll_addr,
                            const char *uuid,
                            guint32 route_table,
@@ -396,6 +458,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 	                     iface,
 	                     ifindex,
 	                     hwaddr,
+	                     bcast_hwaddr,
 	                     uuid,
 	                     route_table,
 	                     route_metric,
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index f8f39e53..ff0d6f54 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -50,6 +49,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               const char *iface,
                                               int ifindex,
                                               GBytes *hwaddr,
+                                              GBytes *bcast_hwaddr,
                                               const char *uuid,
                                               guint32 route_table,
                                               guint32 route_metric,
@@ -67,6 +67,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               const char *iface,
                                               int ifindex,
                                               GBytes *hwaddr,
+                                              GBytes *bcast_hwaddr,
                                               const struct in6_addr *ll_addr,
                                               const char *uuid,
                                               guint32 route_table,
@@ -85,7 +86,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
 /* For testing only */
 extern const char* nm_dhcp_helper_path;
 
-extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4];
+extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5];
 
 void nmtst_dhcp_manager_unget (gpointer singleton_instance);
 
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/dhcp/nm-dhcp-nettools.c
new file mode 100644
index 00000000..3aa92180
--- /dev/null
+++ b/src/dhcp/nm-dhcp-nettools.c
@@ -0,0 +1,1278 @@
+/*
+ * 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.
+ */
+
+#include "nm-default.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <net/if_arp.h>
+
+#include "nm-sd-adapt-shared.h"
+#include "hostname-util.h"
+
+#include "nm-glib-aux/nm-dedup-multi.h"
+#include "nm-std-aux/unaligned.h"
+
+#include "nm-utils.h"
+#include "nm-config.h"
+#include "nm-dhcp-utils.h"
+#include "nm-dhcp-options.h"
+#include "nm-core-utils.h"
+#include "NetworkManagerUtils.h"
+#include "platform/nm-platform.h"
+#include "nm-dhcp-client-logging.h"
+#include "n-dhcp4/src/n-dhcp4.h"
+#include "systemd/nm-sd-utils-shared.h"
+
+/*****************************************************************************/
+
+#define NM_TYPE_DHCP_NETTOOLS            (nm_dhcp_nettools_get_type ())
+#define NM_DHCP_NETTOOLS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettools))
+#define NM_DHCP_NETTOOLS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass))
+#define NM_IS_DHCP_NETTOOLS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_NETTOOLS))
+#define NM_IS_DHCP_NETTOOLS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_NETTOOLS))
+#define NM_DHCP_NETTOOLS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass))
+
+typedef struct _NMDhcpNettools NMDhcpNettools;
+typedef struct _NMDhcpNettoolsClass NMDhcpNettoolsClass;
+
+static GType nm_dhcp_nettools_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	NDhcp4Client *client;
+	NDhcp4ClientProbe *probe;
+	NDhcp4ClientLease *lease;
+	GIOChannel *channel;
+	guint event_id;
+} NMDhcpNettoolsPrivate;
+
+struct _NMDhcpNettools {
+	NMDhcpClient parent;
+	NMDhcpNettoolsPrivate _priv;
+};
+
+struct _NMDhcpNettoolsClass {
+	NMDhcpClientClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpNettools, nm_dhcp_nettools, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_NETTOOLS_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpNettools, NM_IS_DHCP_NETTOOLS)
+
+/*****************************************************************************/
+
+#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,
+	NM_IN_ADDR_CLASS_INVALID,
+};
+
+static int
+in_addr_class (struct in_addr addr)
+{
+	switch (ntohl (addr.s_addr) >> 24) {
+	case   0 ... 127:
+		return NM_IN_ADDR_CLASS_A;
+	case 128 ... 191:
+		return NM_IN_ADDR_CLASS_B;
+	case 192 ... 223:
+		return NM_IN_ADDR_CLASS_C;
+	default:
+		return NM_IN_ADDR_CLASS_INVALID;
+	}
+}
+
+static gboolean
+lease_option_consume (void *out,
+                      size_t n_out,
+                      uint8_t **datap,
+                      size_t *n_datap)
+{
+	if (*n_datap < n_out)
+		return FALSE;
+
+	memcpy (out, *datap, n_out);
+	*datap += n_out;
+	*n_datap -= n_out;
+	return TRUE;
+}
+
+static gboolean
+lease_option_next_in_addr (struct in_addr *addrp,
+                           uint8_t **datap,
+                           size_t *n_datap)
+{
+	return lease_option_consume (addrp, sizeof (struct in_addr), datap, n_datap);
+}
+
+static gboolean
+lease_option_next_route (struct in_addr *destp,
+                         uint8_t *plenp,
+                         struct in_addr *gatewayp,
+                         gboolean classless,
+                         uint8_t **datap,
+                         size_t *n_datap)
+{
+	struct in_addr dest = {}, gateway;
+	uint8_t *data = *datap;
+	size_t n_data = *n_datap;
+	uint8_t plen;
+
+	if (classless) {
+		if (!lease_option_consume (&plen, sizeof (plen), &data, &n_data))
+			return FALSE;
+
+		if (plen > 32)
+			return FALSE;
+
+		if (!lease_option_consume (&dest, plen / 8, &data, &n_data))
+			return FALSE;
+	} else {
+		if (!lease_option_next_in_addr (&dest, &data, &n_data))
+			return FALSE;
+
+		switch (in_addr_class (dest)) {
+		case NM_IN_ADDR_CLASS_A:
+			plen = 8;
+			break;
+		case NM_IN_ADDR_CLASS_B:
+			plen = 16;
+			break;
+		case NM_IN_ADDR_CLASS_C:
+			plen = 24;
+			break;
+		case NM_IN_ADDR_CLASS_INVALID:
+			return FALSE;
+		}
+	}
+
+	dest.s_addr = nm_utils_ip4_address_clear_host_address (dest.s_addr, plen);
+
+	if (!lease_option_next_in_addr (&gateway, &data, &n_data))
+		return FALSE;
+
+	*destp = dest;
+	*plenp = plen;
+	*gatewayp = gateway;
+	*datap = data;
+	*n_datap = n_data;
+	return TRUE;
+}
+
+static gboolean
+lease_option_print_label (GString *str, size_t n_label, uint8_t **datap, size_t *n_datap)
+{
+	for (size_t i = 0; i < n_label; ++i) {
+		uint8_t c;
+
+		if (!lease_option_consume(&c, sizeof (c), datap, n_datap))
+			return FALSE;
+
+		switch (c) {
+                case 'a' ... 'z':
+                case 'A' ... 'Z':
+		case '0' ... '9':
+                case '-':
+		case '_':
+			g_string_append_c(str, c);
+			break;
+		case '.':
+		case '\\':
+			g_string_append_printf(str, "\\%c", c);
+			break;
+		default:
+			g_string_append_printf(str, "\\%3d", c);
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+lease_option_print_domain_name (GString *str, uint8_t *cache, size_t *n_cachep, uint8_t **datap, size_t *n_datap)
+{
+	uint8_t *domain;
+	size_t n_domain, n_cache = *n_cachep;
+	uint8_t **domainp = datap;
+	size_t *n_domainp = n_datap;
+	gboolean first = TRUE;
+	uint8_t c;
+
+	/*
+	 * We are given two adjacent memory regions. The @cache contains alreday parsed
+	 * domain names, and the @datap contains the remaining data to parse.
+	 *
+	 * A domain name is formed from a sequence of labels. Each label start with
+	 * a length byte, where the two most significant bits are unset. A zero-length
+	 * label indicates the end of the domain name.
+	 *
+	 * Alternatively, a label can be followed by an offset (indicated by the two
+	 * most significant bits being set in the next byte that is read). The offset
+	 * is an offset into the cache, where the next label of the domain name can
+	 * be found.
+	 *
+	 * Note, that each time a jump to an offset is performed, the size of the
+	 * cache shrinks, so this is guaranteed to terminate.
+	 */
+	if (cache + n_cache != *datap)
+		return FALSE;
+
+	for (;;) {
+		if (!lease_option_consume(&c, sizeof (c), domainp, n_domainp))
+			return FALSE;
+
+		switch (c & 0xC0) {
+		case 0x00: /* label length */
+		{
+			size_t n_label = c;
+
+			if (n_label == 0) {
+				/*
+				 * We reached the final label of the domain name. Adjust
+				 * the cache to include the consumed data, and return.
+				 */
+				*n_cachep = *datap - cache;
+				return TRUE;
+			}
+
+			if (!first) {
+				g_string_append_c(str, '.');
+				first = FALSE;
+			}
+
+			if (!lease_option_print_label (str, n_label, domainp, n_domainp))
+				return FALSE;
+
+			break;
+		}
+		case 0xC0: /* back pointer */
+		{
+			size_t offset = (c & 0x3F) << 16;
+
+			/*
+			 * The offset is given as two bytes (in big endian), where the
+			 * two high bits are masked out.
+			 */
+
+			if (!lease_option_consume (&c, sizeof (c), domainp, n_domainp))
+				return FALSE;
+
+			offset += c;
+
+			if (offset >= n_cache)
+				return FALSE;
+
+			domain = cache + offset;
+			n_domain = n_cache - offset;
+			n_cache = offset;
+
+			domainp = &domain;
+			n_domainp = &n_domain;
+
+			break;
+		}
+		default:
+			return FALSE;
+		}
+	}
+}
+
+static gboolean
+lease_get_in_addr (NDhcp4ClientLease *lease,
+                   guint8 option,
+                   struct in_addr *addrp) {
+	struct in_addr addr;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, option, &data, &n_data);
+	if (r)
+		return FALSE;
+
+	if (!lease_option_next_in_addr (&addr, &data, &n_data))
+		return FALSE;
+
+	if (n_data != 0)
+		return FALSE;
+
+	*addrp = addr;
+	return TRUE;
+}
+
+static gboolean
+lease_get_u16 (NDhcp4ClientLease *lease,
+               uint8_t option,
+               uint16_t *u16p)
+{
+	uint8_t *data;
+	size_t n_data;
+	uint16_t be16;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, option, &data, &n_data);
+	if (r)
+		return FALSE;
+
+	if (n_data != sizeof (be16))
+		return FALSE;
+
+	memcpy (&be16, data, sizeof (be16));
+
+	*u16p = ntohs(be16);
+	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 ();
+	struct in_addr a_address;
+	struct in_addr a_netmask;
+	guint32 a_plen;
+	guint64 a_lifetime;
+
+	n_dhcp4_client_lease_get_yiaddr (lease, &a_address);
+	if (a_address.s_addr == INADDR_ANY) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get address from lease");
+		return FALSE;
+	}
+
+	/* n_dhcp4_client_lease_get_lifetime() never fails */
+	n_dhcp4_client_lease_get_lifetime (lease, &a_lifetime);
+
+	if (!lease_get_in_addr (lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get netmask from lease");
+		return FALSE;
+	}
+
+	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,
+	                           addr_str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
+	                           nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+
+	LOG_LEASE (LOGD_DHCP4, "expires in %u seconds",
+	           (guint) ((a_lifetime - ts)/1000000000));
+	nm_dhcp_option_add_option_u64 (options,
+	                               _nm_dhcp_option_dhcp4_options,
+	                               NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
+	                               (guint64) (a_lifetime / 1000000000));
+
+	nm_ip4_config_add_address (ip4_config,
+	                           &((const NMPlatformIP4Address) {
+	                                   .address      = a_address.s_addr,
+	                                   .peer_address = a_address.s_addr,
+	                                   .plen         = a_plen,
+	                                   .addr_source  = NM_IP_CONFIG_SOURCE_DHCP,
+	                                   .timestamp    = ts / 1000000000,
+	                                   .lifetime     = (a_lifetime - ts) / 1000000000,
+	                                   .preferred    = (a_lifetime - ts) / 1000000000,
+	                           }));
+
+	return TRUE;
+}
+
+static void
+lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
+                                 const char *iface,
+                                 NMIP4Config *ip4_config,
+                                 GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+	struct in_addr addr;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, &data, &n_data);
+	if (r)
+		return;
+
+	nm_gstring_prepare (&str);
+
+	while (lease_option_next_in_addr (&addr, &data, &n_data)) {
+
+		nm_utils_inet4_ntop (addr.s_addr, addr_str);
+		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
+
+		if (   addr.s_addr == 0
+		    || nm_ip4_addr_is_localhost (addr.s_addr)) {
+			/* Skip localhost addresses, like also networkd does.
+			 * See https://github.com/systemd/systemd/issues/4524. */
+			continue;
+		}
+		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,
+	                           str->str);
+}
+
+static void
+lease_parse_routes (NDhcp4ClientLease *lease,
+                    const char *iface,
+                    NMIP4Config *ip4_config,
+                    GHashTable *options,
+                    guint32 route_table,
+                    guint32 route_metric)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	char dest_str[NM_UTILS_INET_ADDRSTRLEN];
+	char gateway_str[NM_UTILS_INET_ADDRSTRLEN];
+	const char *s;
+	struct in_addr dest, gateway;
+	uint8_t plen;
+	guint32 m;
+	gboolean has_router_from_classless = FALSE, has_classless = FALSE;
+	guint32 default_route_metric = route_metric;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, &data, &n_data);
+	if (!r) {
+		nm_gstring_prepare (&str);
+
+		has_classless = TRUE;
+
+		while (lease_option_next_route (&dest, &plen, &gateway, TRUE, &data, &n_data)) {
+
+			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,
+			                        (int) plen,
+			                        gateway_str);
+
+			if (plen == 0) {
+				/* if there are multiple default routes, we add them with differing
+				 * metrics. */
+				m = default_route_metric;
+				if (default_route_metric < G_MAXUINT32)
+					default_route_metric++;
+
+				has_router_from_classless = TRUE;
+			} else {
+				m = route_metric;
+                        }
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .network       = dest.s_addr,
+			                             .plen          = plen,
+			                             .gateway       = gateway.s_addr,
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .metric        = m,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                         }),
+			                         NULL);
+		}
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
+		                           str->str);
+	}
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &data, &n_data);
+	if (!r) {
+		nm_gstring_prepare (&str);
+
+		while (lease_option_next_route (&dest, &plen, &gateway, FALSE, &data, &n_data)) {
+
+			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,
+			                        (int) plen,
+			                        gateway_str);
+
+			if (has_classless) {
+				/* RFC 3443: if the DHCP server returns both a Classless Static Routes
+				 * option and a Static Routes option, the DHCP client MUST ignore the
+				 * Static Routes option. */
+				continue;
+			}
+
+			if (plen == 0) {
+				/* for option 33 (static route), RFC 2132 says:
+				 *
+				 * The default route (0.0.0.0) is an illegal destination for a static
+				 * route. */
+				continue;
+			}
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .network       = dest.s_addr,
+			                             .plen          = plen,
+			                             .gateway       = gateway.s_addr,
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .metric        = route_metric,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                         }),
+			                         NULL);
+		}
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
+		                           str->str);
+	}
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_ROUTER, &data, &n_data);
+	if (!r) {
+		nm_gstring_prepare (&str);
+
+		while (lease_option_next_in_addr (&gateway, &data, &n_data)) {
+			s = nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), s);
+
+			if (gateway.s_addr == 0) {
+				/* silently skip 0.0.0.0 */
+				continue;
+			}
+
+			if (has_router_from_classless) {
+				/* If the DHCP server returns both a Classless Static Routes option and a
+				 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
+				 *
+				 * Be more lenient and ignore the Router option only if Classless Static
+				 * Routes contain a default gateway (as other DHCP backends do).
+				 */
+				continue;
+			}
+
+			/* if there are multiple default routes, we add them with differing
+			 * metrics. */
+			m = default_route_metric;
+			if (default_route_metric < G_MAXUINT32)
+				default_route_metric++;
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                                 .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                                 .gateway       = gateway.s_addr,
+			                                 .table_coerced = nm_platform_route_table_coerce (route_table),
+			                                 .metric        = m,
+			                         }),
+			                         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,
+		                           str->str);
+	}
+}
+
+static void
+lease_parse_mtu (NDhcp4ClientLease *lease,
+                 const char *iface,
+                 NMIP4Config *ip4_config,
+                 GHashTable *options)
+{
+	uint16_t mtu;
+
+	if (!lease_get_u16 (lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &mtu))
+		return;
+
+	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,
+	                               mtu);
+	nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
+}
+
+static void
+lease_parse_metered (NDhcp4ClientLease *lease,
+                     const char *iface,
+                     NMIP4Config *ip4_config,
+                     GHashTable *options)
+{
+	gboolean metered = FALSE;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &data, &n_data);
+	if (r) {
+		metered = FALSE;
+	} else {
+		metered = !!memmem (data, n_data, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
+	}
+
+	LOG_LEASE (LOGD_DHCP4, "%s", metered ? "metered" : "unmetered");
+	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;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+	struct in_addr addr;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_NTP_SERVER, &data, &n_data);
+	if (r)
+		return;
+
+	nm_gstring_prepare (&str);
+
+	while (lease_option_next_in_addr (&addr, &data, &n_data)) {
+		nm_utils_inet4_ntop (addr.s_addr, addr_str);
+		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);
+}
+
+static void
+lease_parse_hostname (NDhcp4ClientLease *lease,
+                      const char *iface,
+                      GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &data, &n_data);
+	if (r)
+		return;
+
+	str = g_string_new_len ((char *)data, n_data);
+
+	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);
+}
+
+static void
+lease_parse_domainname (NDhcp4ClientLease *lease,
+                        const char *iface,
+                        NMIP4Config *ip4_config,
+                        GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	gs_strfreev char **domains = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &data, &n_data);
+	if (r)
+		return;
+
+	str = g_string_new_len ((char *)data, n_data);
+
+	/* Multiple domains sometimes stuffed into option 15 "Domain Name". */
+	domains = g_strsplit (str->str, " ", 0);
+	nm_gstring_prepare (&str);
+
+	for (char **d = domains; *d; d++) {
+		if (is_localhost(*d))
+			return;
+
+		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);
+}
+
+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;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &data, &n_data);
+	if (r)
+		return;
+
+	cache = 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);
+	}
+	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,
+	                           str->str);
+}
+
+static void
+lease_parse_root_path (NDhcp4ClientLease *lease,
+                       const char *iface,
+                       GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &data, &n_data);
+	if (r)
+		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);
+}
+
+static void
+lease_parse_wpad (NDhcp4ClientLease *lease,
+                  const char *iface,
+                  GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, &data, &n_data);
+	if (r)
+		return;
+
+	str = g_string_new_len ((char *)data, n_data);
+	LOG_LEASE (LOGD_DHCP4, "wpad '%s'", str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
+	                           str->str);
+}
+
+static NMIP4Config *
+lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
+                     const char *iface,
+                     int ifindex,
+                     NDhcp4ClientLease *lease,
+                     guint32 route_table,
+                     guint32 route_metric,
+                     GHashTable **out_options,
+                     GError **error)
+{
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
+
+	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;
+
+	if (!lease_parse_address (lease, iface, 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_hostname (lease, iface, options);
+	lease_parse_ntps (lease, iface, options);
+	lease_parse_root_path (lease, iface, options);
+	lease_parse_wpad (lease, iface, options);
+
+	NM_SET_OUT (out_options, g_steal_pointer (&options));
+	return g_steal_pointer (&ip4_config);
+}
+
+/*****************************************************************************/
+
+static void
+bound4_handle (NMDhcpNettools *self, NDhcp4ClientLease *lease)
+{
+	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;
+	GError *error = NULL;
+
+	_LOGT ("lease available");
+
+	ip4_config = lease_to_ip4_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
+	                                  iface,
+	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
+	                                  lease,
+	                                  nm_dhcp_client_get_route_table (NM_DHCP_CLIENT (self)),
+	                                  nm_dhcp_client_get_route_metric (NM_DHCP_CLIENT (self)),
+	                                  &options,
+	                                  &error);
+	if (!ip4_config) {
+		_LOGW ("%s", error->message);
+		g_clear_error (&error);
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
+	}
+
+	nm_dhcp_option_add_requests_to_options (options, _nm_dhcp_option_dhcp4_options);
+
+	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+	                          NM_DHCP_STATE_BOUND,
+	                          NM_IP_CONFIG_CAST (ip4_config),
+	                          options);
+}
+
+static gboolean
+dhcp4_event_handle (NMDhcpNettools *self,
+                    NDhcp4ClientEvent *event)
+{
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	int r;
+
+	_LOGT ("client event %d", event->event);
+
+	switch (event->event) {
+	case N_DHCP4_CLIENT_EVENT_OFFER:
+		/* always accept the first lease */
+		r = n_dhcp4_client_lease_select (event->offer.lease);
+		if (r) {
+			_LOGW ("selecting lease failed: %d", r);
+		}
+		break;
+	case N_DHCP4_CLIENT_EVENT_EXPIRED:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_EXPIRE, NULL, NULL);
+		break;
+	case N_DHCP4_CLIENT_EVENT_RETRACTED:
+	case N_DHCP4_CLIENT_EVENT_CANCELLED:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		break;
+	case N_DHCP4_CLIENT_EVENT_GRANTED:
+		priv->lease = n_dhcp4_client_lease_ref (event->granted.lease);
+		bound4_handle (self, event->granted.lease);
+		break;
+	case N_DHCP4_CLIENT_EVENT_EXTENDED:
+		bound4_handle (self, event->extended.lease);
+		break;
+	case N_DHCP4_CLIENT_EVENT_DOWN:
+		/* ignore down events, they are purely informational */
+		break;
+	default:
+		_LOGW ("unhandled DHCP event %d", event->event);
+		break;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+dhcp4_event_cb (GIOChannel *source,
+                GIOCondition condition,
+                gpointer data)
+{
+	NMDhcpNettools *self = data;
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	NDhcp4ClientEvent *event;
+	int r;
+
+	r = n_dhcp4_client_dispatch (priv->client);
+	if (r < 0)
+		return G_SOURCE_CONTINUE;
+
+	while (!n_dhcp4_client_pop_event (priv->client, &event) && event) {
+		dhcp4_event_handle (self, event);
+	}
+
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+nettools_create (NMDhcpNettools *self,
+                 const char *dhcp_anycast_addr,
+                 GError **error)
+{
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	nm_auto (n_dhcp4_client_config_freep) NDhcp4ClientConfig *config = NULL;
+	nm_auto (n_dhcp4_client_unrefp) NDhcp4Client *client = NULL;
+	GBytes *hwaddr;
+	GBytes *bcast_hwaddr;
+	const uint8_t *hwaddr_arr;
+	const uint8_t *bcast_hwaddr_arr;
+	gsize hwaddr_len;
+	gsize bcast_hwaddr_len;
+	GBytes *client_id;
+	gs_unref_bytes GBytes *client_id_new = NULL;
+	const uint8_t *client_id_arr;
+	size_t client_id_len;
+	int r, fd, arp_type, transport;
+
+	g_return_val_if_fail (!priv->client, FALSE);
+
+	hwaddr = nm_dhcp_client_get_hw_addr (NM_DHCP_CLIENT (self));
+	if (   !hwaddr
+	    || !(hwaddr_arr = g_bytes_get_data (hwaddr, &hwaddr_len))
+	    || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len)) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
+		return FALSE;
+	}
+
+	bcast_hwaddr = nm_dhcp_client_get_broadcast_hw_addr (NM_DHCP_CLIENT (self));
+	bcast_hwaddr_arr = g_bytes_get_data (bcast_hwaddr, &bcast_hwaddr_len);
+
+	switch (arp_type) {
+	case ARPHRD_ETHER:
+		transport = N_DHCP4_TRANSPORT_ETHERNET;
+		break;
+	case ARPHRD_INFINIBAND:
+		transport = N_DHCP4_TRANSPORT_INFINIBAND;
+		break;
+	default:
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "unsupported ARP type");
+		return FALSE;
+	}
+
+	/* Note that we always set a client-id. In particular for infiniband that is necessary,
+	 * see https://tools.ietf.org/html/rfc4390#section-2.1 . */
+	client_id = nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self));
+	if (!client_id) {
+		client_id_new = nm_utils_dhcp_client_id_mac (arp_type, hwaddr_arr, hwaddr_len);
+		client_id = client_id_new;
+	}
+
+	if (   !(client_id_arr = g_bytes_get_data (client_id, &client_id_len))
+	    || client_id_len < 2) {
+
+		/* invalid client-ids are not expected. */
+		nm_assert_not_reached ();
+
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "no valid IPv4 client-id");
+		return FALSE;
+	}
+
+	r = n_dhcp4_client_config_new (&config);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to create client-config: %s");
+		return FALSE;
+	}
+
+	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);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to set client-id: %s");
+		return FALSE;
+	}
+
+	r = n_dhcp4_client_new (&client, config);
+	if (r) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "failed to create client: error %d", r);
+		return FALSE;
+	}
+
+	priv->client = client;
+	client = NULL;
+
+	n_dhcp4_client_get_fd (priv->client, &fd);
+	priv->channel = g_io_channel_unix_new (fd);
+	priv->event_id = g_io_add_watch (priv->channel, G_IO_IN, dhcp4_event_cb, self);
+
+	return TRUE;
+}
+
+static gboolean
+_accept (NMDhcpClient *client,
+         GError **error)
+{
+	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	int r;
+
+	g_return_val_if_fail (priv->lease, FALSE);
+
+	_LOGT ("accept");
+
+	r = n_dhcp4_client_lease_accept (priv->lease);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to accept lease: %s");
+		return FALSE;
+	}
+
+	priv->lease = n_dhcp4_client_lease_unref (priv->lease);
+
+	return TRUE;
+}
+
+static gboolean
+decline (NMDhcpClient *client,
+         const char *error_message,
+         GError **error)
+{
+	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	int r;
+
+	g_return_val_if_fail (priv->lease, FALSE);
+
+	_LOGT ("dhcp4-client: decline");
+
+	r = n_dhcp4_client_lease_decline (priv->lease, error_message);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to decline lease: %s");
+		return FALSE;
+	}
+
+	priv->lease = n_dhcp4_client_lease_unref (priv->lease);
+
+	return TRUE;
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const char *last_ip4_address,
+           GError **error)
+{
+	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);
+	struct in_addr last_addr = { 0 };
+	const char *hostname;
+	int r, i;
+
+	g_return_val_if_fail (!priv->probe, FALSE);
+
+	if (!nettools_create (self, dhcp_anycast_addr, error))
+		return FALSE;
+
+	r = n_dhcp4_client_probe_config_new (&config);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to create dhcp-client-probe-config: %s");
+		return FALSE;
+	}
+
+	/*
+	 * FIXME:
+	 * Select, or configure, a reasonable start delay, to protect poor servers beeing flooded.
+	 */
+	n_dhcp4_client_probe_config_set_start_delay (config, 1);
+
+	if (last_ip4_address) {
+		inet_pton (AF_INET, last_ip4_address, &last_addr);
+		n_dhcp4_client_probe_config_set_requested_ip (config, last_addr);
+	}
+
+	/* Add requested options */
+	for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) {
+		if (_nm_dhcp_option_dhcp4_options[i].include) {
+			nm_assert (_nm_dhcp_option_dhcp4_options[i].option_num <= 255);
+			n_dhcp4_client_probe_config_request_option (config,
+			                                            _nm_dhcp_option_dhcp4_options[i].option_num);
+		}
+	}
+
+	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;
+			}
+
+			r = n_dhcp4_client_probe_config_append_option (config,
+			                                               NM_DHCP_OPTION_DHCP4_CLIENT_FQDN,
+			                                               buffer,
+			                                               3 + r);
+			if (r) {
+				nm_utils_error_set_errno (error, r, "failed to set DHCP FQDN: %s");
+				return FALSE;
+			}
+		} else {
+			r = n_dhcp4_client_probe_config_append_option (config,
+			                                               NM_DHCP_OPTION_DHCP4_HOST_NAME,
+			                                               hostname,
+			                                               strlen (hostname));
+			if (r) {
+				nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
+				return FALSE;
+			}
+		}
+	}
+
+	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");
+		return FALSE;
+	}
+
+	_LOGT ("dhcp-client4: start %p", (gpointer) priv->client);
+
+	nm_dhcp_client_start_timeout (client);
+	return TRUE;
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const struct in6_addr *ll_addr,
+           NMSettingIP6ConfigPrivacy privacy,
+           guint needed_prefixes,
+           GError **error)
+{
+	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "nettools plugin does not support IPv6");
+	return FALSE;
+}
+
+static void
+stop (NMDhcpClient *client,
+      gboolean release)
+{
+	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_nettools_parent_class)->stop (client, release);
+
+	_LOGT ("dhcp-client4: stop %p",
+	       (gpointer) priv->client);
+
+	priv->probe = n_dhcp4_client_probe_free (priv->probe);
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_nettools_init (NMDhcpNettools *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE ((NMDhcpNettools *) object);
+
+	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);
+	nm_clear_pointer (&priv->probe, n_dhcp4_client_probe_free);
+	nm_clear_pointer (&priv->client, n_dhcp4_client_unref);
+
+	G_OBJECT_CLASS (nm_dhcp_nettools_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_nettools_class_init (NMDhcpNettoolsClass *class)
+{
+	NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = dispose;
+
+	client_class->ip4_start = ip4_start;
+	client_class->ip6_start = ip6_start;
+	client_class->accept = _accept;
+	client_class->decline = decline;
+	client_class->stop = stop;
+}
+
+const NMDhcpClientFactory _nm_dhcp_client_factory_nettools = {
+	.name = "nettools",
+	.get_type = nm_dhcp_nettools_get_type,
+	.get_path = NULL,
+};
diff --git a/src/dhcp/nm-dhcp-options.c b/src/dhcp/nm-dhcp-options.c
new file mode 100644
index 00000000..21963070
--- /dev/null
+++ b/src/dhcp/nm-dhcp-options.c
@@ -0,0 +1,278 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dhcp-options.h"
+
+
+#define REQPREFIX "requested_"
+
+#define REQ(_num, _name, _include) \
+	{ \
+		.name = REQPREFIX""_name, \
+		.option_num = _num, \
+		.include = _include, \
+	}
+
+const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
+	REQ (NM_DHCP_OPTION_DHCP4_SUBNET_MASK,                       "subnet_mask",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TIME_OFFSET,                       "time_offset",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,                "domain_name_servers",             TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_HOST_NAME,                         "host_name",                       TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,                       "domain_name",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,                     "interface_mtu",                   TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BROADCAST,                         "broadcast_address",               TRUE ),
+	/* RFC 3442: The Classless Static Routes option code MUST appear in the parameter
+	 *   request list prior to both the Router option code and the Static
+	 *   Routes option code, if present. */
+	REQ (NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,            "rfc3442_classless_static_routes", TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROUTER,                            "routers",                         TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,                      "static_routes",                   TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NIS_DOMAIN,                        "nis_domain",                      TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NIS_SERVERS,                       "nis_servers",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NTP_SERVER,                        "ntp_servers",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SERVER_ID,                         "dhcp_server_identifier",          TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,                "domain_search",                   TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE,    "ms_classless_static_routes",      TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,       "wpad",                            TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROOT_PATH,                         "root_path",                       TRUE ),
+
+	REQ (NM_DHCP_OPTION_DHCP4_TIME_SERVERS,                      "time_servers",                    FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IEN116_NAME_SERVERS,               "ien116_name_servers",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOG_SERVERS,                       "log_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_COOKIE_SERVERS,                    "cookie_servers",                  FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LPR_SERVERS,                       "lpr_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IMPRESS_SERVERS,                   "impress_servers",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RESOURCE_LOCATION_SERVERS,         "resource_location_servers",       FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BOOT_FILE_SIZE,                    "boot_size",                       FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_MERIT_DUMP,                        "merit_dump",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SWAP_SERVER,                       "swap_server",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_EXTENSIONS_PATH,                   "extensions_path",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ENABLE_IP_FORWARDING,              "ip_forwarding",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ENABLE_SRC_ROUTING,                "non_local_source_routing",        FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_POLICY_FILTER,                     "policy_filter",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_MDR,                     "max_dgram_reassembly",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_TTL,                     "default_ip_ttl",                  FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_MTU_AGING_TIMEOUT,       "path_mtu_aging_timeout",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PATH_MTU_PLATEAU_TABLE,            "path_mtu_plateau_table",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ALL_SUBNETS_LOCAL,                 "all_subnets_local",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PERFORM_MASK_DISCOVERY,            "perform_mask_discovery",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_MASK_SUPPLIER,                     "mask_supplier",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROUTER_DISCOVERY,                  "router_discovery",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROUTER_SOLICITATION_ADDR,          "router_solicitation_address",     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TRAILER_ENCAPSULATION,             "trailer_encapsulation",           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT,                 "arp_cache_timeout",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION,           "ieee802_3_encapsulation",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL,                   "default_tcp_ttl",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL,            "tcp_keepalive_internal",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE,             "tcp_keepalive_garbage",           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC,                   "vendor_encapsulated_options",     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER,                "netbios_name_servers",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETBIOS_DD_SERVER,                 "netbios_dd_server",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_FONT_SERVERS,                      "font_servers",                    FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_X_DISPLAY_MANAGER,                 "x_display_manager",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,             "dhcp_lease_time",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME,                   "dhcp_renewal_time",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME,                 "dhcp_rebinding_time",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_CLIENT_ID,                         "dhcp_client_identifier",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE,                 "tcode",                           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NWIP_DOMAIN,                       "nwip_domain",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NWIP_SUBOPTIONS,                   "nwip_suboptions",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NISPLUS_DOMAIN,                    "nisplus_domain",                  FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NISPLUS_SERVERS,                   "nisplus_servers",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TFTP_SERVER_NAME,                  "tftp_server_name",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME,                     "bootfile_name",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_MOBILE_IP_HOME_AGENT,              "mobile_ip_home_agent",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SMTP_SERVER,                       "smtp_server",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_POP_SERVER,                        "pop_server",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NNTP_SERVER,                       "nntp_server",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_WWW_SERVER,                        "www_server",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_FINGER_SERVER,                     "finger_server",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IRC_SERVER,                        "irc_server",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_STREETTALK_SERVER,                 "streettalk_server",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_STREETTALK_DIR_ASSIST_SERVER,      "streettalk_directory_assistance_server", FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SLP_DIRECTORY_AGENT,               "slp_directory_agent",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SLP_SERVICE_SCOPE,                 "slp_service_scope",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_CLIENT_FQDN,                       "fqdn",                            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RELAY_AGENT_INFORMATION,           "relay_agent_information",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NDS_SERVERS,                       "nds_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NDS_TREE_NAME,                     "nds_tree_name",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NDS_CONTEXT,                       "nds_context",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_NAMES,             "bcms_controller_names",           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_ADDRESS,           "bcms_controller_address",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_CLIENT_LAST_TRANSACTION,           "client_last_transaction_time",    FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ASSOCIATED_IP,                     "associated_ip",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PXE_SYSTEM_TYPE,                   "pxe_system_type",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PXE_INTERFACE_ID,                  "pxe_interface_id",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID,                     "pxe_client_id",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_UAP_SERVERS,                       "uap_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC,                     "geoconf_civic",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS,            "netinfo_server_address",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG,                "netinfo_server_tag",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DEFAULT_URL,                       "default_url",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_AUTO_CONFIG,                       "auto_config",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NAME_SERVICE_SEARCH,               "name_service_search",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SUBNET_SELECTION,                  "subnet_selection",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_VIVCO,                             "vivco",                           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_VIVSO,                             "vivso",                           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PANA_AGENT,                        "pana_agent",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_LOST,                           "v4_lost",                         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SIP_UA_CS_DOMAINS,                 "sip_ua_cs_domains",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IPV4_ADDRESS_ANDSF,                "ipv4_address_andsf",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RDNSS_SELECTION,                   "rndss_selection",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS,               "tftp_server_address",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS,                     "v4_portparams",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL,                 "v4_captive_portal",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE,                 "loader_configfile",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX,                 "loader_pathprefix",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME,                 "loader_reboottime",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_OPTION_6RD,                        "option_6rd",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_ACCESS_DOMAIN,                  "v4_access_domain",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_224,                       "private_224",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_225,                       "private_225",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_226,                       "private_226",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_227,                       "private_227",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_228,                       "private_228",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_229,                       "private_229",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_230,                       "private_230",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_231,                       "private_231",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_232,                       "private_232",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_233,                       "private_233",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_234,                       "private_234",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_235,                       "private_235",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_236,                       "private_236",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_237,                       "private_237",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_238,                       "private_238",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_239,                       "private_239",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_240,                       "private_240",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_241,                       "private_241",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_242,                       "private_242",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_243,                       "private_243",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_244,                       "private_244",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_245,                       "private_245",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_246,                       "private_246",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_247,                       "private_247",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_248,                       "private_248",                     FALSE ),
+	/* NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE */
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_250,                       "private_250",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_251,                       "private_251",                     FALSE ),
+	/* NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY */
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_253,                       "private_253",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_254,                       "private_254",                     FALSE ),
+
+	/* Internal values */
+	REQ (NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,                     "ip_address",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NM_EXPIRY,                         "expiry",                          FALSE ),
+
+	{ 0 }
+};
+
+const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = {
+	REQ (NM_DHCP_OPTION_DHCP6_CLIENTID,                         "dhcp6_client_id",     FALSE ),
+
+	/* Don't request server ID by default; some servers don't reply to
+	 * Information Requests that request the Server ID.
+	 */
+	REQ (NM_DHCP_OPTION_DHCP6_SERVERID,                         "dhcp6_server_id",     FALSE ),
+
+	REQ (NM_DHCP_OPTION_DHCP6_DNS_SERVERS,                      "dhcp6_name_servers",  TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP6_DOMAIN_LIST,                      "dhcp6_domain_search", TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP6_SNTP_SERVERS,                     "dhcp6_sntp_servers",  TRUE ),
+
+	/* Internal values */
+	REQ (NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,                    "ip6_address",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN,                     "ip6_prefixlen",       FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_PREFERRED_LIFE,                "preferred_life",      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_MAX_LIFE,                      "max_life",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_STARTS,                        "starts",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_LIFE_STARTS,                   "life_starts",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_RENEW,                         "renew",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_REBIND,                        "rebind",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_IAID,                          "iaid",                FALSE ),
+
+	{ 0 }
+};
+
+
+const char *
+nm_dhcp_option_request_string (const NMDhcpOption *requests, guint option)
+{
+	guint i = 0;
+
+	while (requests[i].name) {
+		if (requests[i].option_num == option)
+			return requests[i].name + NM_STRLEN (REQPREFIX);
+		i++;
+	}
+
+	/* Option should always be found */
+	nm_assert_not_reached ();
+	return NULL;
+}
+
+void
+nm_dhcp_option_take_option (GHashTable *options,
+             const NMDhcpOption *requests,
+             guint option,
+             char *value)
+{
+	nm_assert (options);
+	nm_assert (requests);
+	nm_assert (value);
+
+	g_hash_table_insert (options,
+	                     (gpointer) nm_dhcp_option_request_string (requests, option),
+	                     value);
+}
+
+void
+nm_dhcp_option_add_option (GHashTable *options, const NMDhcpOption *requests, guint option, const char *value)
+{
+	if (options)
+		nm_dhcp_option_take_option (options, requests, option, g_strdup (value));
+}
+
+void
+nm_dhcp_option_add_option_u64 (GHashTable *options, const NMDhcpOption *requests, guint option, guint64 value)
+{
+	if (options)
+		nm_dhcp_option_take_option (options, requests, option, g_strdup_printf ("%" G_GUINT64_FORMAT, value));
+}
+
+void
+nm_dhcp_option_add_requests_to_options (GHashTable *options, const NMDhcpOption *requests)
+{
+	guint i;
+
+	if (!options)
+		return;
+
+	for (i = 0; requests[i].name; i++) {
+		if (requests[i].include)
+			g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
+	}
+}
+
+GHashTable *
+nm_dhcp_option_create_options_dict (void)
+{
+	return g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+}
+
diff --git a/src/dhcp/nm-dhcp-options.h b/src/dhcp/nm-dhcp-options.h
new file mode 100644
index 00000000..0dc7a276
--- /dev/null
+++ b/src/dhcp/nm-dhcp-options.h
@@ -0,0 +1,204 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_DHCP_OPTIONS_H__
+#define __NM_DHCP_OPTIONS_H__
+
+typedef enum {
+	NM_DHCP_OPTION_DHCP4_PAD                            = 0,
+	NM_DHCP_OPTION_DHCP4_SUBNET_MASK                    = 1,
+	NM_DHCP_OPTION_DHCP4_TIME_OFFSET                    = 2,
+	NM_DHCP_OPTION_DHCP4_ROUTER                         = 3,
+	NM_DHCP_OPTION_DHCP4_TIME_SERVERS                   = 4,
+	NM_DHCP_OPTION_DHCP4_IEN116_NAME_SERVERS            = 5,
+	NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER             = 6,
+	NM_DHCP_OPTION_DHCP4_LOG_SERVERS                    = 7,
+	NM_DHCP_OPTION_DHCP4_COOKIE_SERVERS                 = 8,
+	NM_DHCP_OPTION_DHCP4_LPR_SERVERS                    = 9,
+	NM_DHCP_OPTION_DHCP4_IMPRESS_SERVERS                = 10,
+	NM_DHCP_OPTION_DHCP4_RESOURCE_LOCATION_SERVERS      = 11,
+	NM_DHCP_OPTION_DHCP4_HOST_NAME                      = 12,
+	NM_DHCP_OPTION_DHCP4_BOOT_FILE_SIZE                 = 13,
+	NM_DHCP_OPTION_DHCP4_MERIT_DUMP                     = 14,
+	NM_DHCP_OPTION_DHCP4_DOMAIN_NAME                    = 15,
+	NM_DHCP_OPTION_DHCP4_SWAP_SERVER                    = 16,
+	NM_DHCP_OPTION_DHCP4_ROOT_PATH                      = 17,
+	NM_DHCP_OPTION_DHCP4_EXTENSIONS_PATH                = 18,
+	NM_DHCP_OPTION_DHCP4_ENABLE_IP_FORWARDING           = 19,
+	NM_DHCP_OPTION_DHCP4_ENABLE_SRC_ROUTING             = 20,
+	NM_DHCP_OPTION_DHCP4_POLICY_FILTER                  = 21,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_MDR                  = 22,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_TTL                  = 23,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_MTU_AGING_TIMEOUT    = 24,
+	NM_DHCP_OPTION_DHCP4_PATH_MTU_PLATEAU_TABLE         = 25,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_MTU                  = 26,
+	NM_DHCP_OPTION_DHCP4_ALL_SUBNETS_LOCAL              = 27,
+	NM_DHCP_OPTION_DHCP4_BROADCAST                      = 28,
+	NM_DHCP_OPTION_DHCP4_PERFORM_MASK_DISCOVERY         = 29,
+	NM_DHCP_OPTION_DHCP4_MASK_SUPPLIER                  = 30,
+	NM_DHCP_OPTION_DHCP4_ROUTER_DISCOVERY               = 31,
+	NM_DHCP_OPTION_DHCP4_ROUTER_SOLICITATION_ADDR       = 32,
+	NM_DHCP_OPTION_DHCP4_STATIC_ROUTE                   = 33,
+	NM_DHCP_OPTION_DHCP4_TRAILER_ENCAPSULATION          = 34,
+	NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT              = 35,
+	NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION        = 36,
+	NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL                = 37,
+	NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL         = 38,
+	NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE          = 39,
+	NM_DHCP_OPTION_DHCP4_NIS_DOMAIN                     = 40,
+	NM_DHCP_OPTION_DHCP4_NIS_SERVERS                    = 41,
+	NM_DHCP_OPTION_DHCP4_NTP_SERVER                     = 42,
+	NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC                = 43,
+	NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER             = 44,
+	NM_DHCP_OPTION_DHCP4_NETBIOS_DD_SERVER              = 45,
+	NM_DHCP_OPTION_DHCP4_FONT_SERVERS                   = 48,
+	NM_DHCP_OPTION_DHCP4_X_DISPLAY_MANAGER              = 49,
+	NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME          = 51,
+	NM_DHCP_OPTION_DHCP4_SERVER_ID                      = 54,
+	NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME                = 58,
+	NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME              = 59,
+	NM_DHCP_OPTION_DHCP4_CLIENT_ID                      = 61,
+	NM_DHCP_OPTION_DHCP4_NWIP_DOMAIN                    = 62,
+	NM_DHCP_OPTION_DHCP4_NWIP_SUBOPTIONS                = 63,
+	NM_DHCP_OPTION_DHCP4_NISPLUS_DOMAIN                 = 64,
+	NM_DHCP_OPTION_DHCP4_NISPLUS_SERVERS                = 65,
+	NM_DHCP_OPTION_DHCP4_TFTP_SERVER_NAME               = 66,
+	NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME                  = 67,
+	NM_DHCP_OPTION_DHCP4_MOBILE_IP_HOME_AGENT           = 68,
+	NM_DHCP_OPTION_DHCP4_SMTP_SERVER                    = 69,
+	NM_DHCP_OPTION_DHCP4_POP_SERVER                     = 70,
+	NM_DHCP_OPTION_DHCP4_NNTP_SERVER                    = 71,
+	NM_DHCP_OPTION_DHCP4_WWW_SERVER                     = 72,
+	NM_DHCP_OPTION_DHCP4_FINGER_SERVER                  = 73,
+	NM_DHCP_OPTION_DHCP4_IRC_SERVER                     = 74,
+	NM_DHCP_OPTION_DHCP4_STREETTALK_SERVER              = 75,
+	NM_DHCP_OPTION_DHCP4_STREETTALK_DIR_ASSIST_SERVER   = 76,
+	NM_DHCP_OPTION_DHCP4_SLP_DIRECTORY_AGENT            = 78,
+	NM_DHCP_OPTION_DHCP4_SLP_SERVICE_SCOPE              = 79,
+	NM_DHCP_OPTION_DHCP4_CLIENT_FQDN                    = 81,
+	NM_DHCP_OPTION_DHCP4_RELAY_AGENT_INFORMATION        = 82,
+	NM_DHCP_OPTION_DHCP4_NDS_SERVERS                    = 85,
+	NM_DHCP_OPTION_DHCP4_NDS_TREE_NAME                  = 86,
+	NM_DHCP_OPTION_DHCP4_NDS_CONTEXT                    = 87,
+	NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_NAMES          = 88,
+	NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_ADDRESS        = 89,
+	NM_DHCP_OPTION_DHCP4_CLIENT_LAST_TRANSACTION        = 91,
+	NM_DHCP_OPTION_DHCP4_ASSOCIATED_IP                  = 92,
+	NM_DHCP_OPTION_DHCP4_PXE_SYSTEM_TYPE                = 93,
+	NM_DHCP_OPTION_DHCP4_PXE_INTERFACE_ID               = 94,
+	NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID                  = 97,
+	NM_DHCP_OPTION_DHCP4_UAP_SERVERS                    = 98,
+	NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC                  = 99,
+	NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE              = 101,
+	NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS         = 112,
+	NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG             = 113,
+	NM_DHCP_OPTION_DHCP4_DEFAULT_URL                    = 114,
+	NM_DHCP_OPTION_DHCP4_AUTO_CONFIG                    = 116,
+	NM_DHCP_OPTION_DHCP4_NAME_SERVICE_SEARCH            = 117,
+	NM_DHCP_OPTION_DHCP4_SUBNET_SELECTION               = 118,
+	NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST             = 119,
+	NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE         = 121,
+	NM_DHCP_OPTION_DHCP4_VIVCO                          = 124,
+	NM_DHCP_OPTION_DHCP4_VIVSO                          = 125,
+	NM_DHCP_OPTION_DHCP4_PANA_AGENT                     = 136,
+	NM_DHCP_OPTION_DHCP4_V4_LOST                        = 137,
+	NM_DHCP_OPTION_DHCP4_SIP_UA_CS_DOMAINS              = 141,
+	NM_DHCP_OPTION_DHCP4_IPV4_ADDRESS_ANDSF             = 142,
+	NM_DHCP_OPTION_DHCP4_RDNSS_SELECTION                = 146,
+	NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS            = 150,
+	NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS                  = 159,
+	NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL              = 160,
+	NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE              = 209,
+	NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX              = 210,
+	NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME              = 211,
+	NM_DHCP_OPTION_DHCP4_OPTION_6RD                     = 212,
+	NM_DHCP_OPTION_DHCP4_V4_ACCESS_DOMAIN               = 213,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_224                    = 224,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_225                    = 225,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_226                    = 226,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_227                    = 227,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_228                    = 228,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_229                    = 229,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_230                    = 230,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_231                    = 231,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_232                    = 232,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_233                    = 233,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_234                    = 234,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_235                    = 235,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_236                    = 236,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_237                    = 237,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_238                    = 238,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_239                    = 239,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_240                    = 240,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_241                    = 241,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_242                    = 242,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_243                    = 243,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_244                    = 244,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_245                    = 245,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_246                    = 246,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_247                    = 247,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_248                    = 248,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE = 249,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_250                    = 250,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_251                    = 251,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY    = 252,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_253                    = 253,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_254                    = 254,
+	NM_DHCP_OPTION_DHCP4_END                            = 255,
+	/* Internal values */
+	NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS                  = 1024,
+	NM_DHCP_OPTION_DHCP4_NM_EXPIRY                      = 1025,
+} NMDhcpOptionDhcp4Options;
+
+typedef enum {
+	NM_DHCP_OPTION_DHCP6_CLIENTID          = 1,
+	NM_DHCP_OPTION_DHCP6_SERVERID          = 2,
+	NM_DHCP_OPTION_DHCP6_DNS_SERVERS       = 23,
+	NM_DHCP_OPTION_DHCP6_DOMAIN_LIST       = 24,
+	NM_DHCP_OPTION_DHCP6_SNTP_SERVERS      = 31,
+	/* Internal values */
+	NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS     = 1026,
+	NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN      = 1027,
+	NM_DHCP_OPTION_DHCP6_NM_PREFERRED_LIFE = 1028,
+	NM_DHCP_OPTION_DHCP6_NM_MAX_LIFE       = 1029,
+	NM_DHCP_OPTION_DHCP6_NM_STARTS         = 1030,
+	NM_DHCP_OPTION_DHCP6_NM_LIFE_STARTS    = 1031,
+	NM_DHCP_OPTION_DHCP6_NM_RENEW          = 1032,
+	NM_DHCP_OPTION_DHCP6_NM_REBIND         = 1033,
+	NM_DHCP_OPTION_DHCP6_NM_IAID           = 1034,
+
+} NMDhcpOptionDhcp6Options;
+
+typedef struct {
+	const char *name;
+	uint16_t option_num;
+	bool include;
+} NMDhcpOption;
+
+extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[];
+extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[];
+
+const char *nm_dhcp_option_request_string (const NMDhcpOption *requests, guint option);
+void nm_dhcp_option_take_option (GHashTable *options, const NMDhcpOption *requests, guint option, char *value);
+void nm_dhcp_option_add_option (GHashTable *options, const NMDhcpOption *requests, guint option, const char *value);
+void nm_dhcp_option_add_option_u64 (GHashTable *options, const NMDhcpOption *requests, guint option, guint64 value);
+void nm_dhcp_option_add_requests_to_options (GHashTable *options, const NMDhcpOption *requests);
+GHashTable *nm_dhcp_option_create_options_dict (void);
+
+#endif /* __NM_DHCP_OPTIONS_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 1cd5ba27..073a6da0 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -1,8 +1,8 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
+/*
+ * 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
@@ -32,11 +32,13 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-dhcp-utils.h"
+#include "nm-dhcp-options.h"
 #include "nm-core-utils.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
 #include "nm-dhcp-client-logging.h"
 #include "systemd/nm-sd.h"
+#include "systemd/nm-sd-utils-dhcp.h"
 
 /*****************************************************************************/
 
@@ -79,160 +81,10 @@ G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
 
 /*****************************************************************************/
 
-#define DHCP_OPTION_NIS_DOMAIN         40
-#define DHCP_OPTION_NIS_SERVERS        41
-
-/* Internal values */
-#define DHCP_OPTION_IP_ADDRESS       1024
-#define DHCP_OPTION_EXPIRY           1025
-#define DHCP6_OPTION_IP_ADDRESS      1026
-#define DHCP6_OPTION_PREFIXLEN       1027
-#define DHCP6_OPTION_PREFERRED_LIFE  1028
-#define DHCP6_OPTION_MAX_LIFE        1029
-#define DHCP6_OPTION_STARTS          1030
-#define DHCP6_OPTION_LIFE_STARTS     1031
-#define DHCP6_OPTION_RENEW           1032
-#define DHCP6_OPTION_REBIND          1033
-#define DHCP6_OPTION_IAID            1034
-
-typedef struct {
-	const char *name;
-	uint16_t option_num;
-	bool include;
-} ReqOption;
-
-#define REQPREFIX "requested_"
-
-#define REQ(_num, _name, _include) \
-	{ \
-		.name = REQPREFIX""_name, \
-		.option_num = _num, \
-		.include = _include, \
-	}
-
-static const ReqOption dhcp4_requests[] = {
-	REQ (SD_DHCP_OPTION_SUBNET_MASK,                    "subnet_mask",                     TRUE ),
-	REQ (SD_DHCP_OPTION_TIME_OFFSET,                    "time_offset",                     TRUE ),
-	REQ (SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             "domain_name_servers",             TRUE ),
-	REQ (SD_DHCP_OPTION_HOST_NAME,                      "host_name",                       TRUE ),
-	REQ (SD_DHCP_OPTION_DOMAIN_NAME,                    "domain_name",                     TRUE ),
-	REQ (SD_DHCP_OPTION_INTERFACE_MTU,                  "interface_mtu",                   TRUE ),
-	REQ (SD_DHCP_OPTION_BROADCAST,                      "broadcast_address",               TRUE ),
-
-	/* RFC 3442: The Classless Static Routes option code MUST appear in the parameter
-	 *   request list prior to both the Router option code and the Static
-	 *   Routes option code, if present. */
-	REQ (SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         "rfc3442_classless_static_routes", TRUE ),
-	REQ (SD_DHCP_OPTION_ROUTER,                         "routers",                         TRUE ),
-	REQ (SD_DHCP_OPTION_STATIC_ROUTE,                   "static_routes",                   TRUE ),
-
-	REQ (DHCP_OPTION_NIS_DOMAIN,                        "nis_domain",                      TRUE ),
-	REQ (DHCP_OPTION_NIS_SERVERS,                       "nis_servers",                     TRUE ),
-	REQ (SD_DHCP_OPTION_NTP_SERVER,                     "ntp_servers",                     TRUE ),
-	REQ (SD_DHCP_OPTION_SERVER_IDENTIFIER,              "dhcp_server_identifier",          TRUE ),
-	REQ (SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             "domain_search",                   TRUE ),
-	REQ (SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, "ms_classless_static_routes",      TRUE ),
-	REQ (SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    "wpad",                            TRUE ),
-	REQ (SD_DHCP_OPTION_ROOT_PATH,                      "root_path",                       TRUE ),
-
-	/* Internal values */
-	REQ (SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          "expiry",                          FALSE ),
-	REQ (SD_DHCP_OPTION_CLIENT_IDENTIFIER,              "dhcp_client_identifier",          FALSE ),
-	REQ (DHCP_OPTION_IP_ADDRESS,                        "ip_address",                      FALSE ),
-
-	{ 0 }
-};
-
-static const ReqOption dhcp6_requests[] = {
-	REQ (SD_DHCP6_OPTION_CLIENTID,                      "dhcp6_client_id",     FALSE ),
-
-	/* Don't request server ID by default; some servers don't reply to
-	 * Information Requests that request the Server ID.
-	 */
-	REQ (SD_DHCP6_OPTION_SERVERID,                      "dhcp6_server_id",     FALSE ),
-
-	REQ (SD_DHCP6_OPTION_DNS_SERVERS,                   "dhcp6_name_servers",  TRUE ),
-	REQ (SD_DHCP6_OPTION_DOMAIN_LIST,                   "dhcp6_domain_search", TRUE ),
-	REQ (SD_DHCP6_OPTION_SNTP_SERVERS,                  "dhcp6_sntp_servers",  TRUE ),
-
-	/* Internal values */
-	REQ (DHCP6_OPTION_IP_ADDRESS,                       "ip6_address",         FALSE ),
-	REQ (DHCP6_OPTION_PREFIXLEN,                        "ip6_prefixlen",       FALSE ),
-	REQ (DHCP6_OPTION_PREFERRED_LIFE,                   "preferred_life",      FALSE ),
-	REQ (DHCP6_OPTION_MAX_LIFE,                         "max_life",            FALSE ),
-	REQ (DHCP6_OPTION_STARTS,                           "starts",              FALSE ),
-	REQ (DHCP6_OPTION_LIFE_STARTS,                      "life_starts",         FALSE ),
-	REQ (DHCP6_OPTION_RENEW,                            "renew",               FALSE ),
-	REQ (DHCP6_OPTION_REBIND,                           "rebind",              FALSE ),
-	REQ (DHCP6_OPTION_IAID,                             "iaid",                FALSE ),
-
-	{ 0 }
-};
-
-static void
-take_option (GHashTable *options,
-             const ReqOption *requests,
-             guint option,
-             char *value)
-{
-	guint i;
-
-	nm_assert (options);
-	nm_assert (requests);
-	nm_assert (value);
-
-	for (i = 0; requests[i].name; i++) {
-		nm_assert (g_str_has_prefix (requests[i].name, REQPREFIX));
-		if (requests[i].option_num == option) {
-			g_hash_table_insert (options,
-			                     (gpointer) (requests[i].name + NM_STRLEN (REQPREFIX)),
-			                     value);
-			return;
-		}
-	}
-
-	/* Option should always be found */
-	nm_assert_not_reached ();
-}
-
-static void
-add_option (GHashTable *options, const ReqOption *requests, guint option, const char *value)
-{
-	if (options)
-		take_option (options, requests, option, g_strdup (value));
-}
-
-static void
-add_option_u64 (GHashTable *options, const ReqOption *requests, guint option, guint64 value)
-{
-	if (options)
-		take_option (options, requests, option, g_strdup_printf ("%" G_GUINT64_FORMAT, value));
-}
-
-static void
-add_requests_to_options (GHashTable *options, const ReqOption *requests)
-{
-	guint i;
-
-	if (!options)
-		return;
-
-	for (i = 0; requests[i].name; i++) {
-		if (requests[i].include)
-			g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
-	}
-}
-
-static GHashTable *
-create_options_dict (void)
-{
-	return g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
-}
-
 #define LOG_LEASE(domain, ...) \
 G_STMT_START { \
 	if (log_lease) { \
-		_LOG2I ((domain), (iface), "  "__VA_ARGS__); \
+		_LOG2D ((domain), (iface), "  "__VA_ARGS__); \
 	} \
 } G_STMT_END
 
@@ -267,9 +119,14 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	gint64 ts_time = time (NULL);
 	struct in_addr a_address;
 	struct in_addr a_netmask;
+	struct in_addr server_id;
+	struct in_addr broadcast;
 	const struct in_addr *a_router;
 	guint32 a_plen;
 	guint32 a_lifetime;
+	guint32 renewal;
+	guint32 rebinding;
+	gs_free nm_sd_dhcp_option *private_options = NULL;
 
 	g_return_val_if_fail (lease != NULL, NULL);
 
@@ -290,26 +147,36 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 
 	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 
-	options = out_options ? create_options_dict () : NULL;
+	options = out_options ? nm_dhcp_option_create_options_dict () : NULL;
 
 	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
-	LOG_LEASE (LOGD_DHCP4, "address %s", addr_str);
-	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, addr_str);
+	LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+	           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS),
+	           addr_str);
+	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, addr_str);
 
 	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
-	LOG_LEASE (LOGD_DHCP4, "plen %u", (guint) a_plen);
-	add_option (options,
-	            dhcp4_requests,
-	            SD_DHCP_OPTION_SUBNET_MASK,
-	            nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
-
-	LOG_LEASE (LOGD_DHCP4, "expires in %u seconds (at %lld)",
+	LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+	           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_SUBNET_MASK),
+	           (guint) a_plen);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           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) (ts_time + a_lifetime));
-	add_option_u64 (options,
-	                dhcp4_requests,
-	                SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
-	                (guint64) (ts_time + a_lifetime));
+	nm_dhcp_option_add_option_u64 (options,
+	                               _nm_dhcp_option_dhcp4_options,
+	                               NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
+	                               a_lifetime);
+	nm_dhcp_option_add_option_u64 (options,
+	                               _nm_dhcp_option_dhcp4_options,
+	                               NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
+	                               (guint64) (ts_time + a_lifetime));
 
 	nm_ip4_config_add_address (ip4_config,
 	                           &((const NMPlatformIP4Address) {
@@ -322,6 +189,28 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	                               .preferred    = a_lifetime,
 	                           }));
 
+	if (sd_dhcp_lease_get_server_identifier (lease, &server_id) >= 0) {
+		nm_utils_inet4_ntop (server_id.s_addr, addr_str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_SERVER_ID),
+		           addr_str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_SERVER_ID,
+		                           addr_str);
+	}
+
+	if (sd_dhcp_lease_get_broadcast (lease, &broadcast) >= 0) {
+		nm_utils_inet4_ntop (broadcast.s_addr, addr_str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_BROADCAST),
+		           addr_str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_BROADCAST,
+		                           addr_str);
+	}
+
 	num = sd_dhcp_lease_get_dns (lease, &addr_list);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
@@ -337,8 +226,13 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			}
 			nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
 		}
-		LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,
+		                           str->str);
 	}
 
 	num = sd_dhcp_lease_get_search_domains (lease, (char ***) &search_domains);
@@ -348,16 +242,23 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			g_string_append (nm_gstring_add_space_delimiter (str), search_domains[i]);
 			nm_ip4_config_add_search (ip4_config, search_domains[i]);
 		}
-		LOG_LEASE (LOGD_DHCP4, "domain search '%s'", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
+		                           str->str);
 	}
 
 	if (sd_dhcp_lease_get_domainname (lease, &s) >= 0) {
 		gs_strfreev char **domains = NULL;
 		char **d;
 
-		LOG_LEASE (LOGD_DHCP4, "domain name '%s'", s);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME),
+		           s);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, s);
 
 		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
 		 * As systemd escapes such characters, split them at \\032. */
@@ -367,8 +268,10 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	}
 
 	if (sd_dhcp_lease_get_hostname (lease, &s) >= 0) {
-		LOG_LEASE (LOGD_DHCP4, "hostname '%s'", s);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, s);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_HOST_NAME),
+		           s);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_HOST_NAME, s);
 	}
 
 	num = sd_dhcp_lease_get_routes (lease, &routes);
@@ -379,10 +282,10 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 
 		for (i = 0; i < num; i++) {
 			switch (sd_dhcp_route_get_option (routes[i])) {
-			case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+			case NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE:
 				has_classless_route = TRUE;
 				break;
-			case SD_DHCP_OPTION_STATIC_ROUTE:
+			case NM_DHCP_OPTION_DHCP4_STATIC_ROUTE:
 				has_static_route = TRUE;
 				break;
 			}
@@ -404,8 +307,8 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			guint32 m;
 
 			option = sd_dhcp_route_get_option (routes[i]);
-			if (!NM_IN_SET (option, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,
-			                        SD_DHCP_OPTION_STATIC_ROUTE))
+			if (!NM_IN_SET (option, NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
+			                        NM_DHCP_OPTION_DHCP4_STATIC_ROUTE))
 				continue;
 
 			if (sd_dhcp_route_get_destination (routes[i], &r_network) < 0)
@@ -422,14 +325,14 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			nm_utils_inet4_ntop (r_gateway.s_addr, gateway_str);
 
 			LOG_LEASE (LOGD_DHCP4,
-			           "%sstatic route %s/%d gw %s",
-			             option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
-			           ? "classless "
+			           "%sstatic_route %s/%d gw %s",
+			             option == NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE
+			           ? "rfc3442_classless_"
 			           : "",
 			           network_net_str,
 			           (int) r_plen,
 			           gateway_str);
-			g_string_append_printf (nm_gstring_add_space_delimiter (  option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
+			g_string_append_printf (nm_gstring_add_space_delimiter (  option == NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE
 			                                                        ? str_classless
 			                                                        : str_static),
 			                        "%s/%d %s",
@@ -437,7 +340,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			                        (int) r_plen,
 			                        gateway_str);
 
-			if (   option == SD_DHCP_OPTION_STATIC_ROUTE
+			if (   option == NM_DHCP_OPTION_DHCP4_STATIC_ROUTE
 			    && has_classless_route) {
 				/* RFC 3443: if the DHCP server returns both a Classless Static Routes
 				 * option and a Static Routes option, the DHCP client MUST ignore the
@@ -446,7 +349,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			}
 
 			if (   r_plen == 0
-			    && option == SD_DHCP_OPTION_STATIC_ROUTE) {
+			    && option == NM_DHCP_OPTION_DHCP4_STATIC_ROUTE) {
 				/* for option 33 (static route), RFC 2132 says:
 				 *
 				 * The default route (0.0.0.0) is an illegal destination for a static
@@ -478,9 +381,15 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 		}
 
 		if (str_classless && str_classless->len > 0)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str_classless->str);
+			nm_dhcp_option_add_option (options,
+		                                   _nm_dhcp_option_dhcp4_options,
+		                                   NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
+		                                   str_classless->str);
 		if (str_static && str_static->len > 0)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_STATIC_ROUTE, str_static->str);
+			nm_dhcp_option_add_option (options,
+			                           _nm_dhcp_option_dhcp4_options,
+			                           NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
+			                           str_static->str);
 	}
 
 	num = sd_dhcp_lease_get_router (lease, &a_router);
@@ -524,14 +433,21 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			                         }),
 			                         NULL);
 		}
-		LOG_LEASE (LOGD_DHCP4, "router %s", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROUTER),
+		           str->str);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROUTER, str->str);
 	}
 
 	if (   sd_dhcp_lease_get_mtu (lease, &mtu) >= 0
 	    && mtu) {
-		LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
-		add_option_u64 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
+		LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU),
+		           mtu);
+		nm_dhcp_option_add_option_u64 (options,
+		                               _nm_dhcp_option_dhcp4_options,
+		                               NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
+		                               mtu);
 		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
 	}
 
@@ -542,19 +458,77 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
 			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 		}
-		LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_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);
 	}
 
 	if (sd_dhcp_lease_get_root_path (lease, &s) >= 0) {
-		LOG_LEASE (LOGD_DHCP4, "root path '%s'", s);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROOT_PATH, s);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROOT_PATH),
+		           s);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROOT_PATH, s);
+	}
+
+	if (sd_dhcp_lease_get_t1 (lease, &renewal) >= 0) {
+		LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME),
+		           renewal);
+		nm_dhcp_option_add_option_u64 (options,
+		                               _nm_dhcp_option_dhcp4_options,
+		                               NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME,
+		                               renewal);
+	}
+
+	if (sd_dhcp_lease_get_t2 (lease, &rebinding) >= 0) {
+		LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME),
+		           rebinding);
+		nm_dhcp_option_add_option_u64 (options,
+		                               _nm_dhcp_option_dhcp4_options,
+		                               NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME,
+		                               rebinding);
+	}
+
+	if (sd_dhcp_lease_get_timezone (lease, &s) >= 0) {
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE),
+		           s);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE,
+		                           s);
 	}
 
 	if (sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len) >= 0)
 		metered = !!memmem (data, data_len, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
 	nm_ip4_config_set_metered (ip4_config, metered);
 
+	num =  nm_sd_dhcp_lease_get_private_options (lease, &private_options);
+	if (num > 0) {
+		for (i = 0; i < num; i++) {
+			char *option_string;
+
+			option_string = nm_utils_bin2hexstr_full (private_options[i].data,
+			                                          private_options[i].data_len,
+			                                          ':', FALSE, NULL);
+			LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+			           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, private_options[i].code),
+			           option_string);
+			if (!options) {
+				g_free (option_string);
+				continue;
+			}
+			nm_dhcp_option_take_option (options,
+			                            _nm_dhcp_option_dhcp4_options,
+			                            private_options[i].code,
+			                            option_string);
+		}
+	}
 	NM_SET_OUT (out_options, g_steal_pointer (&options));
 	return g_steal_pointer (&ip4_config);
 }
@@ -627,7 +601,7 @@ bound4_handle (NMDhcpSystemd *self)
 		return;
 	}
 
-	add_requests_to_options (options, dhcp4_requests);
+	nm_dhcp_option_add_requests_to_options (options, _nm_dhcp_option_dhcp4_options);
 	dhcp_lease_save (lease, priv->lease_file);
 
 	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
@@ -636,13 +610,13 @@ bound4_handle (NMDhcpSystemd *self)
 	                          options);
 }
 
-static void
+static int
 dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 
-	g_assert (priv->client4 == client);
+	nm_assert (priv->client4 == client);
 
 	_LOGD ("client event %d", event);
 
@@ -658,10 +632,14 @@ dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 	case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE:
 		bound4_handle (self);
 		break;
+	case SD_DHCP_CLIENT_EVENT_SELECTING:
+		break;
 	default:
 		_LOGW ("unhandled DHCP event %d", event);
 		break;
 	}
+
+	return 0;
 }
 
 static gboolean
@@ -776,10 +754,10 @@ ip4_start (NMDhcpClient *client,
 	}
 
 	/* Add requested options */
-	for (i = 0; dhcp4_requests[i].name; i++) {
-		if (dhcp4_requests[i].include) {
-			nm_assert (dhcp4_requests[i].option_num <= 255);
-			r = sd_dhcp_client_set_request_option (sd_client, dhcp4_requests[i].option_num);
+	for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) {
+		if (_nm_dhcp_option_dhcp4_options[i].include) {
+			nm_assert (_nm_dhcp_option_dhcp4_options[i].option_num <= 255);
+			r = sd_dhcp_client_set_request_option (sd_client, _nm_dhcp_option_dhcp4_options[i].option_num);
 			nm_assert (r >= 0 || r == -EEXIST);
 		}
 	}
@@ -846,7 +824,7 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 
 	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
 
-	options = out_options ? create_options_dict () : NULL;
+	options = out_options ? nm_dhcp_option_create_options_dict () : NULL;
 
 	sd_dhcp6_lease_reset_address_iter (lease);
 	nm_gstring_prepare (&str);
@@ -866,12 +844,15 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 		nm_utils_inet6_ntop (&tmp_addr, addr_str);
 		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 
-		LOG_LEASE (LOGD_DHCP6,
-		           "address %s",
+		LOG_LEASE (LOGD_DHCP6, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp6_options, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS),
 		           nm_platform_ip6_address_to_string (&address, sbuf, sizeof (sbuf)));
 	};
 	if (str->len)
-		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp6_options,
+		                           NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,
+		                           str->str);
 
 	if (   !info_only
 	    && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
@@ -890,8 +871,13 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
 		}
-		LOG_LEASE (LOGD_DHCP6, "nameserver %s", str->str);
-		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
+		LOG_LEASE (LOGD_DHCP6, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp6_options, NM_DHCP_OPTION_DHCP6_DNS_SERVERS),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp6_options,
+		                           NM_DHCP_OPTION_DHCP6_DNS_SERVERS,
+		                           str->str);
 	}
 
 	num = sd_dhcp6_lease_get_domains (lease, &domains);
@@ -901,8 +887,13 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 			g_string_append (nm_gstring_add_space_delimiter (str), domains[i]);
 			nm_ip6_config_add_search (ip6_config, domains[i]);
 		}
-		LOG_LEASE (LOGD_DHCP6, "domain name '%s'", str->str);
-		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
+		LOG_LEASE (LOGD_DHCP6, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp6_options, NM_DHCP_OPTION_DHCP6_DOMAIN_LIST),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp6_options,
+		                           NM_DHCP_OPTION_DHCP6_DOMAIN_LIST,
+		                           str->str);
 	}
 
 	NM_SET_OUT (out_options, g_steal_pointer (&options));
@@ -955,7 +946,7 @@ dhcp6_event_cb (sd_dhcp6_client *client, int event, gpointer user_data)
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 
-	g_assert (priv->client6 == client);
+	nm_assert (priv->client6 == client);
 
 	_LOGD ("client event %d", event);
 
@@ -1075,9 +1066,9 @@ ip6_start (NMDhcpClient *client,
 	}
 
 	/* Add requested options */
-	for (i = 0; dhcp6_requests[i].name; i++) {
-		if (dhcp6_requests[i].include) {
-			r = sd_dhcp6_client_set_request_option (sd_client, dhcp6_requests[i].option_num);
+	for (i = 0; _nm_dhcp_option_dhcp6_options[i].name; i++) {
+		if (_nm_dhcp_option_dhcp6_options[i].include) {
+			r = sd_dhcp6_client_set_request_option (sd_client, _nm_dhcp_option_dhcp6_options[i].option_num);
 			nm_assert (r >= 0 || r == -EEXIST);
 		}
 	}
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 7aa867c0..3f9368f2 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 5c127bd1..39ae7693 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 1eac3643..2cc7e5ca 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index 118082a5..e4d4c348 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)