about summary refs log tree commit diff
path: root/src/nm-ip4-config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-ip4-config.c')
-rw-r--r--src/nm-ip4-config.c2364
1 files changed, 1452 insertions, 912 deletions
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index ae6af9c3..e2f99e24 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -25,12 +25,16 @@
 
 #include <string.h>
 #include <arpa/inet.h>
+#include <resolv.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
 
 #include "nm-utils.h"
+#include "platform/nmp-object.h"
 #include "platform/nm-platform.h"
 #include "platform/nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
-#include "nm-route-manager.h"
 #include "nm-core-internal.h"
 
 #include "introspection/org.freedesktop.NetworkManager.IP4Config.h"
@@ -43,7 +47,232 @@ G_STATIC_ASSERT (G_MAXUINT >= 0xFFFFFFFF);
 
 /*****************************************************************************/
 
+static gboolean
+_route_valid (const NMPlatformIP4Route *r)
+{
+	return    r
+	       && r->plen <= 32
+	       && r->network == nm_utils_ip4_address_clear_host_address (r->network, r->plen);
+}
+
+/*****************************************************************************/
+
+static void
+_idx_obj_id_hash_update (const NMDedupMultiIdxType *idx_type,
+                         const NMDedupMultiObj *obj,
+                         NMHashState *h)
+{
+	nmp_object_id_hash_update ((NMPObject *) obj, h);
+}
+
+static gboolean
+_idx_obj_id_equal (const NMDedupMultiIdxType *idx_type,
+                   const NMDedupMultiObj *obj_a,
+                   const NMDedupMultiObj *obj_b)
+{
+	return nmp_object_id_equal ((NMPObject *) obj_a, (NMPObject *) obj_b);
+}
+
+void
+nm_ip_config_dedup_multi_idx_type_init (NMIPConfigDedupMultiIdxType *idx_type,
+                                        NMPObjectType obj_type)
+{
+	static const NMDedupMultiIdxTypeClass idx_type_class = {
+		.idx_obj_id_hash_update = _idx_obj_id_hash_update,
+		.idx_obj_id_equal = _idx_obj_id_equal,
+	};
+
+	nm_dedup_multi_idx_type_init ((NMDedupMultiIdxType *) idx_type,
+	                              &idx_type_class);
+	idx_type->obj_type = obj_type;
+}
+
+/*****************************************************************************/
+
+gboolean
+_nm_ip_config_add_obj (NMDedupMultiIndex *multi_idx,
+                       NMIPConfigDedupMultiIdxType *idx_type,
+                       int ifindex,
+                       const NMPObject *obj_new,
+                       const NMPlatformObject *pl_new,
+                       gboolean merge,
+                       gboolean append_force,
+                       const NMPObject **out_obj_old /* returns a reference! */,
+                       const NMPObject **out_obj_new /* does not return a reference */)
+{
+	NMPObject obj_new_stackinit;
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_new;
+
+	nm_assert (multi_idx);
+	nm_assert (idx_type);
+	nm_assert (NM_IN_SET (idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                                          NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                          NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                          NMP_OBJECT_TYPE_IP6_ROUTE));
+	nm_assert (ifindex > 0);
+
+	/* we go through extra lengths to accept a full obj_new object. That one,
+	 * can be reused by increasing the ref-count. */
+	if (!obj_new) {
+		nm_assert (pl_new);
+		obj_new = nmp_object_stackinit (&obj_new_stackinit, idx_type->obj_type, pl_new);
+		obj_new_stackinit.object.ifindex = ifindex;
+	} else {
+		nm_assert (!pl_new);
+		nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
+		if (obj_new->object.ifindex != ifindex) {
+			obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+			obj_new_stackinit.object.ifindex = ifindex;
+		}
+	}
+	nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
+	nm_assert (nmp_object_is_alive (obj_new));
+
+	entry_old = nm_dedup_multi_index_lookup_obj (multi_idx, &idx_type->parent, obj_new);
+
+	if (entry_old) {
+		gboolean modified = FALSE;
+		const NMPObject *obj_old = entry_old->obj;
+
+		if (nmp_object_equal (obj_new, obj_old)) {
+			nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
+			goto append_force_and_out;
+		}
+
+		/* if @merge, we merge the new object with the existing one.
+		 * Otherwise, we replace it entirely. */
+		if (merge) {
+			switch (idx_type->obj_type) {
+			case NMP_OBJECT_TYPE_IP4_ADDRESS:
+			case NMP_OBJECT_TYPE_IP6_ADDRESS:
+				/* for addresses that we read from the kernel, we keep the timestamps as defined
+				 * by the previous source (item_old). The reason is, that the other source configured the lifetimes
+				 * with "what should be" and the kernel values are "what turned out after configuring it".
+				 *
+				 * For other sources, the longer lifetime wins. */
+				if (   (   obj_new->ip_address.addr_source == NM_IP_CONFIG_SOURCE_KERNEL
+				        && obj_old->ip_address.addr_source != NM_IP_CONFIG_SOURCE_KERNEL)
+				    || nm_platform_ip_address_cmp_expiry (NMP_OBJECT_CAST_IP_ADDRESS (obj_old), NMP_OBJECT_CAST_IP_ADDRESS(obj_new)) > 0) {
+					obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+					obj_new_stackinit.ip_address.timestamp = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->timestamp;
+					obj_new_stackinit.ip_address.lifetime  = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->lifetime;
+					obj_new_stackinit.ip_address.preferred = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->preferred;
+					modified = TRUE;
+				}
+
+				/* keep the maximum addr_source. */
+				if (obj_new->ip_address.addr_source < obj_old->ip_address.addr_source) {
+					obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+					obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source;
+					modified = TRUE;
+				}
+				break;
+			case NMP_OBJECT_TYPE_IP4_ROUTE:
+			case NMP_OBJECT_TYPE_IP6_ROUTE:
+				/* keep the maximum rt_source. */
+				if (obj_new->ip_route.rt_source < obj_old->ip_route.rt_source) {
+					obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+					obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source;
+					modified = TRUE;
+				}
+				break;
+			default:
+				nm_assert_not_reached ();
+				break;
+			}
+
+			if (   modified
+			    && nmp_object_equal (obj_new, obj_old)) {
+				nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
+				goto append_force_and_out;
+			}
+		}
+	}
+
+	if (!nm_dedup_multi_index_add_full (multi_idx,
+	                                    &idx_type->parent,
+	                                    obj_new,
+	                                    NM_DEDUP_MULTI_IDX_MODE_APPEND,
+	                                    NULL,
+	                                    entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
+	                                    NULL,
+	                                    &entry_new,
+	                                    out_obj_old)) {
+		nm_assert_not_reached ();
+		NM_SET_OUT (out_obj_new, NULL);
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_obj_new, entry_new->obj);
+	return TRUE;
+
+append_force_and_out:
+	NM_SET_OUT (out_obj_old, nmp_object_ref (entry_old->obj));
+	NM_SET_OUT (out_obj_new, entry_old->obj);
+	if (append_force) {
+		if (nm_dedup_multi_entry_reorder (entry_old, NULL, TRUE))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+/**
+ * _nm_ip_config_lookup_ip_route:
+ * @multi_idx:
+ * @idx_type:
+ * @needle:
+ * @cmp_type: after lookup, filter the result by comparing with @cmp_type. Only
+ *   return the result, if it compares equal to @needle according to this @cmp_type.
+ *   Note that the index uses %NM_PLATFORM_IP_ROUTE_CMP_TYPE_DST type, so passing
+ *   that compare-type means not to filter any further.
+ *
+ * Returns: the found entry or %NULL.
+ */
+const NMDedupMultiEntry *
+_nm_ip_config_lookup_ip_route (const NMDedupMultiIndex *multi_idx,
+                               const NMIPConfigDedupMultiIdxType *idx_type,
+                               const NMPObject *needle,
+                               NMPlatformIPRouteCmpType cmp_type)
+{
+	const NMDedupMultiEntry *entry;
+
+	nm_assert (multi_idx);
+	nm_assert (idx_type);
+	nm_assert (NM_IN_SET (idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
+	nm_assert (NMP_OBJECT_GET_TYPE (needle) == idx_type->obj_type);
+
+	entry = nm_dedup_multi_index_lookup_obj (multi_idx,
+	                                         &idx_type->parent,
+	                                         needle);
+	if (!entry)
+		return NULL;
+
+	if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
+		nm_assert (   (   NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE
+		               && nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (entry->obj), NMP_OBJECT_CAST_IP4_ROUTE (needle), cmp_type) == 0)
+		           || (   NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP6_ROUTE
+		               && nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (entry->obj), NMP_OBJECT_CAST_IP6_ROUTE (needle), cmp_type) == 0));
+	} else {
+		if (NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE) {
+			if (nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (entry->obj),
+			                               NMP_OBJECT_CAST_IP4_ROUTE (needle),
+			                               cmp_type) != 0)
+				return NULL;
+		} else {
+			if (nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (entry->obj),
+			                               NMP_OBJECT_CAST_IP6_ROUTE (needle),
+			                               cmp_type) != 0)
+				return NULL;
+		}
+	}
+	return entry;
+}
+
+/*****************************************************************************/
+
 NM_GOBJECT_PROPERTIES_DEFINE (NMIP4Config,
+	PROP_MULTI_IDX,
 	PROP_IFINDEX,
 	PROP_ADDRESS_DATA,
 	PROP_ADDRESSES,
@@ -59,18 +288,11 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMIP4Config,
 );
 
 typedef struct {
-	bool never_default:1;
 	bool metered:1;
-	bool has_gateway:1;
-	guint32 gateway;
-	guint32 mss;
 	guint32 mtu;
 	int ifindex;
 	NMIPConfigSource mtu_source;
 	gint dns_priority;
-	gint64 route_metric;
-	GArray *addresses;
-	GArray *routes;
 	GArray *nameservers;
 	GPtrArray *domains;
 	GPtrArray *searches;
@@ -80,6 +302,18 @@ typedef struct {
 	GArray *wins;
 	GVariant *address_data_variant;
 	GVariant *addresses_variant;
+	GVariant *route_data_variant;
+	GVariant *routes_variant;
+	NMDedupMultiIndex *multi_idx;
+	const NMPObject *best_default_route;
+	union {
+		NMIPConfigDedupMultiIdxType idx_ip4_addresses_;
+		NMDedupMultiIdxType idx_ip4_addresses;
+	};
+	union {
+		NMIPConfigDedupMultiIdxType idx_ip4_routes_;
+		NMDedupMultiIdxType idx_ip4_routes;
+	};
 } NMIP4ConfigPrivate;
 
 struct _NMIP4Config {
@@ -97,10 +331,24 @@ G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, NM_TYPE_EXPORTED_OBJECT)
 
 /*****************************************************************************/
 
+static void _add_address (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new);
+static void _add_route (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Route *new, const NMPObject **out_obj_new);
+static const NMDedupMultiEntry *_lookup_route (const NMIP4Config *self,
+                                               const NMPObject *needle,
+                                               NMPlatformIPRouteCmpType cmp_type);
+
+/*****************************************************************************/
+
 int
-nm_ip4_config_get_ifindex (const NMIP4Config *config)
+nm_ip4_config_get_ifindex (const NMIP4Config *self)
+{
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex;
+}
+
+NMDedupMultiIndex *
+nm_ip4_config_get_multi_idx (const NMIP4Config *self)
 {
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->ifindex;
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->multi_idx;
 }
 
 /*****************************************************************************/
@@ -114,83 +362,167 @@ _ipv4_is_zeronet (in_addr_t network)
 
 /*****************************************************************************/
 
-/**
- * nm_ip4_config_capture_resolv_conf():
- * @nameservers: array of guint32
- * @rc_contents: the contents of a resolv.conf or %NULL to read /etc/resolv.conf
- *
- * Reads all resolv.conf IPv4 nameservers and adds them to @nameservers.
- *
- * Returns: %TRUE if nameservers were added, %FALSE if @nameservers is unchanged
- */
-gboolean
-nm_ip4_config_capture_resolv_conf (GArray *nameservers,
-                                   GPtrArray *dns_options,
-                                   const char *rc_contents)
+const NMDedupMultiHeadEntry *
+nm_ip4_config_lookup_addresses (const NMIP4Config *self)
 {
-	GPtrArray *read_ns, *read_options;
-	guint i, j;
-	gboolean changed = FALSE;
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	g_return_val_if_fail (nameservers != NULL, FALSE);
+	return nm_dedup_multi_index_lookup_head (priv->multi_idx,
+	                                         &priv->idx_ip4_addresses,
+	                                         NULL);
+}
 
-	read_ns = nm_utils_read_resolv_conf_nameservers (rc_contents);
-	if (!read_ns)
-		return FALSE;
+void
+nm_ip_config_iter_ip4_address_init (NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
+{
+	g_return_if_fail (NM_IS_IP4_CONFIG (self));
+	nm_dedup_multi_iter_init (ipconf_iter, nm_ip4_config_lookup_addresses (self));
+}
 
-	for (i = 0; i < read_ns->len; i++) {
-		const char *s = g_ptr_array_index (read_ns, i);
-		guint32 ns = 0;
+/*****************************************************************************/
 
-		if (!inet_pton (AF_INET, s, (void *) &ns) || !ns)
-			continue;
+const NMDedupMultiHeadEntry *
+nm_ip4_config_lookup_routes (const NMIP4Config *self)
+{
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-		/* Ignore duplicates */
-		for (j = 0; j < nameservers->len; j++) {
-			if (g_array_index (nameservers, guint32, j) == ns)
-				break;
-		}
+	return nm_dedup_multi_index_lookup_head (priv->multi_idx,
+	                                         &priv->idx_ip4_routes,
+	                                         NULL);
+}
 
-		if (j == nameservers->len) {
-			g_array_append_val (nameservers, ns);
-			changed = TRUE;
+void
+nm_ip_config_iter_ip4_route_init (NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
+{
+	g_return_if_fail (NM_IS_IP4_CONFIG (self));
+	nm_dedup_multi_iter_init (ipconf_iter, nm_ip4_config_lookup_routes (self));
+}
+
+/*****************************************************************************/
+
+const NMPObject *
+_nm_ip_config_best_default_route_find_better (const NMPObject *obj_cur, const NMPObject *obj_cmp)
+{
+	int addr_family;
+	int c;
+	guint metric_cur, metric_cmp;
+
+	nm_assert (   !obj_cur
+	           || NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_cur), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
+	nm_assert (   !obj_cmp
+	           || (   !obj_cur
+	               && NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_cmp), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE))
+	           || NMP_OBJECT_GET_TYPE (obj_cur) == NMP_OBJECT_GET_TYPE (obj_cmp));
+	nm_assert (   !obj_cur
+	           || nm_ip_config_best_default_route_is (obj_cur));
+
+	/* assumes that @obj_cur is already the best default route (or NULL). It checks whether
+	 * @obj_cmp is also a default route and returns the best of both. */
+	if (   obj_cmp
+	    && nm_ip_config_best_default_route_is (obj_cmp)) {
+		if (!obj_cur)
+			return obj_cmp;
+
+		addr_family = NMP_OBJECT_GET_CLASS (obj_cmp)->addr_family;
+		metric_cur = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (obj_cur)->metric);
+		metric_cmp = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (obj_cmp)->metric);
+
+		if (metric_cmp < metric_cur)
+			return obj_cmp;
+
+		if (metric_cmp == metric_cur) {
+			/* Routes have the same metric. We still want to deterministically
+			 * prefer one or the other. It's important to consistently choose one
+			 * or the other, so that the order doesn't matter how routes are added
+			 * (and merged). */
+			c = nmp_object_cmp (obj_cur, obj_cmp);
+			if (c != 0)
+				return c < 0 ? obj_cur : obj_cmp;
+
+			/* as last resort, compare pointers. */
+			if (obj_cmp < obj_cur)
+				return obj_cmp;
 		}
 	}
-	g_ptr_array_unref (read_ns);
+	return obj_cur;
+}
 
-	if (dns_options) {
-		read_options = nm_utils_read_resolv_conf_dns_options (rc_contents);
-		if (!read_options)
-			return changed;
+gboolean
+_nm_ip_config_best_default_route_set (const NMPObject **best_default_route, const NMPObject *new_candidate)
+{
+	if (new_candidate == *best_default_route)
+		return FALSE;
+	nmp_object_ref (new_candidate);
+	nm_clear_nmp_object (best_default_route);
+	*best_default_route = new_candidate;
+	return TRUE;
+}
 
-		for (i = 0; i < read_options->len; i++) {
-			const char *s = g_ptr_array_index (read_options, i);
+gboolean
+_nm_ip_config_best_default_route_merge (const NMPObject **best_default_route, const NMPObject *new_candidate)
+{
+	new_candidate = _nm_ip_config_best_default_route_find_better (*best_default_route,
+	                                                              new_candidate);
+	return _nm_ip_config_best_default_route_set (best_default_route, new_candidate);
+}
 
-			if (_nm_utils_dns_option_validate (s, NULL, NULL, FALSE, _nm_utils_dns_option_descs) &&
-				_nm_utils_dns_option_find_idx (dns_options, s) < 0) {
-				g_ptr_array_add (dns_options, g_strdup (s));
-				changed = TRUE;
-			}
-		}
-		g_ptr_array_unref (read_options);
+const NMPObject *
+nm_ip4_config_best_default_route_get (const NMIP4Config *self)
+{
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), NULL);
+
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->best_default_route;
+}
+
+const NMPObject *
+_nm_ip4_config_best_default_route_find (const NMIP4Config *self)
+{
+	NMDedupMultiIter ipconf_iter;
+	const NMPObject *new_best_default_route = NULL;
+
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, NULL) {
+		new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route,
+		                                                                       ipconf_iter.current->obj);
 	}
