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.

This allows NetworkManager to repopulate resolv.conf if anything changes.
For example, if the system does not define a persistent hostname, then
when a device which has generated a connection activates, a hostname
lookup will be performed.  The results of that lookup may change resolv.conf,
and thus NetworkManager must rewrite resolv.conf.  Without capturing
DNS information at startup when generating connections, an empty
resolv.conf would be written.
This commit is contained in:
Dan Williams 2013-11-19 22:28:36 -06:00
parent fab6260bfa
commit 12d96c30f2
10 changed files with 425 additions and 10 deletions

1
.gitignore vendored
View file

@ -184,6 +184,7 @@ valgrind-*.log
/src/tests/test-ip6-config
/src/tests/test-policy-hosts
/src/tests/test-wifi-ap-utils
/src/tests/test-resolvconf-capture
/src/dhcp-manager/tests/test-dhcp-dhclient
/src/config/tests/test-config

View file

@ -25,6 +25,7 @@
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <resolv.h>
#include "NetworkManagerUtils.h"
#include "nm-utils.h"
@ -611,3 +612,50 @@ nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id)
return g_strdup_printf ("%s.%d", parent_iface, vlan_id);
}
/**
* nm_utils_read_resolv_conf_nameservers():
* @rc_contents: contents of a resolv.conf; or %NULL to read /etc/resolv.conf
*
* Reads all nameservers out of @rc_contents or /etc/resolv.conf and returns
* them.
*
* Returns: a #GPtrArray of 'char *' elements of each nameserver line from
* @contents or resolv.conf
*/
GPtrArray *
nm_utils_read_resolv_conf_nameservers (const char *rc_contents)
{
GPtrArray *nameservers = NULL;
char *contents = NULL;
char **lines, **iter;
char *p;
if (rc_contents)
contents = g_strdup (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++) {
if (!g_str_has_prefix (*iter, "nameserver"))
continue;
p = *iter + strlen ("nameserver");
if (!g_ascii_isspace (*p++))
continue;
/* Skip intermediate whitespace */
while (g_ascii_isspace (*p))
p++;
g_strchomp (p);
g_ptr_array_add (nameservers, g_strdup (p));
}
g_strfreev (lines);
g_free (contents);
return nameservers;
}

View file

@ -84,4 +84,6 @@ void nm_utils_complete_generic (NMConnection *connection,
char *nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id);
GPtrArray *nm_utils_read_resolv_conf_nameservers (const char *rc_contents);
#endif /* NETWORK_MANAGER_UTILS_H */

View file

@ -357,7 +357,7 @@ static void link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *
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);
@ -6503,7 +6503,7 @@ capture_lease_config (NMDevice *device,
}
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;
@ -6515,10 +6515,10 @@ update_ip_config (NMDevice *self, gboolean capture_dhcp)
/* 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);
}
@ -6532,7 +6532,7 @@ update_ip_config (NMDevice *self, gboolean capture_dhcp)
/* 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 */

View file

@ -29,6 +29,7 @@
#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)
@ -111,6 +112,52 @@ same_prefix (guint32 address1, guint32 address2, int plen)
/******************************************************************/
/**
* 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 IPv6 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)
{
GPtrArray *read_ns;
guint i, j;
gboolean changed = FALSE;
g_return_val_if_fail (nameservers != NULL, FALSE);
read_ns = nm_utils_read_resolv_conf_nameservers (rc_contents);
if (!read_ns)
return FALSE;
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 duplicates */
for (j = 0; j < nameservers->len; j++) {
if (g_array_index (nameservers, guint32, j) == ns)
break;
}
if (j == nameservers->len) {
g_array_append_val (nameservers, ns);
changed = TRUE;
}
}
g_ptr_array_unref (read_ns);
return changed;
}
static gboolean
addresses_are_duplicate (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b, gboolean consider_plen)
{
@ -125,12 +172,13 @@ routes_are_duplicate (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b,
}
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;
gboolean has_gateway = FALSE;
/* Slaves have no IP configuration */
if (nm_platform_link_get_master (ifindex) > 0)
@ -154,12 +202,21 @@ nm_ip4_config_capture (int ifindex)
priv->gateway = route->gateway;
gateway_changed = TRUE;
}
has_gateway = TRUE;
/* 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 && 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);

View file

@ -59,7 +59,7 @@ 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);
@ -144,4 +144,10 @@ guint32 nm_ip4_config_get_mtu (const NMIP4Config *config);
void nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only);
gboolean nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b);
/******************************************************/
/* Testing-only functions */
gboolean nm_ip4_config_capture_resolv_conf (GArray *nameservers,
const char *rc_contents);
#endif /* NM_IP4_CONFIG_H */

View file

