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.c1912
1 files changed, 1356 insertions, 556 deletions
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 0722981e..e27c0436 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -1,5 +1,5 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
+/* NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -15,27 +15,22 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2008 Red Hat, Inc.
+ * Copyright (C) 2005 - 2014 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include <glib.h>
-#include <stdio.h>
 #include <string.h>
-#include "nm-ip4-config.h"
-#include "nm-dbus-manager.h"
-#include "NetworkManager.h"
-#include "NetworkManagerUtils.h"
-#include "nm-setting-ip4-config.h"
-#include "nm-utils.h"
 
-#include <netlink/route/addr.h>
-#include <netlink/utils.h>
-#include <netinet/in.h>
+#include "nm-ip4-config.h"
 
-#include "nm-ip4-config-glue.h"
+#include "libgsystem.h"
+#include "nm-platform.h"
+#include "nm-utils.h"
+#include "nm-dbus-manager.h"
 #include "nm-dbus-glib-types.h"
-
+#include "nm-ip4-config-glue.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils-private.h"
 
 G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, G_TYPE_OBJECT)
 
@@ -44,51 +39,38 @@ G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, G_TYPE_OBJECT)
 typedef struct {
 	char *path;
 
-	GSList *addresses;
-	guint32	ptp_address;
-
-	guint32	mtu;	/* Maximum Transmission Unit of the interface */
-	guint32	mss;	/* Maximum Segment Size of the route */
-
+	gboolean never_default;
+	guint32 gateway;
+	GArray *addresses;
+	GArray *routes;
 	GArray *nameservers;
 	GPtrArray *domains;
 	GPtrArray *searches;
-
-	GArray *wins;
-
+	guint32 mss;
 	GArray *nis;
-	char * nis_domain;
-
-	GSList *routes;
-
-	gboolean never_default;
+	char *nis_domain;
+	GArray *wins;
+	guint32 mtu;
 } NMIP4ConfigPrivate;
 
+/* internal guint32 are assigned to gobject properties of type uint. Ensure, that uint is large enough */
+G_STATIC_ASSERT (sizeof (uint) >= sizeof (guint32));
+G_STATIC_ASSERT (G_MAXUINT >= 0xFFFFFFFF);
 
 enum {
 	PROP_0,
+	PROP_GATEWAY,
 	PROP_ADDRESSES,
+	PROP_ROUTES,
 	PROP_NAMESERVERS,
 	PROP_DOMAINS,
-	PROP_ROUTES,
+	PROP_SEARCHES,
 	PROP_WINS_SERVERS,
 
 	LAST_PROP
 };
-
-
-static struct nl_addr *
-nm_utils_ip4_addr_to_nl_addr (guint32 ip4_addr)
-{
-	struct nl_addr * nla = NULL;
-
-	if (!(nla = nl_addr_alloc (sizeof (in_addr_t))))
-		return NULL;
-	nl_addr_set_family (nla, AF_INET);
-	nl_addr_set_binary_addr (nla, &ip4_addr, sizeof (guint32));
-
-	return nla;
-}
+static GParamSpec *obj_properties[LAST_PROP] = { NULL, };
+#define _NOTIFY(config, prop)    G_STMT_START { g_object_notify_by_pspec (G_OBJECT (config), obj_properties[prop]); } G_STMT_END
 
 
 NMIP4Config *
@@ -97,722 +79,1447 @@ nm_ip4_config_new (void)
 	return (NMIP4Config *) g_object_new (NM_TYPE_IP4_CONFIG, NULL);
 }
 
+
 void
 nm_ip4_config_export (NMIP4Config *config)
 {
-	NMIP4ConfigPrivate *priv;
-	NMDBusManager *dbus_mgr;
-	DBusGConnection *connection;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 	static guint32 counter = 0;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	g_return_if_fail (priv->path == NULL);
-
-	dbus_mgr = nm_dbus_manager_get ();
-	connection = nm_dbus_manager_get_connection (dbus_mgr);
-	priv->path = g_strdup_printf (NM_DBUS_PATH "/IP4Config/%d", counter++);
-
-	dbus_g_connection_register_g_object (connection, priv->path, G_OBJECT (config));
-	g_object_unref (dbus_mgr);
+	if (!priv->path) {
+		priv->path = g_strdup_printf (NM_DBUS_PATH "/IP4Config/%d", counter++);
+		nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, config);
+	}
 }
 
 const char *
-nm_ip4_config_get_dbus_path (NMIP4Config *config)
+nm_ip4_config_get_dbus_path (const NMIP4Config *config)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), NULL);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->path;
+	return priv->path;
 }
 
-void
-nm_ip4_config_take_address (NMIP4Config *config, NMIP4Address *address)
+static gboolean
+same_prefix (guint32 address1, guint32 address2, int plen)
 {
-	NMIP4ConfigPrivate *priv;
+	guint32 masked1 = ntohl (address1) >> (32 - plen);
+	guint32 masked2 = ntohl (address2) >> (32 - plen);
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (address != NULL);
-
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	priv->addresses = g_slist_append (priv->addresses, address);
+	return masked1 == masked2;
 }
 
-void
-nm_ip4_config_add_address (NMIP4Config *config,
-                           NMIP4Address *address)
+/******************************************************************/
+
+/**
+ * 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,
+                                   const char *rc_contents)
 {
-	NMIP4ConfigPrivate *priv;
-	GSList *iter;
+	GPtrArray *read_ns;
+	guint i, j;
+	gboolean changed = FALSE;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (address != NULL);
+	g_return_val_if_fail (nameservers != NULL, FALSE);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	for (iter = priv->addresses; iter; iter = g_slist_next (iter)) {
-		if (nm_ip4_address_compare ((NMIP4Address *) iter->data, address))
-			return;
-	}
+	read_ns = nm_utils_read_resolv_conf_nameservers (rc_contents);
+	if (!read_ns)
+		return FALSE;
 
-	priv->addresses = g_slist_append (priv->addresses, nm_ip4_address_dup (address));
-}
+	for (i = 0; i < read_ns->len; i++) {
+		const char *s = g_ptr_array_index (read_ns, i);
+		guint32 ns = 0;
 
-void
-nm_ip4_config_replace_address (NMIP4Config *config,
-                               guint i,
-                               NMIP4Address *new_address)
-{
-	NMIP4ConfigPrivate *priv;
-	GSList *old;
+		if (!inet_pton (AF_INET, s, (void *) &ns) || !ns)
+			continue;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+		/* Ignore duplicates */
+		for (j = 0; j < nameservers->len; j++) {
+			if (g_array_index (nameservers, guint32, j) == ns)
+				break;
+		}
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	old = g_slist_nth (priv->addresses, i);
-	g_return_if_fail (old != NULL);
-	nm_ip4_address_unref ((NMIP4Address *) old->data);
+		if (j == nameservers->len) {
+			g_array_append_val (nameservers, ns);
+			changed = TRUE;
+		}
+	}
 
-	old->data = nm_ip4_address_dup (new_address);
+	g_ptr_array_unref (read_ns);
+	return changed;
 }
 
-NMIP4Address *nm_ip4_config_get_address (NMIP4Config *config, guint i)
+static gboolean
+addresses_are_duplicate (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b, gboolean consider_plen)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), NULL);
-
-	return (NMIP4Address *) g_slist_nth_data (NM_IP4_CONFIG_GET_PRIVATE (config)->addresses, i);
+	return a->address == b->address && (!consider_plen || a->plen == b->plen);
 }
 
-guint32 nm_ip4_config_get_num_addresses (NMIP4Config *config)
+static gboolean
+routes_are_duplicate (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, gboolean consider_gateway_and_metric)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
-
-	return g_slist_length (NM_IP4_CONFIG_GET_PRIVATE (config)->addresses);
+	return a->network == b->network && a->plen == b->plen &&
+	       (!consider_gateway_and_metric || (a->gateway == b->gateway && a->metric == b->metric));
 }
 
-guint32 nm_ip4_config_get_ptp_address (NMIP4Config *config)
+NMIP4Config *
+nm_ip4_config_capture (int ifindex, gboolean capture_resolv_conf)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	NMIP4Config *config;
+	NMIP4ConfigPrivate *priv;
+	guint i;
+	guint lowest_metric = G_MAXUINT;
+	guint32 old_gateway = 0;
+	gboolean has_gateway = FALSE;
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->ptp_address;
-}
+	/* Slaves have no IP configuration */
+	if (nm_platform_link_get_master (ifindex) > 0)
+		return NULL;
 
-void nm_ip4_config_set_ptp_address (NMIP4Config *config, guint32 ptp_addr)
-{
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	config = nm_ip4_config_new ();
+	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 (ifindex);
+	priv->routes = nm_platform_ip4_route_get_all (ifindex, TRUE);
+
+	/* Extract gateway from default route */
+	old_gateway = priv->gateway;
+	for (i = 0; i < priv->routes->len; i++) {
+		const NMPlatformIP4Route *route = &g_array_index (priv->routes, NMPlatformIP4Route, i);
+
+		if (route->network == 0) {
+			if (route->metric < lowest_metric) {
+				priv->gateway = route->gateway;
+				lowest_metric = route->metric;
+			}
+			has_gateway = TRUE;
+			/* Remove the default route from the list */
+			g_array_remove_index (priv->routes, i);
+			i--;
+		}
+	}
+
+	/* If there is a host route to the gateway, ignore that route.  It is
+	 * automatically added by NetworkManager when needed.
+	 */
+	if (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--;
+			}
+		}
+	}
 
