Blob Blame History Raw
From 00009b2a238555b6777aab79b3190a8a46df56d2 Mon Sep 17 00:00:00 2001
From: Dan Williams <dcbw@redhat.com>
Date: Tue, 19 Nov 2013 22:28:36 -0600
Subject: [PATCH] core: capture DNS configuration from resolv.conf when
 generating connections

If the interface who's IP configuration is being captured has the default
route, then read DNS servers from resolv.conf into the NMIP[4|6]Config.
---
 .gitignore                          |   1 +
 src/NetworkManagerUtils.c           | 138 ++++++++++++++++++++++++++++
 src/NetworkManagerUtils.h           |   8 ++
 src/devices/nm-device.c             |  10 +-
 src/nm-ip4-config.c                 |  13 ++-
 src/nm-ip4-config.h                 |   2 +-
 src/nm-ip6-config.c                 |  11 ++-
 src/nm-ip6-config.h                 |   2 +-
 src/tests/Makefile.am               |  12 ++-
 src/tests/test-resolvconf-capture.c | 176 ++++++++++++++++++++++++++++++++++++
 10 files changed, 362 insertions(+), 11 deletions(-)
 create mode 100644 src/tests/test-resolvconf-capture.c

diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index c2cf5e7..5a5fb7f 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -21,14 +21,15 @@
 
 #include <glib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
+#include <resolv.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-logging.h"
 #include "nm-device.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-ip4-config.h"
@@ -607,7 +608,136 @@ nm_utils_complete_generic (NMConnection *connection,
 char *
 nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id)
 {
 	/* Basically VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD */
 	return g_strdup_printf ("%s.%d", parent_iface, vlan_id);
 }
 
+static GPtrArray *
+read_nameservers (const char *test_rc_contents)
+{
+	GPtrArray *nameservers = NULL;
+	char *contents = NULL;
+	char **lines, **iter;
+	char *p;
+
+	if (test_rc_contents)
+		contents = g_strdup (test_rc_contents);
+	else {
+		if (!g_file_get_contents (_PATH_RESCONF, &contents, NULL, NULL))
+			return NULL;
+	}
+
+	nameservers = g_ptr_array_new_full (3, g_free);
+
+	lines = g_strsplit_set (contents, "\r\n", -1);
+	for (iter = lines; iter && *iter; iter++) {
+		if (!g_str_has_prefix (*iter, "nameserver"))
+			continue;
+		p = *iter + strlen ("nameserver");
+		while (g_ascii_isspace (*p))
+			p++;
+
+		g_ptr_array_add (nameservers, g_strdup (p));
+	}
+
+	g_free (contents);
+	g_strfreev (lines);
+	return nameservers;
+}
+
+#define IS_10(a)  ((ntohl (a) & 0xff000000) == 0x0a000000)
+#define IS_172(a) ((ntohl (a) & 0xfff00000) == 0xac100000)
+#define IS_192(a) ((ntohl (a) & 0xffff0000) == 0xc0a80000)
+
+/**
+ * nm_utils_capture_resolv_conf_ip4():
+ * @addresses: array of #NMPlatformIP4Address structures
+ * @nameservers: array of guint32
+ * @test_rc_contents: for testing; the contents of resolv.conf
+ *
+ * Reads all /etc/resolv.conf IPv4 nameservers and adds them to @nameservers,
+ * which checks to ensure that private-network nameservers are not added if
+ * @addresses does not contain an address in that private network.
+ */
+void
+nm_utils_capture_resolv_conf_ip4 (const GArray *addresses,
+                                  GArray *nameservers,
+                                  const char *test_rc_contents)
+{
+	GPtrArray *read_ns;
+	gboolean has_10 = FALSE, has_172 = FALSE, has_192 = FALSE;
+	guint i;
+
+	g_return_if_fail (nameservers != NULL);
+
+	read_ns = read_nameservers (test_rc_contents);
+	if (!read_ns)
+		return;
+
+	for (i = 0; addresses && i < addresses->len; i++) {
+		NMPlatformIP4Address *addr = &g_array_index (addresses, NMPlatformIP4Address, i);
+
+		if (IS_10 (addr->address))
+			has_10 = TRUE;
+		else if (IS_172 (addr->address))
+			has_172 = TRUE;
+		else if (IS_192 (addr->address))
+			has_192 = TRUE;
+	}
+
+	for (i = 0; i < read_ns->len; i++) {
+		const char *s = g_ptr_array_index (read_ns, i);
+		guint32 ns = 0;
+
+		if (!inet_pton (AF_INET, s, (void *) &ns) || !ns)
+			continue;
+
+		/* Ignore any private-network addresses that aren't in this
+		 * interface's address space.
+		 */
+		if (   (IS_10 (ns) && !has_10)
+		    || (IS_172 (ns) && !has_172)
+		    || (IS_192 (ns) && !has_192))
+			continue;
+
+		g_array_append_val (nameservers, ns);
+	}
+
+	g_ptr_array_unref (read_ns);
+}
+
+/**
+ * nm_utils_capture_resolv_conf_ip6():
+ * @addresses: array of #NMPlatformIP6Address structures
+ * @nameservers: array of struct in6_addr
+ * @test_rc_contents: for testing; the contents of resolv.conf
+ *
+ * Reads all /etc/resolv.conf IPv6 nameservers and adds them to @nameservers.
+ */
+void
+nm_utils_capture_resolv_conf_ip6 (const GArray *addresses,
+                                  GArray *nameservers,
+                                  const char *test_rc_contents)
+{
+	GPtrArray *read_ns;
+	guint i;
+
+	g_return_if_fail (nameservers != NULL);
+
+	read_ns = read_nameservers (test_rc_contents);
+	if (!read_ns)
+		return;
+
+	for (i = 0; i < read_ns->len; i++) {
+		const char *s = g_ptr_array_index (read_ns, i);
+		struct in6_addr ns = IN6ADDR_ANY_INIT;
+
+		if (!inet_pton (AF_INET6, s, (void *) &ns) || IN6_IS_ADDR_UNSPECIFIED (&ns))
+			continue;
+
+		g_array_append_val (nameservers, ns);
+	}
+
+	g_ptr_array_unref (read_ns);
+}
+
diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h
index 93ec111..cd30ece 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/NetworkManagerUtils.h
@@ -80,8 +80,16 @@ void nm_utils_complete_generic (NMConnection *connection,
                                 const GSList *existing,
                                 const char *format,
                                 const char *preferred,
                                 gboolean default_enable_ipv6);
 
 char *nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id);
 
