summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
committerMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
commit90e8691111889a7b5f3c812f5a41f15a8a058913 (patch)
treef101a879eca27c34a9bfa5f3da52266b22539a36 /src/dhcp
parentbdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff)
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/nm-dhcp-client-logging.h21
-rw-r--r--src/dhcp/nm-dhcp-client.c166
-rw-r--r--src/dhcp/nm-dhcp-client.h29
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c60
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h9
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c104
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c272
-rw-r--r--src/dhcp/nm-dhcp-listener.c67
-rw-r--r--src/dhcp/nm-dhcp-manager.c55
-rw-r--r--src/dhcp/nm-dhcp-manager.h16
-rw-r--r--src/dhcp/nm-dhcp-systemd.c271
-rw-r--r--src/dhcp/nm-dhcp-utils.c88
-rw-r--r--src/dhcp/nm-dhcp-utils.h10
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c84
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c140
15 files changed, 941 insertions, 451 deletions
diff --git a/src/dhcp/nm-dhcp-client-logging.h b/src/dhcp/nm-dhcp-client-logging.h
index 1047a7d7..1ed47170 100644
--- a/src/dhcp/nm-dhcp-client-logging.h
+++ b/src/dhcp/nm-dhcp-client-logging.h
@@ -23,6 +23,23 @@
 
 #include "nm-dhcp-client.h"
 
+static inline NMLogDomain
+_nm_dhcp_client_get_domain (NMDhcpClient *self)
+{
+	if (self) {
+		switch (nm_dhcp_client_get_addr_family (self)) {
+		case AF_INET:
+			return LOGD_DHCP4;
+		case AF_INET6:
+			return LOGD_DHCP6;
+		default:
+			nm_assert_not_reached ();
+			break;
+		}
+	}
+	return LOGD_DHCP;
+}
+
 #define _NMLOG_PREFIX_NAME    "dhcp"
 #define _NMLOG_DOMAIN         LOGD_DHCP
 #define _NMLOG(level, ...) \
@@ -38,9 +55,7 @@
         if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \
             NMDhcpClient *_self = (NMDhcpClient *) (self); \
             const char *__ifname = _self ? nm_dhcp_client_get_iface (_self) : NULL; \
-            const NMLogDomain _domain = !_self \
-                                            ? LOGD_DHCP \
-                                            : (nm_dhcp_client_get_ipv6 (_self) ? LOGD_DHCP6 : LOGD_DHCP4); \
+            const NMLogDomain _domain = _nm_dhcp_client_get_domain (_self); \
             \
             nm_log (_level, _domain, __ifname, NULL, \
                     "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 0906f5be..20ea092f 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -29,6 +29,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <uuid/uuid.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
+#include "nm-utils/nm-random-utils.h"
 
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
@@ -48,33 +52,36 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_MULTI_IDX,
+	PROP_ADDR_FAMILY,
 	PROP_IFACE,
 	PROP_IFINDEX,
 	PROP_HWADDR,
-	PROP_IPV6,
 	PROP_UUID,
-	PROP_PRIORITY,
+	PROP_ROUTE_TABLE,
+	PROP_ROUTE_METRIC,
 	PROP_TIMEOUT,
 );
 
 typedef struct _NMDhcpClientPrivate {
+	NMDedupMultiIndex *multi_idx;
 	char *       iface;
-	int          ifindex;
 	GByteArray * hwaddr;
-	gboolean     ipv6;
 	char *       uuid;
-	guint32      priority;
-	guint32      timeout;
 	GByteArray * duid;
 	GBytes *     client_id;
 	char *       hostname;
-	gboolean     use_fqdn;
-
-	NMDhcpState  state;
 	pid_t        pid;
 	guint        timeout_id;
 	guint        watch_id;
-	gboolean     info_only;
+	int          addr_family;
+	int          ifindex;
+	guint32      route_table;
+	guint32      route_metric;
+	guint32      timeout;
+	NMDhcpState  state;
+	bool         info_only:1;
+	bool         use_fqdn:1;
 } NMDhcpClientPrivate;
 
 G_DEFINE_TYPE_EXTENDED (NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {})
@@ -91,6 +98,14 @@ nm_dhcp_client_get_pid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->pid;
 }
 
+NMDedupMultiIndex *
+nm_dhcp_client_get_multi_idx (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->multi_idx;
+}
+
 const char *
 nm_dhcp_client_get_iface (NMDhcpClient *self)
 {
@@ -107,12 +122,12 @@ nm_dhcp_client_get_ifindex (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->ifindex;
 }
 
-gboolean
-nm_dhcp_client_get_ipv6 (NMDhcpClient *self)
+int
+nm_dhcp_client_get_addr_family (NMDhcpClient *self)
 {
-	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), AF_UNSPEC);
 
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->ipv6;
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->addr_family;
 }
 
 const char *
@@ -140,11 +155,19 @@ nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 }
 
 guint32
-nm_dhcp_client_get_priority (NMDhcpClient *self)
+nm_dhcp_client_get_route_table (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), RT_TABLE_MAIN);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_table;
+}
+
+guint32
+nm_dhcp_client_get_route_metric (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), G_MAXUINT32);
 
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->priority;
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_metric;
 }
 
 guint32