-	NM_IP4_CONFIG_GET_PRIVATE (config)->ptp_address = ptp_addr;
+	/* If the interface has the default route, and has IPv4 addresses, capture
+	 * nameservers from /etc/resolv.conf.
+	 */
+	if (priv->addresses->len && has_gateway && capture_resolv_conf) {
+		if (nm_ip4_config_capture_resolv_conf (priv->nameservers, NULL))
+			_NOTIFY (config, PROP_NAMESERVERS);
+	}
+
+	/* actually, nobody should be connected to the signal, just to be sure, notify */
+	_NOTIFY (config, PROP_ADDRESSES);
+	_NOTIFY (config, PROP_ROUTES);
+	if (priv->gateway != old_gateway)
+		_NOTIFY (config, PROP_GATEWAY);
+
+	return config;
 }
 
-void nm_ip4_config_add_nameserver (NMIP4Config *config, guint32 nameserver)
+gboolean
+nm_ip4_config_commit (const NMIP4Config *config, int ifindex)
 {
-	NMIP4ConfigPrivate *priv;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	int mtu = nm_ip4_config_get_mtu (config);
 	int i;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (nameserver > 0);
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (config != NULL, FALSE);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	for (i = 0; i < priv->nameservers->len; i++) {
-		guint32 s = g_array_index (priv->nameservers, guint32, i);
+	/* Addresses */
+	nm_platform_ip4_address_sync (ifindex, priv->addresses);
 
-		/* No dupes */
-		if (nameserver == s)
-			return;
+	/* Routes */
+	{
+		int count = nm_ip4_config_get_num_routes (config);
+		GArray *routes = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformIP4Route), count);
+		NMPlatformIP4Route route;
+		gboolean success;
+
+		for (i = 0; i < count; i++) {
+			memcpy (&route, nm_ip4_config_get_route (config, i), sizeof (route));
+
+			/* Don't add the route if it's more specific than one of the subnets
+			 * the device already has an IP address on.
+			 */
+			if (   route.gateway == 0
+			    && nm_ip4_config_destination_is_direct (config, route.network, route.plen))
+				continue;
+
+			/* Don't add the default route if the connection
+			 * is never supposed to be the default connection.
+			 */
+			if (nm_ip4_config_get_never_default (config) && route.network == 0)
+				continue;
+
+			g_array_append_val (routes, route);
+		}
+
+		success = nm_platform_ip4_route_sync (ifindex, routes);
+		g_array_unref (routes);
+		if (!success)
+			return FALSE;
 	}
 
-	g_array_append_val (priv->nameservers, nameserver);
+	/* MTU */
+	if (mtu && mtu != nm_platform_link_get_mtu (ifindex))
+		nm_platform_link_set_mtu (ifindex, mtu);
+
+	return TRUE;
 }
 
-guint32 nm_ip4_config_get_nameserver (NMIP4Config *config, guint i)
+void
+nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIP4Config *setting, int default_route_metric)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	guint naddresses, nroutes, nnameservers, nsearches;
+	int i;
 
-	return g_array_index (NM_IP4_CONFIG_GET_PRIVATE (config)->nameservers, guint32, i);
-}
+	if (!setting)
+		return;
 
-guint32 nm_ip4_config_get_num_nameservers (NMIP4Config *config)
-{
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	g_object_freeze_notify (G_OBJECT (config));
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->nameservers->len;
-}
+	naddresses = nm_setting_ip4_config_get_num_addresses (setting);
+	nroutes = nm_setting_ip4_config_get_num_routes (setting);
+	nnameservers = nm_setting_ip4_config_get_num_dns (setting);
+	nsearches = nm_setting_ip4_config_get_num_dns_searches (setting);
 
-void nm_ip4_config_reset_nameservers (NMIP4Config *config)
-{
-	NMIP4ConfigPrivate *priv;
+	/* Gateway */
+	if (nm_setting_ip4_config_get_never_default (setting))
+		nm_ip4_config_set_never_default (config, TRUE);
+	else if (nm_setting_ip4_config_get_ignore_auto_routes (setting))
+		nm_ip4_config_set_never_default (config, FALSE);
+	for (i = 0; i < naddresses; i++) {
+		guint32 gateway = nm_ip4_address_get_gateway (nm_setting_ip4_config_get_address (setting, i));
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+		if (gateway) {
+			nm_ip4_config_set_gateway (config, gateway);
+			break;
+		}
+	}
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	if (priv->nameservers->len)
-		g_array_remove_range (priv->nameservers, 0, priv->nameservers->len);
+	/* Addresses */
+	for (i = 0; i < naddresses; i++) {
+		NMIP4Address *s_addr = nm_setting_ip4_config_get_address (setting, i);
+		const char *label = NM_UTILS_PRIVATE_CALL (nm_setting_ip4_config_get_address_label (setting, i));
+		NMPlatformIP4Address address;
+
+		memset (&address, 0, sizeof (address));
+		address.address = nm_ip4_address_get_address (s_addr);
+		address.plen = nm_ip4_address_get_prefix (s_addr);
+		address.lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
+		address.preferred = NM_PLATFORM_LIFETIME_PERMANENT;
+		address.source = NM_PLATFORM_SOURCE_USER;
+		if (label)
+			g_strlcpy (address.label, label, sizeof (address.label));
+
+		nm_ip4_config_add_address (config, &address);
+	}
+
+	/* Routes */
+	if (nm_setting_ip4_config_get_ignore_auto_routes (setting))
+		nm_ip4_config_reset_routes (config);
+	for (i = 0; i < nroutes; i++) {
+		NMIP4Route *s_route = nm_setting_ip4_config_get_route (setting, i);
+		NMPlatformIP4Route route;
+
+		memset (&route, 0, sizeof (route));
+		route.network = nm_ip4_route_get_dest (s_route);
+		route.plen = nm_ip4_route_get_prefix (s_route);
+		route.gateway = nm_ip4_route_get_next_hop (s_route);
+		route.metric = nm_ip4_route_get_metric (s_route);
+		if (!route.metric)
+			route.metric = default_route_metric;
+		route.source = NM_PLATFORM_SOURCE_USER;
+
+		nm_ip4_config_add_route (config, &route);
+	}
+
+	/* DNS */
+	if (nm_setting_ip4_config_get_ignore_auto_dns (setting)) {
+		nm_ip4_config_reset_nameservers (config);
+		nm_ip4_config_reset_domains (config);
+		nm_ip4_config_reset_searches (config);
+	}
+	for (i = 0; i < nnameservers; i++)
+		nm_ip4_config_add_nameserver (config, nm_setting_ip4_config_get_dns (setting, i));
+	for (i = 0; i < nsearches; i++)
+		nm_ip4_config_add_search (config, nm_setting_ip4_config_get_dns_search (setting, i));
+
+	g_object_thaw_notify (G_OBJECT (config));
 }
 
-void nm_ip4_config_add_wins (NMIP4Config *config, guint32 wins)
+NMSetting *
+nm_ip4_config_create_setting (const NMIP4Config *config)
 {
-	NMIP4ConfigPrivate *priv;
+	NMSettingIP4Config *s_ip4;
+	guint32 gateway;
+	guint naddresses, nroutes, nnameservers, nsearches;
+	const char *method = NULL;
 	int i;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (wins > 0);
+	s_ip4 = NM_SETTING_IP4_CONFIG (nm_setting_ip4_config_new ());
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	for (i = 0; i < priv->wins->len; i++) {
-		guint32 s = g_array_index (priv->wins, guint32, i);
+	if (!config) {
+		g_object_set (s_ip4,
+		              NM_SETTING_IP4_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+		              NULL);
+		return NM_SETTING (s_ip4);
+	}
 
-		/* No dupes */
-		if (wins == s)
-			return;
+	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);
+
+	/* Addresses */
+	for (i = 0; i < naddresses; i++) {
+		const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
+		NMIP4Address *s_addr;
+
+		/* Detect dynamic address */
+		if (address->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
+			method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+			continue;
+		}
+
+		/* Static address found. */
+		if (!method)
+			method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
+
+		s_addr = nm_ip4_address_new ();
+
+		nm_ip4_address_set_address (s_addr, address->address);
+		nm_ip4_address_set_prefix (s_addr, address->plen);
+		/* For backwards compatibility, attach the gateway to an address if it's
+		 * in the same subnet.
+		 */
+		if (same_prefix (address->address, gateway, address->plen))
+			nm_ip4_address_set_gateway (s_addr, gateway);
+
+		if (*address->label)
+			NM_UTILS_PRIVATE_CALL (nm_setting_ip4_config_add_address_with_label (s_ip4, s_addr, address->label));
+		else
+			nm_setting_ip4_config_add_address (s_ip4, s_addr);
+		nm_ip4_address_unref (s_addr);
 	}
 
-	g_array_append_val (priv->wins, wins);
-}
+	/* Use 'disabled' if the method wasn't previously set */
+	if (!method)
+		method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+	g_object_set (s_ip4, NM_SETTING_IP4_CONFIG_METHOD, method, NULL);
 