+	return new_best_default_route;
+}
+
+in_addr_t
+nmtst_ip4_config_get_gateway (NMIP4Config *config)
+{
+	const NMPObject *rt;
 
-	return changed;
+	g_assert (NM_IS_IP4_CONFIG (config));
+
+	rt = nm_ip4_config_best_default_route_get (config);
+	if (!rt)
+		return 0;
+	return NMP_OBJECT_CAST_IP4_ROUTE (rt)->gateway;
 }
 
-static gboolean
-addresses_are_duplicate (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
+/*****************************************************************************/
+
+static void
+_notify_addresses (NMIP4Config *self)
 {
-	return    a->address == b->address
-	       && a->plen == b->plen
-	       && ((a->peer_address ^ b->peer_address) & nm_utils_ip4_prefix_to_netmask (a->plen)) == 0;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	nm_clear_g_variant (&priv->address_data_variant);
+	nm_clear_g_variant (&priv->addresses_variant);
+	_notify (self, PROP_ADDRESS_DATA);
+	_notify (self, PROP_ADDRESSES);
 }
 
-static gboolean
-routes_are_duplicate (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, gboolean consider_gateway_and_metric)
+static void
+_notify_routes (NMIP4Config *self)
 {
-	return a->network == b->network && a->plen == b->plen &&
-	       (!consider_gateway_and_metric || (a->gateway == b->gateway && a->metric == b->metric));
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	nm_assert (priv->best_default_route == _nm_ip4_config_best_default_route_find (self));
+	nm_clear_g_variant (&priv->route_data_variant);
+	nm_clear_g_variant (&priv->routes_variant);
+	_notify (self, PROP_ROUTE_DATA);
+	_notify (self, PROP_ROUTES);
 }
 
 /*****************************************************************************/
@@ -203,11 +535,12 @@ _addresses_sort_cmp_get_prio (in_addr_t addr)
 	return 1;
 }
 
-static gint
-_addresses_sort_cmp (gconstpointer a, gconstpointer b)
+static int
+_addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
 {
 	gint p1, p2;
-	const NMPlatformIP4Address *a1 = a, *a2 = b;
+	const NMPlatformIP4Address *a1 = NMP_OBJECT_CAST_IP4_ADDRESS (*((const NMPObject **) a));
+	const NMPlatformIP4Address *a2 = NMP_OBJECT_CAST_IP4_ADDRESS (*((const NMPObject **) b));
 	guint32 n1, n2;
 
 	/* Sort by address type. For example link local will
@@ -229,29 +562,19 @@ _addresses_sort_cmp (gconstpointer a, gconstpointer b)
 	 * subnet (and thus also the primary/secondary role) is
 	 * preserved.
 	 */
-	n1 = a1->address & nm_utils_ip4_prefix_to_netmask (a1->plen);
-	n2 = a2->address & nm_utils_ip4_prefix_to_netmask (a2->plen);
+	n1 = a1->address & _nm_utils_ip4_prefix_to_netmask (a1->plen);
+	n2 = a2->address & _nm_utils_ip4_prefix_to_netmask (a2->plen);
 
 	return memcmp (&n1, &n2, sizeof (guint32));
 }
 
 /*****************************************************************************/
 
-static void
-notify_addresses (NMIP4Config *self)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
-
-	nm_clear_g_variant (&priv->address_data_variant);
-	nm_clear_g_variant (&priv->addresses_variant);
-	_notify (self, PROP_ADDRESS_DATA);
-	_notify (self, PROP_ADDRESSES);
-}
-
-static gint
-sort_captured_addresses (gconstpointer a, gconstpointer b)
+static int
+sort_captured_addresses (const CList *lst_a, const CList *lst_b, gconstpointer user_data)
 {
-	const NMPlatformIP4Address *addr_a = a, *addr_b = b;
+	const NMPlatformIP4Address *addr_a = NMP_OBJECT_CAST_IP4_ADDRESS (c_list_entry (lst_a, NMDedupMultiEntry, lst_entries)->obj);
+	const NMPlatformIP4Address *addr_b = NMP_OBJECT_CAST_IP4_ADDRESS (c_list_entry (lst_b, NMDedupMultiEntry, lst_entries)->obj);
 
 	/* Primary addresses first */
 	return NM_FLAGS_HAS (addr_a->n_ifa_flags, IFA_F_SECONDARY) -
@@ -259,175 +582,237 @@ sort_captured_addresses (gconstpointer a, gconstpointer b)
 }
 
 NMIP4Config *
