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.c598
1 files changed, 397 insertions, 201 deletions
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index f625d350..78e7fb78 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -26,24 +26,22 @@
 
 #include "nm-ip4-config.h"
 
+#include "nm-default.h"
 #include "nm-utils.h"
 #include "nm-platform.h"
-#include "nm-dbus-manager.h"
-#include "nm-dbus-glib-types.h"
-#include "nm-ip4-config-glue.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 #include "nm-route-manager.h"
-#include "gsystem-local-alloc.h"
+#include "nm-core-internal.h"
 #include "nm-macros-internal.h"
 
-G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, G_TYPE_OBJECT)
+#include "nmdbus-ip4-config.h"
 
-#define NM_IP4_CONFIG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IP4_CONFIG, NMIP4ConfigPrivate))
+G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, NM_TYPE_EXPORTED_OBJECT)
 
-typedef struct {
-	char *path;
+#define NM_IP4_CONFIG_GET_PRIVATE(o) ((o)->priv)
 
+typedef struct _NMIP4ConfigPrivate {
 	gboolean never_default;
 	guint32 gateway;
 	gboolean has_gateway;
@@ -52,12 +50,14 @@ typedef struct {
 	GArray *nameservers;
 	GPtrArray *domains;
 	GPtrArray *searches;
+	GPtrArray *dns_options;
 	guint32 mss;
 	GArray *nis;
 	char *nis_domain;
 	GArray *wins;
 	guint32 mtu;
 	NMIPConfigSource mtu_source;
+	int ifindex;
 	gint64 route_metric;
 	gboolean metered;
 } NMIP4ConfigPrivate;
@@ -68,6 +68,7 @@ G_STATIC_ASSERT (G_MAXUINT >= 0xFFFFFFFF);
 
 enum {
 	PROP_0,
+	PROP_IFINDEX,
 	PROP_ADDRESS_DATA,
 	PROP_ADDRESSES,
 	PROP_ROUTE_DATA,
@@ -76,6 +77,7 @@ enum {
 	PROP_NAMESERVERS,
 	PROP_DOMAINS,
 	PROP_SEARCHES,
+	PROP_DNS_OPTIONS,
 	PROP_WINS_SERVERS,
 
 	LAST_PROP
@@ -83,41 +85,28 @@ enum {
 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 *
-nm_ip4_config_new (void)
+nm_ip4_config_new (int ifindex)
 {
-	return (NMIP4Config *) g_object_new (NM_TYPE_IP4_CONFIG, NULL);
+	g_return_val_if_fail (ifindex >= -1, NULL);
+	return (NMIP4Config *) g_object_new (NM_TYPE_IP4_CONFIG,
+	                                     NM_IP4_CONFIG_IFINDEX, ifindex,
+	                                     NULL);
 }
 
-
-void
-nm_ip4_config_export (NMIP4Config *config)
+int
+nm_ip4_config_get_ifindex (const NMIP4Config *config)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	static guint32 counter = 0;
-
-	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);
-	}
+	return NM_IP4_CONFIG_GET_PRIVATE (config)->ifindex;
 }
 
-const char *
-nm_ip4_config_get_dbus_path (const NMIP4Config *config)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	return priv->path;
-}
+/******************************************************************/
 
 static gboolean
-same_prefix (guint32 address1, guint32 address2, int plen)
+_ipv4_is_zeronet (in_addr_t network)
 {
-	guint32 masked1 = ntohl (address1) >> (32 - plen);
-	guint32 masked2 = ntohl (address2) >> (32 - plen);
-
-	return masked1 == masked2;
+	/* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */
+	return (network & htonl(0xff000000)) == htonl(0x00000000);
 }
 
 /******************************************************************/
@@ -133,9 +122,10 @@ same_prefix (guint32 address1, guint32 address2, int plen)
  */
 gboolean
 nm_ip4_config_capture_resolv_conf (GArray *nameservers,
+                                   GPtrArray *dns_options,
                                    const char *rc_contents)
 {
-	GPtrArray *read_ns;
+	GPtrArray *read_ns, *read_options;
 	guint i, j;
 	gboolean changed = FALSE;
 
@@ -163,15 +153,34 @@ nm_ip4_config_capture_resolv_conf (GArray *nameservers,
 			changed = TRUE;
 		}
 	}
-
 	g_ptr_array_unref (read_ns);
+
+	if (dns_options) {
+		read_options = nm_utils_read_resolv_conf_dns_options (rc_contents);
+		if (!read_options)
+			return changed;
+
+		for (i = 0; i < read_options->len; i++) {
+			const char *s = g_ptr_array_index (read_options, i);
+
+			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);
+	}
+
 	return changed;
 }
 
 static gboolean