-guint32 nm_ip4_config_get_wins (NMIP4Config *config, guint i)
-{
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	/* Routes */
+	for (i = 0; i < nroutes; i++) {
+		const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
+		NMIP4Route *s_route;
 
-	return g_array_index (NM_IP4_CONFIG_GET_PRIVATE (config)->wins, guint32, i);
-}
+		/* Ignore default route. */
+		if (!route->plen)
+			continue;
 
-guint32 nm_ip4_config_get_num_wins (NMIP4Config *config)
-{
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+		/* Ignore routes provided by external sources */
+		if (route->source != NM_PLATFORM_SOURCE_USER)
+			continue;
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->wins->len;
-}
+		s_route = nm_ip4_route_new ();
+		nm_ip4_route_set_dest (s_route, route->network);
+		nm_ip4_route_set_prefix (s_route, route->plen);
+		nm_ip4_route_set_next_hop (s_route, route->gateway);
+		nm_ip4_route_set_metric (s_route, route->metric);
 
-void nm_ip4_config_reset_wins (NMIP4Config *config)
-{
-	NMIP4ConfigPrivate *priv;
+		nm_setting_ip4_config_add_route (s_ip4, s_route);
+		nm_ip4_route_unref (s_route);
+	}
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	/* DNS */
+	for (i = 0; i < nnameservers; i++) {
+		guint32 nameserver = nm_ip4_config_get_nameserver (config, i);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	if (priv->wins->len)
-		g_array_remove_range (priv->wins, 0, priv->wins->len);
+		nm_setting_ip4_config_add_dns (s_ip4, nameserver);
+	}
+	for (i = 0; i < nsearches; i++) {
+		const char *search = nm_ip4_config_get_search (config, i);
+
+		nm_setting_ip4_config_add_dns_search (s_ip4, search);
+	}
+
+	return NM_SETTING (s_ip4);
 }
 
+/******************************************************************/
+
 void
-nm_ip4_config_take_route (NMIP4Config *config, NMIP4Route *route)
+nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src)
 {
-	NMIP4ConfigPrivate *priv;
+	guint32 i;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (route != NULL);
+	g_return_if_fail (src != NULL);
+	g_return_if_fail (dst != NULL);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	priv->routes = g_slist_append (priv->routes, route);
+	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));
+
+	/* nameservers */
+	for (i = 0; i < nm_ip4_config_get_num_nameservers (src); i++)
+		nm_ip4_config_add_nameserver (dst, nm_ip4_config_get_nameserver (src, i));
+
+	/* default gateway */
+	if (!nm_ip4_config_get_gateway (dst))
+		nm_ip4_config_set_gateway (dst, nm_ip4_config_get_gateway (src));
+
+	/* 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));
+
+	/* domains */
+	for (i = 0; i < nm_ip4_config_get_num_domains (src); i++)
+		nm_ip4_config_add_domain (dst, nm_ip4_config_get_domain (src, i));
+
+	/* dns searches */
+	for (i = 0; i < nm_ip4_config_get_num_searches (src); i++)
+		nm_ip4_config_add_search (dst, nm_ip4_config_get_search (src, i));
+
+	/* MSS */
+	if (!nm_ip4_config_get_mss (dst))
+		nm_ip4_config_set_mss (dst, nm_ip4_config_get_mss (src));
+
+	/* MTU */
+	if (!nm_ip4_config_get_mtu (dst))
+		nm_ip4_config_set_mtu (dst, nm_ip4_config_get_mtu (src));
+
+	/* NIS */
+	for (i = 0; i < nm_ip4_config_get_num_nis_servers (src); i++)
+		nm_ip4_config_add_nis_server (dst, nm_ip4_config_get_nis_server (src, i));
+
+	if (nm_ip4_config_get_nis_domain (src))
+		nm_ip4_config_set_nis_domain (dst, nm_ip4_config_get_nis_domain (src));
+
+	/* WINS */
+	for (i = 0; i < nm_ip4_config_get_num_wins (src); i++)
+		nm_ip4_config_add_wins (dst, nm_ip4_config_get_wins (src, i));
+
+	g_object_thaw_notify (G_OBJECT (dst));
 }
 
+/**
+ * nm_ip4_config_subtract:
+ * @dst: config from which to remove everything in @src
+ * @src: config to remove from @dst
+ *
+ * Removes everything in @src from @dst.
+ */
 void
-nm_ip4_config_add_route (NMIP4Config *config, NMIP4Route *route)
+nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 {
-	NMIP4ConfigPrivate *priv;
-	GSList *iter;
+	guint32 i, j;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (route != NULL);
+	g_return_if_fail (src != NULL);
+	g_return_if_fail (dst != NULL);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	for (iter = priv->routes; iter; iter = g_slist_next (iter)) {
-		if (nm_ip4_route_compare ((NMIP4Route *) iter->data, route))
-			return;
+	g_object_freeze_notify (G_OBJECT (dst));
+
+	/* addresses */
+	for (i = 0; i < nm_ip4_config_get_num_addresses (src); i++) {
+		const NMPlatformIP4Address *src_addr = nm_ip4_config_get_address (src, i);
+
+		for (j = 0; j < nm_ip4_config_get_num_addresses (dst); j++) {
+			const NMPlatformIP4Address *dst_addr = nm_ip4_config_get_address (dst, j);
+
+			if (src_addr->address == dst_addr->address &&
+			    src_addr->plen == dst_addr->plen) {
+				nm_ip4_config_del_address (dst, j);
+				break;
+			}
+		}
 	}
 
-	priv->routes = g_slist_append (priv->routes, nm_ip4_route_dup (route));
-}
+	/* nameservers */
+	for (i = 0; i < nm_ip4_config_get_num_nameservers (src); i++) {
+		guint32 src_ns = nm_ip4_config_get_nameserver (src, i);
 
-void
-nm_ip4_config_replace_route (NMIP4Config *config,
-							 guint i,
-							 NMIP4Route *new_route)
-{
-	NMIP4ConfigPrivate *priv;
-	GSList *old;
+		for (j = 0; j < nm_ip4_config_get_num_nameservers (dst); j++) {
+			guint32 dst_ns = nm_ip4_config_get_nameserver (dst, j);
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+			if (dst_ns == src_ns) {
+				nm_ip4_config_del_nameserver (dst, j);
+				break;
+			}
+		}
+	}
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	old = g_slist_nth (priv->routes, i);
-	g_return_if_fail (old != NULL);
-	nm_ip4_route_unref ((NMIP4Route *) old->data);
+	/* default gateway */
+	if (nm_ip4_config_get_gateway (src) == nm_ip4_config_get_gateway (dst))
+		nm_ip4_config_set_gateway (dst, 0);
 
-	old->data = nm_ip4_route_dup (new_route);
-}
+	/* routes */
+	for (i = 0; i < nm_ip4_config_get_num_routes (src); i++) {
+		const NMPlatformIP4Route *src_route = nm_ip4_config_get_route (src, i);
 
-NMIP4Route *
-nm_ip4_config_get_route (NMIP4Config *config, guint i)
-{
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), NULL);
+		for (j = 0; j < nm_ip4_config_get_num_routes (dst); j++) {
+			const NMPlatformIP4Route *dst_route = nm_ip4_config_get_route (dst, j);
+
+			if (src_route->network == dst_route->network && src_route->plen == dst_route->plen) {
+				nm_ip4_config_del_route (dst, j);
+				break;
+			}
+		}
+	}
+
+	/* domains */
+	for (i = 0; i < nm_ip4_config_get_num_domains (src); i++) {
+		const char *src_domain = nm_ip4_config_get_domain (src, i);
+
+		for (j = 0; j < nm_ip4_config_get_num_domains (dst); j++) {
+			const char *dst_domain = nm_ip4_config_get_domain (dst, j);
+
+			if (g_strcmp0 (src_domain, dst_domain) == 0) {
+				nm_ip4_config_del_domain (dst, j);
+				break;
+			}
+		}
+	}
+
+	/* dns searches */
+	for (i = 0; i < nm_ip4_config_get_num_searches (src); i++) {
+		const char *src_search = nm_ip4_config_get_search (src, i);
+
+		for (j = 0; j < nm_ip4_config_get_num_searches (dst); j++) {
+			const char *dst_search = nm_ip4_config_get_search (dst, j);
+
+			if (g_strcmp0 (src_search, dst_search) == 0) {
+				nm_ip4_config_del_search (dst, j);
+				break;
+			}
+		}
+	}
+
+	/* 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_set_mtu (dst, 0);
+
+	/* NIS */
+	for (i = 0; i < nm_ip4_config_get_num_nis_servers (src); i++) {
+		guint32 src_nis = nm_ip4_config_get_nis_server (src, i);
+
+		for (j = 0; j < nm_ip4_config_get_num_nis_servers (dst); j++) {
+			guint32 dst_nis = nm_ip4_config_get_nis_server (dst, j);
+
+			if (dst_nis == src_nis) {
+				nm_ip4_config_del_nis_server (dst, j);
+				break;
+			}
+		}
+	}
+
+	if (g_strcmp0 (nm_ip4_config_get_nis_domain (src), nm_ip4_config_get_nis_domain (dst)) == 0)
+		nm_ip4_config_set_nis_domain (dst, NULL);
+
+	/* WINS */
+	for (i = 0; i < nm_ip4_config_get_num_wins (src); i++) {
+		guint32 src_wins = nm_ip4_config_get_wins (src, i);
+
+		for (j = 0; j < nm_ip4_config_get_num_wins (dst); j++) {
+			guint32 dst_wins = nm_ip4_config_get_wins (dst, j);
+
+			if (dst_wins == src_wins) {
+				nm_ip4_config_del_wins (dst, j);
+				break;
+			}
+		}
+	}
 