+void nm_utils_capture_resolv_conf_ip4 (const GArray *addresses,
+                                       GArray *nameservers,
+                                       const char *test_rc_contents);
+
+void nm_utils_capture_resolv_conf_ip6 (const GArray *addresses,
+                                       GArray *nameservers,
+                                       const char *test_rc_contents);
+
 #endif /* NETWORK_MANAGER_UTILS_H */
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index cc678ce..f03ecbb 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -353,15 +353,15 @@ static void cp_connection_updated (NMConnectionProvider *cp, NMConnection *conne
 
 static const char *state_to_string (NMDeviceState state);
 
 static void link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformReason reason, NMDevice *device);
 static void check_carrier (NMDevice *device);
 
 static void nm_device_queued_ip_config_change_clear (NMDevice *self);
-static void update_ip_config (NMDevice *self, gboolean capture_dhcp);
+static void update_ip_config (NMDevice *self, gboolean initial);
 static void device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformReason reason, gpointer user_data);
 
 static void nm_device_slave_notify_enslave (NMDevice *dev, gboolean success);
 static void nm_device_slave_notify_release (NMDevice *dev, gboolean master_failed);
 
 static void addrconf6_start_with_link_ready (NMDevice *self);
 
@@ -6493,44 +6493,44 @@ capture_lease_config (NMDevice *device,
 		if (out_ip6_config && strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
 			/* FIXME: implement find_ip6_lease_config() */
 		}
 	}
 }
 
 static void
-update_ip_config (NMDevice *self, gboolean capture_dhcp)
+update_ip_config (NMDevice *self, gboolean initial)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex;
 	gboolean linklocal6_just_completed = FALSE;
 
 	ifindex = nm_device_get_ip_ifindex (self);
 	if (!ifindex)
 		return;
 
 	/* IPv4 */
 	g_clear_object (&priv->ext_ip4_config);
-	priv->ext_ip4_config = nm_ip4_config_capture (ifindex);
+	priv->ext_ip4_config = nm_ip4_config_capture (ifindex, initial);
 
 	if (priv->ext_ip4_config) {
-		if (capture_dhcp) {
+		if (initial) {
 			g_clear_object (&priv->dev_ip4_config);
 			capture_lease_config (self, priv->ext_ip4_config, &priv->dev_ip4_config, NULL, NULL);
 		}
 		if (priv->dev_ip4_config)
 			nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config);
 		if (priv->vpn4_config)
 			nm_ip4_config_subtract (priv->ext_ip4_config, priv->vpn4_config);
 
 		ip4_config_merge_and_apply (self, NULL, FALSE, NULL);
 	}
 
 	/* IPv6 */
 	g_clear_object (&priv->ext_ip6_config);