-addresses_are_duplicate (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b, gboolean consider_plen)
+addresses_are_duplicate (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
 {
-	return a->address == b->address && (!consider_plen || a->plen == b->plen);
+	return    a->address == b->address
+	       && a->plen == b->plen
+	       && ((a->peer_address ^ b->peer_address) & nm_utils_ip4_prefix_to_netmask (a->plen)) == 0;
 }
 
 static gboolean
@@ -195,7 +204,7 @@ nm_ip4_config_capture (int ifindex, gboolean capture_resolv_conf)
 	if (nm_platform_link_get_master (NM_PLATFORM_GET, ifindex) > 0)
 		return NULL;
 
-	config = nm_ip4_config_new ();
+	config = nm_ip4_config_new (ifindex);
 	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
 	g_array_unref (priv->addresses);
@@ -247,7 +256,7 @@ nm_ip4_config_capture (int ifindex, gboolean capture_resolv_conf)
 	 * nameservers from /etc/resolv.conf.
 	 */
 	if (priv->addresses->len && priv->has_gateway && capture_resolv_conf) {
-		if (nm_ip4_config_capture_resolv_conf (priv->nameservers, NULL))
+		if (nm_ip4_config_capture_resolv_conf (priv->nameservers, priv->dns_options, NULL))
 			_NOTIFY (config, PROP_NAMESERVERS);
 	}
 
@@ -298,7 +307,16 @@ nm_ip4_config_commit (const NMIP4Config *config, int ifindex, gboolean routes_fu
 
 				route.ifindex = ifindex;
 				route.source = NM_IP_CONFIG_SOURCE_KERNEL;
-				route.network = nm_utils_ip4_address_clear_host_address (addr->address, addr->plen);
+
+				/* The destination network depends on the peer-address. */
+				route.network = nm_utils_ip4_address_clear_host_address (addr->peer_address, addr->plen);
+
+				if (_ipv4_is_zeronet (route.network)) {
+					/* Kernel doesn't add device-routes for destinations that
+					 * start with 0.x.y.z. Skip them. */
+					continue;
+				}
+
 				route.plen = addr->plen;
 				route.pref_src = addr->address;
 				route.metric = default_route_metric;
@@ -386,6 +404,7 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 
 		memset (&address, 0, sizeof (address));
 		nm_ip_address_get_address_binary (s_addr, &address.address);
+		address.peer_address = address.address;
 		address.plen = nm_ip_address_get_prefix (s_addr);
 		address.lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
 		address.preferred = NM_PLATFORM_LIFETIME_PERMANENT;
@@ -435,6 +454,12 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 	for (i = 0; i < nsearches; i++)
 		nm_ip4_config_add_search (config, 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));
+		i++;
+	}
+
 	g_object_thaw_notify (G_OBJECT (config));
 }
 
@@ -443,7 +468,7 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 {
 	NMSettingIPConfig *s_ip4;
 	guint32 gateway;
-	guint naddresses, nroutes, nnameservers, nsearches;
+	guint naddresses, nroutes, nnameservers, nsearches, noptions;
 	const char *method = NULL;
 	int i;
 	gint64 route_metric;
@@ -462,6 +487,7 @@ nm_ip4_config_create_setting (const NMIP4Config *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);
 
 	/* Addresses */
@@ -537,6 +563,12 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 		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);
+
+		nm_setting_ip_config_add_dns_option (s_ip4, option);
+	}
+
 	return NM_SETTING (s_ip4);
 }
 