-	return (NMIP4Route *) g_slist_nth_data (NM_IP4_CONFIG_GET_PRIVATE (config)->routes, i);
+	g_object_thaw_notify (G_OBJECT (dst));
 }
 
-guint32 nm_ip4_config_get_num_routes (NMIP4Config *config)
+
+/**
+ * nm_ip4_config_replace:
+ * @dst: config from which to remove everything in @src
+ * @src: config to remove from @dst
+ * @relevant_changes: return whether there are changes to the
+ * destination object that are relevant. This is equal to
+ * nm_ip4_config_equal() showing any difference.
+ *
+ * Replaces everything in @dst with @src so that the two configurations
+ * contain the same content -- with the exception of the dbus path.
+ *
+ * Returns: whether the @dst instance changed in any way (including minor changes,
+ * that are not signaled by the output parameter @relevant_changes).
+ */
+gboolean
+nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+#ifndef G_DISABLE_ASSERT
+	gboolean config_equal;
+#endif
+	gboolean has_minor_changes = FALSE, has_relevant_changes = FALSE, are_equal;
+	guint i, num;
+	NMIP4ConfigPrivate *dst_priv, *src_priv;
+	const NMPlatformIP4Address *dst_addr, *src_addr;
+	const NMPlatformIP4Route *dst_route, *src_route;
+
+	g_return_val_if_fail (src != NULL, FALSE);
+	g_return_val_if_fail (dst != NULL, FALSE);
+	g_return_val_if_fail (src != dst, FALSE);
+
+#ifndef G_DISABLE_ASSERT
+	config_equal = nm_ip4_config_equal (dst, src);
+#endif
+
+	dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
+	src_priv = NM_IP4_CONFIG_GET_PRIVATE (src);
+
+	g_object_freeze_notify (G_OBJECT (dst));
+
+	/* 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) {
+		nm_ip4_config_set_gateway (dst, src_priv->gateway);
+		has_relevant_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, TRUE)) {
+					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;
+	}
+
+	/* 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;
+				}
+			}
+		}
+	} 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;
+	}
+
+	/* nameservers */
+	num = nm_ip4_config_get_num_nameservers (src);
+	are_equal = num == nm_ip4_config_get_num_nameservers (dst);
+	if (are_equal) {
+		for (i = 0; i < num; i++ ) {
+			if (nm_ip4_config_get_nameserver (src, i) != nm_ip4_config_get_nameserver (dst, i)) {
+				are_equal = FALSE;
+				break;
+			}
+		}
+	}
+	if (!are_equal) {
+		nm_ip4_config_reset_nameservers (dst);
+		for (i = 0; i < num; i++)
+			nm_ip4_config_add_nameserver (dst, nm_ip4_config_get_nameserver (src, i));
+		has_relevant_changes = TRUE;
+	}
+
+	/* domains */
+	num = nm_ip4_config_get_num_domains (src);
+	are_equal = num == nm_ip4_config_get_num_domains (dst);
+	if (are_equal) {
+		for (i = 0; i < num; i++ ) {
+			if (g_strcmp0 (nm_ip4_config_get_domain (src, i),
+			               nm_ip4_config_get_domain (dst, i))) {
+				are_equal = FALSE;
+				break;
+			}
+		}
+	}
+	if (!are_equal) {
+		nm_ip4_config_reset_domains (dst);
+		for (i = 0; i < num; i++)
+			nm_ip4_config_add_domain (dst, nm_ip4_config_get_domain (src, i));
+		has_relevant_changes = TRUE;
+	}
+
+	/* dns searches */
+	num = nm_ip4_config_get_num_searches (src);
+	are_equal = num == nm_ip4_config_get_num_searches (dst);
+	if (are_equal) {
+		for (i = 0; i < num; i++ ) {
+			if (g_strcmp0 (nm_ip4_config_get_search (src, i),
+			               nm_ip4_config_get_search (dst, i))) {
+				are_equal = FALSE;
+				break;
+			}
+		}
+	}
+	if (!are_equal) {
+		nm_ip4_config_reset_searches (dst);
+		for (i = 0; i < num; i++)
+			nm_ip4_config_add_search (dst, nm_ip4_config_get_search (src, i));
+		has_relevant_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);
+	if (are_equal) {
+		for (i = 0; i < num; i++ ) {
+			if (nm_ip4_config_get_nis_server (src, i) != nm_ip4_config_get_nis_server (dst, i)) {
+				are_equal = FALSE;
+				break;
+			}
+		}
+	}
+	if (!are_equal) {
+		nm_ip4_config_reset_nis_servers (dst);
+		for (i = 0; i < num; i++)
+			nm_ip4_config_add_nis_server (dst, nm_ip4_config_get_nis_server (src, i));
+		has_relevant_changes = TRUE;
+	}
+
+	/* nis_domain */
+	if (g_strcmp0 (src_priv->nis_domain, dst_priv->nis_domain)) {
+		nm_ip4_config_set_nis_domain (dst, src_priv->nis_domain);
+		has_relevant_changes = TRUE;
+	}
+
+	/* wins */
+	num = nm_ip4_config_get_num_wins (src);
+	are_equal = num == nm_ip4_config_get_num_wins (dst);
+	if (are_equal) {
+		for (i = 0; i < num; i++ ) {
+			if (nm_ip4_config_get_wins (src, i) != nm_ip4_config_get_wins (dst, i)) {
+				are_equal = FALSE;
+				break;
+			}
+		}
+	}
+	if (!are_equal) {
+		nm_ip4_config_reset_wins (dst);
+		for (i = 0; i < num; i++)
+			nm_ip4_config_add_wins (dst, nm_ip4_config_get_wins (src, i));
+		has_relevant_changes = TRUE;
+	}
+
+	/* mtu */
+	if (src_priv->mtu != dst_priv->mtu) {
+		nm_ip4_config_set_mtu (dst, src_priv->mtu);
+		has_minor_changes = TRUE;
+	}
+
+	/* config_equal does not compare *all* the fields, therefore, we might have has_minor_changes
+	 * regardless of config_equal. But config_equal must correspond to has_relevant_changes. */
+	g_assert (config_equal == !has_relevant_changes);
 
-	return g_slist_length (NM_IP4_CONFIG_GET_PRIVATE (config)->routes);
+	g_object_thaw_notify (G_OBJECT (dst));
+
+	if (relevant_changes)
+		*relevant_changes = has_relevant_changes;
+
+	return has_relevant_changes || has_minor_changes;
 }
 
-void nm_ip4_config_reset_routes (NMIP4Config *config)
+void
+nm_ip4_config_dump (const NMIP4Config *config, const char *detail)
 {
-	NMIP4ConfigPrivate *priv;
+	guint32 i, tmp;
+	const char *str;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	g_return_if_fail (config != NULL);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	g_slist_foreach (priv->routes, (GFunc) g_free, NULL);
-	priv->routes = NULL;
+	g_message ("--------- NMIP4Config %p (%s)", config, detail);
+
+	str = nm_ip4_config_get_dbus_path (config);
+	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)));
+
+	/* default gateway */
+	tmp = nm_ip4_config_get_gateway (config);
+	g_message ("     gw: %s", nm_utils_inet4_ntop (tmp, NULL));
+
+	/* nameservers */
+	for (i = 0; i < nm_ip4_config_get_num_nameservers (config); i++) {
+		tmp = nm_ip4_config_get_nameserver (config, 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)));
+
+	/* domains */
+	for (i = 0; i < nm_ip4_config_get_num_domains (config); i++)
+		g_message (" domain: %s", nm_ip4_config_get_domain (config, 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));
+
+	g_message ("    mss: %u", nm_ip4_config_get_mss (config));
+	g_message ("    mtu: %u", nm_ip4_config_get_mtu (config));
+
+	/* NIS */
+	for (i = 0; i < nm_ip4_config_get_num_nis_servers (config); i++) {
+		tmp = nm_ip4_config_get_nis_server (config, i);
+		g_message ("    nis: %s", nm_utils_inet4_ntop (tmp, NULL));
+	}
+
+	g_message (" nisdmn: %s", nm_ip4_config_get_nis_domain (config));
+
+	/* WINS */
+	for (i = 0; i < nm_ip4_config_get_num_wins (config); i++) {
+		tmp = nm_ip4_config_get_wins (config, i);
+		g_message ("   wins: %s", nm_utils_inet4_ntop (tmp, NULL));
+	}
+
+	g_message (" n-dflt: %d", nm_ip4_config_get_never_default (config));
 }
 