@@ -303,8 +326,8 @@ nm_dhcp_client_set_state (NMDhcpClient *self,
 		watch_cleanup (self);
 
 	if (new_state == NM_DHCP_STATE_BOUND) {
-		g_assert (   (priv->ipv6 && NM_IS_IP6_CONFIG (ip_config))
-		          || (!priv->ipv6 && NM_IS_IP4_CONFIG (ip_config)));
+		g_assert (   (priv->addr_family == AF_INET  && NM_IS_IP4_CONFIG (ip_config))
+		          || (priv->addr_family == AF_INET6 && NM_IS_IP6_CONFIG (ip_config)));
 		g_assert (options);
 	} else {
 		g_assert (ip_config == NULL);
@@ -319,7 +342,8 @@ nm_dhcp_client_set_state (NMDhcpClient *self,
 	if ((priv->state == new_state) && (new_state != NM_DHCP_STATE_BOUND))
 		return;
 
-	if (priv->ipv6 && new_state == NM_DHCP_STATE_BOUND) {
+	if (   priv->addr_family == AF_INET6
+	    && new_state == NM_DHCP_STATE_BOUND) {
 		char *start, *iaid;
 
 		iaid = g_hash_table_lookup (options, "iaid");
@@ -392,6 +416,10 @@ nm_dhcp_client_start_timeout (NMDhcpClient *self)
 
 	/* 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;
+
 	priv->timeout_id = g_timeout_add_seconds (priv->timeout,
 	                                          transaction_timeout,
 	                                          self);
@@ -426,10 +454,13 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 
 	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
 	g_return_val_if_fail (priv->pid == -1, FALSE);
-	g_return_val_if_fail (priv->ipv6 == FALSE, FALSE);
+	g_return_val_if_fail (priv->addr_family == AF_INET, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
 
-	_LOGI ("activation: beginning transaction (timeout in %d seconds)", priv->timeout);
+	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
+		_LOGI ("activation: beginning transaction (no timeout)");
+	else
+		_LOGI ("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
 
 	if (dhcp_client_id)
 		tmp = nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id);
@@ -451,8 +482,6 @@ generate_duid_from_machine_id (void)
 	gsize sumlen = sizeof (buffer);
 	const guint16 duid_type = g_htons (4);
 	uuid_t uuid;
-	GRand *generator;
-	guint i;
 	gs_free char *machine_id_s = NULL;
 	gs_free char *str = NULL;
 
@@ -468,10 +497,7 @@ generate_duid_from_machine_id (void)
 		             "or " LOCALSTATEDIR "/lib/dbus/machine-id to generate "
 		             "DHCPv6 DUID; creating non-persistent random DUID.");
 
-		generator = g_rand_new ();
-		for (i = 0; i < sizeof (buffer) / sizeof (guint32); i++)
-			((guint32 *) buffer)[i] = g_rand_int (generator);
-		g_rand_free (generator);
+		nm_utils_random_bytes (buffer, sizeof (buffer));
 	}
 
 	/* Generate a DHCP Unique Identifier for DHCPv6 using the
@@ -528,7 +554,7 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 
 	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
 	g_return_val_if_fail (priv->pid == -1, FALSE);
-	g_return_val_if_fail (priv->ipv6 == TRUE, FALSE);
+	g_return_val_if_fail (priv->addr_family == AF_INET6, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
 
 	/* If we don't have one yet, read the default DUID for this DHCPv6 client
@@ -544,8 +570,10 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 
 	priv->info_only = info_only;
 
-	_LOGI ("activation: beginning transaction (timeout in %d seconds)",
-	       priv->timeout);
+	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
+		_LOGI ("activation: beginning transaction (no timeout)");
+	else
+		_LOGI ("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
 
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self,
 	                                                   dhcp_anycast_addr,
@@ -744,7 +772,7 @@ nm_dhcp_client_handle_event (gpointer unused,
 		GVariant *value;
 
 		/* Copy options */
-		str_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		str_options = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 		g_variant_iter_init (&iter, options);
 		while (g_variant_iter_next (&iter, "{&sv}", &name, &value)) {
 			maybe_add_option (self, str_options, name, value);
@@ -763,18 +791,20 @@ nm_dhcp_client_handle_event (gpointer unused,
 		/* Create the IP config */
 		g_warn_if_fail (g_hash_table_size (str_options));
 		if (g_hash_table_size (str_options)) {
-			if (priv->ipv6) {
-				prefix = nm_dhcp_utils_ip6_prefix_from_options (str_options);
-				ip_config = (GObject *) nm_dhcp_utils_ip6_config_from_options (priv->ifindex,
+			if (priv->addr_family == AF_INET) {
+				ip_config = (GObject *) nm_dhcp_utils_ip4_config_from_options (nm_dhcp_client_get_multi_idx (self),
+				                                                               priv->ifindex,
 				                                                               priv->iface,
 				                                                               str_options,
-				                                                               priv->priority,
-				                                                               priv->info_only);
+				                                                               priv->route_table,
+				                                                               priv->route_metric);
 			} else {
-				ip_config = (GObject *) nm_dhcp_utils_ip4_config_from_options (priv->ifindex,
+				prefix = nm_dhcp_utils_ip6_prefix_from_options (str_options);
+				ip_config = (GObject *) nm_dhcp_utils_ip6_config_from_options (nm_dhcp_client_get_multi_idx (self),
+				                                                               priv->ifindex,
 				                                                               priv->iface,
 				                                                               str_options,
-				                                                               priv->priority);
+				                                                               priv->info_only);
 			}
 		}
 	}
@@ -822,14 +852,14 @@ get_property (GObject *object, guint prop_id,
 	case PROP_HWADDR:
 		g_value_set_boxed (value, priv->hwaddr);
 		break;
-	case PROP_IPV6:
-		g_value_set_boolean (value, priv->ipv6);
+	case PROP_ADDR_FAMILY:
+		g_value_set_int (value, priv->addr_family);
 		break;
 	case PROP_UUID:
 		g_value_set_string (value, priv->uuid);
 		break;
-	case PROP_PRIORITY:
-		g_value_set_uint (value, priv->priority);
+	case PROP_ROUTE_METRIC:
+		g_value_set_uint (value, priv->route_metric);
 		break;
 	case PROP_TIMEOUT:
 		g_value_set_uint (value, priv->timeout);
@@ -847,6 +877,13 @@ set_property (GObject *object, guint prop_id,
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
 
 	switch (prop_id) {
+	case PROP_MULTI_IDX:
+		/* construct-only */
+		priv->multi_idx = g_value_get_pointer (value);
+		if (!priv->multi_idx)
+			g_return_if_reached ();
+		nm_dedup_multi_index_ref (priv->multi_idx);
+		break;
 	case PROP_IFACE:
 		/* construct-only */
 		priv->iface = g_value_dup_string (value);
@@ -860,19 +897,26 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->hwaddr = g_value_dup_boxed (value);
 		break;
-	case PROP_IPV6:
+	case PROP_ADDR_FAMILY:
 		/* construct-only */
-		priv->ipv6 = g_value_get_boolean (value);
+		priv->addr_family = g_value_get_int (value);
+		if (!NM_IN_SET (priv->addr_family, AF_INET, AF_INET6))
+			g_return_if_reached ();
 		break;
 	case PROP_UUID:
 		/* construct-only */
 		priv->uuid = g_value_dup_string (value);
 		break;
-	case PROP_PRIORITY:
+	case PROP_ROUTE_TABLE:
+		/* construct-only */
+		priv->route_table = g_value_get_uint (value);
+		break;
+	case PROP_ROUTE_METRIC:
 		/* construct-only */
-		priv->priority = g_value_get_uint (value);
+		priv->route_metric = g_value_get_uint (value);
 		break;
 	case PROP_TIMEOUT:
+		/* construct-only */
 		priv->timeout = g_value_get_uint (value);
 		break;
 	default:
@@ -924,6 +968,8 @@ dispose (GObject *object)
 	}
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
+
+	priv->multi_idx = nm_dedup_multi_index_unref (priv->multi_idx);
 }
 
 static void
@@ -940,6 +986,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	client_class->stop = stop;
 	client_class->get_duid = get_duid;
 
+	obj_properties[PROP_MULTI_IDX] =
+	    g_param_spec_pointer (NM_DHCP_CLIENT_MULTI_IDX, "", "",
+	                            G_PARAM_WRITABLE
+	                          | G_PARAM_CONSTRUCT_ONLY
+	                          | G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_IFACE] =
 	    g_param_spec_string (NM_DHCP_CLIENT_INTERFACE, "", "",
 	                         NULL,
@@ -958,11 +1010,11 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_IPV6] =
-	    g_param_spec_boolean (NM_DHCP_CLIENT_IPV6, "", "",
-	                          FALSE,
-	                          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,
+	                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_UUID] =
 	    g_param_spec_string (NM_DHCP_CLIENT_UUID, "", "",
@@ -970,15 +1022,21 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_PRIORITY] =
-	    g_param_spec_uint (NM_DHCP_CLIENT_PRIORITY, "", "",
+	obj_properties[PROP_ROUTE_TABLE] =
+	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_TABLE, "", "",
+	                       0, G_MAXUINT32, RT_TABLE_MAIN,
+	                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_ROUTE_METRIC] =
+	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_METRIC, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_TIMEOUT] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_TIMEOUT, "", "",
-	                       0, G_MAXUINT, 45,
+	                       1, G_MAXINT32, NM_DHCP_TIMEOUT_DEFAULT,
 	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index e41a59a2..02804002 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -24,6 +24,9 @@
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
 
+#define NM_DHCP_TIMEOUT_DEFAULT ((guint32) 45) /* default DHCP timeout, in seconds */
+#define NM_DHCP_TIMEOUT_INFINITY G_MAXINT32
+
 #define NM_TYPE_DHCP_CLIENT            (nm_dhcp_client_get_type ())
 #define NM_DHCP_CLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClient))
 #define NM_DHCP_CLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
@@ -32,16 +35,19 @@
 #define NM_DHCP_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
 
 #define NM_DHCP_CLIENT_INTERFACE "iface"
+#define NM_DHCP_CLIENT_ADDR_FAMILY "addr-family"
 #define NM_DHCP_CLIENT_IFINDEX   "ifindex"
 #define NM_DHCP_CLIENT_HWADDR    "hwaddr"
-#define NM_DHCP_CLIENT_IPV6      "ipv6"
 #define NM_DHCP_CLIENT_UUID      "uuid"
-#define NM_DHCP_CLIENT_PRIORITY  "priority"
+#define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
+#define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
 #define NM_DHCP_CLIENT_TIMEOUT   "timeout"
+#define NM_DHCP_CLIENT_MULTI_IDX "multi-idx"
 
 #define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed"
 #define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
 
+
 typedef enum {
 	NM_DHCP_STATE_UNKNOWN = 0,
 	NM_DHCP_STATE_BOUND,        /* new lease or lease changed */
@@ -101,21 +107,25 @@ typedef struct {
 
 GType nm_dhcp_client_get_type (void);
 
+struct _NMDedupMultiIndex *nm_dhcp_client_get_multi_idx (NMDhcpClient *self);
+
 pid_t nm_dhcp_client_get_pid (NMDhcpClient *self);
 
+int nm_dhcp_client_get_addr_family (NMDhcpClient *self);
+
 const char *nm_dhcp_client_get_iface (NMDhcpClient *self);
 
 int         nm_dhcp_client_get_ifindex (NMDhcpClient *self);
 
-gboolean nm_dhcp_client_get_ipv6 (NMDhcpClient *self);
-
 const char *nm_dhcp_client_get_uuid (NMDhcpClient *self);
 
 const GByteArray *nm_dhcp_client_get_duid (NMDhcpClient *self);
 
 const GByteArray *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
-guint32 nm_dhcp_client_get_priority (NMDhcpClient *self);
+guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
+
+guint32 nm_dhcp_client_get_route_metric (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_timeout (NMDhcpClient *self);
 
@@ -173,13 +183,16 @@ typedef struct {
 	GType (*get_type)(void);
 	const char *name;
 	const char *(*get_path) (void);
-	GSList *(*get_lease_ip_configs) (const char *iface,
+	GSList *(*get_lease_ip_configs) (struct _NMDedupMultiIndex *multi_idx,
+	                                 int addr_family,
+	                                 const char *iface,
 	                                 int ifindex,
 	                                 const char *uuid,
-	                                 gboolean ipv6,
-	                                 guint32 default_route_metric);
+	                                 guint32 route_table,
+	                                 guint32 route_metric);
 } NMDhcpClientFactory;
 
+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;
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 6a1b6865..e63e6a86 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -25,6 +25,8 @@
 #include <ctype.h>
 #include <arpa/inet.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-dhcp-utils.h"
 #include "nm-ip4-config.h"
 #include "nm-utils.h"
@@ -162,13 +164,9 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 static void
 add_hostname6 (GString *str, const char *hostname)
 {
-	/* dhclient only supports the fqdn.fqdn for DHCPv6 and requires a fully-
-	 * qualified name for this option, so we must require one here too.
-	 */
-	if (hostname && strchr (hostname, '.')) {
+	if (hostname) {
 		g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
 		g_string_append (str,
-		                 "send fqdn.encoded on;\n"
 		                 "send fqdn.server-update on;\n");
 		g_string_append_c (str, '\n');
 	}
@@ -261,7 +259,7 @@ read_interface (const char *line, char *interface, guint size)
 
 char *
 nm_dhcp_dhclient_create_config (const char *interface,
-                                gboolean is_ip6,
+                                int addr_family,
                                 GBytes *client_id,
                                 const char *anycast_addr,
                                 const char *hostname,
@@ -277,6 +275,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 	int i;
 
 	g_return_val_if_fail (!anycast_addr || nm_utils_hwaddr_valid (anycast_addr, ETH_ALEN), NULL);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
 
 	new_contents = g_string_new (_("# Created by NetworkManager\n"));
 	fqdn_opts = g_ptr_array_sized_new (5);
@@ -397,18 +396,18 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		g_string_append_printf (new_contents, "timeout %u;\n", timeout);
 	}
 
-	if (is_ip6) {
-		add_hostname6 (new_contents, hostname);
-		add_request (reqs, "dhcp6.name-servers");
-		add_request (reqs, "dhcp6.domain-search");
-		add_request (reqs, "dhcp6.client-id");
-	} else {
+	if (addr_family == AF_INET) {
 		add_ip4_config (new_contents, client_id, hostname, use_fqdn);
 		add_request (reqs, "rfc3442-classless-static-routes");
 		add_request (reqs, "ms-classless-static-routes");
 		add_request (reqs, "static-routes");
 		add_request (reqs, "wpad");
 		add_request (reqs, "ntp-servers");
+	} else {
+		add_hostname6 (new_contents, hostname);
+		add_request (reqs, "dhcp6.name-servers");
+		add_request (reqs, "dhcp6.domain-search");
+		add_request (reqs, "dhcp6.client-id");
 	}
 
 	if (reset_reqlist)
@@ -686,24 +685,30 @@ lease_validity_span (const char *str_expire, GDateTime *now)
 
 /**
  * nm_dhcp_dhclient_read_lease_ip_configs:
+ * @multi_idx: the multi index instance for the ip config object
+ * @addr_family: whether to read IPv4 or IPv6 leases
  * @iface: the interface name to match leases with
  * @ifindex: interface index of @iface
+ * @route_table: the route table for the default route.
+ * @route_metric: the route metric for the default route.
  * @contents: the contents of a dhclient leasefile
- * @ipv6: whether to read IPv4 or IPv6 leases
  * @now: the current UTC date/time; pass %NULL to automatically use current
  *  UTC time.  Testcases may need a different value for 'now'
  *
  * Reads dhclient leases from @contents and parses them into either
- * #NMIP4Config or #NMIP6Config objects depending on the value of @ipv6.
+ * #NMIP4Config or #NMIP6Config objects depending on the value of @addr_family.
  *
- * Returns: a #GSList of #NMIP4Config objects (if @ipv6 is %FALSE) or a list of
- * #NMIP6Config objects (if @ipv6 is %TRUE) containing the lease data.
+ * Returns: a #GSList of #NMIP4Config objects (if @addr_family is %AF_INET) or a list of
+ * #NMIP6Config objects (if @addr_family is %AF_INET6) containing the lease data.
  */
 GSList *
-nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
+nm_dhcp_dhclient_read_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                        int addr_family,
+                                        const char *iface,
                                         int ifindex,
+                                        guint32 route_table,
+                                        guint32 route_metric,
                                         const char *contents,
-                                        gboolean ipv6,
                                         GDateTime *now)
 {
 	GSList *parsed = NULL, *iter, *leases = NULL;
@@ -712,6 +717,7 @@ nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
 	gint32 now_monotonic_ts;
 
 	g_return_val_if_fail (contents != NULL, NULL);
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	split = g_strsplit_set (contents, "\n\r", -1);
 	if (!split)
@@ -733,7 +739,7 @@ nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
 				g_hash_table_destroy (hash);
 			}
 
-			hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+			hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 		} else if (hash && strlen (*line))
 			add_lease_option (hash, *line);
 	}
@@ -804,15 +810,25 @@ nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
 
 		/* Get default netmask for the IP according to appropriate class. */
 		if (!address.plen)
-			address.plen = nm_utils_ip4_get_default_prefix (address.address);
+			address.plen = _nm_utils_ip4_get_default_prefix (address.address);
 
 		address.timestamp = now_monotonic_ts;
 		address.lifetime = address.preferred = expiry;
 		address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
 
-		ip4 = nm_ip4_config_new (ifindex);
+		ip4 = nm_ip4_config_new (multi_idx, ifindex);
 		nm_ip4_config_add_address (ip4, &address);
-		nm_ip4_config_set_gateway (ip4, gw);
+
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+				.gateway = gw,
+				.table_coerced = nm_platform_route_table_coerce (route_table),
+				.metric = route_metric,
+			};
+
+			nm_ip4_config_add_route (ip4, &r, NULL);
+		}
 
 		value = g_hash_table_lookup (hash, "option domain-name-servers");
 		if (value) {
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index 2268890b..94de1963 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -23,7 +23,7 @@
 #include "nm-setting-ip6-config.h"
 
 char *nm_dhcp_dhclient_create_config (const char *interface,
-                                      gboolean is_ip6,
+                                      int addr_family,
                                       GBytes *client_id,
                                       const char *anycast_addr,
                                       const char *hostname,
@@ -43,10 +43,13 @@ gboolean nm_dhcp_dhclient_save_duid (const char *leasefile,
                                      const char *escaped_duid,
                                      GError **error);
 
-GSList *nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
+GSList *nm_dhcp_dhclient_read_lease_ip_configs (struct _NMDedupMultiIndex *multi_idx,
+                                                int addr_family,
+                                                const char *iface,
                                                 int ifindex,
+                                                guint32 route_table,
+                                                guint32 route_metric,
                                                 const char *contents,
-                                                gboolean ipv6,
                                                 GDateTime *now);
 
 GBytes *nm_dhcp_dhclient_get_client_id_from_config_file (const char *path);
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index f20158c6..74d920a8 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -38,6 +38,8 @@
 #include <arpa/inet.h>
 #include <ctype.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-utils.h"
 #include "nm-dhcp-dhclient-utils.h"
 #include "nm-dhcp-manager.h"
@@ -47,6 +49,15 @@
 
 /*****************************************************************************/
 
+static const char *
+_addr_family_to_path_part (int addr_family)
+{
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	return (addr_family == AF_INET6) ? "6" : "";
+}
+
+/*****************************************************************************/
+
 #define NM_TYPE_DHCP_DHCLIENT            (nm_dhcp_dhclient_get_type ())
 #define NM_DHCP_DHCLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient))
 #define NM_DHCP_DHCLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
@@ -92,9 +103,9 @@ nm_dhcp_dhclient_get_path (void)
 
 /**
  * get_dhclient_leasefile():
+ * @addr_family: AF_INET or AF_INET6
  * @iface: the interface name of the device on which DHCP will be done
  * @uuid: the connection UUID to which the returned lease should belong
- * @ipv6: %TRUE for IPv6, %FALSE for IPv4
  * @out_preferred_path: on return, the "most preferred" leasefile path
  *
  * Returns the path of an existing leasefile (if any) for this interface and
@@ -104,16 +115,16 @@ nm_dhcp_dhclient_get_path (void)
  * Returns: an existing leasefile, or %NULL if no matching leasefile could be found
  */
 static char *
-get_dhclient_leasefile (const char *iface,
+get_dhclient_leasefile (int addr_family,
+                        const char *iface,
                         const char *uuid,
-                        gboolean ipv6,
                         char **out_preferred_path)
 {
 	char *path;
 
 	/* /var/lib/NetworkManager is the preferred leasefile path */
 	path = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s-%s.lease",
-	                        ipv6 ? "6" : "",
+	                        _addr_family_to_path_part (addr_family),
 	                        uuid,
 	                        iface);
 	if (out_preferred_path)
@@ -131,14 +142,14 @@ get_dhclient_leasefile (const char *iface,
 	/* Old Debian, SUSE, and Mandriva location */
 	g_free (path);
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease",
-	                        ipv6 ? "6" : "", uuid, iface);
+	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 
 	/* Old Red Hat and Fedora location */
 	g_free (path);
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease",
-	                        ipv6 ? "6" : "", uuid, iface);
+	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 
@@ -148,37 +159,36 @@ get_dhclient_leasefile (const char *iface,
 }
 
 static GSList *
-nm_dhcp_dhclient_get_lease_ip_configs (const char *iface,
+nm_dhcp_dhclient_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                       int addr_family,
+                                       const char *iface,
                                        int ifindex,
                                        const char *uuid,
-                                       gboolean ipv6,
-                                       guint32 default_route_metric)
+                                       guint32 route_table,
+                                       guint32 route_metric)
 {
-	char *contents = NULL;
-	char *leasefile;
-	GSList *leases = NULL;
+	gs_free char *contents = NULL;
+	gs_free char *leasefile = NULL;
 
-	leasefile = get_dhclient_leasefile (iface, uuid, FALSE, NULL);
+	leasefile = get_dhclient_leasefile (addr_family, iface, uuid, NULL);
 	if (!leasefile)
 		return NULL;
 
 	if (   g_file_test (leasefile, G_FILE_TEST_EXISTS)
 	    && g_file_get_contents (leasefile, &contents, NULL, NULL)
 	    && contents
-	    && contents[0])
-		leases = nm_dhcp_dhclient_read_lease_ip_configs (iface, ifindex, contents, ipv6, NULL);
-
-	g_free (leasefile);
-	g_free (contents);
-
-	return leases;
+	    && contents[0]) {
+		return nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, addr_family, iface, ifindex,
+		                                               route_table, route_metric, contents, NULL);
+	}
+	return NULL;
 }
 
 static gboolean
 merge_dhclient_config (NMDhcpDhclient *self,
+                       int addr_family,
                        const char *iface,
                        const char *conf_file,
-                       gboolean is_ip6,
                        GBytes *client_id,
                        const char *anycast_addr,
                        const char *hostname,
@@ -204,10 +214,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
 		}
 	}
 
-	if (is_ip6 && hostname && !strchr (hostname, '.'))
-		_LOGW ("hostname is not a FQDN, it will be ignored");
-
-	new = nm_dhcp_dhclient_create_config (iface, is_ip6, client_id, anycast_addr, hostname, timeout,
+	new = nm_dhcp_dhclient_create_config (iface, addr_family, client_id, anycast_addr, hostname, timeout,
 	                                      use_fqdn, orig_path, orig, out_new_client_id);
 	g_assert (new);
 	success = g_file_set_contents (conf_file, new, -1, error);
@@ -218,7 +225,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
 }
 
 static char *
-find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid, gboolean ipv6)
+find_existing_config (NMDhcpDhclient *self, int addr_family, const char *iface, const char *uuid)
 {
 	char *path;
 
@@ -227,20 +234,20 @@ find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid,
 	 * or generic.
 	 */
 	if (uuid) {
-		path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", uuid);
+		path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), uuid);
 		_LOGD ("looking for existing config %s", path);
 		if (g_file_test (path, G_FILE_TEST_EXISTS))
 			return path;
 		g_free (path);
 	}
 
-	path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (NMCONFDIR "/dhclient%s.conf", ipv6 ? "6" : "");
+	path = g_strdup_printf (NMCONFDIR "/dhclient%s.conf", _addr_family_to_path_part (addr_family));
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
@@ -254,25 +261,25 @@ find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid,
 	 * which is then used by many other distributions. Some distributions
 	 * (including Fedora) don't even provide a default configuration file.
 	 */
-	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (SYSCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	path = g_strdup_printf (SYSCONFDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s.conf", ipv6 ? "6" : "");
+	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s.conf", _addr_family_to_path_part (addr_family));
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (SYSCONFDIR "/dhclient%s.conf", ipv6 ? "6" : "");
+	path = g_strdup_printf (SYSCONFDIR "/dhclient%s.conf", _addr_family_to_path_part (addr_family));
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
@@ -290,8 +297,8 @@ find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid,
  */
 static char *
 create_dhclient_config (NMDhcpDhclient *self,
+                        int addr_family,
                         const char *iface,
-                        gboolean is_ip6,
                         const char *uuid,
                         GBytes *client_id,
                         const char *dhcp_anycast_addr,
@@ -306,17 +313,17 @@ create_dhclient_config (NMDhcpDhclient *self,
 
 	g_return_val_if_fail (iface != NULL, NULL);
 
-	new = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s.conf", is_ip6 ? "6" : "", iface);
+	new = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("creating composite dhclient config %s", new);
 
-	orig = find_existing_config (self, iface, uuid, is_ip6);
+	orig = find_existing_config (self, addr_family, iface, uuid);
 	if (orig)
 		_LOGD ("merging existing dhclient config %s", orig);
 	else
 		_LOGD ("no existing dhclient configuration to merge");
 
 	error = NULL;
-	success = merge_dhclient_config (self, iface, new, is_ip6, client_id, dhcp_anycast_addr,
+	success = merge_dhclient_config (self, addr_family, iface, new, client_id, dhcp_anycast_addr,
 	                                 hostname, timeout, use_fqdn, orig, out_new_client_id, &error);
 	if (!success) {
 		_LOGW ("error creating dhclient configuration: %s", error->message);
@@ -343,14 +350,15 @@ dhclient_start (NMDhcpClient *client,
 	GError *error = NULL;
 	const char *iface, *uuid, *system_bus_address, *dhclient_path = NULL;
 	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
-	gboolean ipv6, success;
+	int addr_family;
+	gboolean success;
 	char *escaped, *preferred_leasefile_path = NULL;
 
 	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
 
 	iface = nm_dhcp_client_get_iface (client);
 	uuid = nm_dhcp_client_get_uuid (client);
-	ipv6 = nm_dhcp_client_get_ipv6 (client);
+	addr_family = nm_dhcp_client_get_addr_family (client);
 
 	dhclient_path = nm_dhcp_dhclient_get_path ();
 	if (!dhclient_path) {
@@ -359,8 +367,8 @@ dhclient_start (NMDhcpClient *client,
 	}
 
 	pid_file = g_strdup_printf (RUNSTATEDIR "/dhclient%s-%s.pid",
-		                        ipv6 ? "6" : "",
-		                        iface);
+	                            _addr_family_to_path_part (addr_family),
+	                            iface);
 
 	/* Kill any existing dhclient from the pidfile */
 	binary_name = g_path_get_basename (dhclient_path);
@@ -374,7 +382,7 @@ dhclient_start (NMDhcpClient *client,
 	}
 
 	g_free (priv->lease_file);
-	priv->lease_file = get_dhclient_leasefile (iface, uuid, ipv6, &preferred_leasefile_path);
+	priv->lease_file = get_dhclient_leasefile (addr_family, iface, uuid, &preferred_leasefile_path);
 	if (!priv->lease_file) {
 		/* No existing leasefile, dhclient will create one at the preferred path */
 		priv->lease_file = g_strdup (preferred_leasefile_path);
@@ -400,7 +408,7 @@ dhclient_start (NMDhcpClient *client,
 	g_free (preferred_leasefile_path);
 
 	/* Save the DUID to the leasefile dhclient will actually use */
-	if (ipv6) {
+	if (addr_family == AF_INET6) {
 		escaped = nm_dhcp_dhclient_escape_duid (duid);
 		success = nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &error);
 		g_free (escaped);
@@ -424,7 +432,7 @@ dhclient_start (NMDhcpClient *client,
 	if (release)
 		g_ptr_array_add (argv, (gpointer) "-r");
 
-	if (ipv6) {
+	if (addr_family == AF_INET6) {
 		g_ptr_array_add (argv, (gpointer) "-6");
 		if (mode_opt)
 			g_ptr_array_add (argv, (gpointer) mode_opt);
@@ -507,7 +515,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	timeout = nm_dhcp_client_get_timeout (client);
 	use_fqdn = nm_dhcp_client_get_use_fqdn (client);
 
-	priv->conf_file = create_dhclient_config (self, iface, FALSE, uuid, client_id, dhcp_anycast_addr,
+	priv->conf_file = create_dhclient_config (self, AF_INET, iface, uuid, client_id, dhcp_anycast_addr,
 	                                          hostname, timeout, use_fqdn, &new_client_id);
 	if (priv->conf_file) {
 		if (new_client_id)
@@ -538,7 +546,7 @@ ip6_start (NMDhcpClient *client,
 	hostname = nm_dhcp_client_get_hostname (client);
 	timeout = nm_dhcp_client_get_timeout (client);
 
-	priv->conf_file = create_dhclient_config (self, iface, TRUE, uuid, NULL, dhcp_anycast_addr,
+	priv->conf_file = create_dhclient_config (self, AF_INET6, iface, uuid, NULL, dhcp_anycast_addr,
 	                                          hostname, timeout, TRUE, NULL);
 	if (!priv->conf_file) {
 		_LOGW ("error creating dhclient configuration file");
@@ -605,9 +613,9 @@ get_duid (NMDhcpClient *client)
 	GError *error = NULL;
 
 	/* Look in interface-specific leasefile first for backwards compat */
-	leasefile = get_dhclient_leasefile (nm_dhcp_client_get_iface (client),
+	leasefile = get_dhclient_leasefile (AF_INET6,
+	                                    nm_dhcp_client_get_iface (client),
 	                                    nm_dhcp_client_get_uuid (client),
-	                                    TRUE,
 	                                    NULL);
 	if (leasefile) {
 		_LOGD ("looking for DUID in '%s'", leasefile);
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
new file mode 100644
index 00000000..d7ddd194
--- /dev/null
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -0,0 +1,272 @@
+/* -*- 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
+ * 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 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) 2017 juga <juga at riseup dot net>
+ */
+
+#include "nm-default.h"
+
+#if WITH_DHCPCANON
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "nm-utils.h"
+#include "nm-dhcp-manager.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dhcp-listener.h"
+#include "nm-dhcp-client-logging.h"
+
+#define NM_TYPE_DHCP_DHCPCANON            (nm_dhcp_dhcpcanon_get_type ())
+#define NM_DHCP_DHCPCANON(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanon))
+#define NM_DHCP_DHCPCANON_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
+#define NM_IS_DHCP_DHCPCANON(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_DHCPCANON))
+#define NM_IS_DHCP_DHCPCANON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_DHCPCANON))
+#define NM_DHCP_DHCPCANON_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
+
+typedef struct _NMDhcpDhcpcanon NMDhcpDhcpcanon;
+typedef struct _NMDhcpDhcpcanonClass NMDhcpDhcpcanonClass;
+
+static GType nm_dhcp_dhcpcanon_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	char *conf_file;
+	const char *def_leasefile;
+	char *lease_file;
+	char *pid_file;
+	NMDhcpListener *dhcp_listener;
+} NMDhcpDhcpcanonPrivate;
+
+struct _NMDhcpDhcpcanon {
+	NMDhcpClient parent;
+	NMDhcpDhcpcanonPrivate _priv;
+};
+
+struct _NMDhcpDhcpcanonClass {
+	NMDhcpClientClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpDhcpcanon, nm_dhcp_dhcpcanon, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_DHCPCANON_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpDhcpcanon, NM_IS_DHCP_DHCPCANON)
+
+/*****************************************************************************/
+
+static const char *
+nm_dhcp_dhcpcanon_get_path (void)
+{
+	return nm_utils_find_helper ("dhcpcanon", DHCPCANON_PATH, NULL);
+}
+
+
+static gboolean
+dhcpcanon_start (NMDhcpClient *client,
+                const char *mode_opt,
+                const GByteArray *duid,
+                gboolean release,
+                pid_t *out_pid,
+                int prefixes)
+{
+	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+	GPtrArray *argv = NULL;
+	pid_t pid;
+	GError *error = NULL;
+	const char *iface, *system_bus_address, *dhcpcanon_path = NULL;
+	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
+	int addr_family;
+
+	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
+
+	iface = nm_dhcp_client_get_iface (client);
+	addr_family = nm_dhcp_client_get_addr_family (client);
+	dhcpcanon_path = nm_dhcp_dhcpcanon_get_path ();
+	_LOGD ("dhcpcanon_path: %s", dhcpcanon_path);
+	if (!dhcpcanon_path) {
+		_LOGW ("dhcpcanon could not be found");
+		return FALSE;
+	}
+
+	pid_file = g_strdup_printf (RUNSTATEDIR "/dhcpcanon%c-%s.pid",
+	                            nm_utils_addr_family_to_char (addr_family),
+	                            iface);
+	_LOGD ("pid_file: %s", pid_file);
+
+	/* Kill any existing dhcpcanon from the pidfile */
+	binary_name = g_path_get_basename (dhcpcanon_path);
+	nm_dhcp_client_stop_existing (pid_file, binary_name);
+	g_free (binary_name);
+
+	argv = g_ptr_array_new ();
+	g_ptr_array_add (argv, (gpointer) dhcpcanon_path);
+
+	g_ptr_array_add (argv, (gpointer) "-sf");	/* Set script file */
+	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
+
+	if (pid_file) {
+		g_ptr_array_add (argv, (gpointer) "-pf");	/* Set pid file */
+		g_ptr_array_add (argv, (gpointer) pid_file);
+	}
+
+	if (priv->conf_file) {
+		g_ptr_array_add (argv, (gpointer) "-cf");	/* Set interface config file */
+		g_ptr_array_add (argv, (gpointer) priv->conf_file);
+	}
+
+	/* Usually the system bus address is well-known; but if it's supposed
+	 * to be something else, we need to push it to dhcpcanon, since dhcpcanon
+	 * sanitizes the environment it gives the action scripts.
+	 */
+	system_bus_address = getenv ("DBUS_SYSTEM_BUS_ADDRESS");
+	if (system_bus_address) {
+		system_bus_address_env = g_strdup_printf ("DBUS_SYSTEM_BUS_ADDRESS=%s", system_bus_address);
+		g_ptr_array_add (argv, (gpointer) "-e");
+		g_ptr_array_add (argv, (gpointer) system_bus_address_env);
+	}
+
+
+	g_ptr_array_add (argv, (gpointer) iface);
+	g_ptr_array_add (argv, NULL);
+
+	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
+	g_free (cmd_str);
+
+	if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
+	                   G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+	                   nm_utils_setpgid, NULL, &pid, &error)) {
+		 g_assert (pid > 0);
+		_LOGI ("dhcpcanon started with pid %d", pid);
+		nm_dhcp_client_watch_child (client, pid);
+		priv->pid_file = pid_file;
+	} else {
+		_LOGW ("dhcpcanon failed to start: '%s'", error->message);
+		g_error_free (error);
+		g_free (pid_file);
+	}
+
+	g_ptr_array_free (argv, TRUE);
+	g_free (system_bus_address_env);
+	return pid > 0 ? TRUE : FALSE;
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+{
+	gboolean success = FALSE;
+	success = dhcpcanon_start (client, NULL, NULL, FALSE, NULL, 0);
+	return success;
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const struct in6_addr *ll_addr,
+           gboolean info_only,
+           NMSettingIP6ConfigPrivacy privacy,
+           const GByteArray *duid,
+           guint needed_prefixes)
+{
+	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
+
+	_LOGW ("the dhcpcd backend does not support IPv6");
+	return FALSE;
+}
+static void
+stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+{
+	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release, duid);
+
+	if (priv->pid_file) {
+		if (remove (priv->pid_file) == -1)
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+		g_free (priv->pid_file);
+		priv->pid_file = NULL;
+	}
+}
+
+static void
+state_changed (NMDhcpClient *client,
+               NMDhcpState state,
+               GObject *ip_config,
+               GHashTable *options)
+{
+	if (nm_dhcp_client_get_client_id (client))
+		return;
+	if (state != NM_DHCP_STATE_BOUND)
+		return;
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_dhcpcanon_init (NMDhcpDhcpcanon *self)
+{
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+
+	priv->dhcp_listener = g_object_ref (nm_dhcp_listener_get ());
+	g_signal_connect (priv->dhcp_listener,
+	                  NM_DHCP_LISTENER_EVENT,
+	                  G_CALLBACK (nm_dhcp_client_handle_event),
+	                  self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE ((NMDhcpDhcpcanon *) object);
+
+	if (priv->dhcp_listener) {
+		g_signal_handlers_disconnect_by_func (priv->dhcp_listener,
+		                                      G_CALLBACK (nm_dhcp_client_handle_event),
+		                                      NM_DHCP_DHCPCANON (object));
+		g_clear_object (&priv->dhcp_listener);
+	}
+
+	nm_clear_g_free (&priv->pid_file);
+
+	G_OBJECT_CLASS (nm_dhcp_dhcpcanon_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_dhcpcanon_class_init (NMDhcpDhcpcanonClass *dhcpcanon_class)
+{
+	NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (dhcpcanon_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (dhcpcanon_class);
+
+	object_class->dispose = dispose;
+
+	client_class->ip4_start = ip4_start;
+	client_class->ip6_start = ip6_start;
+	client_class->stop = stop;
+	client_class->state_changed = state_changed;
+}
+
+const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = {
+	.name = "dhcpcanon",
+	.get_type = nm_dhcp_dhcpcanon_get_type,
+	.get_path = nm_dhcp_dhcpcanon_get_path,
+	.get_lease_ip_configs = NULL,
+};
+
+#endif /* WITH_DHCPCANON */
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index ca697ab3..a0449816 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -31,6 +31,7 @@
 
 #include "nm-dhcp-helper-api.h"
 #include "nm-dhcp-client.h"
+#include "nm-dhcp-manager.h"
 #include "nm-core-internal.h"
 #include "nm-bus-manager.h"
 #include "NetworkManagerUtils.h"
@@ -40,10 +41,13 @@
 
 /*****************************************************************************/
 
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[3] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 	/* the order here matters, as we will try the plugins in this order to find
 	 * the first available plugin. */
 
+#if WITH_DHCPCANON
+	&_nm_dhcp_client_factory_dhcpcanon,
+#endif
 #if WITH_DHCLIENT
 	&_nm_dhcp_client_factory_dhclient,
 #endif
@@ -135,49 +139,35 @@ get_option (GVariant *options, const char *key)
 }
 
 static void
-_method_call (GDBusConnection *connection,
-              const char *sender,
-              const char *object_path,
-              const char *interface_name,
-              const char *method_name,
-              GVariant *parameters,
-              GDBusMethodInvocation *invocation,
-              gpointer user_data)
+_method_call_handle (NMDhcpListener *self,
+                     GVariant *parameters)
 {
-	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
-	char *iface = NULL;
-	char *pid_str = NULL;
-	char *reason = NULL;
-	gint pid;
+	gs_free char *iface = NULL;
+	gs_free char *pid_str = NULL;
+	gs_free char *reason = NULL;
+	gs_unref_variant GVariant *options;
+	int pid;
 	gboolean handled = FALSE;
-	GVariant *options;
-
-	if (!nm_streq0 (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME))
-		g_return_if_reached ();
-	if (!nm_streq0 (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY))
-		g_return_if_reached ();
-	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
-		g_return_if_reached ();
 
 	g_variant_get (parameters, "(@a{sv})", &options);
 
 	iface = get_option (options, "interface");
 	if (iface == NULL) {
 		_LOGW ("dhcp-event: didn't have associated interface.");
-		goto out;
+		return;
 	}
 
 	pid_str = get_option (options, "pid");
 	pid = _nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1);
 	if (pid == -1) {
 		_LOGW ("dhcp-event: couldn't convert PID '%s' to an integer", pid_str ? pid_str : "(null)");
-		goto out;
+		return;
 	}
 
 	reason = get_option (options, "reason");
 	if (reason == NULL) {
 		_LOGW ("dhcp-event: (pid %d) DHCP event didn't have a reason", pid);
-		goto out;
+		return;
 	}
 
 	g_signal_emit (self, signals[EVENT], 0, iface, pid, options, reason, &handled);
@@ -188,12 +178,29 @@ _method_call (GDBusConnection *connection,
 		} else
 			_LOGW ("dhcp-event: (pid %d) unhandled DHCP event for interface %s", pid, iface);
 	}
+}
+
+static void
+_method_call (GDBusConnection *connection,
+              const char *sender,
+              const char *object_path,
+              const char *interface_name,
+              const char *method_name,
+              GVariant *parameters,
+              GDBusMethodInvocation *invocation,
+              gpointer user_data)
+{
+	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
+
+	if (!nm_streq0 (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME))
+		g_return_if_reached ();
+	if (!nm_streq0 (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY))
+		g_return_if_reached ();
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
+		g_return_if_reached ();
+
+	_method_call_handle (self, parameters);
 
-out:
-	g_free (iface);
-	g_free (pid_str);
-	g_free (reason);
-	g_variant_unref (options);
 	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index fff9f9ec..f5c7c84b 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -34,11 +34,11 @@
 #include <fcntl.h>
 #include <stdio.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
 
-#define DHCP_TIMEOUT 45 /* default DHCP timeout, in seconds */
-
 /*****************************************************************************/
 
 typedef struct {
@@ -95,7 +95,7 @@ _client_factory_available (const NMDhcpClientFactory *client_factory)
 /*****************************************************************************/
 
 static NMDhcpClient *
-get_client_for_ifindex (NMDhcpManager *manager, int ifindex, gboolean ip6)
+get_client_for_ifindex (NMDhcpManager *manager, int addr_family, int ifindex)
 {
 	NMDhcpManagerPrivate *priv;
 	GHashTableIter iter;
@@ -111,7 +111,7 @@ get_client_for_ifindex (NMDhcpManager *manager, int ifindex, gboolean ip6)
 		NMDhcpClient *candidate = NM_DHCP_CLIENT (value);
 
 		if (   nm_dhcp_client_get_ifindex (candidate) == ifindex
-		    && nm_dhcp_client_get_ipv6 (candidate) == ip6)
+		    && nm_dhcp_client_get_addr_family (candidate) == addr_family)
 			return candidate;
 	}
 
@@ -152,12 +152,14 @@ client_state_changed (NMDhcpClient *client,
 
 static NMDhcpClient *
 client_start (NMDhcpManager *self,
+              int addr_family,
+              NMDedupMultiIndex *multi_idx,
               const char *iface,
               int ifindex,
               const GByteArray *hwaddr,
               const char *uuid,
-              guint32 priority,
-              gboolean ipv6,
+              guint32 route_table,
+              guint32 route_metric,
               const struct in6_addr *ipv6_ll_addr,
               const char *dhcp_client_id,
               guint32 timeout,
@@ -185,7 +187,7 @@ client_start (NMDhcpManager *self,
 		return NULL;
 
 	/* Kill any old client instance */
-	client = get_client_for_ifindex (self, ifindex, ipv6);
+	client = get_client_for_ifindex (self, addr_family, ifindex);
 	if (client) {
 		g_object_ref (client);
 		remove_client (self, client);
@@ -195,21 +197,23 @@ client_start (NMDhcpManager *self,
 
 	/* And make a new one */
 	client = g_object_new (priv->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_IPV6, ipv6,
 	                       NM_DHCP_CLIENT_UUID, uuid,
-	                       NM_DHCP_CLIENT_PRIORITY, priority,
-	                       NM_DHCP_CLIENT_TIMEOUT, timeout ? timeout : DHCP_TIMEOUT,
+	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
+	                       NM_DHCP_CLIENT_ROUTE_METRIC, (guint) route_metric,
+	                       NM_DHCP_CLIENT_TIMEOUT, (guint) timeout,
 	                       NULL);
 	g_hash_table_insert (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client, g_object_ref (client));
 	g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self);
 
-	if (ipv6)
-		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
-	else
+	if (addr_family == AF_INET)
 		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, hostname_use_fqdn, last_ip4_address);
+	else
+		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
 
 	if (!success) {
 		remove_client (self, client);
@@ -222,11 +226,13 @@ client_start (NMDhcpManager *self,
 /* Caller owns a reference to the NMDhcpClient on return */
 NMDhcpClient *
 nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
+                           NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
                            const GByteArray *hwaddr,
                            const char *uuid,
-                           guint32 priority,
+                           guint32 route_table,
+                           guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            const char *dhcp_fqdn,
@@ -267,7 +273,8 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 		}
 	}
 
-	return client_start (self, iface, ifindex, hwaddr, uuid, priority, FALSE, NULL,
+	return client_start (self, AF_INET, multi_idx, iface, ifindex, hwaddr, uuid,
+	                     route_table, route_metric, NULL,
 	                     dhcp_client_id, timeout, dhcp_anycast_addr, hostname,
 	                     use_fqdn, FALSE, 0, last_ip_address, 0);
 }
@@ -275,12 +282,14 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 /* Caller owns a reference to the NMDhcpClient on return */
 NMDhcpClient *
 nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
+                           NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
                            const GByteArray *hwaddr,
                            const struct in6_addr *ll_addr,
                            const char *uuid,
-                           guint32 priority,
+                           guint32 route_table,
+                           guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            guint32 timeout,
@@ -299,8 +308,9 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 		/* Always prefer the explicit dhcp-hostname if given */
 		hostname = dhcp_hostname ? dhcp_hostname : priv->default_hostname;
 	}
-	return client_start (self, iface, ifindex, hwaddr, uuid, priority, TRUE,
-	                     ll_addr, NULL, timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
+	return client_start (self, AF_INET6, multi_idx, iface, ifindex, hwaddr, uuid,
+	                     route_table, route_metric, ll_addr,
+	                     NULL, timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
 	                     privacy, NULL, needed_prefixes);
 }
 
@@ -320,11 +330,13 @@ nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager, const char *hostna
 
 GSList *
 nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                      NMDedupMultiIndex *multi_idx,
+                                      int addr_family,
                                       const char *iface,
                                       int ifindex,
                                       const char *uuid,
-                                      gboolean ipv6,
-                                      guint32 default_route_metric)
+                                      guint32 route_table,
+                                      guint32 route_metric)
 {
 	NMDhcpManagerPrivate *priv;
 
@@ -332,11 +344,12 @@ nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
 	g_return_val_if_fail (iface != NULL, NULL);
 	g_return_val_if_fail (ifindex >= -1, NULL);
 	g_return_val_if_fail (uuid != NULL, NULL);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
 
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 	if (   priv->client_factory
 	    && priv->client_factory->get_lease_ip_configs)
-		return priv->client_factory->get_lease_ip_configs (iface, ifindex, uuid, ipv6, default_route_metric);
+		return priv->client_factory->get_lease_ip_configs (multi_idx, addr_family, iface, ifindex, uuid, route_table, route_metric);
 	return NULL;
 }
 
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index 66fdd145..078117ff 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -46,11 +46,13 @@ void           nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager,
                                                      const char *hostname);
 
 NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
+                                              struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
                                               const GByteArray *hwaddr,
                                               const char *uuid,
-                                              guint32 priority,
+                                              guint32 route_table,
+                                              guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               const char *dhcp_fqdn,
@@ -60,12 +62,14 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               const char *last_ip_address);
 
 NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
+                                              struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
                                               const GByteArray *hwaddr,
                                               const struct in6_addr *ll_addr,
                                               const char *uuid,
-                                              guint32 priority,
+                                              guint32 route_table,
+                                              guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               guint32 timeout,
@@ -75,15 +79,17 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               guint needed_prefixes);
 
 GSList *       nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                                     struct _NMDedupMultiIndex *multi_idx,
+                                                     int addr_family,
                                                      const char *iface,
                                                      int ifindex,
                                                      const char *uuid,
-                                                     gboolean ipv6,
-                                                     guint32 default_route_metric);
+                                                     guint32 route_table,
+                                                     guint32 route_metric);
 
 /* For testing only */
 extern const char* nm_dhcp_helper_path;
 
-extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[3];
+extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4];
 
 #endif /* __NETWORKMANAGER_DHCP_MANAGER_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index aa902701..9b1a4433 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -28,6 +28,8 @@
 #include <ctype.h>
 #include <net/if_arp.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-utils.h"
 #include "nm-dhcp-utils.h"
 #include "NetworkManagerUtils.h"
@@ -79,9 +81,6 @@ G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
 
 #define DHCP_OPTION_NIS_DOMAIN         40
 #define DHCP_OPTION_NIS_SERVERS        41
-#define DHCP_OPTION_DOMAIN_SEARCH     119
-#define DHCP_OPTION_MS_ROUTES         249
-#define DHCP_OPTION_WPAD              252
 
 /* Internal values */
 #define DHCP_OPTION_IP_ADDRESS       1024
@@ -105,53 +104,53 @@ typedef struct {
 #define REQPREFIX "requested_"
 
 static const ReqOption dhcp4_requests[] = {
-	{ SD_DHCP_OPTION_SUBNET_MASK,            REQPREFIX "subnet_mask",                     TRUE },
-	{ SD_DHCP_OPTION_TIME_OFFSET,            REQPREFIX "time_offset",                     TRUE },
-	{ SD_DHCP_OPTION_ROUTER,                 REQPREFIX "routers",                         TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,     REQPREFIX "domain_name_servers",             TRUE },
-	{ SD_DHCP_OPTION_HOST_NAME,              REQPREFIX "host_name",                       TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME,            REQPREFIX "domain_name",                     TRUE },
-	{ SD_DHCP_OPTION_INTERFACE_MTU,          REQPREFIX "interface_mtu",                   TRUE },
-	{ SD_DHCP_OPTION_BROADCAST,              REQPREFIX "broadcast_address",               TRUE },
-	{ SD_DHCP_OPTION_STATIC_ROUTE,           REQPREFIX "static_routes",                   TRUE },
-	{ DHCP_OPTION_NIS_DOMAIN,                REQPREFIX "nis_domain",                      TRUE },
-	{ DHCP_OPTION_NIS_SERVERS,               REQPREFIX "nis_servers",                     TRUE },
-	{ SD_DHCP_OPTION_NTP_SERVER,             REQPREFIX "ntp_servers",                     TRUE },
-	{ SD_DHCP_OPTION_SERVER_IDENTIFIER,      REQPREFIX "dhcp_server_identifier",          TRUE },
-	{ DHCP_OPTION_DOMAIN_SEARCH,             REQPREFIX "domain_search",                   TRUE },
-	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, REQPREFIX "rfc3442_classless_static_routes", TRUE },
-	{ DHCP_OPTION_MS_ROUTES,                 REQPREFIX "ms_classless_static_routes",      TRUE },
-	{ DHCP_OPTION_WPAD,                      REQPREFIX "wpad",                            TRUE },
+	{ SD_DHCP_OPTION_SUBNET_MASK,                    REQPREFIX "subnet_mask",                     TRUE },
+	{ SD_DHCP_OPTION_TIME_OFFSET,                    REQPREFIX "time_offset",                     TRUE },
+	{ SD_DHCP_OPTION_ROUTER,                         REQPREFIX "routers",                         TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             REQPREFIX "domain_name_servers",             TRUE },
+	{ SD_DHCP_OPTION_HOST_NAME,                      REQPREFIX "host_name",                       TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_NAME,                    REQPREFIX "domain_name",                     TRUE },
+	{ SD_DHCP_OPTION_INTERFACE_MTU,                  REQPREFIX "interface_mtu",                   TRUE },
+	{ SD_DHCP_OPTION_BROADCAST,                      REQPREFIX "broadcast_address",               TRUE },
+	{ SD_DHCP_OPTION_STATIC_ROUTE,                   REQPREFIX "static_routes",                   TRUE },
+	{ DHCP_OPTION_NIS_DOMAIN,                        REQPREFIX "nis_domain",                      TRUE },
+	{ DHCP_OPTION_NIS_SERVERS,                       REQPREFIX "nis_servers",                     TRUE },
+	{ SD_DHCP_OPTION_NTP_SERVER,                     REQPREFIX "ntp_servers",                     TRUE },
+	{ SD_DHCP_OPTION_SERVER_IDENTIFIER,              REQPREFIX "dhcp_server_identifier",          TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             REQPREFIX "domain_search",                   TRUE },
+	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         REQPREFIX "rfc3442_classless_static_routes", TRUE },
+	{ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, REQPREFIX "ms_classless_static_routes",      TRUE },
+	{ SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    REQPREFIX "wpad",                            TRUE },
 
 	/* Internal values */
-	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,  REQPREFIX "expiry",                          FALSE },
-	{ SD_DHCP_OPTION_CLIENT_IDENTIFIER,      REQPREFIX "dhcp_client_identifier",          FALSE },
-	{ DHCP_OPTION_IP_ADDRESS,                REQPREFIX "ip_address",                      FALSE },
+	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          REQPREFIX "expiry",                          FALSE },
+	{ SD_DHCP_OPTION_CLIENT_IDENTIFIER,              REQPREFIX "dhcp_client_identifier",          FALSE },
+	{ DHCP_OPTION_IP_ADDRESS,                        REQPREFIX "ip_address",                      FALSE },
 	{ 0, NULL, FALSE }
 };
 
 static const ReqOption dhcp6_requests[] = {
-	{ SD_DHCP6_OPTION_CLIENTID,              REQPREFIX "dhcp6_client_id",     TRUE },
+	{ SD_DHCP6_OPTION_CLIENTID,                      REQPREFIX "dhcp6_client_id",     TRUE },
 
 	/* Don't request server ID by default; some servers don't reply to
 	 * Information Requests that request the Server ID.
 	 */
-	{ SD_DHCP6_OPTION_SERVERID,              REQPREFIX "dhcp6_server_id",     FALSE },
+	{ SD_DHCP6_OPTION_SERVERID,                      REQPREFIX "dhcp6_server_id",     FALSE },
 
-	{ SD_DHCP6_OPTION_DNS_SERVERS,           REQPREFIX "dhcp6_name_servers",  TRUE },
-	{ SD_DHCP6_OPTION_DOMAIN_LIST,           REQPREFIX "dhcp6_domain_search", TRUE },
-	{ SD_DHCP6_OPTION_SNTP_SERVERS,          REQPREFIX "dhcp6_sntp_servers",  TRUE },
+	{ SD_DHCP6_OPTION_DNS_SERVERS,                   REQPREFIX "dhcp6_name_servers",  TRUE },
+	{ SD_DHCP6_OPTION_DOMAIN_LIST,                   REQPREFIX "dhcp6_domain_search", TRUE },
+	{ SD_DHCP6_OPTION_SNTP_SERVERS,                  REQPREFIX "dhcp6_sntp_servers",  TRUE },
 
 	/* Internal values */
-	{ DHCP6_OPTION_IP_ADDRESS,               REQPREFIX "ip6_address",         FALSE },
-	{ DHCP6_OPTION_PREFIXLEN,                REQPREFIX "ip6_prefixlen",       FALSE },
-	{ DHCP6_OPTION_PREFERRED_LIFE,           REQPREFIX "preferred_life",      FALSE },
-	{ DHCP6_OPTION_MAX_LIFE,                 REQPREFIX "max_life",            FALSE },
-	{ DHCP6_OPTION_STARTS,                   REQPREFIX "starts",              FALSE },
-	{ DHCP6_OPTION_LIFE_STARTS,              REQPREFIX "life_starts",         FALSE },
-	{ DHCP6_OPTION_RENEW,                    REQPREFIX "renew",               FALSE },
-	{ DHCP6_OPTION_REBIND,                   REQPREFIX "rebind",              FALSE },
-	{ DHCP6_OPTION_IAID,                     REQPREFIX "iaid",                FALSE },
+	{ DHCP6_OPTION_IP_ADDRESS,                       REQPREFIX "ip6_address",         FALSE },
+	{ DHCP6_OPTION_PREFIXLEN,                        REQPREFIX "ip6_prefixlen",       FALSE },
+	{ DHCP6_OPTION_PREFERRED_LIFE,                   REQPREFIX "preferred_life",      FALSE },
+	{ DHCP6_OPTION_MAX_LIFE,                         REQPREFIX "max_life",            FALSE },
+	{ DHCP6_OPTION_STARTS,                           REQPREFIX "starts",              FALSE },
+	{ DHCP6_OPTION_LIFE_STARTS,                      REQPREFIX "life_starts",         FALSE },
+	{ DHCP6_OPTION_RENEW,                            REQPREFIX "renew",               FALSE },
+	{ DHCP6_OPTION_REBIND,                           REQPREFIX "rebind",              FALSE },
+	{ DHCP6_OPTION_IAID,                             REQPREFIX "iaid",                FALSE },
 	{ 0, NULL, FALSE }
 };
 
@@ -212,16 +211,18 @@ add_requests_to_options (GHashTable *options, const ReqOption *requests)
 #define LOG_LEASE(domain, ...) \
 G_STMT_START { \
 	if (log_lease) { \
-		_LOG2I ((domain), (iface), __VA_ARGS__); \
+		_LOG2I ((domain), (iface), "  "__VA_ARGS__); \
 	} \
 } G_STMT_END
 
 static NMIP4Config *
-lease_to_ip4_config (const char *iface,
+lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
+                     const char *iface,
                      int ifindex,
                      sd_dhcp_lease *lease,
                      GHashTable *options,
-                     guint32 default_priority,
+                     guint32 route_table,
+                     guint32 route_metric,
                      gboolean log_lease,
                      GError **error)
 {
@@ -229,11 +230,12 @@ lease_to_ip4_config (const char *iface,
 	struct in_addr tmp_addr;
 	const struct in_addr *addr_list;
 	char buf[INET_ADDRSTRLEN];
-	const char *str;
+	const char *s;
 	guint32 lifetime = 0, i;
 	NMPlatformIP4Address address;
-	GString *l;
+	nm_auto_free_gstring GString *str = NULL;
 	gs_free sd_dhcp_route **routes = NULL;
+	const char *const*search_domains = NULL;
 	guint16 mtu;
 	int r, num;
 	guint64 end_time;
@@ -241,24 +243,26 @@ lease_to_ip4_config (const char *iface,
 	gsize data_len;
 	gboolean metered = FALSE;
 	gboolean static_default_gateway = FALSE;
+	gboolean gateway_has = FALSE;
+	in_addr_t gateway = 0;
 
 	g_return_val_if_fail (lease != NULL, NULL);
 
-	ip4_config = nm_ip4_config_new (ifindex);
+	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 
 	/* Address */
 	sd_dhcp_lease_get_address (lease, &tmp_addr);
 	memset (&address, 0, sizeof (address));
 	address.address = tmp_addr.s_addr;
 	address.peer_address = tmp_addr.s_addr;
-	str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-	LOG_LEASE (LOGD_DHCP4, "  address %s", str);
-	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, str);
+	s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+	LOG_LEASE (LOGD_DHCP4, "address %s", s);
+	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, s);
 
 	/* Prefix/netmask */
 	sd_dhcp_lease_get_netmask (lease, &tmp_addr);
 	address.plen = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
-	LOG_LEASE (LOGD_DHCP4, "  plen %d", address.plen);
+	LOG_LEASE (LOGD_DHCP4, "plen %d", address.plen);
 	add_option (options,
 	            dhcp4_requests,
 	            SD_DHCP_OPTION_SUBNET_MASK,
@@ -269,7 +273,7 @@ lease_to_ip4_config (const char *iface,
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 	address.lifetime = address.preferred = lifetime;
 	end_time = (guint64) time (NULL) + lifetime;
-	LOG_LEASE (LOGD_DHCP4, "  expires in %" G_GUINT32_FORMAT " seconds", lifetime);
+	LOG_LEASE (LOGD_DHCP4, "expires in %" G_GUINT32_FORMAT " seconds", lifetime);
 	add_option_u64 (options,
 	                dhcp4_requests,
 	                SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
@@ -281,47 +285,58 @@ lease_to_ip4_config (const char *iface,
 	/* DNS Servers */
 	num = sd_dhcp_lease_get_dns (lease, &addr_list);
 	if (num > 0) {
-		l = g_string_sized_new (30);
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			if (addr_list[i].s_addr) {
 				nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
-				str = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
-				LOG_LEASE (LOGD_DHCP4, "  nameserver '%s'", str);
-				g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+				s = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
+				LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", s);
+				g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
 			}
 		}
-		if (l->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, l->str);
-		g_string_free (l, TRUE);
+		if (str->len)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
+	}
+
+	/* Search domains */
+	num = sd_dhcp_lease_get_search_domains (lease, (char ***) &search_domains);
+	if (num > 0) {
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			nm_ip4_config_add_search (ip4_config, search_domains[i]);
+			g_string_append_printf (str, "%s%s", str->len ? " " : "", search_domains[i]);
+			LOG_LEASE (LOGD_DHCP4, "domain search '%s'", search_domains[i]);
+		}
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
 	}
 
 	/* Domain Name */
-	r = sd_dhcp_lease_get_domainname (lease, &str);
+	r = sd_dhcp_lease_get_domainname (lease, &s);
 	if (r == 0) {
 		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
 		 * As systemd escapes such characters, split them at \\032. */
-		char **domains = g_strsplit (str, "\\032", 0);
-		char **s;
+		char **domains = g_strsplit (s, "\\032", 0);
+		char **d;
 
-		for (s = domains; *s; s++) {
-			LOG_LEASE (LOGD_DHCP4, "  domain name '%s'", *s);
-			nm_ip4_config_add_domain (ip4_config, *s);
+		for (d = domains; *d; d++) {
+			LOG_LEASE (LOGD_DHCP4, "domain name '%s'", *d);
+			nm_ip4_config_add_domain (ip4_config, *d);
 		}
 		g_strfreev (domains);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, str);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
 	}
 
 	/* Hostname */
-	r = sd_dhcp_lease_get_hostname (lease, &str);
+	r = sd_dhcp_lease_get_hostname (lease, &s);
 	if (r == 0) {
-		LOG_LEASE (LOGD_DHCP4, "  hostname '%s'", str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, str);
+		LOG_LEASE (LOGD_DHCP4, "hostname '%s'", s);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, s);
 	}
 
 	/* Routes */
 	num = sd_dhcp_lease_get_routes (lease, &routes);
 	if (num > 0) {
-		l = g_string_sized_new (30);
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			NMPlatformIP4Route route = { 0 };
 			const char *gw_str;
@@ -330,12 +345,13 @@ lease_to_ip4_config (const char *iface,
 
 			if (sd_dhcp_route_get_destination (routes[i], &a) < 0)
 				continue;
-			route.network = a.s_addr;
 
 			if (   sd_dhcp_route_get_destination_prefix_length (routes[i], &plen) < 0
 			    || plen > 32)
 				continue;
+
 			route.plen = plen;
+			route.network = nm_utils_ip4_address_clear_host_address (a.s_addr, plen);
 
 			if (sd_dhcp_route_get_gateway (routes[i], &a) < 0)
 				continue;
@@ -343,28 +359,29 @@ lease_to_ip4_config (const char *iface,
 
 			if (route.plen) {
 				route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-				route.metric = default_priority;
-				nm_ip4_config_add_route (ip4_config, &route);
+				route.metric = route_metric;
+				route.table_coerced = nm_platform_route_table_coerce (route_table);
+				nm_ip4_config_add_route (ip4_config, &route, NULL);
 
-				str = nm_utils_inet4_ntop (route.network, buf);
+				s = nm_utils_inet4_ntop (route.network, buf);
 				gw_str = nm_utils_inet4_ntop (route.gateway, NULL);
-				LOG_LEASE (LOGD_DHCP4, "  static route %s/%d gw %s", str, route.plen, gw_str);
+				LOG_LEASE (LOGD_DHCP4, "static route %s/%d gw %s", s, route.plen, gw_str);
 
-				g_string_append_printf (l, "%s%s/%d %s", l->len ? " " : "", str, route.plen, gw_str);
+				g_string_append_printf (str, "%s%s/%d %s", str->len ? " " : "", s, route.plen, gw_str);
 			} else {
 				if (!static_default_gateway) {
 					static_default_gateway = TRUE;
-					nm_ip4_config_set_gateway (ip4_config, route.gateway);
+					gateway_has = TRUE;
+					gateway = route.gateway;
 
-					str = nm_utils_inet4_ntop (route.gateway, NULL);
-					LOG_LEASE (LOGD_DHCP4, "  gateway %s", str);
-					add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str);
+					s = nm_utils_inet4_ntop (route.gateway, NULL);
+					LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
+					add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
 				}
 			}
 		}
-		if (l->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, l->str);
-		g_string_free (l, TRUE);
+		if (str->len)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str->str);
 	}
 
 	/* If the DHCP server returns both a Classless Static Routes option and a
@@ -376,32 +393,43 @@ lease_to_ip4_config (const char *iface,
 	if (!static_default_gateway) {
 		r = sd_dhcp_lease_get_router (lease, &tmp_addr);
 		if (r == 0) {
-			nm_ip4_config_set_gateway (ip4_config, tmp_addr.s_addr);
-			str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-			LOG_LEASE (LOGD_DHCP4, "  gateway %s", str);
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str);
+			gateway_has = TRUE;
+			gateway = tmp_addr.s_addr;
+			s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+			LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
 		}
 	}
 
+	if (gateway_has) {
+		const NMPlatformIP4Route rt = {
+			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+			.gateway = gateway,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric = route_metric,
+		};
+
+		nm_ip4_config_add_route (ip4_config, &rt, NULL);
+	}
+
 	/* MTU */
 	r = sd_dhcp_lease_get_mtu (lease, &mtu);
 	if (r == 0 && mtu) {
 		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
 		add_option_u32 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
-		LOG_LEASE (LOGD_DHCP4, "  mtu %u", mtu);
+		LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
 	}
 
 	/* NTP servers */
 	num = sd_dhcp_lease_get_ntp (lease, &addr_list);
 	if (num > 0) {
-		l = g_string_sized_new (30);
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			str = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
-			LOG_LEASE (LOGD_DHCP4, "  ntp server '%s'", str);
-			g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+			s = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
+			LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", s);
+			g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
 		}
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, l->str);
-		g_string_free (l, TRUE);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
 	}
 
 	r = sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len);
@@ -415,20 +443,22 @@ lease_to_ip4_config (const char *iface,
 /*****************************************************************************/
 
 static char *
-get_leasefile_path (const char *iface, const char *uuid, gboolean ipv6)
+get_leasefile_path (int addr_family, const char *iface, const char *uuid)
 {
 	return g_strdup_printf (NMSTATEDIR "/internal%s-%s-%s.lease",
-	                        ipv6 ? "6" : "",
+	                        addr_family == AF_INET6 ? "6" : "",
 	                        uuid,
 	                        iface);
 }
 
 static GSList *
-nm_dhcp_systemd_get_lease_ip_configs (const char *iface,
+nm_dhcp_systemd_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                      int addr_family,
+                                      const char *iface,
                                       int ifindex,
                                       const char *uuid,
-                                      gboolean ipv6,
-                                      guint32 default_route_metric)
+                                      guint32 route_table,
+                                      guint32 route_metric)
 {
 	GSList *leases = NULL;
 	gs_free char *path = NULL;
@@ -436,13 +466,13 @@ nm_dhcp_systemd_get_lease_ip_configs (const char *iface,
 	NMIP4Config *ip4_config;
 	int r;
 
-	if (ipv6)
+	if (addr_family != AF_INET)
 		return NULL;
 
-	path = get_leasefile_path (iface, uuid, FALSE);
+	path = get_leasefile_path (addr_family, iface, uuid);
 	r = dhcp_lease_load (&lease, path);
 	if (r == 0 && lease) {
-		ip4_config = lease_to_ip4_config (iface, ifindex, lease, NULL, default_route_metric, FALSE, NULL);
+		ip4_config = lease_to_ip4_config (multi_idx, iface, ifindex, lease, NULL, route_table, route_metric, FALSE, NULL);
 		if (ip4_config)
 			leases = g_slist_append (leases, ip4_config);
 		sd_dhcp_lease_unref (lease);
@@ -495,12 +525,14 @@ bound4_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
-	ip4_config = lease_to_ip4_config (iface,
+	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+	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,
 	                                  options,
-	                                  nm_dhcp_client_get_priority (NM_DHCP_CLIENT (self)),
+	                                  nm_dhcp_client_get_route_table (NM_DHCP_CLIENT (self)),
+	                                  nm_dhcp_client_get_route_metric (NM_DHCP_CLIENT (self)),
 	                                  TRUE,
 	                                  &error);
 	if (ip4_config) {
@@ -589,9 +621,9 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	g_assert (priv->client6 == NULL);
 
 	g_free (priv->lease_file);
-	priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), FALSE);
+	priv->lease_file = get_leasefile_path (AF_INET, iface, nm_dhcp_client_get_uuid (client));
 
-	r = sd_dhcp_client_new (&priv->client4);
+	r = sd_dhcp_client_new (&priv->client4, FALSE);
 	if (r < 0) {
 		_LOGW ("failed to create client (%d)", r);
 		return FALSE;
@@ -716,7 +748,8 @@ error:
 }
 
 static NMIP6Config *
-lease_to_ip6_config (const char *iface,
+lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
+                     const char *iface,
                      int ifindex,
                      sd_dhcp6_lease *lease,
                      GHashTable *options,
@@ -729,17 +762,17 @@ lease_to_ip6_config (const char *iface,
 	NMIP6Config *ip6_config;
 	const char *addr_str;
 	char **domains;
-	GString *str;
+	nm_auto_free_gstring GString *str = NULL;
 	int num, i;
 	gint32 ts;
 
 	g_return_val_if_fail (lease, NULL);
-	ip6_config = nm_ip6_config_new (ifindex);
+	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
 	ts = nm_utils_get_monotonic_timestamp_s ();
-	str = g_string_sized_new (30);
 
 	/* Addresses */
 	sd_dhcp6_lease_reset_address_iter (lease);
+	nm_gstring_prepare (&str);
 	while (sd_dhcp6_lease_get_address (lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
 		NMPlatformIP6Address address = {
 			.plen = 128,
@@ -756,17 +789,14 @@ lease_to_ip6_config (const char *iface,
 		g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
 
 		LOG_LEASE (LOGD_DHCP6,
-		           "  address %s",
+		           "address %s",
 		           nm_platform_ip6_address_to_string (&address, NULL, 0));
 	};
 
-	if (str->len) {
+	if (str->len)
 		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
-		g_string_set_size (str , 0);
-	}
 
 	if (!info_only && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
-		g_string_free (str, TRUE);
 		g_object_unref (ip6_config);
 		g_set_error_literal (error,
 		                     NM_MANAGER_ERROR,
@@ -778,30 +808,28 @@ lease_to_ip6_config (const char *iface,
 	/* DNS servers */
 	num = sd_dhcp6_lease_get_dns (lease, &dns);
 	if (num > 0) {
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
 			addr_str = nm_utils_inet6_ntop (&dns[i], NULL);
 			g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
-			LOG_LEASE (LOGD_DHCP6, "  nameserver %s", addr_str);
+			LOG_LEASE (LOGD_DHCP6, "nameserver %s", addr_str);
 		}
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
-		g_string_set_size (str, 0);
 	}
 
 	/* Search domains */
 	num = sd_dhcp6_lease_get_domains (lease, &domains);
 	if (num > 0) {
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			nm_ip6_config_add_search (ip6_config, domains[i]);
 			g_string_append_printf (str, "%s%s", str->len ? " " : "", domains[i]);
-			LOG_LEASE (LOGD_DHCP6, "  domain name '%s'", domains[i]);
+			LOG_LEASE (LOGD_DHCP6, "domain name '%s'", domains[i]);
 		}
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
-		g_string_set_size (str, 0);
 	}
 
-	g_string_free (str, TRUE);
-
 	return ip6_config;
 }
 
@@ -825,8 +853,9 @@ bound6_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
-	ip6_config = lease_to_ip6_config (iface,
+	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+	ip6_config = lease_to_ip6_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
+	                                  iface,
 	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
 	                                  lease,
 	                                  options,
@@ -893,7 +922,7 @@ ip6_start (NMDhcpClient *client,
 	g_return_val_if_fail (duid != NULL, FALSE);
 
 	g_free (priv->lease_file);
-	priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), TRUE);
+	priv->lease_file = get_leasefile_path (AF_INET6, iface, nm_dhcp_client_get_uuid (client));
 	priv->info_only = info_only;
 
 	r = sd_dhcp6_client_new (&priv->client6);
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index e55a21b4..4b2d57b9 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -24,6 +24,8 @@
 #include <unistd.h>
 #include <arpa/inet.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-dhcp-utils.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
@@ -36,7 +38,8 @@
 static gboolean
 ip4_process_dhcpcd_rfc3442_routes (const char *iface,
                                    const char *str,
-                                   guint32 priority,
+                                   guint32 route_table,
+                                   guint32 route_metric,
                                    NMIP4Config *ip4_config,
                                    guint32 *gwaddr)
 {
@@ -84,12 +87,13 @@ ip4_process_dhcpcd_rfc3442_routes (const char *iface,
 		} else {
 			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1));
 			memset (&route, 0, sizeof (route));
-			route.network = rt_addr;
+			route.network = nm_utils_ip4_address_clear_host_address (rt_addr, rt_cidr);
 			route.plen = rt_cidr;
 			route.gateway = rt_route;
 			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-			route.metric = priority;
-			nm_ip4_config_add_route (ip4_config, &route);
+			route.metric = route_metric;
+			route.table_coerced = nm_platform_route_table_coerce (route_table);
+			nm_ip4_config_add_route (ip4_config, &route, NULL);
 		}
 	}
 
@@ -142,8 +146,7 @@ process_dhclient_rfc3442_route (const char **octets,
 			goto error;
 		}
 		g_free (str_addr);
-		tmp_addr &= nm_utils_ip4_prefix_to_netmask ((guint32) tmp);
-		route->network = tmp_addr;
+		route->network = nm_utils_ip4_address_clear_host_address (tmp_addr, tmp);
 	}
 
 	/* Handle next hop */
@@ -165,7 +168,8 @@ error:
 static gboolean
 ip4_process_dhclient_rfc3442_routes (const char *iface,
                                      const char *str,
-                                     guint32 priority,
+                                     guint32 route_table,
+                                     guint32 route_metric,
                                      NMIP4Config *ip4_config,
                                      guint32 *gwaddr)
 {
@@ -197,8 +201,9 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 
 			/* normal route */
 			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-			route.metric = priority;
-			nm_ip4_config_add_route (ip4_config, &route);
+			route.metric = route_metric;
+			route.table_coerced = nm_platform_route_table_coerce (route_table);
+			nm_ip4_config_add_route (ip4_config, &route, NULL);
 
 			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s",
 			        nm_utils_inet4_ntop (route.network, addr), route.plen,
@@ -214,7 +219,8 @@ out:
 static gboolean
 ip4_process_classless_routes (const char *iface,
                               GHashTable *options,
-                              guint32 priority,
+                              guint32 route_table,
+                              guint32 route_metric,
                               NMIP4Config *ip4_config,
                               guint32 *gwaddr)
 {
@@ -270,16 +276,17 @@ ip4_process_classless_routes (const char *iface,
 
 	if (strchr (str, '/')) {
 		/* dhcpcd format */
-		return ip4_process_dhcpcd_rfc3442_routes (iface, str, priority, ip4_config, gwaddr);
+		return ip4_process_dhcpcd_rfc3442_routes (iface, str, route_table, route_metric, ip4_config, gwaddr);
 	}
 
-	return ip4_process_dhclient_rfc3442_routes (iface, str, priority, ip4_config, gwaddr);
+	return ip4_process_dhclient_rfc3442_routes (iface, str, route_table, route_metric, ip4_config, gwaddr);
 }
 
 static void
 process_classful_routes (const char *iface,
                          GHashTable *options,
-                         guint32 priority,
+                         guint32 route_table,
+                         guint32 route_metric,
                          NMIP4Config *ip4_config)
 {
 	const char *str;
@@ -316,16 +323,19 @@ process_classful_routes (const char *iface,
 		   The Static Routes option (option 33) does not provide a subnet mask
 		   for each route - it is assumed that the subnet mask is implicit in
 		   whatever network number is specified in each route entry */
-		route.plen = nm_utils_ip4_get_default_prefix (rt_addr);
-		if (rt_addr & ~nm_utils_ip4_prefix_to_netmask (route.plen)) {
+		route.plen = _nm_utils_ip4_get_default_prefix (rt_addr);
+		if (rt_addr & ~_nm_utils_ip4_prefix_to_netmask (route.plen)) {
 			/* RFC 943: target not "this network"; using host routing */
 			route.plen = 32;
 		}
 		route.gateway = rt_route;
 		route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-		route.metric = priority;
+		route.metric = route_metric;
+		route.table_coerced = nm_platform_route_table_coerce (route_table);
+
+		route.network = nm_utils_ip4_address_clear_host_address (route.network, route.plen);
 
-		nm_ip4_config_add_route (ip4_config, &route);
+		nm_ip4_config_add_route (ip4_config, &route, NULL);
 		_LOG2I (LOGD_DHCP, iface, "  static route %s",
 		             nm_platform_ip4_route_to_string (&route, NULL, 0));
 	}
@@ -383,22 +393,25 @@ ip4_add_domain_search (gpointer data, gpointer user_data)
 }
 
 NMIP4Config *
-nm_dhcp_utils_ip4_config_from_options (int ifindex,
+nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
+                                       int ifindex,
                                        const char *iface,
                                        GHashTable *options,
-                                       guint32 priority)
+                                       guint32 route_table,
+                                       guint32 route_metric)
 {
 	NMIP4Config *ip4_config = NULL;
 	guint32 tmp_addr;
 	in_addr_t addr;
 	NMPlatformIP4Address address;
 	char *str = NULL;
-	guint32 gwaddr = 0;
+	gboolean gateway_has = FALSE;
+	guint32 gateway = 0;
 	guint8 plen = 0;
 
 	g_return_val_if_fail (options != NULL, NULL);
 
-	ip4_config = nm_ip4_config_new (ifindex);
+	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 	memset (&address, 0, sizeof (address));
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 
@@ -414,7 +427,7 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 		_LOG2I (LOGD_DHCP4, iface, "  plen %d (%s)", plen, str);
 	} else {
 		/* Get default netmask for the IP according to appropriate class. */
-		plen = nm_utils_ip4_get_default_prefix (addr);
+		plen = _nm_utils_ip4_get_default_prefix (addr);
 		_LOG2I (LOGD_DHCP4, iface, "  plen %d (default)", plen);
 	}
 	nm_platform_ip4_address_set_addr (&address, addr, plen);
@@ -422,12 +435,12 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 	/* Routes: if the server returns classless static routes, we MUST ignore
 	 * the 'static_routes' option.
 	 */
-	if (!ip4_process_classless_routes (iface, options, priority, ip4_config, &gwaddr))
-		process_classful_routes (iface, options, priority, ip4_config);
+	if (!ip4_process_classless_routes (iface, options, route_table, route_metric, ip4_config, &gateway))
+		process_classful_routes (iface, options, route_table, route_metric, ip4_config);
 
-	if (gwaddr) {
-		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gwaddr, NULL));
-		nm_ip4_config_set_gateway (ip4_config, gwaddr);
+	if (gateway) {
+		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, NULL));
+		gateway_has = TRUE;
 	} else {
 		/* If the gateway wasn't provided as a classless static route with a
 		 * subnet length of 0, try to find it using the old-style 'routers' option.
@@ -439,9 +452,9 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 
 			for (s = routers; *s; s++) {
 				/* FIXME: how to handle multiple routers? */
-				if (inet_pton (AF_INET, *s, &gwaddr) > 0) {
-					nm_ip4_config_set_gateway (ip4_config, gwaddr);
+				if (inet_pton (AF_INET, *s, &gateway) > 0) {
 					_LOG2I (LOGD_DHCP4, iface, "  gateway %s", *s);
+					gateway_has = TRUE;
 					break;
 				} else
 					_LOG2W (LOGD_DHCP4, iface, "ignoring invalid gateway '%s'", *s);
@@ -450,6 +463,17 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 		}
 	}
 
+	if (gateway_has) {
+		const NMPlatformIP4Route r = {
+			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+			.gateway = gateway,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric = route_metric,
+		};
+
+		nm_ip4_config_add_route (ip4_config, &r, NULL);
+	}
+
 	str = g_hash_table_lookup (options, "dhcp_lease_time");
 	if (str) {
 		address.lifetime = address.preferred = strtoul (str, NULL, 10);
@@ -616,10 +640,10 @@ nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options)
 }
 
 NMIP6Config *
-nm_dhcp_utils_ip6_config_from_options (int ifindex,
+nm_dhcp_utils_ip6_config_from_options (NMDedupMultiIndex *multi_idx,
+                                       int ifindex,
                                        const char *iface,
                                        GHashTable *options,
-                                       guint32 priority,
                                        gboolean info_only)
 {
 	NMIP6Config *ip6_config = NULL;
@@ -633,7 +657,7 @@ nm_dhcp_utils_ip6_config_from_options (int ifindex,
 	address.plen = 128;
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 
-	ip6_config = nm_ip6_config_new (ifindex);
+	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
 
 	str = g_hash_table_lookup (options, "max_life");
 	if (str) {
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 05982b16..32140f48 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -24,15 +24,17 @@
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
 
-NMIP4Config *nm_dhcp_utils_ip4_config_from_options (int ifindex,
+NMIP4Config *nm_dhcp_utils_ip4_config_from_options (struct _NMDedupMultiIndex *multi_idx,
+                                                    int ifindex,
                                                     const char *iface,
                                                     GHashTable *options,
-                                                    guint priority);
+                                                    guint32 route_table,
+                                                    guint32 route_metric);
 
-NMIP6Config *nm_dhcp_utils_ip6_config_from_options (int ifindex,
+NMIP6Config *nm_dhcp_utils_ip6_config_from_options (struct _NMDedupMultiIndex *multi_idx,
+                                                    int ifindex,
                                                     const char *iface,
                                                     GHashTable *options,
-                                                    guint priority,
                                                     gboolean info_only);
 
 NMPlatformIP6Address nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options);
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 5816932b..f2e1f321 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -23,6 +23,9 @@
 #include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
 
 #include "NetworkManagerUtils.h"
 #include "dhcp/nm-dhcp-dhclient-utils.h"
@@ -35,10 +38,14 @@
 
 #define DEBUG 1
 
+static const int IFINDEX = 5;
+static const guint32 ROUTE_TABLE = RT_TABLE_MAIN;
+static const guint32 ROUTE_METRIC = 100;
+
 static void
 test_config (const char *orig,
              const char *expected,
-             gboolean ipv6,
+             int addr_family,
              const char *hostname,
              guint32 timeout,
              gboolean use_fqdn,
@@ -57,7 +64,7 @@ test_config (const char *orig,
 	}
 
 	new = nm_dhcp_dhclient_create_config (iface,
-	                                      ipv6,
+	                                      addr_family,
 	                                      client_id,
 	                                      anycast_addr,
 	                                      hostname,
@@ -106,7 +113,7 @@ static const char *orig_missing_expected = \
 static void
 test_orig_missing (void)
 {
-	test_config (NULL, orig_missing_expected, FALSE, NULL, 0, FALSE, NULL, NULL, "eth0", NULL);
+	test_config (NULL, orig_missing_expected, AF_INET, NULL, 0, FALSE, NULL, NULL, "eth0", NULL);
 }
 
 /*****************************************************************************/
@@ -135,7 +142,7 @@ static void
 test_override_client_id (void)
 {
 	test_config (override_client_id_orig, override_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "11:22:33:44:55:66",
 	             NULL,
 	             "eth0",
@@ -164,7 +171,7 @@ static void
 test_quote_client_id (void)
 {
 	test_config (NULL, quote_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "1234",
 	             NULL,
 	             "eth0",
@@ -193,7 +200,7 @@ static void
 test_ascii_client_id (void)
 {
 	test_config (NULL, ascii_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "qb:cd:ef:12:34:56",
 	             NULL,
 	             "eth0",
@@ -222,7 +229,7 @@ static void
 test_hex_single_client_id (void)
 {
 	test_config (NULL, hex_single_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "ab:cd:e:12:34:56",
 	             NULL,
 	             "eth0",
@@ -259,7 +266,7 @@ test_existing_hex_client_id (void)
 
 	new_client_id = g_bytes_new (bytes, sizeof (bytes));
 	test_config (existing_hex_client_id_orig, existing_hex_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             new_client_id,
 	             "eth0",
@@ -299,7 +306,7 @@ test_existing_ascii_client_id (void)
 	memcpy (buf + 1, EACID, NM_STRLEN (EACID));
 	new_client_id = g_bytes_new (buf, sizeof (buf));
 	test_config (existing_ascii_client_id_orig, existing_ascii_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             new_client_id,
 	             "eth0",
@@ -328,7 +335,7 @@ static void
 test_fqdn (void)
 {
 	test_config (NULL, fqdn_expected,
-	             FALSE, "foo.bar.com", 0,
+	             AF_INET, "foo.bar.com", 0,
 	             TRUE, NULL,
 	             NULL,
 	             "eth0",
@@ -368,7 +375,7 @@ test_fqdn_options_override (void)
 {
 	test_config (fqdn_options_override_orig,
 	             fqdn_options_override_expected,
-	             FALSE, "example2.com", 0,
+	             AF_INET, "example2.com", 0,
 	             TRUE, NULL,
 	             NULL,
 	             "eth0",
@@ -401,7 +408,7 @@ static void
 test_override_hostname (void)
 {
 	test_config (override_hostname_orig, override_hostname_expected,
-	             FALSE, "blahblah", 0, FALSE,
+	             AF_INET, "blahblah", 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -418,7 +425,6 @@ static const char *override_hostname6_expected = \
 	"# Merged from /path/to/dhclient.conf\n"
 	"\n"
 	"send fqdn.fqdn \"blahblah.local\"; # added by NetworkManager\n"
-	"send fqdn.encoded on;\n"
 	"send fqdn.server-update on;\n"
 	"\n"
 	"also request dhcp6.name-servers;\n"
@@ -430,7 +436,7 @@ static void
 test_override_hostname6 (void)
 {
 	test_config (override_hostname6_orig, override_hostname6_expected,
-	             TRUE, "blahblah.local", 0, TRUE,
+	             AF_INET6, "blahblah.local", 0, TRUE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -442,6 +448,9 @@ test_override_hostname6 (void)
 static const char *nonfqdn_hostname6_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
+	"send fqdn.fqdn \"blahblah\"; # added by NetworkManager\n"
+	"send fqdn.server-update on;\n"
+	"\n"
 	"also request dhcp6.name-servers;\n"
 	"also request dhcp6.domain-search;\n"
 	"also request dhcp6.client-id;\n"
@@ -450,9 +459,9 @@ static const char *nonfqdn_hostname6_expected = \
 static void
 test_nonfqdn_hostname6 (void)
 {
-	/* Non-FQDN hostname can't be used with dhclient */
+	/* Non-FQDN hostname can now be used with dhclient */
 	test_config (NULL, nonfqdn_hostname6_expected,
-	             TRUE, "blahblah", 0, TRUE,
+	             AF_INET6, "blahblah", 0, TRUE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -487,7 +496,7 @@ static void
 test_existing_alsoreq (void)
 {
 	test_config (existing_alsoreq_orig, existing_alsoreq_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -525,7 +534,7 @@ static void
 test_existing_req (void)
 {
 	test_config (existing_req_orig, existing_req_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -564,7 +573,7 @@ static void
 test_existing_multiline_alsoreq (void)
 {
 	test_config (existing_multiline_alsoreq_orig, existing_multiline_alsoreq_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -778,7 +787,7 @@ static void
 test_interface1 (void)
 {
 	test_config (interface1_orig, interface1_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -823,7 +832,7 @@ static void
 test_interface2 (void)
 {
 	test_config (interface2_orig, interface2_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth1",
@@ -877,7 +886,7 @@ test_config_req_intf (void)
 		"\n";
 
 	test_config (orig, expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -889,6 +898,7 @@ test_config_req_intf (void)
 static void
 test_read_lease_ip4_config_basic (void)
 {
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
@@ -905,7 +915,7 @@ test_read_lease_ip4_config_basic (void)
 
 	/* Date from before the least expiration */
 	now = g_date_time_new_utc (2013, 11, 1, 19, 55, 32);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
 	g_assert_cmpint (g_slist_length (leases), ==, 2);
 
 	/* IP4Config #1 */
@@ -914,19 +924,19 @@ test_read_lease_ip4_config_basic (void)
 
 	/* Address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
-	g_assert (inet_aton ("192.168.1.180", (struct in_addr *) &expected_addr));
-	addr = nm_ip4_config_get_address (config, 0);
+	expected_addr = nmtst_inet4_from_string ("192.168.1.180");
+	addr = _nmtst_ip4_config_get_address (config, 0);
 	g_assert_cmpint (addr->address, ==, expected_addr);
 	g_assert_cmpint (addr->peer_address, ==, expected_addr);
 	g_assert_cmpint (addr->plen, ==, 24);
 
 	/* Gateway */
-	g_assert (inet_aton ("192.168.1.1", (struct in_addr *) &expected_addr));
-	g_assert_cmpint (nm_ip4_config_get_gateway (config), ==, expected_addr);
+	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
+	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
 
 	/* DNS */
 	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 1);
-	g_assert (inet_aton ("192.168.1.1", (struct in_addr *) &expected_addr));
+	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
 	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
 
 	g_assert_cmpint (nm_ip4_config_get_num_domains (config), ==, 0);
@@ -937,21 +947,21 @@ test_read_lease_ip4_config_basic (void)
 
 	/* Address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
-	g_assert (inet_aton ("10.77.52.141", (struct in_addr *) &expected_addr));
-	addr = nm_ip4_config_get_address (config, 0);
+	expected_addr = nmtst_inet4_from_string ("10.77.52.141");
+	addr = _nmtst_ip4_config_get_address (config, 0);
 	g_assert_cmpint (addr->address, ==, expected_addr);
 	g_assert_cmpint (addr->peer_address, ==, expected_addr);
 	g_assert_cmpint (addr->plen, ==, 8);
 
 	/* Gateway */
-	g_assert (inet_aton ("10.77.52.254", (struct in_addr *) &expected_addr));
-	g_assert_cmpint (nm_ip4_config_get_gateway (config), ==, expected_addr);
+	expected_addr = nmtst_inet4_from_string ("10.77.52.254");
+	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
 
 	/* DNS */
 	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 2);
-	g_assert (inet_aton ("8.8.8.8", (struct in_addr *) &expected_addr));
+	expected_addr = nmtst_inet4_from_string ("8.8.8.8");
 	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
-	g_assert (inet_aton ("8.8.4.4", (struct in_addr *) &expected_addr));
+	expected_addr = nmtst_inet4_from_string ("8.8.4.4");
 	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 1), ==, expected_addr);
 
 	/* Domains */
@@ -966,6 +976,7 @@ test_read_lease_ip4_config_basic (void)
 static void
 test_read_lease_ip4_config_expired (void)
 {
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
@@ -979,7 +990,7 @@ test_read_lease_ip4_config_expired (void)
 
 	/* Date from *after* the lease expiration */
 	now = g_date_time_new_utc (2013, 12, 1, 19, 55, 32);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
 	g_assert (leases == NULL);
 
 	g_date_time_unref (now);
@@ -989,6 +1000,7 @@ test_read_lease_ip4_config_expired (void)
 static void
 test_read_lease_ip4_config_expect_failure (gconstpointer user_data)
 {
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
@@ -1001,7 +1013,7 @@ test_read_lease_ip4_config_expect_failure (gconstpointer user_data)
 
 	/* Date from before the least expiration */
 	now = g_date_time_new_utc (2013, 11, 1, 1, 1, 1);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
 	g_assert (leases == NULL);
 
 	g_date_time_unref (now);
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index ffd63493..72f31191 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -22,7 +22,9 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <string.h>
+#include <linux/rtnetlink.h>
 
+#include "nm-utils/nm-dedup-multi.h"
 #include "nm-utils.h"
 
 #include "dhcp/nm-dhcp-utils.h"
@@ -30,6 +32,20 @@
 
 #include "nm-test-utils-core.h"
 
+static NMIP4Config *
+_ip4_config_from_options (int ifindex,
+                          const char *iface,
+                          GHashTable *options,
+                          guint32 route_metric)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+	NMIP4Config *config;
+
+	config = nm_dhcp_utils_ip4_config_from_options (multi_idx, ifindex, iface, options, RT_TABLE_MAIN, route_metric);
+	g_assert (config);
+	return config;
+}
+
 typedef struct {
 	const char *name;
 	const char *value;
@@ -41,7 +57,7 @@ fill_table (const Option *test_options, GHashTable *table)
 	const Option *opt;
 
 	if (!table)
-		table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+		table = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
 	for (opt = test_options; opt->name; opt++)
 		g_hash_table_insert (table, (gpointer) opt->name, (gpointer) opt->value);
 	return table;
@@ -86,12 +102,11 @@ test_generic_options (void)
 	const char *expected_route2_gw = "10.1.1.1";
 
 	options = fill_table (generic_options, NULL);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (inet_pton (AF_INET, expected_addr, &tmp) > 0);
 	g_assert (address->address == tmp);
 	g_assert (address->peer_address == tmp);
@@ -99,7 +114,7 @@ test_generic_options (void)
 
 	/* Gateway */
 	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
-	g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp);
+	g_assert (nmtst_ip4_config_get_gateway (ip4_config) == tmp);
 
 	g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 0);
 
@@ -118,10 +133,10 @@ test_generic_options (void)
 	g_assert (nm_ip4_config_get_nameserver (ip4_config, 1) == tmp);
 
 	/* Routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 
 	/* Route #1 */
-	route = nm_ip4_config_get_route (ip4_config, 0);
+	route = _nmtst_ip4_config_get_route (ip4_config, 0);
 	g_assert (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0);
 	g_assert (route->network == tmp);
 	g_assert (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0);
@@ -130,14 +145,18 @@ test_generic_options (void)
 	g_assert_cmpint (route->metric, ==, 0);
 
 	/* Route #2 */
-	route = nm_ip4_config_get_route (ip4_config, 1);
-	g_assert (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0);
-	g_assert (route->network == tmp);
-	g_assert (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0);
-	g_assert (route->gateway == tmp);
+	route = _nmtst_ip4_config_get_route (ip4_config, 1);
+	g_assert (route->network == nmtst_inet4_from_string (expected_route2_dest));
+	g_assert (route->gateway == nmtst_inet4_from_string (expected_route2_gw));
 	g_assert_cmpint (route->plen, ==, 32);
 	g_assert_cmpint (route->metric, ==, 0);
 
+	route = _nmtst_ip4_config_get_route (ip4_config, 2);
+	g_assert (route->network == nmtst_inet4_from_string ("0.0.0.0"));
+	g_assert (route->gateway == nmtst_inet4_from_string ("192.168.1.1"));
+	g_assert_cmpint (route->plen, ==, 0);
+	g_assert_cmpint (route->metric, ==, 0);
+
 	g_hash_table_destroy (options);
 }
 
@@ -157,12 +176,11 @@ test_wins_options (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (address);
 	g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 2);
 	g_assert (inet_pton (AF_INET, expected_wins1, &tmp) > 0);
@@ -184,16 +202,14 @@ test_vendor_option_metered (void)
 	};
 
 	options = fill_table (generic_options, NULL);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_assert (nm_ip4_config_get_metered (ip4_config) == FALSE);
 	g_hash_table_destroy (options);
 	g_clear_object (&ip4_config);
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_assert (nm_ip4_config_get_metered (ip4_config) == TRUE);
 	g_hash_table_destroy (options);
 }
@@ -210,7 +226,7 @@ ip4_test_route (NMIP4Config *ip4_config,
 
 	g_assert (expected_prefix <= 32);
 
-	route = nm_ip4_config_get_route (ip4_config, route_num);
+	route = _nmtst_ip4_config_get_route (ip4_config, route_num);
 	g_assert (inet_pton (AF_INET, expected_dest, &tmp) > 0);
 	g_assert (route->network == tmp);
 	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
@@ -226,7 +242,7 @@ ip4_test_gateway (NMIP4Config *ip4_config, const char *expected_gw)
 
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
 	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
-	g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp);
+	g_assert (nmtst_ip4_config_get_gateway (ip4_config) == tmp);
 }
 
 static void
@@ -246,13 +262,13 @@ test_classless_static_routes_1 (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -274,13 +290,13 @@ test_classless_static_routes_2 (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -303,13 +319,13 @@ test_fedora_dhclient_classless_static_routes (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 25);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 7);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", expected_route1_gw, 0);
 
 	/* Gateway */
 	ip4_test_gateway (ip4_config, expected_gateway);
@@ -335,13 +351,13 @@ test_dhclient_invalid_classless_routes_1 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -366,16 +382,16 @@ test_dhcpcd_invalid_classless_routes_1 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* Test falling back to old-style static routes if the classless static
 	 * routes are invalid.
 	 */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -399,16 +415,16 @@ test_dhclient_invalid_classless_routes_2 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* Test falling back to old-style static routes if the classless static
 	 * routes are invalid.
 	 */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -432,8 +448,7 @@ test_dhcpcd_invalid_classless_routes_2 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* Test falling back to old-style static routes if the classless static
@@ -441,9 +456,10 @@ test_dhcpcd_invalid_classless_routes_2 (void)
 	 */
 
 	/* Routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -465,13 +481,13 @@ test_dhclient_invalid_classless_routes_3 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -493,13 +509,13 @@ test_dhcpcd_invalid_classless_routes_3 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*DHCP provided invalid classless static route*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -519,12 +535,12 @@ test_dhclient_gw_in_classless_routes (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", "192.2.3.4", 0);
 
 	/* Gateway */
 	ip4_test_gateway (ip4_config, expected_gateway);
@@ -547,12 +563,12 @@ test_dhcpcd_gw_in_classless_routes (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", "192.2.3.4", 0);
 
 	/* Gateway */
 	ip4_test_gateway (ip4_config, expected_gateway);
@@ -575,8 +591,7 @@ test_escaped_domain_searches (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* domain searches */
 	g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 3);
@@ -602,8 +617,7 @@ test_invalid_escaped_domain_searches (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*invalid domain search*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* domain searches */
@@ -623,11 +637,10 @@ test_ip4_missing_prefix (const char *ip, guint32 expected_prefix)
 	g_hash_table_insert (options, "ip_address", (gpointer) ip);
 	g_hash_table_remove (options, "subnet_mask");
 
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (address);
 	g_assert_cmpint (address->plen, ==, expected_prefix);
 
@@ -668,11 +681,10 @@ test_ip4_prefix_classless (void)
 	g_hash_table_insert (options, "ip_address", "172.16.54.22");
 	g_hash_table_insert (options, "subnet_mask", "255.255.252.0");
 
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (address);
 	g_assert_cmpint (address->plen, ==, 22);