-nm_ip4_config_capture (NMPlatform *platform, int ifindex, gboolean capture_resolv_conf)
+nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf)
 {
-	NMIP4Config *config;
+	NMIP4Config *self;
 	NMIP4ConfigPrivate *priv;
-	guint i;
-	guint32 lowest_metric = G_MAXUINT32;
-	guint32 old_gateway = 0;
-	gboolean old_has_gateway = FALSE;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *plobj = NULL;
+	gboolean has_addresses = FALSE;
+
+	nm_assert (ifindex > 0);
 
 	/* Slaves have no IP configuration */
 	if (nm_platform_link_get_master (platform, ifindex) > 0)
 		return NULL;
 
-	config = nm_ip4_config_new (ifindex);
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	g_array_unref (priv->addresses);
-	g_array_unref (priv->routes);
-
-	priv->addresses = nm_platform_ip4_address_get_all (platform, ifindex);
-	g_array_sort (priv->addresses, sort_captured_addresses);
-
-	priv->routes = nm_platform_ip4_route_get_all (platform, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-
-	/* Extract gateway from default route */
-	old_gateway = priv->gateway;
-	old_has_gateway = priv->has_gateway;
-	for (i = 0; i < priv->routes->len; ) {
-		const NMPlatformIP4Route *route = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
-			if (route->metric < lowest_metric) {
-				priv->gateway = route->gateway;
-				lowest_metric = route->metric;
-			}
-			priv->has_gateway = TRUE;
-			/* Remove the default route from the list */
-			g_array_remove_index_fast (priv->routes, i);
-			continue;
+	self = nm_ip4_config_new (multi_idx, ifindex);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	head_entry = nm_platform_lookup_addrroute (platform,
+	                                           NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                                           ifindex);
+	if (head_entry) {
+		nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+			if (!_nm_ip_config_add_obj (priv->multi_idx,
+			                            &priv->idx_ip4_addresses_,
+			                            ifindex,
+			                            plobj,
+			                            NULL,
+			                            FALSE,
+			                            TRUE,
+			                            NULL,
+			                            NULL))
+				nm_assert_not_reached ();
 		}
-		i++;
+		head_entry = nm_ip4_config_lookup_addresses (self);
+		nm_assert (head_entry);
+		nm_dedup_multi_head_entry_sort (head_entry,
+		                                sort_captured_addresses,
+		                                NULL);
+		has_addresses = TRUE;
+		_notify_addresses (self);
 	}
 
-	/* we detect the route metric based on the default route. All non-default
-	 * routes have their route metrics explicitly set. */
-	priv->route_metric = priv->has_gateway ? (gint64) lowest_metric : (gint64) -1;
+	head_entry = nm_platform_lookup_addrroute (platform,
+	                                           NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                           ifindex);
 
-	/* If there is a host route to the gateway, ignore that route.  It is
-	 * automatically added by NetworkManager when needed.
-	 */
-	if (priv->has_gateway) {
-		for (i = 0; i < priv->routes->len; i++) {
-			const NMPlatformIP4Route *route = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-			if (   (route->plen == 32)
-			    && (route->network == priv->gateway)
-			    && (route->gateway == 0)) {
-				g_array_remove_index (priv->routes, i);
-				i--;
-			}
-		}
-	}
+	/* Extract gateway from default route */
+	nmp_cache_iter_for_each (&iter, head_entry, &plobj)
+		_add_route (self, plobj, NULL, NULL);
 
 	/* If the interface has the default route, and has IPv4 addresses, capture
 	 * nameservers from /etc/resolv.conf.
 	 */
-	if (priv->addresses->len && priv->has_gateway && capture_resolv_conf) {
-		if (nm_ip4_config_capture_resolv_conf (priv->nameservers, priv->dns_options, NULL))
-			_notify (config, PROP_NAMESERVERS);
+	if (   has_addresses
+	    && priv->best_default_route
+	    && capture_resolv_conf) {
+		gs_free char *rc_contents = NULL;
+
+		if (g_file_get_contents (_PATH_RESCONF, &rc_contents, NULL, NULL)) {
+			if (nm_utils_resolve_conf_parse (AF_INET,
+			                                 rc_contents,
+			                                 priv->nameservers,
+			                                 priv->dns_options))
+				_notify (self, PROP_NAMESERVERS);
+		}
 	}
 
-	/* actually, nobody should be connected to the signal, just to be sure, notify */
-	_notify (config, PROP_ADDRESS_DATA);
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ADDRESSES);
-	_notify (config, PROP_ROUTES);
-	if (   priv->gateway != old_gateway
-	    || priv->has_gateway != old_has_gateway)
-		_notify (config, PROP_GATEWAY);
-
-	return config;
+	return self;
 }
 
-gboolean
-nm_ip4_config_commit (const NMIP4Config *config, NMPlatform *platform, NMRouteManager *route_manager, int ifindex, gboolean routes_full_sync, gint64 default_route_metric)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	gs_unref_ptrarray GPtrArray *added_addresses = NULL;
+void
+nm_ip4_config_add_dependent_routes (NMIP4Config *self,
+                                    guint32 route_table,
+                                    guint32 route_metric,
+                                    GPtrArray **out_ip4_dev_route_blacklist)
+{
+	const NMIP4ConfigPrivate *priv;
+	GPtrArray *ip4_dev_route_blacklist = NULL;
+	const NMPlatformIP4Address *my_addr;
+	const NMPlatformIP4Route *my_route;
+	int ifindex;
+	NMDedupMultiIter iter;
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (config != NULL, FALSE);
+	g_return_if_fail (NM_IS_IP4_CONFIG (self));
 
-	/* Addresses */
-	nm_platform_ip4_address_sync (platform, ifindex, priv->addresses,
-	                              default_route_metric >= 0 ? &added_addresses : NULL);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	/* Routes */
-	{
-		guint i;
-		guint count = nm_ip4_config_get_num_routes (config);
-		GArray *routes = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformIP4Route), count);
-		gboolean success;
-		gs_unref_array GArray *device_route_purge_list = NULL;
-
-		if (   default_route_metric >= 0
-		    && added_addresses) {
-			/* For IPv6, we explicitly add the device-routes (onlink) to NMIP6Config.
-			 * As we don't do that for IPv4, add it here shortly before syncing
-			 * the routes. For NMRouteManager these routes are very much important. */
-			for (i = 0; i < added_addresses->len; i++) {
-				const NMPlatformIP4Address *addr = added_addresses->pdata[i];
-				NMPlatformIP4Route route = { 0 };
-
-				if (addr->plen == 0)
-					continue;
+	ifindex = nm_ip4_config_get_ifindex (self);
+	g_return_if_fail (ifindex > 0);
 
-				nm_assert (addr->plen <= 32);
+	/* For IPv6 slaac, we explicitly add the device-routes (onlink) to NMIP6Config.
+	 * As we don't do that for IPv4 (and manual IPv6 addresses), add them explicitly. */
 
-				route.ifindex = ifindex;
-				route.rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
+	nm_ip_config_iter_ip4_address_for_each (&iter, self, &my_addr) {
+		nm_auto_nmpobj NMPObject *r = NULL;
+		NMPlatformIP4Route *route;
+		in_addr_t network;
 
-				/* The destination network depends on the peer-address. */
-				route.network = nm_utils_ip4_address_clear_host_address (addr->peer_address, addr->plen);
+		if (my_addr->plen == 0)
+			continue;
 
-				if (_ipv4_is_zeronet (route.network)) {
-					/* Kernel doesn't add device-routes for destinations that
-					 * start with 0.x.y.z. Skip them. */
-					continue;
-				}
+		nm_assert (my_addr->plen <= 32);
 
-				route.plen = addr->plen;
-				route.pref_src = addr->address;
-				route.metric = default_route_metric;
+		/* The destination network depends on the peer-address. */
+		network = nm_utils_ip4_address_clear_host_address (my_addr->peer_address, my_addr->plen);
 
-				g_array_append_val (routes, route);
+		if (_ipv4_is_zeronet (network)) {
+			/* Kernel doesn't add device-routes for destinations that
+			 * start with 0.x.y.z. Skip them. */
+			continue;
+		}
 
-				if (default_route_metric != NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE) {
-					if (!device_route_purge_list)
-						device_route_purge_list = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-					route.metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE;
-					g_array_append_val (device_route_purge_list, route);
-				}
+		r = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
+		route = NMP_OBJECT_CAST_IP4_ROUTE (r);
+
+		route->ifindex = ifindex;
+		route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
+		route->network = network;
+		route->plen = my_addr->plen;
+		route->pref_src = my_addr->address;
+		route->table_coerced = nm_platform_route_table_coerce (route_table);
+		route->metric = route_metric;
+		route->scope_inv = nm_platform_route_scope_inv (NM_RT_SCOPE_LINK);
+
+		nm_platform_ip_route_normalize (AF_INET, (NMPlatformIPRoute *) route);
+
+		if (_lookup_route (self,
+		                   r,
+		                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) {
+			/* we already track this route. Don't add it again. */
+		} else
+			_add_route (self, r, NULL, NULL);
+
+		if (   out_ip4_dev_route_blacklist
+		    && (   route_table != RT_TABLE_MAIN
+		        || route_metric != NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE)) {
+			nm_auto_nmpobj NMPObject *r_dev = NULL;
+
+			r_dev = nmp_object_clone (r, FALSE);
+			route = NMP_OBJECT_CAST_IP4_ROUTE (r_dev);
+			route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_MAIN);
+			route->metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE;
+
+			nm_platform_ip_route_normalize (AF_INET, (NMPlatformIPRoute *) route);
+
+			if (_lookup_route (self,
+			                   r_dev,
+			                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) {
+				/* we track such a route explicitly. Don't blacklist it. */
+			} else {
+				if (!ip4_dev_route_blacklist)
+					ip4_dev_route_blacklist = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+
+				g_ptr_array_add (ip4_dev_route_blacklist,
+				                 g_steal_pointer (&r_dev));
 			}
 		}
+	}
 
-		for (i = 0; i < count; i++) {
-			const NMPlatformIP4Route *route;
+again:
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &my_route) {
+		NMPlatformIP4Route rt;
 
-			route = nm_ip4_config_get_route (config, i);
-			/* duplicates in @routes are no problem as route-manager handles them
-			 * gracefully (by ignoring them). */
-			g_array_append_vals (routes, route, 1);
-		}
-
-		nm_route_manager_ip4_route_register_device_route_purge_list (route_manager, device_route_purge_list);
+		if (   !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (my_route)
+		    || my_route->gateway == 0
+		    || NM_IS_IP_CONFIG_SOURCE_RTPROT (my_route->rt_source)
+		    || nm_ip4_config_get_direct_route_for_host (self,
+		                                                my_route->gateway,
+		                                                nm_platform_route_table_uncoerce (my_route->table_coerced, TRUE)))
+			continue;
 
-		success = nm_route_manager_ip4_route_sync (route_manager, ifindex, routes, default_route_metric < 0, routes_full_sync);
-		g_array_unref (routes);
-		if (!success)
-			return FALSE;
+		rt = *my_route;
+		rt.network = my_route->gateway;
+		rt.plen = 32;
+		rt.gateway = 0;
+		_add_route (self, NULL, &rt, NULL);
+		/* adding the route might have invalidated the iteration. Start again. */
+		goto again;
 	}
 
-	return TRUE;
+	NM_SET_OUT (out_ip4_dev_route_blacklist, ip4_dev_route_blacklist);
+}
+
+gboolean
+nm_ip4_config_commit (const NMIP4Config *self,
+                      NMPlatform *platform,
+                      NMIPRouteTableSyncMode route_table_sync)
+{
+	gs_unref_ptrarray GPtrArray *addresses = NULL;
+	gs_unref_ptrarray GPtrArray *routes = NULL;
+	gs_unref_ptrarray GPtrArray *routes_prune = NULL;
+	int ifindex;
+	gboolean success = TRUE;
+
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), FALSE);
+
+	ifindex = nm_ip4_config_get_ifindex (self);
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	addresses = nm_dedup_multi_objs_to_ptr_array_head (nm_ip4_config_lookup_addresses (self),
+	                                                   NULL, NULL);
+
+	routes = nm_dedup_multi_objs_to_ptr_array_head (nm_ip4_config_lookup_routes (self),
+	                                                NULL, NULL);
+
+	routes_prune = nm_platform_ip_route_get_prune_list (platform,
+	                                                    AF_INET,
+	                                                    ifindex,
+	                                                    route_table_sync);
+
+	nm_platform_ip4_address_sync (platform, ifindex, addresses);
+
+	if (!nm_platform_ip_route_sync (platform,
+	                                AF_INET,
+	                                ifindex,
+	                                routes,
+	                                routes_prune,
+	                                NULL))
+		success = FALSE;
+
+	return success;
 }
 
 static void
-merge_route_attributes (NMIPRoute *s_route, NMPlatformIP4Route *r)
+merge_route_attributes (NMIPRoute *s_route,
+                        NMPlatformIP4Route *r,
+                        guint32 route_table)
 {
 	GVariant *variant;
+	guint32 u32;
 	in_addr_t addr;
 
 #define GET_ATTR(name, field, variant_type, type) \
@@ -435,6 +820,12 @@ merge_route_attributes (NMIPRoute *s_route, NMPlatformIP4Route *r)
 	if (variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_ ## variant_type)) \
 		r->field = g_variant_get_ ## type (variant);
 
+	variant = nm_ip_route_get_attribute (s_route, NM_IP_ROUTE_ATTRIBUTE_TABLE);
+	u32 =   variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)
+	      ? g_variant_get_uint32 (variant)
+	      : 0;
+	r->table_coerced = nm_platform_route_table_coerce (u32 ?: (route_table ?: RT_TABLE_MAIN));
+
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TOS,            tos,            BYTE,     byte);
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_WINDOW,         window,         UINT32,   uint32);
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_CWND,           cwnd,           UINT32,   uint32);
@@ -457,20 +848,25 @@ merge_route_attributes (NMIPRoute *s_route, NMPlatformIP4Route *r)
 }
 
 void
-nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, guint32 default_route_metric)
+nm_ip4_config_merge_setting (NMIP4Config *self,
+                             NMSettingIPConfig *setting,
+                             guint32 route_table,
+                             guint32 route_metric)
 {
 	NMIP4ConfigPrivate *priv;
 	guint naddresses, nroutes, nnameservers, nsearches;
 	int i, priority;
+	const char *gateway_str;
+	guint32 gateway_bin;
 
 	if (!setting)
 		return;
 
 	g_return_if_fail (NM_IS_SETTING_IP4_CONFIG (setting));
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	g_object_freeze_notify (G_OBJECT (config));
+	g_object_freeze_notify (G_OBJECT (self));
 
 	naddresses = nm_setting_ip_config_get_num_addresses (setting);
 	nroutes = nm_setting_ip_config_get_num_routes (setting);
@@ -478,20 +874,20 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 	nsearches = nm_setting_ip_config_get_num_dns_searches (setting);
 
 	/* Gateway */
-	if (nm_setting_ip_config_get_never_default (setting))
-		nm_ip4_config_set_never_default (config, TRUE);
-	else if (nm_setting_ip_config_get_ignore_auto_routes (setting))
-		nm_ip4_config_set_never_default (config, FALSE);
-	if (nm_setting_ip_config_get_gateway (setting)) {
-		guint32 gateway;
+	if (   !nm_setting_ip_config_get_never_default (setting)
+	    && (gateway_str = nm_setting_ip_config_get_gateway (setting))
+	    && inet_pton (AF_INET, gateway_str, &gateway_bin) == 1
+	    && gateway_bin) {
+		const NMPlatformIP4Route r = {
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.gateway = gateway_bin,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric = route_metric,
+		};
 
-		inet_pton (AF_INET, nm_setting_ip_config_get_gateway (setting), &gateway);
-		nm_ip4_config_set_gateway (config, gateway);
+		_add_route (self, NULL, &r, NULL);
 	}
 
-	if (priv->route_metric  == -1)
-		priv->route_metric = nm_setting_ip_config_get_route_metric (setting);
-
 	/* Addresses */
 	for (i = 0; i < naddresses; i++) {
 		NMIPAddress *s_addr = nm_setting_ip_config_get_address (setting, i);
@@ -511,16 +907,21 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 		if (label)
 			g_strlcpy (address.label, g_variant_get_string (label, NULL), sizeof (address.label));
 
-		nm_ip4_config_add_address (config, &address);
+		_add_address (self, NULL, &address);
 	}
 
 	/* Routes */
 	if (nm_setting_ip_config_get_ignore_auto_routes (setting))
-		nm_ip4_config_reset_routes (config);
+		nm_ip4_config_reset_routes (self);
 	for (i = 0; i < nroutes; i++) {
 		NMIPRoute *s_route = nm_setting_ip_config_get_route (setting, i);
 		NMPlatformIP4Route route;
 
+		if (nm_ip_route_get_family (s_route) != AF_INET) {
+			nm_assert_not_reached ();
+			continue;
+		}
+
 		memset (&route, 0, sizeof (route));
 		nm_ip_route_get_dest_binary (s_route, &route.network);
 
@@ -531,73 +932,74 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 
 		nm_ip_route_get_next_hop_binary (s_route, &route.gateway);
 		if (nm_ip_route_get_metric (s_route) == -1)
-			route.metric = default_route_metric;
+			route.metric = route_metric;
 		else
 			route.metric = nm_ip_route_get_metric (s_route);
 		route.rt_source = NM_IP_CONFIG_SOURCE_USER;
 
-		merge_route_attributes (s_route, &route);
-		nm_ip4_config_add_route (config, &route);
+		route.network = nm_utils_ip4_address_clear_host_address (route.network, route.plen);
+
+		merge_route_attributes (s_route, &route, route_table);
+		_add_route (self, NULL, &route, NULL);
 	}
 
 	/* DNS */
 	if (nm_setting_ip_config_get_ignore_auto_dns (setting)) {
-		nm_ip4_config_reset_nameservers (config);
-		nm_ip4_config_reset_domains (config);
-		nm_ip4_config_reset_searches (config);
+		nm_ip4_config_reset_nameservers (self);
+		nm_ip4_config_reset_domains (self);
+		nm_ip4_config_reset_searches (self);
 	}
 	for (i = 0; i < nnameservers; i++) {
 		guint32 ip;
 
 		if (inet_pton (AF_INET, nm_setting_ip_config_get_dns (setting, i), &ip) == 1)
-			nm_ip4_config_add_nameserver (config, ip);
+			nm_ip4_config_add_nameserver (self, ip);
 	}
 	for (i = 0; i < nsearches; i++)
-		nm_ip4_config_add_search (config, nm_setting_ip_config_get_dns_search (setting, i));
+		nm_ip4_config_add_search (self, nm_setting_ip_config_get_dns_search (setting, i));
 
 	i = 0;
 	while ((i = nm_setting_ip_config_next_valid_dns_option (setting, i)) >= 0) {
-		nm_ip4_config_add_dns_option (config, nm_setting_ip_config_get_dns_option (setting, i));
+		nm_ip4_config_add_dns_option (self, nm_setting_ip_config_get_dns_option (setting, i));
 		i++;
 	}
 
 	priority = nm_setting_ip_config_get_dns_priority (setting);
 	if (priority)
-		nm_ip4_config_set_dns_priority (config, priority);
+		nm_ip4_config_set_dns_priority (self, priority);
 
-	g_object_thaw_notify (G_OBJECT (config));
+	g_object_thaw_notify (G_OBJECT (self));
 }
 
 NMSetting *
-nm_ip4_config_create_setting (const NMIP4Config *config)
+nm_ip4_config_create_setting (const NMIP4Config *self)
 {
+	const NMIP4ConfigPrivate *priv;
 	NMSettingIPConfig *s_ip4;
-	guint32 gateway;
-	guint naddresses, nroutes, nnameservers, nsearches, noptions;
+	guint nnameservers, nsearches, noptions;
 	const char *method = NULL;
 	int i;
-	gint64 route_metric;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
 
 	s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
 
-	if (!config) {
+	if (!self) {
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
 		              NULL);
 		return NM_SETTING (s_ip4);
 	}
 
-	gateway = nm_ip4_config_get_gateway (config);
-	naddresses = nm_ip4_config_get_num_addresses (config);
-	nroutes = nm_ip4_config_get_num_routes (config);
-	nnameservers = nm_ip4_config_get_num_nameservers (config);
-	nsearches = nm_ip4_config_get_num_searches (config);
-	noptions = nm_ip4_config_get_num_dns_options (config);
-	route_metric = nm_ip4_config_get_route_metric (config);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	nnameservers = nm_ip4_config_get_num_nameservers (self);
+	nsearches = nm_ip4_config_get_num_searches (self);
+	noptions = nm_ip4_config_get_num_dns_options (self);
 
 	/* Addresses */
-	for (i = 0; i < naddresses; i++) {
-		const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) {
 		NMIPAddress *s_addr;
 
 		/* Detect dynamic address */
@@ -619,10 +1021,12 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 	}
 
 	/* Gateway */
-	if (   nm_ip4_config_has_gateway (config)
+	if (   priv->best_default_route
 	    && nm_setting_ip_config_get_num_addresses (s_ip4) > 0) {
 		g_object_set (s_ip4,
-		              NM_SETTING_IP_CONFIG_GATEWAY, nm_utils_inet4_ntop (gateway, NULL),
+		              NM_SETTING_IP_CONFIG_GATEWAY,
+		              nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
+		                                   NULL),
 		              NULL);
 	}
 
@@ -632,16 +1036,13 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 
 	g_object_set (s_ip4,
 	              NM_SETTING_IP_CONFIG_METHOD, method,
-	              NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64) route_metric,
 	              NULL);
 
 	/* Routes */
-	for (i = 0; i < nroutes; i++) {
-		const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
 		NMIPRoute *s_route;
 
-		/* Ignore default route. */
-		if (!route->plen)
+		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
 			continue;
 
 		/* Ignore routes provided by external sources */
@@ -658,25 +1059,25 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 
 	/* DNS */
 	for (i = 0; i < nnameservers; i++) {
-		guint32 nameserver = nm_ip4_config_get_nameserver (config, i);
+		guint32 nameserver = nm_ip4_config_get_nameserver (self, i);
 
 		nm_setting_ip_config_add_dns (s_ip4, nm_utils_inet4_ntop (nameserver, NULL));
 	}
 	for (i = 0; i < nsearches; i++) {
-		const char *search = nm_ip4_config_get_search (config, i);
+		const char *search = nm_ip4_config_get_search (self, i);
 
 		nm_setting_ip_config_add_dns_search (s_ip4, search);
 	}
 
 	for (i = 0; i < noptions; i++) {
-		const char *option = nm_ip4_config_get_dns_option (config, i);
+		const char *option = nm_ip4_config_get_dns_option (self, i);
 
 		nm_setting_ip_config_add_dns_option (s_ip4, option);
 	}
 
 	g_object_set (s_ip4,
 	              NM_SETTING_IP_CONFIG_DNS_PRIORITY,
-	              nm_ip4_config_get_dns_priority (config),
+	              nm_ip4_config_get_dns_priority (self),
 	              NULL);
 
 	return NM_SETTING (s_ip4);
@@ -685,11 +1086,16 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFlags merge_flags)
+nm_ip4_config_merge (NMIP4Config *dst,
+                     const NMIP4Config *src,
+                     NMIPConfigMergeFlags merge_flags,
+                     guint32 default_route_metric_penalty)
 {
 	NMIP4ConfigPrivate *dst_priv;
 	const NMIP4ConfigPrivate *src_priv;
 	guint32 i;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address = NULL;
 
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
@@ -700,8 +1106,8 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (src); i++)
-		nm_ip4_config_add_address (dst, nm_ip4_config_get_address (src, i));
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &address)
+		_add_address (dst, NMP_OBJECT_UP_CAST (address), NULL);
 
 	/* nameservers */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