@@ -593,6 +625,12 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 			nm_ip4_config_add_search (dst, nm_ip4_config_get_search (src, i));
 	}
 
+	/* dns options */
+	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
+		for (i = 0; i < nm_ip4_config_get_num_dns_options (src); i++)
+			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));
@@ -635,8 +673,7 @@ _addresses_get_index (const NMIP4Config *self, const NMPlatformIP4Address *addr)
 	for (i = 0; i < priv->addresses->len; i++) {
 		const NMPlatformIP4Address *a = &g_array_index (priv->addresses, NMPlatformIP4Address, i);
 
-		if (addr->address == a->address &&
-		    addr->plen == a->plen)
+		if (addresses_are_duplicate (addr, a))
 			return (int) i;
 	}
 	return -1;
@@ -704,6 +741,21 @@ _searches_get_index (const NMIP4Config *self, const char *search)
 }
 
 static int
+_dns_options_get_index (const NMIP4Config *self, const char *option)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	guint i;
+
+	for (i = 0; i < priv->dns_options->len; i++) {
+		const char *s = g_ptr_array_index (priv->dns_options, i);
+
+		if (g_strcmp0 (option, s) == 0)
+			return (int) i;
+	}
+	return -1;
+}
+
+static int
 _nis_servers_get_index (const NMIP4Config *self, guint32 nis_server)
 {
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
@@ -798,6 +850,13 @@ nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 			nm_ip4_config_del_search (dst, idx);
 	}
 
+	/* dns options */
+	for (i = 0; i < nm_ip4_config_get_num_dns_options (src); i++) {
+		idx = _dns_options_get_index (dst, nm_ip4_config_get_dns_option (src, i));
+		if (idx >= 0)
+			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);
@@ -867,6 +926,7 @@ nm_ip4_config_intersect (NMIP4Config *dst, const NMIP4Config *src)
 
 	/* ignore domains */
 	/* ignore dns searches */
+	/* ignore dns options */
 	/* ignore NIS */
 	/* ignore WINS */
 
@@ -891,7 +951,7 @@ nm_ip4_config_intersect (NMIP4Config *dst, const NMIP4Config *src)
 gboolean
 nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes)
 {
-#ifndef G_DISABLE_ASSERT
+#if NM_MORE_ASSERTS
 	gboolean config_equal;
 #endif
 	gboolean has_minor_changes = FALSE, has_relevant_changes = FALSE, are_equal;
@@ -904,7 +964,7 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 	g_return_val_if_fail (dst != NULL, FALSE);
 	g_return_val_if_fail (src != dst, FALSE);
 
-#ifndef G_DISABLE_ASSERT
+#if NM_MORE_ASSERTS
 	config_equal = nm_ip4_config_equal (dst, src);
 #endif
 
@@ -913,6 +973,12 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 
 	g_object_freeze_notify (G_OBJECT (dst));
 
+	/* ifindex */
+	if (src_priv->ifindex != dst_priv->ifindex) {
+		dst_priv->ifindex = src_priv->ifindex;
+		has_minor_changes = TRUE;
+	}
+
 	/* never_default */
 	if (src_priv->never_default != dst_priv->never_default) {
 		dst_priv->never_default = src_priv->never_default;
@@ -942,7 +1008,8 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 			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)) {
+				if (   !addresses_are_duplicate (src_addr, dst_addr)
+				    || src_addr->peer_address != dst_addr->peer_address) {
 					has_relevant_changes = TRUE;
 					break;
 				}
@@ -1036,6 +1103,25 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_relevant_changes = TRUE;
 	}
 
