core: add support for reading global DNS configuration from keyfile

Add to the NMConfigData object information about global DNS
configuration, which is loaded from user or internal keyfile upon
object construction.
This commit is contained in:
Beniamino Galvani
2015-06-15 09:01:42 +02:00
parent bd27c110a3
commit 55c204b9a3
9 changed files with 457 additions and 0 deletions

View File

@@ -72,10 +72,25 @@ typedef struct {
char *dns_mode;
char *rc_manager;
NMGlobalDnsConfig *global_dns;
/* mutable field */
char *value_cached;
} NMConfigDataPrivate;
struct _NMGlobalDnsDomain {
char *name;
char **servers;
char **options;
};
struct _NMGlobalDnsConfig {
char **searches;
char **options;
GHashTable *domains;
char **domain_list;
gboolean internal;
};
enum {
PROP_0,
@@ -91,6 +106,7 @@ enum {
LAST_PROP
};
G_DEFINE_TYPE (NMConfigData, nm_config_data, G_TYPE_OBJECT)
#define NM_CONFIG_DATA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_CONFIG_DATA, NMConfigDataPrivate))
@@ -534,6 +550,279 @@ nm_config_data_log (const NMConfigData *self, const char *prefix)
/************************************************************************/
const char *const *
nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns)
{
g_return_val_if_fail (dns, NULL);
return (const char *const *) dns->searches;
}
const char *const *
nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns)
{
g_return_val_if_fail (dns, NULL);
return (const char *const *) dns->options;
}
guint
nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns)
{
g_return_val_if_fail (dns, 0);
g_return_val_if_fail (dns->domains, 0);
return g_hash_table_size (dns->domains);
}
NMGlobalDnsDomain *
nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns, guint i)
{
NMGlobalDnsDomain *domain;
g_return_val_if_fail (dns, NULL);
g_return_val_if_fail (dns->domains, NULL);
g_return_val_if_fail (dns->domain_list, NULL);
g_return_val_if_fail (i < g_strv_length (dns->domain_list), NULL);
domain = g_hash_table_lookup (dns->domains, dns->domain_list[i]);
g_return_val_if_fail (domain, NULL);
return domain;
}
NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns, const char *name)
{
g_return_val_if_fail (dns, NULL);
g_return_val_if_fail (dns->domains, NULL);
g_return_val_if_fail (name, NULL);
return g_hash_table_lookup (dns->domains, name);
}
const char *
nm_global_dns_domain_get_name (const NMGlobalDnsDomain *domain)
{
g_return_val_if_fail (domain, NULL);
return (const char *) domain->name;
}
const char *const *
nm_global_dns_domain_get_servers (const NMGlobalDnsDomain *domain)
{
g_return_val_if_fail (domain, NULL);
return (const char *const *) domain->servers;
}
const char *const *
nm_global_dns_domain_get_options (const NMGlobalDnsDomain *domain)
{
g_return_val_if_fail (domain, NULL);
return (const char *const *) domain->options;
}
gboolean
nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns)
{
return dns->internal;
}
gboolean
nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns)
{
g_return_val_if_fail (dns, TRUE);
g_return_val_if_fail (dns->domains, TRUE);
return (!dns->searches || g_strv_length (dns->searches) == 0)
&& (!dns->options || g_strv_length (dns->options) == 0)
&& g_hash_table_size (dns->domains) == 0;
}
static void
global_dns_domain_free (NMGlobalDnsDomain *domain)
{
if (domain) {
g_free (domain->name);
g_strfreev (domain->servers);
g_strfreev (domain->options);
g_free (domain);
}
}
void
nm_global_dns_config_free (NMGlobalDnsConfig *conf)
{
if (conf) {
g_strfreev (conf->searches);
g_strfreev (conf->options);
g_free (conf->domain_list);
g_hash_table_unref (conf->domains);
g_free (conf);
}
}
NMGlobalDnsConfig *
nm_config_data_get_global_dns_config (const NMConfigData *self)
{
g_return_val_if_fail (NM_IS_CONFIG_DATA (self), NULL);
return NM_CONFIG_DATA_GET_PRIVATE (self)->global_dns;
}
static void
global_dns_config_update_domain_list (NMGlobalDnsConfig *dns)
{
guint length;
g_free (dns->domain_list);
dns->domain_list = (char **) g_hash_table_get_keys_as_array (dns->domains, &length);
}
static NMGlobalDnsConfig *
load_global_dns (GKeyFile *keyfile, gboolean internal)
{
NMGlobalDnsConfig *conf;
char *group, *domain_prefix;
gs_strfreev char **groups = NULL;
int g, i, j, domain_prefix_len;
gboolean default_found = FALSE;
char **strv;
group = internal
? NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS
: NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS;
domain_prefix = internal
? NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN
: NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN;
domain_prefix_len = strlen (domain_prefix);
if (!keyfile || !nm_config_keyfile_get_boolean (keyfile, group, NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_ENABLE, FALSE))
return NULL;
conf = g_malloc0 (sizeof (NMGlobalDnsConfig));
conf->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) global_dns_domain_free);
strv = g_key_file_get_string_list (keyfile, group, "searches", NULL, NULL);
if (strv)
conf->searches = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
strv = g_key_file_get_string_list (keyfile, group, "options", NULL, NULL);
if (strv) {
_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
for (i = 0, j = 0; strv[i]; i++) {
if (_nm_utils_dns_option_validate (strv[i], NULL, NULL, TRUE, NULL))
strv[j++] = strv[i];
else
g_free (strv[i]);
}
strv[j] = NULL;
conf->options = strv;
}
groups = g_key_file_get_groups (keyfile, NULL);
for (g = 0; groups[g]; g++) {
char *name;
char **servers = NULL, **options = NULL;
NMGlobalDnsDomain *domain;
if ( !g_str_has_prefix (groups[g], domain_prefix)
|| !groups[g][domain_prefix_len])
continue;
strv = g_key_file_get_string_list (keyfile, groups[g], "servers", NULL, NULL);
if (strv) {
_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
for (i = 0, j = 0; strv[i]; i++) {
if ( nm_utils_ipaddr_valid (AF_INET, strv[i])
|| nm_utils_ipaddr_valid (AF_INET6, strv[i]))
strv[j++] = strv[i];
else
g_free (strv[i]);
}
if (j) {
strv[j] = NULL;
servers = strv;
}
else
g_free (strv);
}
if (!servers)
continue;
strv = g_key_file_get_string_list (keyfile, groups[g], "options", NULL, NULL);
if (strv)
options = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
name = strdup (&groups[g][domain_prefix_len]);
domain = g_malloc0 (sizeof (NMGlobalDnsDomain));
domain->name = name;
domain->servers = servers;
domain->options = options;
g_hash_table_insert (conf->domains, strdup (name), domain);
if (!strcmp (name, "*"))
default_found = TRUE;
}
if (!default_found) {
nm_log_dbg (LOGD_CORE, "%s global DNS configuration is missing default domain, ignore it",
internal ? "internal" : "user");
nm_global_dns_config_free (conf);
return NULL;
}
conf->internal = internal;
global_dns_config_update_domain_list (conf);
return conf;
}
static gboolean
global_dns_equal (NMGlobalDnsConfig *old, NMGlobalDnsConfig *new)
{
NMGlobalDnsDomain *domain_old, *domain_new;
gpointer key, value_old, value_new;
GHashTableIter iter;
if (old == new)
return TRUE;
if (!old || !new)
return FALSE;
if ( !_nm_utils_strv_equal (old->options, new->options)
|| !_nm_utils_strv_equal (old->searches, new->searches))
return FALSE;
if ((!old->domains || !new->domains) && old->domains != new->domains)
return FALSE;
if (g_hash_table_size (old->domains) != g_hash_table_size (new->domains))
return FALSE;
g_hash_table_iter_init (&iter, old->domains);
while (g_hash_table_iter_next (&iter, &key, &value_old)) {
value_new = g_hash_table_lookup (new->domains, key);
if (!value_new)
return FALSE;
domain_old = value_old;
domain_new = value_new;
if ( !_nm_utils_strv_equal (domain_old->options, domain_new->options)
|| !_nm_utils_strv_equal (domain_old->servers, domain_new->servers))
return FALSE;
}
return TRUE;
}
/************************************************************************/
char *
nm_config_data_get_connection_default (const NMConfigData *self,
const char *property,
@@ -683,6 +972,9 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data)
if (g_strcmp0 (nm_config_data_get_rc_manager (old_data), nm_config_data_get_rc_manager (new_data)))
changes |= NM_CONFIG_CHANGE_RC_MANAGER;
if (!global_dns_equal (priv_old->global_dns, priv_new->global_dns))
changes |= NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG;
return changes;
}
@@ -804,6 +1096,8 @@ finalize (GObject *gobject)
g_slist_free_full (priv->ignore_carrier, g_free);
g_slist_free_full (priv->assume_ipv6ll_only, g_free);
nm_global_dns_config_free (priv->global_dns);
if (priv->connection_infos) {
for (i = 0; priv->connection_infos[i].group_name; i++) {
g_free (priv->connection_infos[i].group_name);
@@ -858,6 +1152,10 @@ constructed (GObject *object)
priv->no_auto_default.specs_config = nm_config_get_device_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default", NULL);
priv->global_dns = load_global_dns (priv->keyfile_user, FALSE);
if (!priv->global_dns)
priv->global_dns = load_global_dns (priv->keyfile_intern, TRUE);
G_OBJECT_CLASS (nm_config_data_parent_class)->constructed (object);
}

View File

@@ -76,6 +76,7 @@ typedef enum { /*< flags >*/
NM_CONFIG_CHANGE_NO_AUTO_DEFAULT = (1L << 8),
NM_CONFIG_CHANGE_DNS_MODE = (1L << 9),
NM_CONFIG_CHANGE_RC_MANAGER = (1L << 10),
NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG = (1L << 11),
_NM_CONFIG_CHANGE_LAST,
NM_CONFIG_CHANGE_ALL = ((_NM_CONFIG_CHANGE_LAST - 1) << 1) - 1,
@@ -89,6 +90,9 @@ typedef struct {
GObjectClass parent;
} NMConfigDataClass;
typedef struct _NMGlobalDnsConfig NMGlobalDnsConfig;
typedef struct _NMGlobalDnsDomain NMGlobalDnsDomain;
GType nm_config_data_get_type (void);
NMConfigData *nm_config_data_new (const char *config_main_file,
@@ -124,6 +128,7 @@ const char *nm_config_data_get_rc_manager (const NMConfigData *self);
gboolean nm_config_data_get_ignore_carrier (const NMConfigData *self, NMDevice *device);
gboolean nm_config_data_get_assume_ipv6ll_only (const NMConfigData *self, NMDevice *device);
NMGlobalDnsConfig *nm_config_data_get_global_dns_config (const NMConfigData *self);
char *nm_config_data_get_connection_default (const NMConfigData *self,
const char *property,
@@ -135,6 +140,18 @@ gboolean nm_config_data_is_intern_atomic_group (const NMConfigData *self, const
GKeyFile *nm_config_data_clone_keyfile_intern (const NMConfigData *self);
const char *const *nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns);
const char *const *nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns);
guint nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns);
NMGlobalDnsDomain *nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns, guint i);
NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns, const char *name);
const char *nm_global_dns_domain_get_name (const NMGlobalDnsDomain *domain);
const char *const *nm_global_dns_domain_get_servers (const NMGlobalDnsDomain *domain);
const char *const *nm_global_dns_domain_get_options (const NMGlobalDnsDomain *domain);
gboolean nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns);
gboolean nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns);
void nm_global_dns_config_free (NMGlobalDnsConfig *conf);
/* private accessors */
GKeyFile *_nm_config_data_get_keyfile (const NMConfigData *self);
GKeyFile *_nm_config_data_get_keyfile_user (const NMConfigData *self);