-void nm_ip4_config_add_domain (NMIP4Config *config, const char *domain)
+gboolean
+nm_ip4_config_destination_is_direct (const NMIP4Config *config, guint32 network, int plen)
 {
-	NMIP4ConfigPrivate *priv;
+	guint naddresses = nm_ip4_config_get_num_addresses (config);
 	int i;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (domain != NULL);
-	g_return_if_fail (strlen (domain) > 0);
-
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	for (i = 0; i < naddresses; i++) {
+		const NMPlatformIP4Address *item = nm_ip4_config_get_address (config, i);
 
-	for (i = 0; i < priv->domains->len; i++) {
-		if (!strcmp (g_ptr_array_index (priv->domains, i), domain))
-			return;
+		if (item->plen <= plen && same_prefix (item->address, network, item->plen))
+			return TRUE;
 	}
 
-	g_ptr_array_add (priv->domains, g_strdup (domain));
+	return FALSE;
 }
 
-const char *nm_ip4_config_get_domain (NMIP4Config *config, guint i)
+/******************************************************************/
+
+void
+nm_ip4_config_set_never_default (NMIP4Config *config, gboolean never_default)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), NULL);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return (const char *) g_ptr_array_index (NM_IP4_CONFIG_GET_PRIVATE (config)->domains, i);
+	priv->never_default = !!never_default;
 }
 
-guint32 nm_ip4_config_get_num_domains (NMIP4Config *config)
+gboolean
+nm_ip4_config_get_never_default (const NMIP4Config *config)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->domains->len;
+	return priv->never_default;
 }
 
-void nm_ip4_config_reset_domains (NMIP4Config *config)
+void
+nm_ip4_config_set_gateway (NMIP4Config *config, guint32 gateway)
 {
-	NMIP4ConfigPrivate *priv;
-	int i;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	if (priv->gateway != gateway) {
+		priv->gateway = gateway;
+		_NOTIFY (config, PROP_GATEWAY);
+	}
+}
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	for (i = 0; i < priv->domains->len; i++)
-		g_free (g_ptr_array_index (priv->domains, i));
-	g_ptr_array_free (priv->domains, TRUE);
-	priv->domains = g_ptr_array_sized_new (3);
+guint32
+nm_ip4_config_get_gateway (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	return priv->gateway;
 }
 
-void nm_ip4_config_add_search (NMIP4Config *config, const char *search)
+/******************************************************************/
+
+void
+nm_ip4_config_reset_addresses (NMIP4Config *config)
 {
-	NMIP4ConfigPrivate *priv;
-	int i;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	g_return_if_fail (config != NULL);
-	g_return_if_fail (search != NULL);
-	g_return_if_fail (strlen (search) > 0);
+	if (priv->addresses->len != 0) {
+		g_array_set_size (priv->addresses, 0);
+		_NOTIFY (config, PROP_ADDRESSES);
+	}
+}
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+/**
+ * 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)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMPlatformIP4Address item_old;
+	int i;
 
-	for (i = 0; i < priv->searches->len; i++) {
-		if (!strcmp (g_ptr_array_index (priv->searches, i), search))
-			return;
+	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, FALSE)) {
+			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->source = MAX (item_old.source, new->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->source == NM_PLATFORM_SOURCE_KERNEL && new->source != item_old.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;
+		}
 	}
 
-	g_ptr_array_add (priv->searches, g_strdup (search));
+	g_array_append_val (priv->addresses, *new);
+NOTIFY:
+	_NOTIFY (config, PROP_ADDRESSES);
 }
 
-const char *nm_ip4_config_get_search (NMIP4Config *config, guint i)
+void
+nm_ip4_config_del_address (NMIP4Config *config, guint i)
 {
-	g_return_val_if_fail (config != NULL, NULL);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	g_return_if_fail (i < priv->addresses->len);
 
-	return (const char *) g_ptr_array_index (NM_IP4_CONFIG_GET_PRIVATE (config)->searches, i);
+	g_array_remove_index (priv->addresses, i);
+	_NOTIFY (config, PROP_ADDRESSES);
 }
 
-guint32 nm_ip4_config_get_num_searches (NMIP4Config *config)
+guint
+nm_ip4_config_get_num_addresses (const NMIP4Config *config)
 {
-	g_return_val_if_fail (config != NULL, 0);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->searches->len;
+	return priv->addresses->len;
 }
 
-void nm_ip4_config_reset_searches (NMIP4Config *config)
+const NMPlatformIP4Address *
+nm_ip4_config_get_address (const NMIP4Config *config, guint i)
 {
-	NMIP4ConfigPrivate *priv;
-	int i;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	return &g_array_index (priv->addresses, NMPlatformIP4Address, i);
+}
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	for (i = 0; i < priv->searches->len; i++)
-		g_free (g_ptr_array_index (priv->searches, i));
-	g_ptr_array_free (priv->searches, TRUE);
-	priv->searches = g_ptr_array_sized_new (3);
+gboolean
+nm_ip4_config_address_exists (const NMIP4Config *config,
+                              const NMPlatformIP4Address *needle)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	guint i;
+
+	for (i = 0; i < priv->addresses->len; i++) {
+		const NMPlatformIP4Address *haystack = &g_array_index (priv->addresses, NMPlatformIP4Address, i);
+
+		if (needle->address == haystack->address && needle->plen == haystack->plen)
+			return TRUE;
+	}
+	return FALSE;
 }
 
-guint32 nm_ip4_config_get_mtu (NMIP4Config *config)
+/******************************************************************/
+
+void
+nm_ip4_config_reset_routes (NMIP4Config *config)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->mtu;
+	if (priv->routes->len != 0) {
+		g_array_set_size (priv->routes, 0);
+		_NOTIFY (config, PROP_ROUTES);
+	}
 }
 
-void nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu)
+/**
+ * nm_ip4_config_add_route:
+ * @config: the #NMIP4Config
+ * @new: the new route to add to @config
+ *
+ * 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
+ * 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)
 {
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMPlatformSource old_source;
+	int i;
+
+	g_return_if_fail (new != NULL);
+
+	for (i = 0; i < priv->routes->len; i++ ) {
+		NMPlatformIP4Route *item = &g_array_index (priv->routes, NMPlatformIP4Route, i);
 
-	NM_IP4_CONFIG_GET_PRIVATE (config)->mtu = mtu;
+		if (routes_are_duplicate (item, new, FALSE)) {
+			if (nm_platform_ip4_route_cmp (item, new) == 0)
+				return;
+			old_source = item->source;
+			memcpy (item, new, sizeof (*item));
+			/* Restore highest priority source */
+			item->source = MAX (old_source, new->source);
+			goto NOTIFY;
+		}
+	}
+
+	g_array_append_val (priv->routes, *new);
+NOTIFY:
+	_NOTIFY (config, PROP_ROUTES);
 }
 
-guint32 nm_ip4_config_get_mss (NMIP4Config *config)
+void
+nm_ip4_config_del_route (NMIP4Config *config, guint i)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	g_return_if_fail (i < priv->routes->len);
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->mss;
+	g_array_remove_index (priv->routes, i);
+	_NOTIFY (config, PROP_ROUTES);
 }
 
-void nm_ip4_config_set_mss (NMIP4Config *config, guint32 mss)
+guint
+nm_ip4_config_get_num_routes (const NMIP4Config *config)
 {
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	NM_IP4_CONFIG_GET_PRIVATE (config)->mss = mss;
+	return priv->routes->len;
 }
 
-gboolean
-nm_ip4_config_get_never_default (NMIP4Config *config)
+const NMPlatformIP4Route *
+nm_ip4_config_get_route (const NMIP4Config *config, guint i)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), FALSE);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->never_default;
+	return &g_array_index (priv->routes, NMPlatformIP4Route, i);
 }
 
+/******************************************************************/
+
 void
-nm_ip4_config_set_never_default (NMIP4Config *config, gboolean never_default)
+nm_ip4_config_reset_nameservers (NMIP4Config *config)
 {
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	NM_IP4_CONFIG_GET_PRIVATE (config)->never_default = never_default;
+	if (priv->nameservers->len != 0) {
+		g_array_set_size (priv->nameservers, 0);
+		_NOTIFY (config, PROP_NAMESERVERS);
+	}
 }
 
-void nm_ip4_config_add_nis_server (NMIP4Config *config, guint32 nis)
+void
+nm_ip4_config_add_nameserver (NMIP4Config *config, guint32 new)
 {
-	NMIP4ConfigPrivate *priv;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 	int i;
 
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
-	g_return_if_fail (nis > 0);
-
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	for (i = 0; i < priv->nis->len; i++) {
-		guint32 s = g_array_index (priv->nis, guint32, i);
+	g_return_if_fail (new != 0);
 
-		/* No dupes */
-		g_return_if_fail (nis != s);
-	}
+	for (i = 0; i < priv->nameservers->len; i++)
+		if (new == g_array_index (priv->nameservers, guint32, i))
+			return;
 
-	g_array_append_val (priv->nis, nis);
+	g_array_append_val (priv->nameservers, new);
+	_NOTIFY (config, PROP_NAMESERVERS);
 }
 