-	priv->ext_ip6_config = nm_ip6_config_capture (ifindex);
+	priv->ext_ip6_config = nm_ip6_config_capture (ifindex, initial);
 	if (priv->ext_ip6_config) {
 
 		/* Check this before modifying ext_ip6_config */
 		linklocal6_just_completed = priv->linklocal6_timeout_id &&
 		                            linklocal6_config_is_ready (priv->ext_ip6_config);
 
 		if (priv->ac_ip6_config)
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 5229ef9..e27a557 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -25,14 +25,15 @@
 
 #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"
 
 
 G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, G_TYPE_OBJECT)
 
 #define NM_IP4_CONFIG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IP4_CONFIG, NMIP4ConfigPrivate))
 
 typedef struct {
@@ -121,20 +122,20 @@ static gboolean
 routes_are_duplicate (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, gboolean consider_gateway_and_metric)
 {
 	return a->network == b->network && a->plen == b->plen &&
 	       (!consider_gateway_and_metric || (a->gateway == b->gateway && a->metric == b->metric));
 }
 
 NMIP4Config *
-nm_ip4_config_capture (int ifindex)
+nm_ip4_config_capture (int ifindex, gboolean capture_resolv_conf)
 {
 	NMIP4Config *config;
 	NMIP4ConfigPrivate *priv;
-	guint i;
 	gboolean gateway_changed = FALSE;
+	guint i;
 
 	/* Slaves have no IP configuration */
 	if (nm_platform_link_get_master (ifindex) > 0)
 		return NULL;
 
 	config = nm_ip4_config_new ();
 	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
@@ -156,14 +157,22 @@ nm_ip4_config_capture (int ifindex)
 			}
 			/* Remove the default route from the list */
 			g_array_remove_index (priv->routes, i);
 			break;
 		}
 	}
 
+	/* If the interface has the default route, and has IPv4 addresses, capture
+	 * nameservers from /etc/resolv.conf.
+	 */
+	if (priv->addresses->len && priv->gateway && capture_resolv_conf) {
+		nm_utils_capture_resolv_conf_ip4 (priv->addresses, 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 (gateway_changed)
 		_NOTIFY (config, PROP_GATEWAY);
 
 	return config;
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 3b2b250..4ecfb76 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -55,15 +55,15 @@ GType nm_ip4_config_get_type (void);
 NMIP4Config * nm_ip4_config_new (void);
 
 /* D-Bus integration */
 void nm_ip4_config_export (NMIP4Config *config);
 const char * nm_ip4_config_get_dbus_path (const NMIP4Config *config);
 
 /* Integration with nm-platform and nm-setting */
-NMIP4Config *nm_ip4_config_capture (int ifindex);
+NMIP4Config *nm_ip4_config_capture (int ifindex, gboolean capture_resolv_conf);
 gboolean nm_ip4_config_commit (const NMIP4Config *config, int ifindex, int priority);
 void nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIP4Config *setting);
 void nm_ip4_config_update_setting (const NMIP4Config *config, NMSettingIP4Config *setting);
 
 /* Utility functions */
 void nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src);
 void nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src);
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 7522164..726e658 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -26,14 +26,15 @@
 #include "nm-glib-compat.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-ip6-config-glue.h"
+#include "NetworkManagerUtils.h"
 
 G_DEFINE_TYPE (NMIP6Config, nm_ip6_config, G_TYPE_OBJECT)
 
 #define NM_IP6_CONFIG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IP6_CONFIG, NMIP6ConfigPrivate))
 
 typedef struct {
 	char *path;
@@ -121,15 +122,15 @@ static gboolean
 routes_are_duplicate (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b, gboolean consider_gateway_and_metric)
 {
 	return IN6_ARE_ADDR_EQUAL (&a->network, &b->network) && a->plen == b->plen &&
 	       (!consider_gateway_and_metric || (IN6_ARE_ADDR_EQUAL (&a->gateway, &b->gateway) && a->metric == b->metric));
 }
 
 NMIP6Config *
-nm_ip6_config_capture (int ifindex)
+nm_ip6_config_capture (int ifindex, gboolean capture_resolv_conf)
 {
 	NMIP6Config *config;
 	NMIP6ConfigPrivate *priv;
 	guint i;
 	gboolean gateway_changed = FALSE;
 
 	/* Slaves have no IP configuration */
@@ -156,14 +157,22 @@ nm_ip6_config_capture (int ifindex)
 			}
 			/* Remove the default route from the list */
 			g_array_remove_index (priv->routes, i);
 			break;
 		}
 	}
 