@@ -709,20 +1115,25 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 			nm_ip4_config_add_nameserver (dst, nm_ip4_config_get_nameserver (src, i));
 	}
 
-	/* default gateway */
-	if (nm_ip4_config_has_gateway (src))
-		nm_ip4_config_set_gateway (dst, nm_ip4_config_get_gateway (src));
-
 	/* routes */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_ROUTES)) {
-		for (i = 0; i < nm_ip4_config_get_num_routes (src); i++)
-			nm_ip4_config_add_route (dst, nm_ip4_config_get_route (src, i));
-	}
+		const NMPlatformIP4Route *r_src;
 
-	if (dst_priv->route_metric == -1)
-		dst_priv->route_metric = src_priv->route_metric;
-	else if (src_priv->route_metric != -1)
-		dst_priv->route_metric = MIN (dst_priv->route_metric, src_priv->route_metric);
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r_src) {
+			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r_src)) {
+				if (NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES))
+					continue;
+				if (default_route_metric_penalty) {
+					NMPlatformIP4Route r = *r_src;
+
+					r.metric = nm_utils_ip_route_metric_penalize (AF_INET, r.metric, default_route_metric_penalty);
+					_add_route (dst, NULL, &r, NULL);
+					continue;
+				}
+			}
+			_add_route (dst, ipconf_iter.current->obj, NULL, NULL);
+		}
+	}
 
 	/* domains */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
@@ -742,10 +1153,6 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 			nm_ip4_config_add_dns_option (dst, nm_ip4_config_get_dns_option (src, i));
 	}
 
-	/* MSS */
-	if (nm_ip4_config_get_mss (src))
-		nm_ip4_config_set_mss (dst, nm_ip4_config_get_mss (src));
-
 	/* MTU */
 	if (   src_priv->mtu_source > dst_priv->mtu_source
 	    || (   src_priv->mtu_source == dst_priv->mtu_source
@@ -782,21 +1189,6 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 /*****************************************************************************/
 
 static int
-_addresses_get_index (const NMIP4Config *self, const NMPlatformIP4Address *addr)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
-	guint i;
-
-	for (i = 0; i < priv->addresses->len; i++) {
-		const NMPlatformIP4Address *a = &g_array_index (priv->addresses, NMPlatformIP4Address, i);
-
-		if (addresses_are_duplicate (addr, a))
-			return (int) i;
-	}
-	return -1;
-}
-
-static int
 _nameservers_get_index (const NMIP4Config *self, guint32 ns)
 {
 	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
@@ -812,22 +1204,6 @@ _nameservers_get_index (const NMIP4Config *self, guint32 ns)
 }
 
 static int
-_routes_get_index (const NMIP4Config *self, const NMPlatformIP4Route *route)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
-	guint i;
-
-	for (i = 0; i < priv->routes->len; i++) {
-		const NMPlatformIP4Route *r = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-		if (   route->network == r->network
-		    && route->plen == r->plen)
-			return (int) i;
-	}
-	return -1;
-}
-
-static int
 _domains_get_index (const NMIP4Config *self, const char *domain)
 {
 	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
@@ -908,26 +1284,45 @@ _wins_get_index (const NMIP4Config *self, guint32 wins_server)
  * nm_ip4_config_subtract:
  * @dst: config from which to remove everything in @src
  * @src: config to remove from @dst
+ * @default_route_metric_penalty: pretend that on source we applied
+ *   a route penalty on the default-route. It means, for default routes
+ *   we don't remove routes that match exactly, but those with a lower
+ *   metric (with the penalty removed).
  *
  * Removes everything in @src from @dst.
  */
 void
-nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
+nm_ip4_config_subtract (NMIP4Config *dst,
+                        const NMIP4Config *src,
+                        guint32 default_route_metric_penalty)
 {
-	guint32 i;
+	NMIP4ConfigPrivate *dst_priv;
+	guint i;
 	gint idx;
+	const NMPlatformIP4Address *a;
+	const NMPlatformIP4Route *r;
+	NMDedupMultiIter ipconf_iter;
+	gboolean changed;
+	gboolean changed_default_route;
 
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
 
+	dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
+
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (src); i++) {
-		idx = _addresses_get_index (dst, nm_ip4_config_get_address (src, i));
-		if (idx >= 0)
-			nm_ip4_config_del_address (dst, idx);
+	changed = FALSE;
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &a) {
+		if (nm_dedup_multi_index_remove_obj (dst_priv->multi_idx,
+		                                     &dst_priv->idx_ip4_addresses,
+		                                     NMP_OBJECT_UP_CAST (a),
+		                                     NULL))
+			changed = TRUE;
 	}
+	if (changed)
+		_notify_addresses (dst);
 
 	/* nameservers */
 	for (i = 0; i < nm_ip4_config_get_num_nameservers (src); i++) {
@@ -936,22 +1331,46 @@ nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 			nm_ip4_config_del_nameserver (dst, idx);
 	}
 
-	/* default gateway */
-	if (   (nm_ip4_config_has_gateway (src) == nm_ip4_config_has_gateway (dst))
-	    && (nm_ip4_config_get_gateway (src) == nm_ip4_config_get_gateway (dst)))
-		nm_ip4_config_unset_gateway (dst);
-
-	if (!nm_ip4_config_get_num_addresses (dst))
-		nm_ip4_config_unset_gateway (dst);
-
-	/* ignore route_metric */
-
 	/* routes */
-	for (i = 0; i < nm_ip4_config_get_num_routes (src); i++) {
-		idx = _routes_get_index (dst, nm_ip4_config_get_route (src, i));
-		if (idx >= 0)
-			nm_ip4_config_del_route (dst, idx);
+	changed = FALSE;
+	changed_default_route = FALSE;
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r) {
+		const NMPObject *o_src = NMP_OBJECT_UP_CAST (r);
+		NMPObject o_lookup_copy;
+		const NMPObject *o_lookup;
+		nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    && default_route_metric_penalty) {
+			NMPlatformIP4Route *rr;
+
+			/* the default route was penalized when merging it to the combined ip-config.
+			 * When subtracting the routes, we must re-do that process when comparing
+			 * the routes. */
+			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_src);
+			rr = NMP_OBJECT_CAST_IP4_ROUTE (&o_lookup_copy);
+			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET, rr->metric, default_route_metric_penalty);
+		} else
+			o_lookup = o_src;
+
+		if (nm_dedup_multi_index_remove_obj (dst_priv->multi_idx,
+		                                     &dst_priv->idx_ip4_routes,
+		                                     o_lookup,
+		                                     (gconstpointer *) &obj_old)) {
+			if (dst_priv->best_default_route == obj_old) {
+				nm_clear_nmp_object (&dst_priv->best_default_route);
+				changed_default_route = TRUE;
+			}
+			changed = TRUE;
+		}
+	}
+	if (changed_default_route) {
+		_nm_ip_config_best_default_route_set (&dst_priv->best_default_route,
+		                                      _nm_ip4_config_best_default_route_find (dst));
+		_notify (dst, PROP_GATEWAY);
 	}
+	if (changed)
+		_notify_routes (dst);
 
 	/* domains */
 	for (i = 0; i < nm_ip4_config_get_num_domains (src); i++) {
@@ -974,10 +1393,6 @@ nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 			nm_ip4_config_del_dns_option (dst, idx);
 	}
 
-	/* MSS */
-	if (nm_ip4_config_get_mss (src) == nm_ip4_config_get_mss (dst))
-		nm_ip4_config_set_mss (dst, 0);
-
 	/* MTU */
 	if (   nm_ip4_config_get_mtu (src) == nm_ip4_config_get_mtu (dst)
 	    && nm_ip4_config_get_mtu_source (src) == nm_ip4_config_get_mtu_source (dst))
@@ -1008,43 +1423,83 @@ nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 }
 
 void
-nm_ip4_config_intersect (NMIP4Config *dst, const NMIP4Config *src)
+nm_ip4_config_intersect (NMIP4Config *dst,
+                         const NMIP4Config *src,
+                         guint32 default_route_metric_penalty)
 {
-	guint32 i;
-	gint idx;
+	NMIP4ConfigPrivate *dst_priv;
+	const NMIP4ConfigPrivate *src_priv;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *a;
+	const NMPlatformIP4Route *r;
+	const NMPObject *new_best_default_route;
+	gboolean changed;
 
-	g_return_if_fail (src != NULL);
-	g_return_if_fail (dst != NULL);
+	g_return_if_fail (src);
+	g_return_if_fail (dst);
+
+	dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
+	src_priv = NM_IP4_CONFIG_GET_PRIVATE (src);
 
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (dst); ) {
-		idx = _addresses_get_index (src, nm_ip4_config_get_address (dst, i));
-		if (idx < 0)
-			nm_ip4_config_del_address (dst, i);
-		else
-			i++;
+	changed = FALSE;
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) {
+		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+		                                     &src_priv->idx_ip4_addresses,
+		                                     NMP_OBJECT_UP_CAST (a)))
+			continue;
+
+		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+		                                       ipconf_iter.current) != 1)
+			nm_assert_not_reached ();
+		changed = TRUE;
 	}
+	if (changed)
+		_notify_addresses (dst);
 
-	/* ignore route_metric */
 	/* ignore nameservers */
 
-	/* default gateway */
-	if (   !nm_ip4_config_get_num_addresses (dst)
-	    || (nm_ip4_config_has_gateway (src) != nm_ip4_config_has_gateway (dst))
-	    || (nm_ip4_config_get_gateway (src) != nm_ip4_config_get_gateway (dst))) {
-		nm_ip4_config_unset_gateway (dst);
-	}
-
 	/* routes */