-guint32 nm_ip4_config_get_nis_server (NMIP4Config *config, guint i)
+void
+nm_ip4_config_del_nameserver (NMIP4Config *config, guint i)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	g_return_if_fail (i < priv->nameservers->len);
 
-	return g_array_index (NM_IP4_CONFIG_GET_PRIVATE (config)->nis, guint32, i);
+	g_array_remove_index (priv->nameservers, i);
+	_NOTIFY (config, PROP_NAMESERVERS);
 }
 
-guint32 nm_ip4_config_get_num_nis_servers (NMIP4Config *config)
+guint32
+nm_ip4_config_get_num_nameservers (const NMIP4Config *config)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->nis->len;
+	return priv->nameservers->len;
 }
 
-void nm_ip4_config_reset_nis_servers (NMIP4Config *config)
+guint32
+nm_ip4_config_get_nameserver (const NMIP4Config *config, guint i)
 {
-	NMIP4ConfigPrivate *priv;
-
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	if (priv->nis->len)
-		g_array_remove_range (priv->nis, 0, priv->nis->len);
+	return g_array_index (priv->nameservers, guint32, i);
 }
 
+/******************************************************************/
+
 void
-nm_ip4_config_set_nis_domain (NMIP4Config *config, const char *domain)
+nm_ip4_config_reset_domains (NMIP4Config *config)
 {
-	NMIP4ConfigPrivate *priv;
-
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	g_free (priv->nis_domain);
-	priv->nis_domain = g_strdup (domain);
+	if (priv->domains->len != 0) {
+		g_ptr_array_set_size (priv->domains, 0);
+		_NOTIFY (config, PROP_DOMAINS);
+	}
 }
 
-const char *
-nm_ip4_config_get_nis_domain (NMIP4Config *config)
+void
+nm_ip4_config_add_domain (NMIP4Config *config, const char *domain)
 {
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	int i;
 
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->nis_domain;
-}
+	g_return_if_fail (domain != NULL);
+	g_return_if_fail (domain[0] != '\0');
 
-/* libnl convenience/conversion functions */
+	for (i = 0; i < priv->domains->len; i++)
+		if (!g_strcmp0 (g_ptr_array_index (priv->domains, i), domain))
+			return;
 
-static int ip4_addr_to_rtnl_local (guint32 ip4_address, struct rtnl_addr *addr)
+	g_ptr_array_add (priv->domains, g_strdup (domain));
+	_NOTIFY (config, PROP_DOMAINS);
+}
+
+void
+nm_ip4_config_del_domain (NMIP4Config *config, guint i)
 {
-	struct nl_addr * local = NULL;
-	int err = 0;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	g_return_if_fail (i < priv->domains->len);
 
-	g_return_val_if_fail (addr != NULL, -1);
+	g_ptr_array_remove_index (priv->domains, i);
+	_NOTIFY (config, PROP_DOMAINS);
+}
 
-	local = nm_utils_ip4_addr_to_nl_addr (ip4_address);
-	err = rtnl_addr_set_local (addr, local);
-	nl_addr_put (local);
+guint32
+nm_ip4_config_get_num_domains (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return err;
+	return priv->domains->len;
 }
 
-static int ip4_addr_to_rtnl_peer (guint32 ip4_address, struct rtnl_addr *addr)
+const char *
+nm_ip4_config_get_domain (const NMIP4Config *config, guint i)
 {
-	struct nl_addr * peer = NULL;
-	int err = 0;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	g_return_val_if_fail (addr != NULL, -1);
+	return g_ptr_array_index (priv->domains, i);
+}
 
-	peer = nm_utils_ip4_addr_to_nl_addr (ip4_address);
-	err = rtnl_addr_set_peer (addr, peer);
-	nl_addr_put (peer);
+/******************************************************************/
 
-	return err;
+void
+nm_ip4_config_reset_searches (NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	if (priv->searches->len != 0) {
+		g_ptr_array_set_size (priv->searches, 0);
+		_NOTIFY (config, PROP_SEARCHES);
+	}
 }
 
-static int ip4_addr_to_rtnl_broadcast (guint32 ip4_broadcast, struct rtnl_addr *addr)
+void
+nm_ip4_config_add_search (NMIP4Config *config, const char *new)
 {
-	struct nl_addr	* local = NULL;
-	int err = 0;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	int i;
 
-	g_return_val_if_fail (addr != NULL, -1);
+	g_return_if_fail (new != NULL);
+	g_return_if_fail (new[0] != '\0');
 
-	local = nm_utils_ip4_addr_to_nl_addr (ip4_broadcast);
-	err = rtnl_addr_set_broadcast (addr, local);
-	nl_addr_put (local);
+	for (i = 0; i < priv->searches->len; i++)
+		if (!g_strcmp0 (g_ptr_array_index (priv->searches, i), new))
+			return;
 
-	return err;
+	g_ptr_array_add (priv->searches, g_strdup (new));
+	_NOTIFY (config, PROP_SEARCHES);
 }
 
-
-struct rtnl_addr *
-nm_ip4_config_to_rtnl_addr (NMIP4Config *config, guint32 i, guint32 flags)
+void
+nm_ip4_config_del_search (NMIP4Config *config, guint i)
 {
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	NMIP4Address *config_addr;
-	struct rtnl_addr *addr;
-	gboolean success = TRUE;
 
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (config), NULL);
+	g_return_if_fail (i < priv->searches->len);
 
-	config_addr = nm_ip4_config_get_address (config, i);
-	g_return_val_if_fail (config_addr != NULL, NULL);
+	g_ptr_array_remove_index (priv->searches, i);
+	_NOTIFY (config, PROP_SEARCHES);
+}
 
-	if (!(addr = rtnl_addr_alloc()))
-		return NULL;
+guint32
+nm_ip4_config_get_num_searches (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	if (flags & NM_RTNL_ADDR_ADDR)
-		success = (ip4_addr_to_rtnl_local (nm_ip4_address_get_address (config_addr), addr) >= 0);
+	return priv->searches->len;
+}
 
-	if (flags & NM_RTNL_ADDR_PTP_ADDR)
-		success = (ip4_addr_to_rtnl_peer (priv->ptp_address, addr) >= 0);
+const char *
+nm_ip4_config_get_search (const NMIP4Config *config, guint i)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	if (flags & NM_RTNL_ADDR_PREFIX)
-		rtnl_addr_set_prefixlen (addr, nm_ip4_address_get_prefix (config_addr));
+	return g_ptr_array_index (priv->searches, i);
+}
 
-	if (flags & NM_RTNL_ADDR_BROADCAST) {
-		guint32 hostmask, network, bcast, netmask;
+/******************************************************************/
 
-		netmask = nm_utils_ip4_prefix_to_netmask (nm_ip4_address_get_prefix (config_addr));
-		network = ntohl (nm_ip4_address_get_address (config_addr)) & ntohl (netmask);
-		hostmask = ~ntohl (netmask);
-		bcast = htonl (network | hostmask);
+void
+nm_ip4_config_set_mss (NMIP4Config *config, guint32 mss)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-		success = (ip4_addr_to_rtnl_broadcast (bcast, addr) >= 0);
-	}
+	priv->mss = mss;
+}
 
-	if (!success) {
-		rtnl_addr_put (addr);
-		addr = NULL;
-	}
+guint32
+nm_ip4_config_get_mss (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return addr;
+	return priv->mss;
 }
 
-static gboolean
-addr_slist_compare (GSList *a, GSList *b)
+/******************************************************************/
+
+void
+nm_ip4_config_reset_nis_servers (NMIP4Config *config)
 {
-	GSList *iter_a, *iter_b;
-	gboolean found = FALSE;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	for (iter_a = a; iter_a; iter_a = g_slist_next (iter_a)) {
-		NMIP4Address *addr_a = (NMIP4Address *) iter_a->data;
+	g_array_set_size (priv->nis, 0);
+}
 
-		for (iter_b = b, found = FALSE; iter_b; iter_b = g_slist_next (iter_b)) {
-			NMIP4Address *addr_b = (NMIP4Address *) iter_b->data;
+void
+nm_ip4_config_add_nis_server (NMIP4Config *config, guint32 nis)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	int i;
 
-			if (nm_ip4_address_compare (addr_a, addr_b)) {
-				found = TRUE;
-				break;
-			}
-		}
+	for (i = 0; i < priv->nis->len; i++)
+		if (nis == g_array_index (priv->nis, guint32, i))
+			return;
 
-		if (!found)
-			return FALSE;
-	}
-	return TRUE;
+	g_array_append_val (priv->nis, nis);
 }
 
-static gboolean
-route_slist_compare (GSList *a, GSList *b)
+void
+nm_ip4_config_del_nis_server (NMIP4Config *config, guint i)
 {
-	GSList *iter_a, *iter_b;
-	gboolean found = FALSE;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	for (iter_a = a; iter_a; iter_a = g_slist_next (iter_a)) {
-		NMIP4Route *route_a = (NMIP4Route *) iter_a->data;
+	g_return_if_fail (i < priv->nis->len);
 
-		for (iter_b = b, found = FALSE; iter_b; iter_b = g_slist_next (iter_b)) {
-			NMIP4Route *route_b = (NMIP4Route *) iter_b->data;
+	g_array_remove_index (priv->nis, i);
+}
 
-			if (nm_ip4_route_compare (route_a, route_b)) {
-				found = TRUE;
-				break;
-			}
-		}
+guint32
+nm_ip4_config_get_num_nis_servers (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-		if (!found)
-			return FALSE;
-	}
-	return TRUE;
+	return priv->nis->len;
 }
 