+	/* dns options */
+	num = nm_ip4_config_get_num_dns_options (src);
+	are_equal = num == nm_ip4_config_get_num_dns_options (dst);
+	if (are_equal) {
+		for (i = 0; i < num; i++ ) {
+			if (g_strcmp0 (nm_ip4_config_get_dns_option (src, i),
+			               nm_ip4_config_get_dns_option (dst, i))) {
+				are_equal = FALSE;
+				break;
+			}
+		}
+	}
+	if (!are_equal) {
+		nm_ip4_config_reset_dns_options (dst);
+		for (i = 0; i < num; i++)
+			nm_ip4_config_add_dns_option (dst, nm_ip4_config_get_dns_option (src, i));
+		has_relevant_changes = TRUE;
+	}
+
 	/* mss */
 	if (src_priv->mss != dst_priv->mss) {
 		nm_ip4_config_set_mss (dst, src_priv->mss);
@@ -1096,9 +1182,11 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_minor_changes = TRUE;
 	}
 
+#if NM_MORE_ASSERTS
 	/* 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);
+	nm_assert (config_equal == !has_relevant_changes);
+#endif
 
 	g_object_thaw_notify (G_OBJECT (dst));
 
@@ -1114,17 +1202,20 @@ nm_ip4_config_dump (const NMIP4Config *config, const char *detail)
 	guint32 i, tmp;
 	const char *str;
 
-	g_return_if_fail (config != NULL);
-
 	g_message ("--------- NMIP4Config %p (%s)", config, detail);
 
-	str = nm_ip4_config_get_dbus_path (config);
+	if (config == NULL) {
+		g_message (" (null)");
+		return;
+	}
+
+	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (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)));
+		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)) {
@@ -1140,7 +1231,7 @@ nm_ip4_config_dump (const NMIP4Config *config, const char *detail)
 
 	/* 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)));
+		g_message ("     rt: %s", nm_platform_ip4_route_to_string (nm_ip4_config_get_route (config, i), NULL, 0));
 
 	/* domains */
 	for (i = 0; i < nm_ip4_config_get_num_domains (config); i++)
@@ -1150,6 +1241,11 @@ nm_ip4_config_dump (const NMIP4Config *config, const char *detail)
 	for (i = 0; i < nm_ip4_config_get_num_searches (config); i++)
 		g_message (" search: %s", nm_ip4_config_get_search (config, 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));
+
+
 	g_message ("    mss: %"G_GUINT32_FORMAT, nm_ip4_config_get_mss (config));
 	g_message ("    mtu: %"G_GUINT32_FORMAT, nm_ip4_config_get_mtu (config));
 
@@ -1176,12 +1272,22 @@ nm_ip4_config_destination_is_direct (const NMIP4Config *config, guint32 network,
 {
 	guint naddresses = nm_ip4_config_get_num_addresses (config);
 	int 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 && same_prefix (item->address, network, item->plen))
-			return TRUE;
+		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;
@@ -1229,6 +1335,16 @@ nm_ip4_config_unset_gateway (NMIP4Config *config)
 	}
 }
 
+/**
+ * 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)
 {
@@ -1289,7 +1405,7 @@ nm_ip4_config_add_address (NMIP4Config *config, const NMPlatformIP4Address *new)
 	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 (addresses_are_duplicate (item, new)) {
 			if (nm_platform_ip4_address_cmp (item, new) == 0)
 				return;
 
@@ -1356,16 +1472,7 @@ 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;
+	return _addresses_get_index (config, needle) >= 0;
 }
 
 /******************************************************************/
@@ -1401,6 +1508,7 @@ nm_ip4_config_add_route (NMIP4Config *config, const NMPlatformIP4Route *new)
 
 	g_return_if_fail (new != NULL);
 	g_return_if_fail (new->plen > 0);
+	g_assert (priv->ifindex);
 
 	for (i = 0; i < priv->routes->len; i++ ) {
 		NMPlatformIP4Route *item = &g_array_index (priv->routes, NMPlatformIP4Route, i);
@@ -1412,11 +1520,13 @@ nm_ip4_config_add_route (NMIP4Config *config, const NMPlatformIP4Route *new)
 			memcpy (item, new, sizeof (*item));
 			/* Restore highest priority source */
 			item->source = MAX (old_source, new->source);
+			item->ifindex = priv->ifindex;
 			goto NOTIFY;
 		}
 	}
 
 	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);