+	/* If the interface has the default route, and has IPv4 addresses, capture
+	 * nameservers from /etc/resolv.conf.
+	 */
+	if (priv->addresses->len && !IN6_IS_ADDR_UNSPECIFIED (&priv->gateway) && capture_resolv_conf) {
+		nm_utils_capture_resolv_conf_ip6 (priv->addresses, 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 (gateway_changed)
 		_NOTIFY (config, PROP_GATEWAY);
 
 	return config;
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 538490a..2b1ba8b 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -54,15 +54,15 @@ GType nm_ip6_config_get_type (void);
 NMIP6Config * nm_ip6_config_new (void);
 
 /* D-Bus integration */
 void nm_ip6_config_export (NMIP6Config *config);
 const char * nm_ip6_config_get_dbus_path (const NMIP6Config *config);
 
 /* Integration with nm-platform and nm-setting */
-NMIP6Config *nm_ip6_config_capture (int ifindex);
+NMIP6Config *nm_ip6_config_capture (int ifindex, gboolean initial);
 gboolean nm_ip6_config_commit (const NMIP6Config *config, int ifindex, int priority);
 void nm_ip6_config_merge_setting (NMIP6Config *config, NMSettingIP6Config *setting);
 void nm_ip6_config_update_setting (const NMIP6Config *config, NMSettingIP6Config *setting);
 
 /* Utility functions */
 void nm_ip6_config_merge (NMIP6Config *dst, const NMIP6Config *src);
 void nm_ip6_config_subtract (NMIP6Config *dst, const NMIP6Config *src);
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 9d17e9a..eaa9d51 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -12,15 +12,16 @@ AM_CPPFLAGS = \
 
 noinst_PROGRAMS = \
 	test-dhcp-options \
 	test-policy-hosts \
 	test-wifi-ap-utils \
 	test-ip4-config \
 	test-ip6-config \
-	test-dcb
+	test-dcb \
+	test-resolvconf-capture
 
 ####### DHCP options test #######
 
 test_dhcp_options_SOURCES = \
 	test-dhcp-options.c
 
 test_dhcp_options_CPPFLAGS = \
@@ -67,21 +68,30 @@ test_ip6_config_LDADD = \
 
 test_dcb_SOURCES = \
 	test-dcb.c
 
 test_dcb_LDADD = \
 	$(top_builddir)/src/libNetworkManager.la
 
+####### resolv.conf capture test #######
+
+test_resolvconf_capture_SOURCES = \
+	test-resolvconf-capture.c
+
+test_resolvconf_capture_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
 ####### secret agent interface test #######
 
 EXTRA_DIST = test-secret-agent.py
 
 ###########################################
 
 check-local: test-dhcp-options test-policy-hosts test-wifi-ap-utils test-ip4-config test-ip6-config
 	$(abs_builddir)/test-dhcp-options
 	$(abs_builddir)/test-policy-hosts
 	$(abs_builddir)/test-wifi-ap-utils
 	$(abs_builddir)/test-ip4-config
 	$(abs_builddir)/test-ip6-config
 	$(abs_builddir)/test-dcb
+	$(abs_builddir)/test-resolvconf-capture
 
diff --git a/src/tests/test-resolvconf-capture.c b/src/tests/test-resolvconf-capture.c
new file mode 100644
index 0000000..0343d17
--- /dev/null
+++ b/src/tests/test-resolvconf-capture.c
@@ -0,0 +1,176 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include "NetworkManagerUtils.h"
+#include "nm-platform.h"
+
+static void
+test_capture_empty (void)
+{
+	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
+	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
+
+	nm_utils_capture_resolv_conf_ip4 (NULL, ns4, "");
+	g_assert_cmpint (ns4->len, ==, 0);
+
+	nm_utils_capture_resolv_conf_ip6 (NULL, ns6, "");
+	g_assert_cmpint (ns6->len, ==, 0);
+
+	g_array_free (ns4, TRUE);
+	g_array_free (ns6, TRUE);
+}
+
+static void
+assert_dns4_entry (const GArray *a, guint i, const char *s)
+{
+	guint32 n, m;
+
+	g_assert (inet_aton (s, (void *) &n) != 0);
+	m = g_array_index (a, guint32, i);
+	g_assert_cmpint (m, ==, n);
+}
+
+static void
+assert_dns6_entry (const GArray *a, guint i, const char *s)
+{
+	struct in6_addr n = IN6ADDR_ANY_INIT;
+	struct in6_addr *m;
+
+	g_assert (inet_pton (AF_INET6, s, (void *) &n) == 1);
+	m = &g_array_index (a, struct in6_addr, i);
+	g_assert (IN6_ARE_ADDR_EQUAL (&n, m));
+}
+
+static void
+test_capture_basic4 (void)
+{
+	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
+	const char *rc =
+"# neato resolv.conf\r\n"
+"domain foobar.com\r\n"
+"search foobar.com\r\n"
+"nameserver 4.2.2.1\r\n"
+"nameserver 4.2.2.2\r\n"
+"nameserver 192.168.1.1\r\n";
+
+	nm_utils_capture_resolv_conf_ip4 (NULL, ns4, rc);
+	g_assert_cmpint (ns4->len, ==, 2);
+	assert_dns4_entry (ns4, 0, "4.2.2.1");
+	assert_dns4_entry (ns4, 1, "4.2.2.2");
+	/* 192.x not present because no addresses given */
+
+	g_array_free (ns4, TRUE);
+}
+
+static void
+test_capture_basic6 (void)
+{
+	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
+	const char *rc =
+"# neato resolv.conf\r\n"
+"domain foobar.com\r\n"
+"search foobar.com\r\n"
+"nameserver 2001:4860:4860::8888\r\n"
+"nameserver 2001:4860:4860::8844\r\n";
+
+	nm_utils_capture_resolv_conf_ip6 (NULL, ns6, rc);
+	g_assert_cmpint (ns6->len, ==, 2);
+	assert_dns6_entry (ns6, 0, "2001:4860:4860::8888");
+	assert_dns6_entry (ns6, 1, "2001:4860:4860::8844");
+
+	g_array_free (ns6, TRUE);
+}
+
+static void
+test_capture_private_net4 (void)
+{
+	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
+	const char *rc =
+"# neato resolv.conf\r\n"
+"domain foobar.com\r\n"
+"search foobar.com\r\n"
+"nameserver 4.2.2.1\r\n"
+"nameserver 4.2.2.2\r\n"
+"nameserver 10.2.3.4\r\n"
+"nameserver 172.17.6.3\r\n"
+"nameserver 192.168.1.1\r\n";
+	GArray *addrs = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Address));
+	NMPlatformIP4Address a, *b;
+
+	memset (&a, 0, sizeof (a));
+	a.address = htonl (0x0a000006);
+	a.plen = 8;
+	g_array_append_val (addrs, a);
+
+	/* 10.x test */
+	nm_utils_capture_resolv_conf_ip4 (addrs, ns4, rc);
+	g_assert_cmpint (ns4->len, ==, 3);
+	assert_dns4_entry (ns4, 0, "4.2.2.1");
+	assert_dns4_entry (ns4, 1, "4.2.2.2");
+	assert_dns4_entry (ns4, 2, "10.2.3.4");
+
+	/* 172.x test */
+	b = &g_array_index (addrs, NMPlatformIP4Address, 0);
+	b->address = htonl (0xac100003);
+
+	g_array_set_size (ns4, 0);
+	nm_utils_capture_resolv_conf_ip4 (addrs, ns4, rc);
+	g_assert_cmpint (ns4->len, ==, 3);
+	assert_dns4_entry (ns4, 0, "4.2.2.1");
+	assert_dns4_entry (ns4, 1, "4.2.2.2");
+	assert_dns4_entry (ns4, 2, "172.17.6.3");
+
+	/* 192.x test */
+	b = &g_array_index (addrs, NMPlatformIP4Address, 0);
+	b->address = htonl (0xc0a80010);
+
+	g_array_set_size (ns4, 0);
+	nm_utils_capture_resolv_conf_ip4 (addrs, ns4, rc);
+	g_assert_cmpint (ns4->len, ==, 3);
+	assert_dns4_entry (ns4, 0, "4.2.2.1");
+	assert_dns4_entry (ns4, 1, "4.2.2.2");
+	assert_dns4_entry (ns4, 2, "192.168.1.1");
+
+	g_array_free (ns4, TRUE);
+	g_array_free (addrs, TRUE);
+}
+
+/*******************************************/
+
+int
+main (int argc, char **argv)
+{
+	g_test_init (&argc, &argv, NULL);
+
+#if !GLIB_CHECK_VERSION (2,36,0)
+	g_type_init ();
+#endif
+
+	g_test_add_func ("/resolvconf-capture/empty", test_capture_empty);
+	g_test_add_func ("/resolvconf-capture/basic4", test_capture_basic4);
+	g_test_add_func ("/resolvconf-capture/basic6", test_capture_basic6);
+	g_test_add_func ("/resolvconf-capture/private-net4", test_capture_private_net4);
+
+	return g_test_run ();
+}
+
-- 
1.8.3.1