-static gboolean
-string_array_compare (GPtrArray *a, GPtrArray *b)
+guint32
+nm_ip4_config_get_nis_server (const NMIP4Config *config, guint i)
 {
-	int i, j;
-	gboolean found = FALSE;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	for (i = 0; i < a->len; i++) {
-		for (j = 0, found = FALSE; j < b->len; j++) {
-			const char *item_a = g_ptr_array_index (a, i);
-			const char *item_b = g_ptr_array_index (b, j);
+	return g_array_index (priv->nis, guint32, i);
+}
 
-			if ((!item_a && !item_b) || (item_a && item_b && !strcmp (item_a, item_b))) {
-				found = TRUE;
-				break;
-			}
-		}
+void
+nm_ip4_config_set_nis_domain (NMIP4Config *config, const char *domain)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-		if (!found)
-			return FALSE;
-	}
-	return TRUE;
+	g_free (priv->nis_domain);
+	priv->nis_domain = g_strdup (domain);
 }
 
-static gboolean
-addr_array_compare (GArray *a, GArray *b)
+const char *
+nm_ip4_config_get_nis_domain (const NMIP4Config *config)
 {
-	int i, j;
-	gboolean found = FALSE;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	for (i = 0; i < a->len; i++) {
-		for (j = 0, found = FALSE; j < b->len; j++) {
-			if (g_array_index (a, guint32, i) == g_array_index (b, guint32, j)) {
-				found = TRUE;
-				break;
-			}
-		}
+	return priv->nis_domain;
+}
 
-		if (!found)
-			return FALSE;
+/******************************************************************/
+
+void
+nm_ip4_config_reset_wins (NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	if (priv->wins->len != 0) {
+		g_array_set_size (priv->wins, 0);
+		_NOTIFY (config, PROP_WINS_SERVERS);
 	}
-	return TRUE;
 }
 
-NMIP4ConfigCompareFlags
-nm_ip4_config_diff (NMIP4Config *a, NMIP4Config *b)
+void
+nm_ip4_config_add_wins (NMIP4Config *config, guint32 wins)
 {
-	NMIP4ConfigPrivate *a_priv;
-	NMIP4ConfigPrivate *b_priv;
-	NMIP4ConfigCompareFlags flags = NM_IP4_COMPARE_FLAG_NONE;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	int i;
 
-	if ((a && !b) || (b && !a))
-		return NM_IP4_COMPARE_FLAG_ALL;
-	if (!a && !b)
-		return NM_IP4_COMPARE_FLAG_NONE;
+	g_return_if_fail (wins != 0);
 
-	a_priv = NM_IP4_CONFIG_GET_PRIVATE (a);
-	b_priv = NM_IP4_CONFIG_GET_PRIVATE (b);
+	for (i = 0; i < priv->wins->len; i++)
+		if (wins == g_array_index (priv->wins, guint32, i))
+			return;
 
-	if (   !addr_slist_compare (a_priv->addresses, b_priv->addresses)
-	    || !addr_slist_compare (b_priv->addresses, a_priv->addresses))
-		flags |= NM_IP4_COMPARE_FLAG_ADDRESSES;
+	g_array_append_val (priv->wins, wins);
+	_NOTIFY (config, PROP_WINS_SERVERS);
+}
 
-	if (a_priv->ptp_address != b_priv->ptp_address)
-		flags |= NM_IP4_COMPARE_FLAG_PTP_ADDRESS;
+void
+nm_ip4_config_del_wins (NMIP4Config *config, guint i)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	if (   (a_priv->nameservers->len != b_priv->nameservers->len)
-	    || !addr_array_compare (a_priv->nameservers, b_priv->nameservers)
-	    || !addr_array_compare (b_priv->nameservers, a_priv->nameservers))
-		flags |= NM_IP4_COMPARE_FLAG_NAMESERVERS;
+	g_return_if_fail (i < priv->wins->len);
 
-	if (   (a_priv->wins->len != b_priv->wins->len)
-	    || !addr_array_compare (a_priv->wins, b_priv->wins)
-	    || !addr_array_compare (b_priv->wins, a_priv->wins))
-		flags |= NM_IP4_COMPARE_FLAG_WINS_SERVERS;
+	g_array_remove_index (priv->wins, i);
+	_NOTIFY (config, PROP_WINS_SERVERS);
+}
 
-	if (   (a_priv->nis->len != b_priv->nis->len)
-	    || !addr_array_compare (a_priv->nis, b_priv->nis)
-	    || !addr_array_compare (b_priv->nis, a_priv->nis))
-		flags |= NM_IP4_COMPARE_FLAG_NIS_SERVERS;
+guint32
+nm_ip4_config_get_num_wins (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	if (   (a_priv->nis_domain || b_priv->nis_domain)
-		&& (g_strcmp0 (a_priv->nis_domain, b_priv->nis_domain) != 0))
-		flags |= NM_IP4_COMPARE_FLAG_NIS_DOMAIN;
+	return priv->wins->len;
+}
 
-	if (   !route_slist_compare (a_priv->routes, b_priv->routes)
-	    || !route_slist_compare (b_priv->routes, a_priv->routes))
-		flags |= NM_IP4_COMPARE_FLAG_ROUTES;
+guint32
+nm_ip4_config_get_wins (const NMIP4Config *config, guint i)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	if (   (a_priv->domains->len != b_priv->domains->len)
-	    || !string_array_compare (a_priv->domains, b_priv->domains)
-	    || !string_array_compare (b_priv->domains, a_priv->domains))
-		flags |= NM_IP4_COMPARE_FLAG_DOMAINS;
+	return g_array_index (priv->wins, guint32, i);
+}
 
-	if (   (a_priv->searches->len != b_priv->searches->len)
-	    || !string_array_compare (a_priv->searches, b_priv->searches)
-	    || !string_array_compare (b_priv->searches, a_priv->searches))
-		flags |= NM_IP4_COMPARE_FLAG_SEARCHES;
+/******************************************************************/
 
-	if (a_priv->mtu != b_priv->mtu)
-		flags |= NM_IP4_COMPARE_FLAG_MTU;
+void
+nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	if (a_priv->mss != b_priv->mss)
-		flags |= NM_IP4_COMPARE_FLAG_MSS;
+	priv->mtu = mtu;
+}
+
+guint32
+nm_ip4_config_get_mtu (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	return flags;
+	return priv->mtu;
 }
 
+/******************************************************************/
+
 static inline void
 hash_u32 (GChecksum *sum, guint32 n)
 {
@@ -820,36 +1527,32 @@ hash_u32 (GChecksum *sum, guint32 n)
 }
 
 void