@ -30,6 +30,7 @@
#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)
@ -111,6 +112,54 @@ same_prefix (const struct in6_addr *address1, const struct in6_addr *address2, i
/******************************************************************/
/**
* nm_ip6_config_capture_resolv_conf():
* @nameservers: array of struct in6_addr
* @rc_contents: the contents of a resolv.conf or %NULL to read /etc/resolv.conf
*
* Reads all resolv.conf IPv6 nameservers and adds them to @nameservers.
*
* Returns: %TRUE if nameservers were added, %FALSE if @nameservers is unchanged
*/
gboolean
nm_ip6_config_capture_resolv_conf (GArray *nameservers,
const char *rc_contents)
{
GPtrArray *read_ns;
guint i, j;
gboolean changed = FALSE;
g_return_val_if_fail (nameservers != NULL, FALSE);
read_ns = nm_utils_read_resolv_conf_nameservers (rc_contents);
if (!read_ns)
return FALSE;
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;
/* Ignore duplicates */
for (j = 0; j < nameservers->len; j++) {
struct in6_addr *t = &g_array_index (nameservers, struct in6_addr, j);
if (IN6_ARE_ADDR_EQUAL (t, &ns))
break;
}
if (j == nameservers->len) {
g_array_append_val (nameservers, ns);
changed = TRUE;
}
}
g_ptr_array_unref (read_ns);
return changed;
}
static gboolean
addresses_are_duplicate (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b, gboolean consider_plen)
{
@ -125,12 +174,13 @@ routes_are_duplicate (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b,
}
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;
gboolean has_gateway = FALSE;
/* Slaves have no IP configuration */
if (nm_platform_link_get_master (ifindex) > 0)
@ -154,12 +204,21 @@ nm_ip6_config_capture (int ifindex)
priv->gateway = route->gateway;
gateway_changed = TRUE;
}
has_gateway = TRUE;
/* 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 && has_gateway && capture_resolv_conf) {
if (nm_ip6_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);

View file

@ -58,7 +58,7 @@ 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 capture_resolv_conf);
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);
@ -123,4 +123,10 @@ const struct in6_addr *nm_ip6_config_get_ptp_address (const NMIP6Config *config)
void nm_ip6_config_hash (const NMIP6Config *config, GChecksum *sum, gboolean dns_only);
gboolean nm_ip6_config_equal (const NMIP6Config *a, const NMIP6Config *b);
/******************************************************/
/* Testing-only functions */
gboolean nm_ip6_config_capture_resolv_conf (GArray *nameservers,
const char *rc_contents);
#endif /* NM_IP6_CONFIG_H */

View file

@ -16,7 +16,8 @@ noinst_PROGRAMS = \
test-wifi-ap-utils \
test-ip4-config \
test-ip6-config \
test-dcb
test-dcb \
test-resolvconf-capture
####### DHCP options test #######
@ -71,6 +72,14 @@ test_dcb_SOURCES = \
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
@ -84,4 +93,5 @@ check-local: test-dhcp-options test-policy-hosts test-wifi-ap-utils test-ip4-con
$(abs_builddir)/test-ip4-config
$(abs_builddir)/test-ip6-config
$(abs_builddir)/test-dcb
$(abs_builddir)/test-resolvconf-capture

View file

@ -0,0 +1,226 @@
/* -*- 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));
g_assert (nm_ip4_config_capture_resolv_conf (ns4, "") == FALSE);
g_assert_cmpint (ns4->len, ==, 0);
g_assert (nm_ip6_config_capture_resolv_conf (ns6, "") == FALSE);
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";
g_assert (nm_ip4_config_capture_resolv_conf (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");
g_array_free (ns4, TRUE);
}
static void
test_capture_dup4 (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.1\r\n"
"nameserver 4.2.2.2\r\n";
/* Check that duplicates are ignored */
g_assert (nm_ip4_config_capture_resolv_conf (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");
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";
g_assert (nm_ip6_config_capture_resolv_conf (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_dup6 (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::8888\r\n"
"nameserver 2001:4860:4860::8844\r\n";
/* Check that duplicates are ignored */
g_assert (nm_ip6_config_capture_resolv_conf (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_addr4_with_6 (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 2001:4860:4860::8888\r\n";
g_assert (nm_ip4_config_capture_resolv_conf (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");
g_array_free (ns4, TRUE);
}
static void
test_capture_addr6_with_4 (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 4.2.2.1\r\n"
"nameserver 2001:4860:4860::8888\r\n"
"nameserver 2001:4860:4860::8844\r\n";
g_assert (nm_ip6_config_capture_resolv_conf (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_format (void)
{
GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
const char *rc =
" nameserver 4.2.2.1\r\n" /* bad */
"nameserver4.2.2.1\r\n" /* bad */
"nameserver 4.2.2.3\r" /* good */
"nameserver\t\t4.2.2.4\r\n" /* good */
"nameserver 4.2.2.5\t\t\r\n"; /* good */
g_assert (nm_ip4_config_capture_resolv_conf (ns4, rc));
g_assert_cmpint (ns4->len, ==, 3);
assert_dns4_entry (ns4, 0, "4.2.2.3");
assert_dns4_entry (ns4, 1, "4.2.2.4");
assert_dns4_entry (ns4, 2, "4.2.2.5");
g_array_free (ns4, 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/dup4", test_capture_dup4);
g_test_add_func ("/resolvconf-capture/basic6", test_capture_basic6);
g_test_add_func ("/resolvconf-capture/dup6", test_capture_dup6);
g_test_add_func ("/resolvconf-capture/addr4-with-6", test_capture_addr4_with_6);
g_test_add_func ("/resolvconf-capture/addr6-with-4", test_capture_addr6_with_4);
g_test_add_func ("/resolvconf-capture/format", test_capture_format);
return g_test_run ();
}