-	for (i = 0; i < nm_ip4_config_get_num_routes (dst); ) {
-		idx = _routes_get_index (src, nm_ip4_config_get_route (dst, i));
-		if (idx < 0)
-			nm_ip4_config_del_route (dst, i);
-		else
-			i++;
+	changed = FALSE;
+	new_best_default_route = NULL;
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, dst, &r) {
+		const NMPObject *o_dst = NMP_OBJECT_UP_CAST (r);
+		const NMPObject *o_lookup;
+		NMPObject o_lookup_copy;
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    && default_route_metric_penalty) {
+			NMPlatformIP4Route *rr;
+
+			/* the default route was penalized when merging it to the combined ip-config.
+			 * When intersecting the routes, we must re-do that process when comparing
+			 * the routes. */
+			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_dst);
+			rr = NMP_OBJECT_CAST_IP4_ROUTE (&o_lookup_copy);
+			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET, rr->metric, default_route_metric_penalty);
+		} else
+			o_lookup = o_dst;
+
+		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+		                                     &src_priv->idx_ip4_routes,
+		                                     o_lookup)) {
+			new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, o_dst);
+			continue;
+		}
+
+		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+		                                       ipconf_iter.current) != 1)
+			nm_assert_not_reached ();
+		changed = TRUE;
+	}
+	if (_nm_ip_config_best_default_route_set (&dst_priv->best_default_route, new_best_default_route)) {
+		nm_assert (changed);
+		_notify (dst, PROP_GATEWAY);
 	}
+	if (changed)
+		_notify_routes (dst);
 
 	/* ignore domains */
 	/* ignore dns searches */
@@ -1080,8 +1535,9 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 	guint i, num;
 	NMIP4ConfigPrivate *dst_priv;
 	const NMIP4ConfigPrivate *src_priv;
-	const NMPlatformIP4Address *dst_addr, *src_addr;
-	const NMPlatformIP4Route *dst_route, *src_route;
+	NMDedupMultiIter ipconf_iter_src, ipconf_iter_dst;
+	const NMDedupMultiHeadEntry *head_entry_src;
+	const NMPObject *new_best_default_route;
 
 	g_return_val_if_fail (src != NULL, FALSE);
 	g_return_val_if_fail (dst != NULL, FALSE);
@@ -1102,72 +1558,106 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_minor_changes = TRUE;
 	}
 
-	/* never_default */
-	if (src_priv->never_default != dst_priv->never_default) {
-		dst_priv->never_default = src_priv->never_default;
-		has_minor_changes = TRUE;
-	}
-
-	/* default gateway */
-	if (   src_priv->gateway != dst_priv->gateway
-	    || src_priv->has_gateway != dst_priv->has_gateway) {
-		if (src_priv->has_gateway)
-			nm_ip4_config_set_gateway (dst, src_priv->gateway);
-		else
-			nm_ip4_config_unset_gateway (dst);
-		has_relevant_changes = TRUE;
-	}
-
-	if (src_priv->route_metric != dst_priv->route_metric) {
-		dst_priv->route_metric = src_priv->route_metric;
-		has_minor_changes = TRUE;
-	}
-
 	/* addresses */
-	num = nm_ip4_config_get_num_addresses (src);
-	are_equal = num == nm_ip4_config_get_num_addresses (dst);
-	if (are_equal) {
-		for (i = 0; i < num; i++ ) {
-			if (nm_platform_ip4_address_cmp (src_addr = nm_ip4_config_get_address (src, i),
-			                                 dst_addr = nm_ip4_config_get_address (dst, i))) {
-				are_equal = FALSE;
-				if (   !addresses_are_duplicate (src_addr, dst_addr)
-				    || src_addr->peer_address != dst_addr->peer_address) {
-					has_relevant_changes = TRUE;
-					break;
-				}
+	head_entry_src = nm_ip4_config_lookup_addresses (src);
+	nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
+	nm_ip_config_iter_ip4_address_init (&ipconf_iter_dst, dst);
+	are_equal = TRUE;
+	while (TRUE) {
+		gboolean has;
+		const NMPlatformIP4Address *r_src = NULL;
+		const NMPlatformIP4Address *r_dst = NULL;
+
+		has = nm_ip_config_iter_ip4_address_next (&ipconf_iter_src, &r_src);
+		if (has != nm_ip_config_iter_ip4_address_next (&ipconf_iter_dst, &r_dst)) {
+			are_equal = FALSE;
+			has_relevant_changes = TRUE;
+			break;
+		}
+		if (!has)
+			break;
+
+		if (nm_platform_ip4_address_cmp (r_src, r_dst) != 0) {
+			are_equal = FALSE;
+			if (   r_src->address != r_dst->address
+			    || r_src->plen != r_dst->plen
+			    || r_src->peer_address != r_dst->peer_address) {
+				has_relevant_changes = TRUE;
+				break;
 			}
 		}
-	} else
-		has_relevant_changes = TRUE;
+	}
 	if (!are_equal) {
-		nm_ip4_config_reset_addresses (dst);
-		for (i = 0; i < num; i++)
-			nm_ip4_config_add_address (dst, nm_ip4_config_get_address (src, i));
 		has_minor_changes = TRUE;
+		nm_dedup_multi_index_dirty_set_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_addresses);
+		nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) {
+			_nm_ip_config_add_obj (dst_priv->multi_idx,
+			                       &dst_priv->idx_ip4_addresses_,
+			                       dst_priv->ifindex,
+			                       ipconf_iter_src.current->obj,
+			                       NULL,
+			                       FALSE,
+			                       TRUE,
+			                       NULL,
+			                       NULL);
+		}
+		nm_dedup_multi_index_dirty_remove_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_addresses, FALSE);
+		_notify_addresses (dst);
 	}
 
 	/* routes */
-	num = nm_ip4_config_get_num_routes (src);
-	are_equal = num == nm_ip4_config_get_num_routes (dst);
-	if (are_equal) {
-		for (i = 0; i < num; i++ ) {
-			if (nm_platform_ip4_route_cmp (src_route = nm_ip4_config_get_route (src, i),
-			                               dst_route = nm_ip4_config_get_route (dst, i))) {
-				are_equal = FALSE;
-				if (!routes_are_duplicate (src_route, dst_route, TRUE)) {
-					has_relevant_changes = TRUE;
-					break;
-				}
+	head_entry_src = nm_ip4_config_lookup_routes (src);
+	nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
+	nm_ip_config_iter_ip4_route_init (&ipconf_iter_dst, dst);
+	are_equal = TRUE;
+	while (TRUE) {
+		gboolean has;
+		const NMPlatformIP4Route *r_src = NULL;
+		const NMPlatformIP4Route *r_dst = NULL;
+
+		has = nm_ip_config_iter_ip4_route_next (&ipconf_iter_src, &r_src);
+		if (has != nm_ip_config_iter_ip4_route_next (&ipconf_iter_dst, &r_dst)) {
+			are_equal = FALSE;
+			has_relevant_changes = TRUE;
+			break;
+		}
+		if (!has)
+			break;
+
+		if (nm_platform_ip4_route_cmp_full (r_src, r_dst) != 0) {
+			are_equal = FALSE;
+			if (   r_src->plen != r_dst->plen
+			    || !nm_utils_ip4_address_same_prefix (r_src->network, r_dst->network, r_src->plen)
+			    || r_src->gateway != r_dst->gateway
+			    || r_src->metric != r_dst->metric) {
+				has_relevant_changes = TRUE;
+				break;
 			}
 		}
-	} else
-		has_relevant_changes = TRUE;
+	}
 	if (!are_equal) {
-		nm_ip4_config_reset_routes (dst);
-		for (i = 0; i < num; i++)
-			nm_ip4_config_add_route (dst, nm_ip4_config_get_route (src, i));
 		has_minor_changes = TRUE;
+		new_best_default_route = NULL;
+		nm_dedup_multi_index_dirty_set_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_routes);
+		nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) {
+			const NMPObject *o = ipconf_iter_src.current->obj;
+			const NMPObject *obj_new;
+
+			_nm_ip_config_add_obj (dst_priv->multi_idx,
+			                       &dst_priv->idx_ip4_routes_,
+			                       dst_priv->ifindex,
+			                       o,
+			                       NULL,
+			                       FALSE,
+			                       TRUE,
+			                       NULL,
+			                       &obj_new);
+			new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, obj_new);
+		}
+		nm_dedup_multi_index_dirty_remove_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_routes, FALSE);
+		if (_nm_ip_config_best_default_route_set (&dst_priv->best_default_route, new_best_default_route))
+			_notify (dst, PROP_GATEWAY);
+		_notify_routes (dst);
 	}
 
 	/* nameservers */
@@ -1251,12 +1741,6 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_minor_changes = TRUE;
 	}
 
-	/* mss */
-	if (src_priv->mss != dst_priv->mss) {
-		nm_ip4_config_set_mss (dst, src_priv->mss);
-		has_minor_changes = TRUE;
-	}
-
 	/* nis */
 	num = nm_ip4_config_get_num_nis_servers (src);
 	are_equal = num == nm_ip4_config_get_num_nis_servers (dst);
@@ -1327,386 +1811,344 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 }
 
 void
-nm_ip4_config_dump (const NMIP4Config *config, const char *detail)
+nm_ip4_config_dump (const NMIP4Config *self, const char *detail)
 {
-	guint32 i, tmp;
+	guint32 tmp;
+	guint i;
 	const char *str;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
 
-	g_message ("--------- NMIP4Config %p (%s)", config, detail);
+	g_message ("--------- NMIP4Config %p (%s)", self, detail);
 
-	if (config == NULL) {
+	if (self == NULL) {
 		g_message (" (null)");
 		return;
 	}
 
-	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (config));
+	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
 	if (str)
 		g_message ("   path: %s", str);
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (config); i++)
-		g_message ("      a: %s", nm_platform_ip4_address_to_string (nm_ip4_config_get_address (config, i), NULL, 0));
-
-	/* default gateway */
-	if (nm_ip4_config_has_gateway (config)) {
-		tmp = nm_ip4_config_get_gateway (config);
-		g_message ("     gw: %s", nm_utils_inet4_ntop (tmp, NULL));
-	}
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address)
+		g_message ("      a: %s", nm_platform_ip4_address_to_string (address, NULL, 0));
 
 	/* nameservers */
-	for (i = 0; i < nm_ip4_config_get_num_nameservers (config); i++) {
-		tmp = nm_ip4_config_get_nameserver (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_nameservers (self); i++) {
+		tmp = nm_ip4_config_get_nameserver (self, i);
 		g_message ("     ns: %s", nm_utils_inet4_ntop (tmp, NULL));
 	}
 
 	/* routes */
-	for (i = 0; i < nm_ip4_config_get_num_routes (config); i++)
-		g_message ("     rt: %s", nm_platform_ip4_route_to_string (nm_ip4_config_get_route (config, i), NULL, 0));
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route)
+		g_message ("     rt: %s", nm_platform_ip4_route_to_string (route, NULL, 0));
 
 	/* domains */