@@ -1480,28 +1590,6 @@ nm_ip4_config_get_direct_route_for_host (const NMIP4Config *config, guint32 host
 	return best_route;
 }
 
-const NMPlatformIP4Address *
-nm_ip4_config_get_subnet_for_host (const NMIP4Config *config, guint32 host)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	guint i;
-	NMPlatformIP4Address *subnet = NULL;
-
-	g_return_val_if_fail (host, NULL);
-
-	for (i = 0; i < priv->addresses->len; i++) {
-		NMPlatformIP4Address *item = &g_array_index (priv->addresses, NMPlatformIP4Address, i);
-
-		if (subnet && subnet->plen >= item->plen)
-			continue;
-		if (nm_utils_ip4_address_clear_host_address (host, item->plen) != nm_utils_ip4_address_clear_host_address (item->address, item->plen))
-			continue;
-		subnet = item;
-	}
-
-	return subnet;
-}
-
 /******************************************************************/
 
 void
@@ -1690,6 +1778,63 @@ nm_ip4_config_get_search (const NMIP4Config *config, guint i)
 /******************************************************************/
 
 void
+nm_ip4_config_reset_dns_options (NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	if (priv->dns_options->len != 0) {
+		g_ptr_array_set_size (priv->dns_options, 0);
+		_NOTIFY (config, PROP_DNS_OPTIONS);
+	}
+}
+
+void
+nm_ip4_config_add_dns_option (NMIP4Config *config, const char *new)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	int i;
+
+	g_return_if_fail (new != NULL);
+	g_return_if_fail (new[0] != '\0');
+
+	for (i = 0; i < priv->dns_options->len; i++)
+		if (!g_strcmp0 (g_ptr_array_index (priv->dns_options, i), new))
+			return;
+
+	g_ptr_array_add (priv->dns_options, g_strdup (new));
+	_NOTIFY (config, PROP_DNS_OPTIONS);
+}
+
+void
+nm_ip4_config_del_dns_option(NMIP4Config *config, guint i)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	g_return_if_fail (i < priv->dns_options->len);
+
+	g_ptr_array_remove_index (priv->dns_options, i);
+	_NOTIFY (config, PROP_DNS_OPTIONS);
+}
+
+guint32
+nm_ip4_config_get_num_dns_options (const NMIP4Config *config)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	return priv->dns_options->len;
+}
+
+const char *
+nm_ip4_config_get_dns_option (const NMIP4Config *config, guint i)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+	return g_ptr_array_index (priv->dns_options, i);
+}
+
+/******************************************************************/
+
+void
 nm_ip4_config_set_mss (NMIP4Config *config, guint32 mss)
 {
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
@@ -1900,6 +2045,7 @@ nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only
 			const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
 			hash_u32 (sum, address->address);
 			hash_u32 (sum, 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++) {
@@ -1934,6 +2080,12 @@ nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only
 		s = nm_ip4_config_get_search (config, 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);
+		g_checksum_update (sum, (const guint8 *) s, strlen (s));
+	}
+
 }
 
 /**
@@ -1981,13 +2133,17 @@ nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b)
 static void
 nm_ip4_config_init (NMIP4Config *config)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (config, NM_TYPE_IP4_CONFIG, NMIP4ConfigPrivate);
+	config->priv = priv;
 
 	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;
@@ -1996,15 +2152,15 @@ nm_ip4_config_init (NMIP4Config *config)
 static void
 finalize (GObject *object)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (object);
-
-	g_free (priv->path);
+	NMIP4Config *self = NM_IP4_CONFIG (object);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	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_ptr_array_unref (priv->dns_options);
 	g_array_unref (priv->nis);
 	g_free (priv->nis_domain);
 	g_array_unref (priv->wins);
@@ -2013,145 +2169,133 @@ finalize (GObject *object)
 }
 
 static void
-gvalue_destroy (gpointer data)
-{
-	GValue *value = (GValue *) data;
-
-	g_value_unset (value);
-	g_slice_free (GValue, value);
-}
-
-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);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
 
 	switch (prop_id) {
+	case PROP_IFINDEX:
+		g_value_set_int (value, priv->ifindex);
+		break;
 	case PROP_ADDRESS_DATA:
 		{
-			GPtrArray *addresses = g_ptr_array_new ();
+			GVariantBuilder array_builder, addr_builder;
 			int naddr = nm_ip4_config_get_num_addresses (config);
 			int i;
 
+			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
 			for (i = 0; i < naddr; i++) {
 				const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
-				GHashTable *addr_hash;
-				GValue *val;
 
-				addr_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gvalue_destroy);
-
-				val = g_slice_new0 (GValue);
-				g_value_init (val, G_TYPE_STRING);
-				g_value_set_string (val, nm_utils_inet4_ntop (address->address, NULL));
-				g_hash_table_insert (addr_hash, "address", val);
-
-				val = g_slice_new0 (GValue);
-				g_value_init (val, G_TYPE_UINT);
-				g_value_set_uint (val, address->plen);
-				g_hash_table_insert (addr_hash, "prefix", val);
+				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
+				g_variant_builder_add (&addr_builder, "{sv}",
+				                       "address",
+				                       g_variant_new_string (nm_utils_inet4_ntop (address->address, NULL)));
+				g_variant_builder_add (&addr_builder, "{sv}",
+				                       "prefix",
+				                       g_variant_new_uint32 (address->plen));
+				if (address->peer_address != address->address) {
+					g_variant_builder_add (&addr_builder, "{sv}",
+					                       "peer",
+					                       g_variant_new_string (nm_utils_inet4_ntop (address->peer_address, NULL)));
+				}
 
 				if (*address->label) {
-					val = g_slice_new0 (GValue);
-					g_value_init (val, G_TYPE_STRING);
-					g_value_set_string (val, address->label);
-					g_hash_table_insert (addr_hash, "label", val);
+					g_variant_builder_add (&addr_builder, "{sv}",
+					                       "label",
+					                       g_variant_new_string (address->label));
 				}
 
-				g_ptr_array_add (addresses, addr_hash);
+				g_variant_builder_add (&array_builder, "a{sv}", &addr_builder);
 			}
 
-			g_value_take_boxed (value, addresses);
+			g_value_take_variant (value, g_variant_builder_end (&array_builder));
 		}
 		break;
 	case PROP_ADDRESSES:
 		{
-			GPtrArray *addresses = g_ptr_array_new ();
+			GVariantBuilder array_builder;
 			int naddr = nm_ip4_config_get_num_addresses (config);
 			int i;
 
+			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aau"));
 			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);
-				guint32 gateway = i == 0 ? priv->gateway : 0;
+				guint32 dbus_addr[3];
 
-				g_array_append_val (array, address->address);
-				g_array_append_val (array, address->plen);
-				g_array_append_val (array, gateway);
+				dbus_addr[0] = address->address;
+				dbus_addr[1] = address->plen;
+				dbus_addr[2] = i == 0 ? priv->gateway : 0;
 
-				g_ptr_array_add (addresses, array);
+				g_variant_builder_add (&array_builder, "@au",
+				                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
+				                                                  dbus_addr, 3, sizeof (guint32)));
 			}
 
-			g_value_take_boxed (value, addresses);
+			g_value_take_variant (value, g_variant_builder_end (&array_builder));
 		}
 		break;
 	case PROP_ROUTE_DATA:
 		{
-			GPtrArray *routes = g_ptr_array_new ();
+			GVariantBuilder array_builder, route_builder;
 			guint nroutes = nm_ip4_config_get_num_routes (config);
 			int i;
 
+			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);
-				GHashTable *route_hash;
-				GValue *val;
-
-				route_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gvalue_destroy);
-
-				val = g_slice_new0 (GValue);
-				g_value_init (val, G_TYPE_STRING);
-				g_value_set_string (val, nm_utils_inet4_ntop (route->network, NULL));
-				g_hash_table_insert (route_hash, "dest", val);
-
-				val = g_slice_new0 (GValue);
-				g_value_init (val, G_TYPE_UINT);
-				g_value_set_uint (val, route->plen);
-				g_hash_table_insert (route_hash, "prefix", val);
 
+				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) {
-					val = g_slice_new0 (GValue);
-					g_value_init (val, G_TYPE_STRING);
-					g_value_set_string (val, nm_utils_inet4_ntop (route->gateway, NULL));
-					g_hash_table_insert (route_hash, "next-hop", val);
+					g_variant_builder_add (&route_builder, "{sv}",
+					                       "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));
 
-				val = g_slice_new0 (GValue);
-				g_value_init (val, G_TYPE_UINT);
-				g_value_set_uint (val, route->metric);
-				g_hash_table_insert (route_hash, "metric", val);
-
-				g_ptr_array_add (routes, route_hash);
+				g_variant_builder_add (&array_builder, "a{sv}", &route_builder);
 			}
 
-			g_value_take_boxed (value, routes);
+			g_value_take_variant (value, g_variant_builder_end (&array_builder));
 		}
 		break;
 	case PROP_ROUTES:
 		{
-			GPtrArray *routes = g_ptr_array_new ();
+			GVariantBuilder array_builder;
 			guint nroutes = nm_ip4_config_get_num_routes (config);
 			int 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);
-				GArray *array;
+				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;
 
-				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);
+				dbus_route[0] = route->network;
+				dbus_route[1] = route->plen;
+				dbus_route[2] = route->gateway;
+				dbus_route[3] = route->metric;
 
-				g_ptr_array_add (routes, array);
+				g_variant_builder_add (&array_builder, "@au",
+				                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
+				                                                  dbus_route, 4, sizeof (guint32)));
 			}
 
-			g_value_take_boxed (value, routes);
+			g_value_take_variant (value, g_variant_builder_end (&array_builder));
 		}
 		break;
 	case PROP_GATEWAY:
@@ -2161,16 +2305,46 @@ get_property (GObject *object, guint prop_id,
 			g_value_set_string (value, NULL);
 		break;
 	case PROP_NAMESERVERS:
-		g_value_set_boxed (value, priv->nameservers);
+		g_value_take_variant (value,
+		                      g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
+		                                                 priv->nameservers->data,
+		                                                 priv->nameservers->len,
+		                                                 sizeof (guint32)));
 		break;
 	case PROP_DOMAINS:
-		g_value_set_boxed (value, priv->domains);
+		nm_utils_g_value_set_strv (value, priv->domains);
 		break;
 	case PROP_SEARCHES:
-		g_value_set_boxed (value, priv->searches);
+		nm_utils_g_value_set_strv (value, priv->searches);
+		break;
+	case PROP_DNS_OPTIONS:
+		nm_utils_g_value_set_strv (value, priv->dns_options);
 		break;
 	case PROP_WINS_SERVERS:
-		g_value_set_boxed (value, priv->wins);
+		g_value_take_variant (value,
+		                      g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
+		                                                 priv->wins->data,
+		                                                 priv->wins->len,
+		                                                 sizeof (guint32)));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+set_property (GObject *object,
+              guint prop_id,
+              const GValue *value,
+              GParamSpec *pspec)
+{
+	NMIP4Config *self = NM_IP4_CONFIG (object);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_IFINDEX:
+		priv->ifindex = g_value_get_int (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2182,61 +2356,83 @@ static void
 nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
+	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (config_class);
 
 	g_type_class_add_private (config_class, sizeof (NMIP4ConfigPrivate));
 
+	exported_object_class->export_path = NM_DBUS_PATH "/IP4Config/%u";
+
 	object_class->get_property = get_property;
+	object_class->set_property = set_property;
 	object_class->finalize = finalize;
 
+	obj_properties[PROP_IFINDEX] =
+		g_param_spec_int (NM_IP4_CONFIG_IFINDEX, "", "",
+		                  -1, G_MAXINT, -1,
+		                  G_PARAM_READWRITE |
+		                  G_PARAM_CONSTRUCT_ONLY |
+		                  G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_ADDRESS_DATA] =
-		 g_param_spec_boxed (NM_IP4_CONFIG_ADDRESS_DATA, "", "",
-		                     DBUS_TYPE_NM_IP_ADDRESSES,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS);
+		g_param_spec_variant (NM_IP4_CONFIG_ADDRESS_DATA, "", "",
+		                      G_VARIANT_TYPE ("aa{sv}"),
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_ADDRESSES] =
-		g_param_spec_boxed (NM_IP4_CONFIG_ADDRESSES, "", "",
-		                    DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT,
-		                    G_PARAM_READABLE |
-		                    G_PARAM_STATIC_STRINGS);
+		g_param_spec_variant (NM_IP4_CONFIG_ADDRESSES, "", "",
+		                      G_VARIANT_TYPE ("aau"),
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_ROUTE_DATA] =
-		 g_param_spec_boxed (NM_IP4_CONFIG_ROUTE_DATA, "", "",
-		                     DBUS_TYPE_NM_IP_ROUTES,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS);
+		g_param_spec_variant (NM_IP4_CONFIG_ROUTE_DATA, "", "",
+		                      G_VARIANT_TYPE ("aa{sv}"),
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_ROUTES] =
-		g_param_spec_boxed (NM_IP4_CONFIG_ROUTES, "", "",
-		                    DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT,
-		                    G_PARAM_READABLE |
-		                    G_PARAM_STATIC_STRINGS);
+		g_param_spec_variant (NM_IP4_CONFIG_ROUTES, "", "",
+		                      G_VARIANT_TYPE ("aau"),
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_GATEWAY] =
 		g_param_spec_string (NM_IP4_CONFIG_GATEWAY, "", "",
 		                     NULL,
 		                     G_PARAM_READABLE |
 		                     G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_NAMESERVERS] =
-		 g_param_spec_boxed (NM_IP4_CONFIG_NAMESERVERS, "", "",
-		                     DBUS_TYPE_G_UINT_ARRAY,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS);
+		g_param_spec_variant (NM_IP4_CONFIG_NAMESERVERS, "", "",
+		                      G_VARIANT_TYPE ("au"),
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_DOMAINS] =
-		 g_param_spec_boxed (NM_IP4_CONFIG_DOMAINS, "", "",
-		                     DBUS_TYPE_G_ARRAY_OF_STRING,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS);
+		g_param_spec_boxed (NM_IP4_CONFIG_DOMAINS, "", "",
+		                    G_TYPE_STRV,
+		                    G_PARAM_READABLE |
+		                    G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_SEARCHES] =
-		 g_param_spec_boxed (NM_IP4_CONFIG_SEARCHES, "", "",
-		                     DBUS_TYPE_G_ARRAY_OF_STRING,
+		g_param_spec_boxed (NM_IP4_CONFIG_SEARCHES, "", "",
+		                    G_TYPE_STRV,
+		                    G_PARAM_READABLE |
+		                    G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_DNS_OPTIONS] =
+		 g_param_spec_boxed (NM_IP4_CONFIG_DNS_OPTIONS, "", "",
+		                     G_TYPE_STRV,
 		                     G_PARAM_READABLE |
 		                     G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_WINS_SERVERS] =
-		 g_param_spec_boxed (NM_IP4_CONFIG_WINS_SERVERS, "", "",
-		                     DBUS_TYPE_G_UINT_ARRAY,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS);
+		g_param_spec_variant (NM_IP4_CONFIG_WINS_SERVERS, "", "",
+		                      G_VARIANT_TYPE ("au"),
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
 
-	nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
-	                                        G_TYPE_FROM_CLASS (config_class),
-	                                        &dbus_glib_nm_ip4_config_object_info);
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (config_class),
+	                                        NMDBUS_TYPE_IP4_CONFIG_SKELETON,
+	                                        NULL);
 }