View File

@@ -1184,6 +1184,24 @@ intern_config_read (const char *filename,
}
out:
/*
* If user configuration specifies global DNS options, the DNS
* options in internal configuration must be deleted. Otherwise a
* deletion of options from user configuration may cause the
* internal options to appear again.
*/
if (nm_config_keyfile_get_boolean (keyfile_conf, NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS, NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_ENABLE, FALSE)) {
if (g_key_file_remove_group (keyfile_intern, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL))
needs_rewrite = TRUE;
for (g = 0; groups && groups[g]; g++) {
if ( g_str_has_prefix (groups[g], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)
&& groups[g][STRLEN (NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)]) {
g_key_file_remove_group (keyfile_intern, groups[g], NULL);
needs_rewrite = TRUE;
}
}
}
g_key_file_unref (keyfile);
if (out_needs_rewrite)
@@ -1593,6 +1611,8 @@ _change_flags_one_to_string (NMConfigChangeFlags flag)
return "dns-mode";
case NM_CONFIG_CHANGE_RC_MANAGER:
return "rc-manager";
case NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG:
return "global-dns-config";
default:
g_return_val_if_reached ("unknown");
}

View File

@@ -49,17 +49,20 @@ G_BEGIN_DECLS
#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN ".intern."
#define NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION "connection"
#define NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN "global-dns-domain-"
#define NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".test-append-stringlist"
#define NM_CONFIG_KEYFILE_GROUP_MAIN "main"
#define NM_CONFIG_KEYFILE_GROUP_LOGGING "logging"
#define NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY "connectivity"
#define NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS "global-dns"
#define NM_CONFIG_KEYFILE_GROUP_KEYFILE "keyfile"
#define NM_CONFIG_KEYFILE_GROUP_IFUPDOWN "ifupdown"
#define NM_CONFIG_KEYFILE_GROUP_IFNET "ifnet"
#define NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND "backend"
#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_ENABLE "enable"
#define NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS ".was"
#define NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH "auto_refresh"
#define NM_CONFIG_KEYFILE_KEY_IFNET_MANAGED "managed"
@@ -69,6 +72,11 @@ G_BEGIN_DECLS
#define NM_CONFIG_KEYFILE_KEYPREFIX_WAS ".was."
#define NM_CONFIG_KEYFILE_KEYPREFIX_SET ".set."
#define NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS \
NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS
#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN \
NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN
typedef struct NMConfigCmdLineOptions NMConfigCmdLineOptions;
struct _NMConfig {

View File

@@ -30,6 +30,8 @@ TESTS = test-config
EXTRA_DIST = \
NetworkManager.conf \
bad.conf \
global-dns-disabled.conf \
global-dns-invalid.conf \
conf.d/00-overrides.conf \
conf.d/10-more.conf \
conf.d/90-last.conf

View File

@@ -81,3 +81,19 @@ ord.key07=A-0.3.07
ord.key08=A-0.3.08
ord.key09=A-0.3.09
ord.ovw01=A-0.3.ovw01
[global-dns]
enable=yes
searches=foo.com,bar.org
options=debug,edns0
[global-dns-domain-*]
servers=1.1.1.1,bad,1::128
options=opt1,opt2
[global-dns-domain-example.com]
servers=2.2.2.2
# Invalid section: 'servers' key is missing
[global-dns-domain-test.com]
options=opt3

View File

@@ -0,0 +1,8 @@
[global-dns]
enable=no
searches=foo.com
options=timeout:5
[global-dns-domain-*]
servers=1.2.3.4
options=myoption

View File

@@ -0,0 +1,10 @@
# Invalid configuration, since there isn't a default domain section
[global-dns]
enable=yes
searches=foo.com
options=timeout:5
[global-dns-domain-test.com]
servers=1.2.3.4
options=myoption

View File

@@ -243,6 +243,83 @@ test_config_override (void)
g_object_unref (config);
}
static void
test_config_global_dns (void)
{
NMConfig *config;
const NMGlobalDnsConfig *dns;
NMGlobalDnsDomain *domain;
const char *const *strv;
config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL,
"/no/such/dir", "", NULL);
dns = nm_config_data_get_global_dns_config (nm_config_get_data_orig (config));
g_assert (dns);
strv = nm_global_dns_config_get_searches (dns);
g_assert (strv);
g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2);
g_assert_cmpstr (strv[0], ==, "foo.com");
g_assert_cmpstr (strv[1], ==, "bar.org");
strv = nm_global_dns_config_get_options (dns);
g_assert (strv);
g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2);
g_assert_cmpstr (strv[0], ==, "debug");
g_assert_cmpstr (strv[1], ==, "edns0");
g_assert_cmpuint (nm_global_dns_config_get_num_domains (dns), ==, 2);
/* Default domain */
domain = nm_global_dns_config_lookup_domain (dns, "*");
g_assert (domain);
strv = nm_global_dns_domain_get_servers (domain);
g_assert (strv);
g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2);
g_assert_cmpstr (strv[0], ==, "1.1.1.1");
g_assert_cmpstr (strv[1], ==, "1::128");
strv = nm_global_dns_domain_get_options (domain);
g_assert (strv);
g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2);
g_assert_cmpstr (strv[0], ==, "opt1");
g_assert_cmpstr (strv[1], ==, "opt2");
/* 'example.com' domain */
domain = nm_global_dns_config_lookup_domain (dns, "example.com");
g_assert (domain);
strv = nm_global_dns_domain_get_servers (domain);
g_assert (strv);
g_assert_cmpuint (g_strv_length ((char **) strv), ==, 1);
g_assert_cmpstr (strv[0], ==, "2.2.2.2");
strv = nm_global_dns_domain_get_options (domain);
g_assert (!strv || g_strv_length ((char **) strv) == 0);
/* Non-existent domain 'test.com' */
domain = nm_global_dns_config_lookup_domain (dns, "test.com");
g_assert (!domain);
g_object_unref (config);
/* Check that a file without "enable=yes" gives a NULL configuration */
config = setup_config (NULL, SRCDIR "/global-dns-disabled.conf", "", NULL,
"/no/such/dir", "", NULL);
dns = nm_config_data_get_global_dns_config (nm_config_get_data_orig (config));
g_assert (!dns);
g_object_unref (config);
/* Check that a file without a default domain section gives a NULL configuration */
config = setup_config (NULL, SRCDIR "/global-dns-invalid.conf", "", NULL,
"/no/such/dir", "", NULL);
dns = nm_config_data_get_global_dns_config (nm_config_get_data_orig (config));
g_assert (!dns);
g_object_unref (config);
}
static void
test_config_no_auto_default (void)
{
@@ -852,6 +929,7 @@ main (int argc, char **argv)
g_test_add_func ("/config/confdir-parse-error", test_config_confdir_parse_error);
g_test_add_func ("/config/set-values", test_config_set_values);
g_test_add_func ("/config/global-dns", test_config_global_dns);
g_test_add_func ("/config/signal", test_config_signal);