-	for (i = 0; i < nm_ip4_config_get_num_domains (config); i++)
-		g_message (" domain: %s", nm_ip4_config_get_domain (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_domains (self); i++)
+		g_message (" domain: %s", nm_ip4_config_get_domain (self, i));
 
 	/* dns searches */
-	for (i = 0; i < nm_ip4_config_get_num_searches (config); i++)
-		g_message (" search: %s", nm_ip4_config_get_search (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_searches (self); i++)
+		g_message (" search: %s", nm_ip4_config_get_search (self, i));
 
 	/* dns options */
-	for (i = 0; i < nm_ip4_config_get_num_dns_options (config); i++)
-		g_message (" dnsopt: %s", nm_ip4_config_get_dns_option (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_dns_options (self); i++)
+		g_message (" dnsopt: %s", nm_ip4_config_get_dns_option (self, i));
 
-	g_message (" dnspri: %d", nm_ip4_config_get_dns_priority (config));
+	g_message (" dnspri: %d", nm_ip4_config_get_dns_priority (self));
 
-	g_message ("    mss: %"G_GUINT32_FORMAT, nm_ip4_config_get_mss (config));
-	g_message ("    mtu: %"G_GUINT32_FORMAT" (source: %d)", nm_ip4_config_get_mtu (config), (int) nm_ip4_config_get_mtu_source (config));
+	g_message ("    mtu: %"G_GUINT32_FORMAT" (source: %d)", nm_ip4_config_get_mtu (self), (int) nm_ip4_config_get_mtu_source (self));
 
 	/* NIS */
-	for (i = 0; i < nm_ip4_config_get_num_nis_servers (config); i++) {
-		tmp = nm_ip4_config_get_nis_server (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_nis_servers (self); i++) {
+		tmp = nm_ip4_config_get_nis_server (self, i);
 		g_message ("    nis: %s", nm_utils_inet4_ntop (tmp, NULL));
 	}
 
-	g_message (" nisdmn: %s", nm_ip4_config_get_nis_domain (config) ?: "(none)");
+	g_message (" nisdmn: %s", nm_ip4_config_get_nis_domain (self) ?: "(none)");
 
 	/* WINS */
-	for (i = 0; i < nm_ip4_config_get_num_wins (config); i++) {
-		tmp = nm_ip4_config_get_wins (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_wins (self); i++) {
+		tmp = nm_ip4_config_get_wins (self, i);
 		g_message ("   wins: %s", nm_utils_inet4_ntop (tmp, NULL));
 	}
 
-	g_message (" n-dflt: %d", nm_ip4_config_get_never_default (config));
-	g_message (" mtrd:   %d", (int) nm_ip4_config_get_metered (config));
-}
-
-gboolean
-nm_ip4_config_destination_is_direct (const NMIP4Config *config, guint32 network, guint8 plen)
-{
-	guint naddresses = nm_ip4_config_get_num_addresses (config);
-	guint i;
-	in_addr_t peer_network;
-
-	for (i = 0; i < naddresses; i++) {
-		const NMPlatformIP4Address *item = nm_ip4_config_get_address (config, i);
-
-		if (item->plen > plen)
-			continue;
-
-		peer_network = nm_utils_ip4_address_clear_host_address (item->peer_address, item->plen);
-		if (_ipv4_is_zeronet (peer_network))
-			continue;
-
-		if (peer_network != nm_utils_ip4_address_clear_host_address (network, item->plen))
-			continue;
-
-		return TRUE;
-	}
-
-	return FALSE;
+	g_message (" mtrd:   %d", (int) nm_ip4_config_get_metered (self));
 }
 
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_never_default (NMIP4Config *config, gboolean never_default)
+nm_ip4_config_reset_addresses (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	priv->never_default = never_default;
+	if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
+	                                     &priv->idx_ip4_addresses) > 0)
+		_notify_addresses (self);
 }
 
-gboolean
-nm_ip4_config_get_never_default (const NMIP4Config *config)
+static void
+_add_address (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	return priv->never_default;
+	if (_nm_ip_config_add_obj (priv->multi_idx,
+	                           &priv->idx_ip4_addresses_,
+	                           priv->ifindex,
+	                           obj_new,
+	                           (const NMPlatformObject *) new,
+	                           TRUE,
+	                           FALSE,
+	                           NULL,
+	                           NULL))
+		_notify_addresses (self);
 }
 
+/**
+ * nm_ip4_config_add_address:
+ * @self: the #NMIP4Config
+ * @new: the new address to add to @self
+ *
+ * Adds the new address to @self.  If an address with the same basic properties
+ * (address, prefix) already exists in @self, it is overwritten with the
+ * lifetime and preferred of @new.  The source is also overwritten by the source
+ * from @new if that source is higher priority.
+ */
 void
-nm_ip4_config_set_gateway (NMIP4Config *config, guint32 gateway)
+nm_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	g_return_if_fail (self);
+	g_return_if_fail (new);
+	g_return_if_fail (new->plen > 0 && new->plen <= 32);
+	g_return_if_fail (NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
-	if (priv->gateway != gateway || !priv->has_gateway) {
-		priv->gateway = gateway;
-		priv->has_gateway = TRUE;
-		_notify (config, PROP_GATEWAY);
-	}
+	_add_address (self, NULL, new);
 }
 
 void
-nm_ip4_config_unset_gateway (NMIP4Config *config)
+_nmtst_ip4_config_del_address (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMPlatformIP4Address *a;
 
-	if (priv->has_gateway) {
-		priv->gateway = 0;
-		priv->has_gateway = FALSE;
-		_notify (config, PROP_GATEWAY);
-	}
+	a = _nmtst_ip4_config_get_address (self, i);
+	if (!nm_ip4_config_nmpobj_remove (self,
+	                                  NMP_OBJECT_UP_CAST (a)))
+		g_assert_not_reached ();
 }
 
-/**
- * nm_ip4_config_has_gateway:
- * @config: the #NMIP4Config object
- *
- * NetworkManager's handling of default-routes is limited and usually a default-route
- * cannot have gateway 0.0.0.0. For peer-to-peer routes, we still want to
- * support that, so we need to differenciate between no-default-route and a
- * on-link-default route. Hence nm_ip4_config_has_gateway().
- *
- * Returns: whether the object has a gateway explicitly set. */
-gboolean
-nm_ip4_config_has_gateway (const NMIP4Config *config)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	return priv->has_gateway;
-}
-
-guint32
-nm_ip4_config_get_gateway (const NMIP4Config *config)
+guint
+nm_ip4_config_get_num_addresses (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMDedupMultiHeadEntry *head_entry;
 
-	return priv->gateway;
+	head_entry = nm_ip4_config_lookup_addresses (self);
+	return head_entry ? head_entry->len : 0;
 }
 
-gint64
-nm_ip4_config_get_route_metric (const NMIP4Config *config)
+const NMPlatformIP4Address *
+nm_ip4_config_get_first_address (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP4Address *a = NULL;
 
-	return priv->route_metric;
+	nm_ip_config_iter_ip4_address_for_each (&iter, self, &a)
+		return a;
+	return NULL;
 }
 
-/*****************************************************************************/
-
-void
-nm_ip4_config_reset_addresses (NMIP4Config *config)
+const NMPlatformIP4Address *
+_nmtst_ip4_config_get_address (const NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP4Address *a = NULL;
+	guint j;
 
-	if (priv->addresses->len != 0) {
-		g_array_set_size (priv->addresses, 0);
-		notify_addresses (config);
+	j = 0;
+	nm_ip_config_iter_ip4_address_for_each (&iter, self, &a) {
+		if (i == j)
+			return a;
+		j++;
 	}
+	g_return_val_if_reached (NULL);
 }
 
-/**
- * nm_ip4_config_add_address:
- * @config: the #NMIP4Config
- * @new: the new address to add to @config
- *
- * Adds the new address to @config.  If an address with the same basic properties
- * (address, prefix) already exists in @config, it is overwritten with the
- * lifetime and preferred of @new.  The source is also overwritten by the source
- * from @new if that source is higher priority.
- */
-void
-nm_ip4_config_add_address (NMIP4Config *config, const NMPlatformIP4Address *new)
+gboolean
+nm_ip4_config_address_exists (const NMIP4Config *self,
+                              const NMPlatformIP4Address *needle)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	NMPlatformIP4Address item_old;
-	int i;
-
-	g_return_if_fail (new != NULL);
-
-	for (i = 0; i < priv->addresses->len; i++ ) {
-		NMPlatformIP4Address *item = &g_array_index (priv->addresses, NMPlatformIP4Address, i);
-
-		if (addresses_are_duplicate (item, new)) {
-			if (nm_platform_ip4_address_cmp (item, new) == 0)
-				return;
-
-			/* remember the old values. */
-			item_old = *item;
-			/* Copy over old item to get new lifetime, timestamp, preferred */
-			*item = *new;
-
-			/* But restore highest priority source */
-			item->addr_source = MAX (item_old.addr_source, new->addr_source);
-
-			/* for addresses that we read from the kernel, we keep the timestamps as defined
-			 * by the previous source (item_old). The reason is, that the other source configured the lifetimes
-			 * with "what should be" and the kernel values are "what turned out after configuring it".
-			 *
-			 * For other sources, the longer lifetime wins. */
-			if (   (new->addr_source == NM_IP_CONFIG_SOURCE_KERNEL && new->addr_source != item_old.addr_source)
-			    || nm_platform_ip_address_cmp_expiry ((const NMPlatformIPAddress *) &item_old, (const NMPlatformIPAddress *) new) > 0) {
-				item->timestamp = item_old.timestamp;
-				item->lifetime = item_old.lifetime;
-				item->preferred = item_old.preferred;
-			}
-			if (nm_platform_ip4_address_cmp (&item_old, item) == 0)
-				return;
-			goto NOTIFY;
-		}
-	}
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	NMPObject obj_stack;
 
-	g_array_append_val (priv->addresses, *new);
-NOTIFY:
-	notify_addresses (config);
+	nmp_object_stackinit_id_ip4_address (&obj_stack,
+	                                     priv->ifindex,
+	                                     needle->address,
+	                                     needle->plen,
+	                                     needle->peer_address);
+	return !!nm_dedup_multi_index_lookup_obj (priv->multi_idx,
+	                                          &priv->idx_ip4_addresses,
+	                                          &obj_stack);
 }
 
-void
-nm_ip4_config_del_address (NMIP4Config *config, guint i)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	g_return_if_fail (i < priv->addresses->len);
-
-	g_array_remove_index (priv->addresses, i);
-
-	notify_addresses (config);
-}
+/*****************************************************************************/
 
-guint
-nm_ip4_config_get_num_addresses (const NMIP4Config *config)
+static const NMDedupMultiEntry *
+_lookup_route (const NMIP4Config *self,
+               const NMPObject *needle,
+               NMPlatformIPRouteCmpType cmp_type)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv;
 
-	return priv->addresses->len;
-}
+	nm_assert (NM_IS_IP4_CONFIG (self));
+	nm_assert (NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE);
 
-const NMPlatformIP4Address *
-nm_ip4_config_get_address (const NMIP4Config *config, guint i)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	return &g_array_index (priv->addresses, NMPlatformIP4Address, i);
+	return _nm_ip_config_lookup_ip_route (priv->multi_idx,
+	                                      &priv->idx_ip4_routes_,
+	                                      needle,
+	                                      cmp_type);
 }
 
-gboolean
-nm_ip4_config_address_exists (const NMIP4Config *config,
-                              const NMPlatformIP4Address *needle)
+void
+nm_ip4_config_reset_routes (NMIP4Config *self)
 {
-	return _addresses_get_index (config, needle) >= 0;
-}
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-/*****************************************************************************/
+	if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
+	                                     &priv->idx_ip4_routes) > 0) {
+		if (nm_clear_nmp_object (&priv->best_default_route))
+			_notify (self, PROP_GATEWAY);
+		_notify_routes (self);
+	}
+}
 
-void
-nm_ip4_config_reset_routes (NMIP4Config *config)
+static void
+_add_route (NMIP4Config *self,
+            const NMPObject *obj_new,
+            const NMPlatformIP4Route *new,
+            const NMPObject **out_obj_new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	const NMPObject *obj_new_2;
+
+	nm_assert ((!new) != (!obj_new));
+	nm_assert (!new || _route_valid (new));
+	nm_assert (!obj_new || _route_valid (NMP_OBJECT_CAST_IP4_ROUTE (obj_new)));
+
+	if (_nm_ip_config_add_obj (priv->multi_idx,
+	                           &priv->idx_ip4_routes_,
+	                           priv->ifindex,
+	                           obj_new,
+	                           (const NMPlatformObject *) new,
+	                           TRUE,
+	                           FALSE,
+	                           &obj_old,
+	                           &obj_new_2)) {
+		gboolean changed_default_route = FALSE;
+
+		if (   priv->best_default_route == obj_old
+		    && obj_old != obj_new_2) {
+			changed_default_route = TRUE;
+			nm_clear_nmp_object (&priv->best_default_route);
+		}
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_new_2));
+		if (_nm_ip_config_best_default_route_merge (&priv->best_default_route, obj_new_2))
+			changed_default_route = TRUE;
 
-	if (priv->routes->len != 0) {
-		g_array_set_size (priv->routes, 0);
-		_notify (config, PROP_ROUTE_DATA);
-		_notify (config, PROP_ROUTES);
-	}
+		if (changed_default_route)
+			_notify (self, PROP_GATEWAY);
+		_notify_routes (self);
+	} else
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_new_2));
 }
 
 /**
  * nm_ip4_config_add_route:
- * @config: the #NMIP4Config
- * @new: the new route to add to @config
+ * @self: the #NMIP4Config
+ * @new: the new route to add to @self
+ * @out_obj_new: (allow-none): (out): the added route object. Must be unrefed
+ *   by caller.
  *
- * Adds the new route to @config.  If a route with the same basic properties
- * (network, prefix) already exists in @config, it is overwritten including the
+ * Adds the new route to @self.  If a route with the same basic properties
+ * (network, prefix) already exists in @self, it is overwritten including the
  * gateway and metric of @new.  The source is also overwritten by the source
  * from @new if that source is higher priority.
  */
 void
-nm_ip4_config_add_route (NMIP4Config *config, const NMPlatformIP4Route *new)
+nm_ip4_config_add_route (NMIP4Config *self,
+                         const NMPlatformIP4Route *new,
+                         const NMPObject **out_obj_new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	NMIPConfigSource old_source;
-	int i;
-
-	g_return_if_fail (new != NULL);
-	g_return_if_fail (new->plen > 0 && new->plen <= 32);
-	g_return_if_fail (priv->ifindex > 0);
-
-	for (i = 0; i < priv->routes->len; i++ ) {
-		NMPlatformIP4Route *item = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-		if (routes_are_duplicate (item, new, FALSE)) {
-			if (nm_platform_ip4_route_cmp (item, new) == 0)
-				return;
-			old_source = item->rt_source;
-			memcpy (item, new, sizeof (*item));
-			/* Restore highest priority source */
-			item->rt_source = MAX (old_source, new->rt_source);
-			item->ifindex = priv->ifindex;
-			goto NOTIFY;
-		}
-	}
+	g_return_if_fail (self);
+	g_return_if_fail (new);
+	g_return_if_fail (new->plen <= 32);
+	g_return_if_fail (NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
-	g_array_append_val (priv->routes, *new);
-	g_array_index (priv->routes, NMPlatformIP4Route, priv->routes->len - 1).ifindex = priv->ifindex;
-NOTIFY:
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ROUTES);
+	_add_route (self, NULL, new, out_obj_new);
 }
 
 void
-nm_ip4_config_del_route (NMIP4Config *config, guint i)
+_nmtst_ip4_config_del_route (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	g_return_if_fail (i < priv->routes->len);
+	const NMPlatformIP4Route *r;
 
-	g_array_remove_index (priv->routes, i);
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ROUTES);
+	r = _nmtst_ip4_config_get_route (self, i);
+	if (!nm_ip4_config_nmpobj_remove (self,
+	                                  NMP_OBJECT_UP_CAST (r)))
+		g_assert_not_reached ();
 }
 
 guint
-nm_ip4_config_get_num_routes (const NMIP4Config *config)
+nm_ip4_config_get_num_routes (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMDedupMultiHeadEntry *head_entry;
 
-	return priv->routes->len;
+	head_entry = nm_ip4_config_lookup_routes (self);
+	nm_assert (!head_entry || head_entry->len == c_list_length (&head_entry->lst_entries_head));
+	return head_entry ? head_entry->len : 0;
 }
 
 const NMPlatformIP4Route *
-nm_ip4_config_get_route (const NMIP4Config *config, guint i)
+_nmtst_ip4_config_get_route (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP4Route *r = NULL;
+	guint j;
 
-	return &g_array_index (priv->routes, NMPlatformIP4Route, i);
+	j = 0;
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
+		if (i == j)
+			return r;
+		j++;
+	}
+	g_return_val_if_reached (NULL);
 }
 
 const NMPlatformIP4Route *
-nm_ip4_config_get_direct_route_for_host (const NMIP4Config *config, guint32 host)
+nm_ip4_config_get_direct_route_for_host (const NMIP4Config *self,
+                                         in_addr_t host,
+                                         guint32 route_table)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	guint i;
-	NMPlatformIP4Route *best_route = NULL;
+	const NMPlatformIP4Route *best_route = NULL;
+	const NMPlatformIP4Route *item;
+	NMDedupMultiIter ipconf_iter;
 
 	g_return_val_if_fail (host, NULL);
 