-nm_ip4_config_hash (NMIP4Config *config, GChecksum *sum, gboolean dns_only)
+nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only)
 {
-	guint32 i, n;
+	guint32 i;
 	const char *s;
 
-	g_return_if_fail (config != NULL);
-	g_return_if_fail (sum != NULL);
+	g_return_if_fail (config);
+	g_return_if_fail (sum);
 
-	if (dns_only == FALSE) {
-		for (i = 0; i < nm_ip4_config_get_num_addresses (config); i++) {
-			NMIP4Address *a = nm_ip4_config_get_address (config, i);
+	if (!dns_only) {
+		hash_u32 (sum, nm_ip4_config_get_gateway (config));
 
-			hash_u32 (sum, nm_ip4_address_get_address (a));
-			hash_u32 (sum, nm_ip4_address_get_prefix (a));
-			hash_u32 (sum, nm_ip4_address_get_gateway (a));
+		for (i = 0; i < nm_ip4_config_get_num_addresses (config); i++) {
+			const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
+			hash_u32 (sum, address->address);
+			hash_u32 (sum, address->plen);
 		}
 
 		for (i = 0; i < nm_ip4_config_get_num_routes (config); i++) {
-			NMIP4Route *r = nm_ip4_config_get_route (config, i);
+			const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
 
-			hash_u32 (sum, nm_ip4_route_get_dest (r));
-			hash_u32 (sum, nm_ip4_route_get_prefix (r));
-			hash_u32 (sum, nm_ip4_route_get_next_hop (r));
-			hash_u32 (sum, nm_ip4_route_get_metric (r));
+			hash_u32 (sum, route->network);
+			hash_u32 (sum, route->plen);
+			hash_u32 (sum, route->gateway);
+			hash_u32 (sum, route->metric);
 		}
 
-		n = nm_ip4_config_get_ptp_address (config);
-		if (n)
-			hash_u32 (sum, n);
-
 		for (i = 0; i < nm_ip4_config_get_num_nis_servers (config); i++)
 			hash_u32 (sum, nm_ip4_config_get_nis_server (config, i));
 
@@ -875,16 +1578,60 @@ nm_ip4_config_hash (NMIP4Config *config, GChecksum *sum, gboolean dns_only)
 	}
 }
 
+/**
+ * nm_ip4_config_equal:
+ * @a: first config to compare
+ * @b: second config to compare
+ *
+ * Compares two #NMIP4Configs for basic equality.  This means that all
+ * attributes must exist in the same order in both configs (addresses, routes,
+ * domains, DNS servers, etc) but some attributes (address lifetimes, and address
+ * and route sources) are ignored.
+ *
+ * Returns: %TRUE if the configurations are basically equal to each other,
+ * %FALSE if not
+ */
+gboolean
+nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b)
+{
+	GChecksum *a_checksum = g_checksum_new (G_CHECKSUM_SHA1);
+	GChecksum *b_checksum = g_checksum_new (G_CHECKSUM_SHA1);
+	gsize a_len = g_checksum_type_get_length (G_CHECKSUM_SHA1);
+	gsize b_len = g_checksum_type_get_length (G_CHECKSUM_SHA1);
+	guchar a_data[a_len], b_data[b_len];
+	gboolean equal;
+
+	if (a)
+		nm_ip4_config_hash (a, a_checksum, FALSE);
+	if (b)
+		nm_ip4_config_hash (b, b_checksum, FALSE);
+
+	g_checksum_get_digest (a_checksum, a_data, &a_len);
+	g_checksum_get_digest (b_checksum, b_data, &b_len);
+
+	g_assert (a_len == b_len);
+	equal = !memcmp (a_data, b_data, a_len);
+
+	g_checksum_free (a_checksum);
+	g_checksum_free (b_checksum);
+
+	return equal;
+}
+
+/******************************************************************/
+
 static void
 nm_ip4_config_init (NMIP4Config *config)
 {
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
-	priv->nameservers = g_array_new (FALSE, TRUE, sizeof (guint32));
-	priv->wins = g_array_new (FALSE, TRUE, sizeof (guint32));
-	priv->domains = g_ptr_array_sized_new (3);
-	priv->searches = g_ptr_array_sized_new (3);
+	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->nis = g_array_new (FALSE, TRUE, sizeof (guint32));
+	priv->wins = g_array_new (FALSE, TRUE, sizeof (guint32));
 }
 
 static void
@@ -892,14 +1639,14 @@ finalize (GObject *object)
 {
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (object);
 
-	nm_utils_slist_free (priv->addresses, (GDestroyNotify) nm_ip4_address_unref);
-	nm_utils_slist_free (priv->routes, (GDestroyNotify) nm_ip4_route_unref);
-	g_array_free (priv->wins, TRUE);
-	g_array_free (priv->nameservers, TRUE);
-	g_ptr_array_free (priv->domains, TRUE);
-	g_ptr_array_free (priv->searches, TRUE);
-	g_array_free (priv->nis, TRUE);
+	g_array_unref (priv->addresses);
+	g_array_unref (priv->routes);
+	g_array_unref (priv->nameservers);
+	g_ptr_array_unref (priv->domains);
+	g_ptr_array_unref (priv->searches);
+	g_array_unref (priv->nis);
 	g_free (priv->nis_domain);
+	g_array_unref (priv->wins);
 
 	G_OBJECT_CLASS (nm_ip4_config_parent_class)->finalize (object);
 }
@@ -908,11 +1655,56 @@ 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 (object);
 
 	switch (prop_id) {
+	case PROP_GATEWAY:
+		if (priv->gateway)
+			g_value_set_string (value, nm_utils_inet4_ntop (priv->gateway, NULL));
+		else
+			g_value_set_string (value, NULL);
+		break;
 	case PROP_ADDRESSES:
-		nm_utils_ip4_addresses_to_gvalue (priv->addresses, value);
+		{
+			GPtrArray *addresses = g_ptr_array_new ();
+			int naddr = nm_ip4_config_get_num_addresses (config);
+			int i;
+
+			for (i = 0; i < naddr; i++) {
+				const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
+				GArray *array = g_array_sized_new (FALSE, TRUE, sizeof (guint32), 3);
+
+				g_array_append_val (array, address->address);
+				g_array_append_val (array, address->plen);
+				g_array_append_val (array, priv->gateway);
+
+				g_ptr_array_add (addresses, array);
+			}
+
+			g_value_take_boxed (value, addresses);
+		}
+		break;
+	case PROP_ROUTES:
+		{
+			GPtrArray *routes = g_ptr_array_new ();
+			guint nroutes = nm_ip4_config_get_num_routes (config);
+			int i;
+
+			for (i = 0; i < nroutes; i++) {
+				const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
+				GArray *array = g_array_sized_new (FALSE, TRUE, sizeof (guint32), 4);
+
+				g_array_append_val (array, route->network);
+				g_array_append_val (array, route->plen);
+				g_array_append_val (array, route->gateway);
+				g_array_append_val (array, route->metric);
+
+				g_ptr_array_add (routes, array);
+			}
+
+			g_value_take_boxed (value, routes);
+		}
 		break;
 	case PROP_NAMESERVERS:
 		g_value_set_boxed (value, priv->nameservers);
@@ -920,8 +1712,8 @@ get_property (GObject *object, guint prop_id,
 	case PROP_DOMAINS:
 		g_value_set_boxed (value, priv->domains);
 		break;
-	case PROP_ROUTES:
-		nm_utils_ip4_routes_to_gvalue (priv->routes, value);
+	case PROP_SEARCHES:
+		g_value_set_boxed (value, priv->searches);
 		break;
 	case PROP_WINS_SERVERS:
 		g_value_set_boxed (value, priv->wins);
@@ -939,47 +1731,55 @@ nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 
 	g_type_class_add_private (config_class, sizeof (NMIP4ConfigPrivate));
 
-	/* virtual methods */
 	object_class->get_property = get_property;
 	object_class->finalize = finalize;
 
-	/* properties */
-	g_object_class_install_property
-		(object_class, PROP_ADDRESSES,
+	obj_properties[PROP_GATEWAY] =
+		 g_param_spec_string (NM_IP4_CONFIG_GATEWAY,
+		                      "Gateway",
+		                      "IP4 gateway",
+		                       NULL,
+		                       G_PARAM_READABLE);
+	obj_properties[PROP_ADDRESSES] =
 		 g_param_spec_boxed (NM_IP4_CONFIG_ADDRESSES,
-							"Addresses",
-							"IP4 addresses",
-							DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT,
-							G_PARAM_READABLE));
-	g_object_class_install_property
-		(object_class, PROP_NAMESERVERS,
+		                     "Addresses",
+		                     "IP4 addresses",
+		                     DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT,
+		                     G_PARAM_READABLE);
+	obj_properties[PROP_ROUTES] =
+		 g_param_spec_boxed (NM_IP4_CONFIG_ROUTES,
+		                     "Routes",
+		                     "Routes",
+		                     DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT,
+		                     G_PARAM_READABLE);
+	obj_properties[PROP_NAMESERVERS] =
 		 g_param_spec_boxed (NM_IP4_CONFIG_NAMESERVERS,
-							 "Nameservers",
-							 "DNS list",
-							 DBUS_TYPE_G_UINT_ARRAY,
-							 G_PARAM_READABLE));
-	g_object_class_install_property
-		(object_class, PROP_DOMAINS,
+		                     "Nameservers",
+		                     "DNS list",
+		                     DBUS_TYPE_G_UINT_ARRAY,
+		                     G_PARAM_READABLE);
+	obj_properties[PROP_DOMAINS] =
 		 g_param_spec_boxed (NM_IP4_CONFIG_DOMAINS,
-							 "Domains",
-							 "Domains",
-							 DBUS_TYPE_G_ARRAY_OF_STRING,
-							 G_PARAM_READABLE));
-	g_object_class_install_property
-		(object_class, PROP_ROUTES,
-		 g_param_spec_boxed (NM_IP4_CONFIG_ROUTES,
-						 "Routes",
-						 "Routes",
-						 DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT,
-						 G_PARAM_READABLE));
-	g_object_class_install_property
-		(object_class, PROP_WINS_SERVERS,
+		                     "Domains",
+		                     "Domains",
+		                     DBUS_TYPE_G_ARRAY_OF_STRING,
+		                     G_PARAM_READABLE);
+	obj_properties[PROP_SEARCHES] =
+		 g_param_spec_boxed (NM_IP4_CONFIG_SEARCHES,
+		                     "Searches",
+		                     "Searches",
+		                     DBUS_TYPE_G_ARRAY_OF_STRING,
+		                     G_PARAM_READABLE);
+	obj_properties[PROP_WINS_SERVERS] =
 		 g_param_spec_boxed (NM_IP4_CONFIG_WINS_SERVERS,
-							 "WinsServers",
-							 "WINS server list",
-							 DBUS_TYPE_G_UINT_ARRAY,
-							 G_PARAM_READABLE));
+		                     "WinsServers",
+		                     "WINS server list",
+		                     DBUS_TYPE_G_UINT_ARRAY,
+		                     G_PARAM_READABLE);
+
+	g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
 
-	dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (config_class),
-									 &dbus_glib_nm_ip4_config_object_info);
+	nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
+	                                        G_TYPE_FROM_CLASS (config_class),
+	                                        &dbus_glib_nm_ip4_config_object_info);
 }