-	for (i = 0; i < priv->routes->len; i++) {
-		NMPlatformIP4Route *item = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &item) {
 		if (item->gateway != 0)
 			continue;
 
 		if (best_route && best_route->plen > item->plen)
 			continue;
 
+		if (nm_platform_route_table_uncoerce (item->table_coerced, TRUE) != route_table)
+			continue;
+
 		if (nm_utils_ip4_address_clear_host_address (host, item->plen) != nm_utils_ip4_address_clear_host_address (item->network, item->plen))
 			continue;
 
@@ -1715,27 +2157,26 @@ nm_ip4_config_get_direct_route_for_host (const NMIP4Config *config, guint32 host
 
 		best_route = item;
 	}
-
 	return best_route;
 }
 
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_nameservers (NMIP4Config *config)
+nm_ip4_config_reset_nameservers (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->nameservers->len != 0) {
 		g_array_set_size (priv->nameservers, 0);
-		_notify (config, PROP_NAMESERVERS);
+		_notify (self, PROP_NAMESERVERS);
 	}
 }
 
 void
-nm_ip4_config_add_nameserver (NMIP4Config *config, guint32 new)
+nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (new != 0);
@@ -1745,32 +2186,32 @@ nm_ip4_config_add_nameserver (NMIP4Config *config, guint32 new)
 			return;
 
 	g_array_append_val (priv->nameservers, new);
-	_notify (config, PROP_NAMESERVERS);
+	_notify (self, PROP_NAMESERVERS);
 }
 
 void
-nm_ip4_config_del_nameserver (NMIP4Config *config, guint i)
+nm_ip4_config_del_nameserver (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->nameservers->len);
 
 	g_array_remove_index (priv->nameservers, i);
-	_notify (config, PROP_NAMESERVERS);
+	_notify (self, PROP_NAMESERVERS);
 }
 
 guint
-nm_ip4_config_get_num_nameservers (const NMIP4Config *config)
+nm_ip4_config_get_num_nameservers (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->nameservers->len;
 }
 
 guint32
-nm_ip4_config_get_nameserver (const NMIP4Config *config, guint i)
+nm_ip4_config_get_nameserver (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_array_index (priv->nameservers, guint32, i);
 }
@@ -1778,20 +2219,20 @@ nm_ip4_config_get_nameserver (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_domains (NMIP4Config *config)
+nm_ip4_config_reset_domains (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->domains->len != 0) {
 		g_ptr_array_set_size (priv->domains, 0);
-		_notify (config, PROP_DOMAINS);
+		_notify (self, PROP_DOMAINS);
 	}
 }
 
 void
-nm_ip4_config_add_domain (NMIP4Config *config, const char *domain)
+nm_ip4_config_add_domain (NMIP4Config *self, const char *domain)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (domain != NULL);
@@ -1802,32 +2243,32 @@ nm_ip4_config_add_domain (NMIP4Config *config, const char *domain)
 			return;
 
 	g_ptr_array_add (priv->domains, g_strdup (domain));
-	_notify (config, PROP_DOMAINS);
+	_notify (self, PROP_DOMAINS);
 }
 
 void
-nm_ip4_config_del_domain (NMIP4Config *config, guint i)
+nm_ip4_config_del_domain (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->domains->len);
 
 	g_ptr_array_remove_index (priv->domains, i);
-	_notify (config, PROP_DOMAINS);
+	_notify (self, PROP_DOMAINS);
 }
 
 guint
-nm_ip4_config_get_num_domains (const NMIP4Config *config)
+nm_ip4_config_get_num_domains (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->domains->len;
 }
 
 const char *
-nm_ip4_config_get_domain (const NMIP4Config *config, guint i)
+nm_ip4_config_get_domain (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->domains, i);
 }
@@ -1835,20 +2276,20 @@ nm_ip4_config_get_domain (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_searches (NMIP4Config *config)
+nm_ip4_config_reset_searches (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->searches->len != 0) {
 		g_ptr_array_set_size (priv->searches, 0);
-		_notify (config, PROP_SEARCHES);
+		_notify (self, PROP_SEARCHES);
 	}
 }
 
 void
-nm_ip4_config_add_search (NMIP4Config *config, const char *new)
+nm_ip4_config_add_search (NMIP4Config *self, const char *new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	char *search;
 	size_t len;
 
@@ -1874,32 +2315,32 @@ nm_ip4_config_add_search (NMIP4Config *config, const char *new)
 	}
 
 	g_ptr_array_add (priv->searches, search);
-	_notify (config, PROP_SEARCHES);
+	_notify (self, PROP_SEARCHES);
 }
 
 void
-nm_ip4_config_del_search (NMIP4Config *config, guint i)
+nm_ip4_config_del_search (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->searches->len);
 
 	g_ptr_array_remove_index (priv->searches, i);
-	_notify (config, PROP_SEARCHES);
+	_notify (self, PROP_SEARCHES);
 }
 
 guint
-nm_ip4_config_get_num_searches (const NMIP4Config *config)
+nm_ip4_config_get_num_searches (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->searches->len;
 }
 
 const char *
-nm_ip4_config_get_search (const NMIP4Config *config, guint i)
+nm_ip4_config_get_search (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->searches, i);
 }
@@ -1907,20 +2348,20 @@ nm_ip4_config_get_search (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_dns_options (NMIP4Config *config)
+nm_ip4_config_reset_dns_options (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->dns_options->len != 0) {
 		g_ptr_array_set_size (priv->dns_options, 0);
-		_notify (config, PROP_DNS_OPTIONS);
+		_notify (self, PROP_DNS_OPTIONS);
 	}
 }
 
 void
-nm_ip4_config_add_dns_option (NMIP4Config *config, const char *new)
+nm_ip4_config_add_dns_option (NMIP4Config *self, const char *new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (new != NULL);
@@ -1931,32 +2372,32 @@ nm_ip4_config_add_dns_option (NMIP4Config *config, const char *new)
 			return;
 
 	g_ptr_array_add (priv->dns_options, g_strdup (new));
-	_notify (config, PROP_DNS_OPTIONS);
+	_notify (self, PROP_DNS_OPTIONS);
 }
 
 void
-nm_ip4_config_del_dns_option(NMIP4Config *config, guint i)
+nm_ip4_config_del_dns_option(NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->dns_options->len);
 
 	g_ptr_array_remove_index (priv->dns_options, i);
-	_notify (config, PROP_DNS_OPTIONS);
+	_notify (self, PROP_DNS_OPTIONS);
 }
 
 guint
-nm_ip4_config_get_num_dns_options (const NMIP4Config *config)
+nm_ip4_config_get_num_dns_options (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->dns_options->len;
 }
 
 const char *
-nm_ip4_config_get_dns_option (const NMIP4Config *config, guint i)
+nm_ip4_config_get_dns_option (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->dns_options, i);
 }
@@ -1964,20 +2405,20 @@ nm_ip4_config_get_dns_option (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_dns_priority (NMIP4Config *config, gint priority)
+nm_ip4_config_set_dns_priority (NMIP4Config *self, gint priority)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priority != priv->dns_priority) {
 		priv->dns_priority = priority;
-		_notify (config, PROP_DNS_PRIORITY);
+		_notify (self, PROP_DNS_PRIORITY);
 	}
 }
 
 gint
-nm_ip4_config_get_dns_priority (const NMIP4Config *config)
+nm_ip4_config_get_dns_priority (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->dns_priority;
 }
@@ -1985,35 +2426,17 @@ nm_ip4_config_get_dns_priority (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_mss (NMIP4Config *config, guint32 mss)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	priv->mss = mss;
-}
-
-guint32
-nm_ip4_config_get_mss (const NMIP4Config *config)
+nm_ip4_config_reset_nis_servers (NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	return priv->mss;
-}
-
-/*****************************************************************************/
-
-void
-nm_ip4_config_reset_nis_servers (NMIP4Config *config)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_array_set_size (priv->nis, 0);
 }
 
 void
-nm_ip4_config_add_nis_server (NMIP4Config *config, guint32 nis)
+nm_ip4_config_add_nis_server (NMIP4Config *self, guint32 nis)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	for (i = 0; i < priv->nis->len; i++)
@@ -2024,9 +2447,9 @@ nm_ip4_config_add_nis_server (NMIP4Config *config, guint32 nis)
 }
 
 void
-nm_ip4_config_del_nis_server (NMIP4Config *config, guint i)
+nm_ip4_config_del_nis_server (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->nis->len);
 
@@ -2034,34 +2457,34 @@ nm_ip4_config_del_nis_server (NMIP4Config *config, guint i)
 }
 
 guint
-nm_ip4_config_get_num_nis_servers (const NMIP4Config *config)
+nm_ip4_config_get_num_nis_servers (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->nis->len;
 }
 
 guint32
-nm_ip4_config_get_nis_server (const NMIP4Config *config, guint i)
+nm_ip4_config_get_nis_server (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_array_index (priv->nis, guint32, i);
 }
 
 void
-nm_ip4_config_set_nis_domain (NMIP4Config *config, const char *domain)
+nm_ip4_config_set_nis_domain (NMIP4Config *self, const char *domain)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_free (priv->nis_domain);
 	priv->nis_domain = g_strdup (domain);
 }
 
 const char *
-nm_ip4_config_get_nis_domain (const NMIP4Config *config)
+nm_ip4_config_get_nis_domain (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->nis_domain;
 }
@@ -2069,20 +2492,20 @@ nm_ip4_config_get_nis_domain (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_wins (NMIP4Config *config)
+nm_ip4_config_reset_wins (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->wins->len != 0) {
 		g_array_set_size (priv->wins, 0);
-		_notify (config, PROP_WINS_SERVERS);
+		_notify (self, PROP_WINS_SERVERS);
 	}
 }
 
 void
-nm_ip4_config_add_wins (NMIP4Config *config, guint32 wins)
+nm_ip4_config_add_wins (NMIP4Config *self, guint32 wins)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (wins != 0);
@@ -2092,32 +2515,32 @@ nm_ip4_config_add_wins (NMIP4Config *config, guint32 wins)
 			return;
 
 	g_array_append_val (priv->wins, wins);
-	_notify (config, PROP_WINS_SERVERS);
+	_notify (self, PROP_WINS_SERVERS);
 }
 
 void
-nm_ip4_config_del_wins (NMIP4Config *config, guint i)
+nm_ip4_config_del_wins (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->wins->len);
 
 	g_array_remove_index (priv->wins, i);
-	_notify (config, PROP_WINS_SERVERS);
+	_notify (self, PROP_WINS_SERVERS);
 }
 
 guint
-nm_ip4_config_get_num_wins (const NMIP4Config *config)
+nm_ip4_config_get_num_wins (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->wins->len;
 }
 
 guint32
-nm_ip4_config_get_wins (const NMIP4Config *config, guint i)
+nm_ip4_config_get_wins (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_array_index (priv->wins, guint32, i);
 }
@@ -2125,9 +2548,9 @@ nm_ip4_config_get_wins (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu, NMIPConfigSource source)
+nm_ip4_config_set_mtu (NMIP4Config *self, guint32 mtu, NMIPConfigSource source)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (!mtu)
 		source = NM_IP_CONFIG_SOURCE_UNKNOWN;
@@ -2137,17 +2560,17 @@ nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu, NMIPConfigSource source
 }
 
 guint32
-nm_ip4_config_get_mtu (const NMIP4Config *config)
+nm_ip4_config_get_mtu (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->mtu;
 }
 
 NMIPConfigSource
-nm_ip4_config_get_mtu_source (const NMIP4Config *config)
+nm_ip4_config_get_mtu_source (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->mtu_source;
 }
@@ -2155,23 +2578,102 @@ nm_ip4_config_get_mtu_source (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_metered (NMIP4Config *config, gboolean metered)
+nm_ip4_config_set_metered (NMIP4Config *self, gboolean metered)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	priv->metered = metered;
 }
 
 gboolean
-nm_ip4_config_get_metered (const NMIP4Config *config)
+nm_ip4_config_get_metered (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->metered;
 }
 
 /*****************************************************************************/
 
+const NMPObject *
+nm_ip4_config_nmpobj_lookup (const NMIP4Config *self, const NMPObject *needle)
+{
+	const NMIP4ConfigPrivate *priv;
+	const NMDedupMultiIdxType *idx_type;
+
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), NULL);
+
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	switch (NMP_OBJECT_GET_TYPE (needle)) {
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+		idx_type = &priv->idx_ip4_addresses;
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		idx_type = &priv->idx_ip4_routes;
+		break;
+	default:
+		g_return_val_if_reached (NULL);
+	}
+
+	return nm_dedup_multi_entry_get_obj (nm_dedup_multi_index_lookup_obj (priv->multi_idx,
+	                                                                      idx_type,
+	                                                                      needle));
+}
+
+gboolean
+nm_ip4_config_nmpobj_remove (NMIP4Config *self,
+                             const NMPObject *needle)
+{
+	NMIP4ConfigPrivate *priv;
+	NMDedupMultiIdxType *idx_type;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	guint n;
+
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), FALSE);
+
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	switch (NMP_OBJECT_GET_TYPE (needle)) {
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+		idx_type = &priv->idx_ip4_addresses;
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		idx_type = &priv->idx_ip4_routes;
+		break;
+	default:
+		g_return_val_if_reached (FALSE);
+	}
+
+	n = nm_dedup_multi_index_remove_obj (priv->multi_idx,
+	                                     idx_type,
+	                                     needle,
+	                                     (gconstpointer *) &obj_old);
+	if (n != 1) {
+		nm_assert (n == 0);
+		return FALSE;
+	}
+
+	nm_assert (NMP_OBJECT_GET_TYPE (obj_old) == NMP_OBJECT_GET_TYPE (needle));
+
+	switch (NMP_OBJECT_GET_TYPE (obj_old)) {
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+		_notify_addresses (self);
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		if (priv->best_default_route == obj_old) {
+			if (_nm_ip_config_best_default_route_set (&priv->best_default_route,
+			                                          _nm_ip4_config_best_default_route_find (self)))
+				_notify (self, PROP_GATEWAY);
+		}
+		_notify_routes (self);
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 static inline void
 hash_u32 (GChecksum *sum, guint32 n)
 {
@@ -2179,60 +2681,57 @@ hash_u32 (GChecksum *sum, guint32 n)
 }
 
 void
-nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only)
+nm_ip4_config_hash (const NMIP4Config *self, GChecksum *sum, gboolean dns_only)
 {
 	guint i;
 	const char *s;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
 
-	g_return_if_fail (config);
+	g_return_if_fail (self);
 	g_return_if_fail (sum);
 
 	if (!dns_only) {
-		hash_u32 (sum, nm_ip4_config_has_gateway (config));
-		hash_u32 (sum, nm_ip4_config_get_gateway (config));
-
-		for (i = 0; i < nm_ip4_config_get_num_addresses (config); i++) {
-			const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) {
 			hash_u32 (sum, address->address);
 			hash_u32 (sum, address->plen);
-			hash_u32 (sum, address->peer_address & nm_utils_ip4_prefix_to_netmask (address->plen));
+			hash_u32 (sum, address->peer_address & _nm_utils_ip4_prefix_to_netmask (address->plen));
 		}
 
-		for (i = 0; i < nm_ip4_config_get_num_routes (config); i++) {
-			const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
-
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
 			hash_u32 (sum, route->network);
 			hash_u32 (sum, route->plen);
 			hash_u32 (sum, route->gateway);
 			hash_u32 (sum, route->metric);
 		}
 
-		for (i = 0; i < nm_ip4_config_get_num_nis_servers (config); i++)
-			hash_u32 (sum, nm_ip4_config_get_nis_server (config, i));
+		for (i = 0; i < nm_ip4_config_get_num_nis_servers (self); i++)
+			hash_u32 (sum, nm_ip4_config_get_nis_server (self, i));
 
-		s = nm_ip4_config_get_nis_domain (config);
+		s = nm_ip4_config_get_nis_domain (self);
 		if (s)
 			g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_nameservers (config); i++)
-		hash_u32 (sum, nm_ip4_config_get_nameserver (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_nameservers (self); i++)
+		hash_u32 (sum, nm_ip4_config_get_nameserver (self, i));
 
-	for (i = 0; i < nm_ip4_config_get_num_wins (config); i++)
-		hash_u32 (sum, nm_ip4_config_get_wins (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_wins (self); i++)
+		hash_u32 (sum, nm_ip4_config_get_wins (self, i));
 
-	for (i = 0; i < nm_ip4_config_get_num_domains (config); i++) {
-		s = nm_ip4_config_get_domain (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_domains (self); i++) {
+		s = nm_ip4_config_get_domain (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_searches (config); i++) {
-		s = nm_ip4_config_get_search (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_searches (self); i++) {
+		s = nm_ip4_config_get_search (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_dns_options (config); i++) {
-		s = nm_ip4_config_get_dns_option (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_dns_options (self); i++) {
+		s = nm_ip4_config_get_dns_option (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 }
@@ -2283,8 +2782,12 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMIP4Config *config = NM_IP4_CONFIG (object);
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4Config *self = NM_IP4_CONFIG (object);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Route *route;
+	GVariantBuilder builder_data, builder_legacy;
 
 	switch (prop_id) {
 	case PROP_IFINDEX:
@@ -2292,25 +2795,32 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_ADDRESS_DATA:
 	case PROP_ADDRESSES:
-		{
-			GVariantBuilder array_builder, addr_builder;
-			gs_unref_array GArray *new = NULL;
-			guint naddr, i;
+		nm_assert (!!priv->address_data_variant == !!priv->addresses_variant);
 
-			g_return_if_fail (!!priv->address_data_variant == !!priv->addresses_variant);
+		if (priv->address_data_variant)
+			goto out_addresses_cached;
 
-			if (priv->address_data_variant)
-				goto return_cached;
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
+		g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("aau"));
 
-			naddr = nm_ip4_config_get_num_addresses (config);
-			new = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformIP4Address), naddr);
-			g_array_append_vals (new, priv->addresses->data, priv->addresses->len);
-			g_array_sort (new, _addresses_sort_cmp);
+		head_entry = nm_ip4_config_lookup_addresses (self);
+		if (head_entry) {
+			gs_free const NMPObject **addresses = NULL;
+			guint naddr, i;
+
+			addresses = (const NMPObject **) nm_dedup_multi_objs_to_array_head (head_entry, NULL, NULL, &naddr);
+			nm_assert (addresses && naddr);
+
+			g_qsort_with_data (addresses,
+			                   naddr,
+			                   sizeof (addresses[0]),
+			                   _addresses_sort_cmp,
+			                   NULL);
 
 			/* Build address data variant */
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
 			for (i = 0; i < naddr; i++) {
-				const NMPlatformIP4Address *address = &g_array_index (new, NMPlatformIP4Address, i);
+				GVariantBuilder addr_builder;
+				const NMPlatformIP4Address *address = NMP_OBJECT_CAST_IP4_ADDRESS (addresses[i]);
 
 				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
 				g_variant_builder_add (&addr_builder, "{sv}",
@@ -2331,98 +2841,105 @@ get_property (GObject *object, guint prop_id,
 					                       g_variant_new_string (address->label));
 				}
 
-				g_variant_builder_add (&array_builder, "a{sv}", &addr_builder);
+				g_variant_builder_add (&builder_data, "a{sv}", &addr_builder);
+
+				{
+					const guint32 dbus_addr[3] = {
+					    address->address,
+					    address->plen,
+					    (   i == 0
+					     && priv->best_default_route)
+					       ? NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway
+					       : (guint32) 0,
+					};
+
+					g_variant_builder_add (&builder_legacy, "@au",
+					                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
+					                                                  dbus_addr, 3, sizeof (guint32)));
+				}
 			}
-			priv->address_data_variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
-
-			/* Build addresses variant */
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aau"));
-			for (i = 0; i < naddr; i++) {
-				const NMPlatformIP4Address *address = &g_array_index (new, NMPlatformIP4Address, i);
-				guint32 dbus_addr[3];
+		}
 
-				dbus_addr[0] = address->address;
-				dbus_addr[1] = address->plen;
-				dbus_addr[2] = i == 0 ? priv->gateway : 0;
+		priv->address_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
+		priv->addresses_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
 
-				g_variant_builder_add (&array_builder, "@au",
-				                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
-				                                                  dbus_addr, 3, sizeof (guint32)));
-			}
-			priv->addresses_variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
-
-return_cached:
-			g_value_set_variant (value,
-			                     prop_id == PROP_ADDRESS_DATA ?
-			                     priv->address_data_variant :
-			                     priv->addresses_variant);
-		}
+out_addresses_cached:
+		g_value_set_variant (value,
+		                     prop_id == PROP_ADDRESS_DATA ?
+		                     priv->address_data_variant :
+		                     priv->addresses_variant);
 		break;
 	case PROP_ROUTE_DATA:
-		{
-			GVariantBuilder array_builder, route_builder;
-			guint nroutes = nm_ip4_config_get_num_routes (config);
-			guint i;
+	case PROP_ROUTES:
+		nm_assert (!!priv->route_data_variant == !!priv->routes_variant);
 
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
-			for (i = 0; i < nroutes; i++) {
-				const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
+		if (priv->route_data_variant)
+			goto out_routes_cached;
 
-				g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
-				g_variant_builder_add (&route_builder, "{sv}",
-				                       "dest",
-				                       g_variant_new_string (nm_utils_inet4_ntop (route->network, NULL)));
-				g_variant_builder_add (&route_builder, "{sv}",
-				                       "prefix",
-				                       g_variant_new_uint32 (route->plen));
-				if (route->gateway) {
-					g_variant_builder_add (&route_builder, "{sv}",
-					                       "next-hop",
-					                       g_variant_new_string (nm_utils_inet4_ntop (route->gateway, NULL)));
-				}
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
+		g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("aau"));
+
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
+			GVariantBuilder route_builder;
+
+			nm_assert (_route_valid (route));
+
+			g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "dest",
+			                       g_variant_new_string (nm_utils_inet4_ntop (route->network, NULL)));
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "prefix",
+			                       g_variant_new_uint32 (route->plen));
+			if (route->gateway) {
 				g_variant_builder_add (&route_builder, "{sv}",
-				                       "metric",
-				                       g_variant_new_uint32 (route->metric));
+				                       "next-hop",
+				                       g_variant_new_string (nm_utils_inet4_ntop (route->gateway, NULL)));
+			}
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "metric",
+			                       g_variant_new_uint32 (route->metric));
 
-				g_variant_builder_add (&array_builder, "a{sv}", &route_builder);
+			if (!nm_platform_route_table_is_main (route->table_coerced)) {
+				g_variant_builder_add (&route_builder, "{sv}",
+				                       "table",
+				                       g_variant_new_uint32 (nm_platform_route_table_uncoerce (route->table_coerced, TRUE)));
 			}
 
-			g_value_take_variant (value, g_variant_builder_end (&array_builder));
-		}
-		break;
-	case PROP_ROUTES:
-		{
-			GVariantBuilder array_builder;
-			guint nroutes = nm_ip4_config_get_num_routes (config);
-			guint i;
-
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aau"));
-			for (i = 0; i < nroutes; i++) {
-				const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
-				guint32 dbus_route[4];
-
-				/* legacy versions of nm_ip4_route_set_prefix() in libnm-util assert that the
-				 * plen is positive. Skip the default routes not to break older clients. */
-				if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
-					continue;
+			g_variant_builder_add (&builder_data, "a{sv}", &route_builder);
 
-				dbus_route[0] = route->network;
-				dbus_route[1] = route->plen;
-				dbus_route[2] = route->gateway;
-				dbus_route[3] = route->metric;
+			/* legacy versions of nm_ip4_route_set_prefix() in libnm-util assert that the
+			 * plen is positive. Skip the default routes not to break older clients. */
+			if (   nm_platform_route_table_is_main (route->table_coerced)
+			    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
+				const guint32 dbus_route[4] = {
+				    route->network,
+				    route->plen,
+				    route->gateway,
+				    route->metric,
+				};
 
-				g_variant_builder_add (&array_builder, "@au",
+				g_variant_builder_add (&builder_legacy, "@au",
 				                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
 				                                                  dbus_route, 4, sizeof (guint32)));
 			}
-
-			g_value_take_variant (value, g_variant_builder_end (&array_builder));
 		}
+
+		priv->route_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
+		priv->routes_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
+
+out_routes_cached:
+		g_value_set_variant (value,
+		                     prop_id == PROP_ROUTE_DATA ?
+		                     priv->route_data_variant :
+		                     priv->routes_variant);
 		break;
 	case PROP_GATEWAY:
-		if (priv->has_gateway)
-			g_value_set_string (value, nm_utils_inet4_ntop (priv->gateway, NULL));
-		else
+		if (priv->best_default_route) {
+			g_value_set_string (value,
+			                    nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
+			                                         NULL));
+		} else
 			g_value_set_string (value, NULL);
 		break;
 	case PROP_NAMESERVERS:
@@ -2467,6 +2984,13 @@ set_property (GObject *object,
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	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_IFINDEX:
 		/* construct-only */
 		priv->ifindex = g_value_get_int (value);
@@ -2480,26 +3004,29 @@ set_property (GObject *object,
 /*****************************************************************************/
 
 static void
-nm_ip4_config_init (NMIP4Config *config)
+nm_ip4_config_init (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_addresses,
+	                                        NMP_OBJECT_TYPE_IP4_ADDRESS);
+	nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_routes,
+	                                        NMP_OBJECT_TYPE_IP4_ROUTE);
 
-	priv->addresses = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Address));
-	priv->routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
 	priv->nameservers = g_array_new (FALSE, FALSE, sizeof (guint32));
 	priv->domains = g_ptr_array_new_with_free_func (g_free);
 	priv->searches = g_ptr_array_new_with_free_func (g_free);
 	priv->dns_options = g_ptr_array_new_with_free_func (g_free);
 	priv->nis = g_array_new (FALSE, TRUE, sizeof (guint32));
 	priv->wins = g_array_new (FALSE, TRUE, sizeof (guint32));
-	priv->route_metric = -1;
 }
 
 NMIP4Config *
-nm_ip4_config_new (int ifindex)
+nm_ip4_config_new (NMDedupMultiIndex *multi_idx, int ifindex)
 {
 	g_return_val_if_fail (ifindex >= -1, NULL);
 	return (NMIP4Config *) g_object_new (NM_TYPE_IP4_CONFIG,
+	                                     NM_IP4_CONFIG_MULTI_IDX, multi_idx,
 	                                     NM_IP4_CONFIG_IFINDEX, ifindex,
 	                                     NULL);
 }
@@ -2510,10 +3037,16 @@ finalize (GObject *object)
 	NMIP4Config *self = NM_IP4_CONFIG (object);
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
+	nm_clear_nmp_object (&priv->best_default_route);
+
+	nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip4_addresses);
+	nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip4_routes);
+
 	nm_clear_g_variant (&priv->address_data_variant);
 	nm_clear_g_variant (&priv->addresses_variant);
-	g_array_unref (priv->addresses);
-	g_array_unref (priv->routes);
+	nm_clear_g_variant (&priv->route_data_variant);
+	nm_clear_g_variant (&priv->routes_variant);
+
 	g_array_unref (priv->nameservers);
 	g_ptr_array_unref (priv->domains);
 	g_ptr_array_unref (priv->searches);
@@ -2523,6 +3056,8 @@ finalize (GObject *object)
 	g_array_unref (priv->wins);
 
 	G_OBJECT_CLASS (nm_ip4_config_parent_class)->finalize (object);
+
+	nm_dedup_multi_index_unref (priv->multi_idx);
 }
 
 static void
@@ -2537,6 +3072,11 @@ nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 	object_class->set_property = set_property;
 	object_class->finalize = finalize;
 
+	obj_properties[PROP_MULTI_IDX] =
+	    g_param_spec_pointer (NM_IP4_CONFIG_MULTI_IDX, "", "",
+	                            G_PARAM_WRITABLE
+	                          | G_PARAM_CONSTRUCT_ONLY
+	                          | G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_IFINDEX] =
 	    g_param_spec_int (NM_IP4_CONFIG_IFINDEX, "", "",
 	                      -1, G_MAXINT, -1,