config: support a [connection] section to NetworkManager.conf to specify connection defaults

Add support for a new section [connection] in NetworkManager.conf.
If the connection leaves an option at "unknown"/"default", we can
support overwriting the value from global configuration.

We also support other sections that are named with "connection"
as a prefix, such as [connection2], [connection-wifi]. This is
to support multiple default values that can be applied depending
on the used device.

I think this has great potential. Only downside is that when
the user looks at a connection value, it will see that it is
unspecified. But the actually used value depends on the device
type and might not be obvious.

https://bugzilla.gnome.org/show_bug.cgi?id=695383
https://bugzilla.redhat.com/show_bug.cgi?id=1164677
This commit is contained in:
Thomas Haller
2015-05-15 11:36:28 +02:00
parent f031b926c4
commit dc0193ac02
5 changed files with 272 additions and 0 deletions

View File

@@ -453,6 +453,77 @@ unmanaged-devices=mac:00:22:68:1c:59:b1;mac:00:1E:65:30:D1:C4;interface-name:eth
</para>
</refsect1>
<refsect1>
<title><literal>connection</literal> section</title>
<para>This section allows to specify default values for
connections. Not all properties can be overwritten, only a selected
list below. You can have multiple <literal>connection</literal>
sections, by having different sections with a name that all start
with "connection".</para>
<para>
Example:
<programlisting>
[connection]
ipv6.ip6-privacy=0
[connection-wifi-wlan0]
match-device=interface-name:wlan0
ipv4.route-metric=50
[connection-wifi-other]
match-device=type:wifi
ipv4.route-metric=55
ipv6.ip6-privacy=1
</programlisting>
</para>
<para>
The sections are considered in order of appearance, with the
exception that the <literal>[connection]</literal> section is always
considered last. In the example above, this order is <literal>[connection-wifi-wlan0]</literal>,
<literal>[connection-wlan-other]</literal>, and <literal>[connection]</literal>.
When checking for a default configuration value, the section are searched until
the requested value is found.
In the example above, "ipv4.route-metric" for wlan0 interface is set to 50,
and for all other Wi-Fi typed interfaces to 55. Also, Wi-Fi devices would have
IPv6 private addresses enabled by default, but other devices would have it disabled.
Note that also "wlan0" gets "ipv6.ip6-privacy=1", because although the section
"[connection-wifi-wlan0]" matches the device, it does not contain that property
and the search continues.
This is just an example, currently these properties are not overwritable.
</para>
<para>
<variablelist>
<varlistentry>
<term><varname>match-device</varname></term>
<listitem><para>An optional device spec that restricts
when the section applies. See <xref linkend="device-spec"/>
for the possible values.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>stop-match</varname></term>
<listitem><para>An optional boolean value which defaults to
<literal>no</literal>. If the section matches (based on
<literal>match-device</literal>), further sections will not be
considered even if the property in question is not present. In
the example above, if <literal>[connection-wifi-wlan0]</literal> would
have <literal>stop-match</literal> set to <literal>yes</literal>,
its <literal>ipv6.ip6-privacy</literal> value would be
unspecified.
</para></listitem>
</varlistentry>
</variablelist>
</para>
<para>
The following properties are supported to have their default values configured:
<variablelist>
</variablelist>
</para>
</refsect1>
<refsect1>
<title><literal>connectivity</literal> section</title>
<para>This section controls NetworkManager's optional connectivity

View File

@@ -27,6 +27,19 @@
#include "gsystem-local-alloc.h"
#include "nm-device.h"
#include "nm-core-internal.h"
#include "nm-macros-internal.h"
typedef struct {
char *group_name;
gboolean stop_match;
struct {
/* have a separate boolean field @has, because a @spec with
* value %NULL does not necessarily mean, that the property
* "match-device" was unspecified. */
gboolean has;
GSList *spec;
} match_device;
} ConnectionInfo;
typedef struct {
char *config_main_file;
@@ -34,6 +47,10 @@ typedef struct {
GKeyFile *keyfile;
/* A zero-terminated list of pre-processed information from the
* [connection] sections. This is to speed up lookup. */
ConnectionInfo *connection_infos;
struct {
char *uri;
char *response;
@@ -172,6 +189,100 @@ nm_config_data_get_assume_ipv6ll_only (const NMConfigData *self, NMDevice *devic
/************************************************************************/
char *
nm_config_data_get_connection_default (const NMConfigData *self,
const char *property,
NMDevice *device)
{
NMConfigDataPrivate *priv;
const ConnectionInfo *connection_info;
g_return_val_if_fail (self, NULL);
g_return_val_if_fail (property && *property, NULL);
g_return_val_if_fail (strchr (property, '.'), NULL);
priv = NM_CONFIG_DATA_GET_PRIVATE (self);
if (!priv->connection_infos)
return NULL;
for (connection_info = &priv->connection_infos[0]; connection_info->group_name; connection_info++) {
char *value;
gboolean match;
value = g_key_file_get_value (priv->keyfile, connection_info->group_name, property, NULL);
if (!value && !connection_info->stop_match)
continue;
match = TRUE;
if (connection_info->match_device.has)
match = device && nm_device_spec_match_list (device, connection_info->match_device.spec);
if (match)
return value;
g_free (value);
}
return NULL;
}
static ConnectionInfo *
_get_connection_infos (GKeyFile *keyfile)
{
char **groups;
guint i;
char *connection_tag = NULL;
GSList *connection_groups = NULL;
ConnectionInfo *connection_infos = NULL;
/* get the list of existing [connection.\+] sections that we consider
* for nm_config_data_get_connection_default(). Also, get them
* in the right order. */
groups = g_key_file_get_groups (keyfile, NULL);
for (i = 0; groups && groups[i]; i++) {
if (g_str_has_prefix (groups[i], "connection")) {
if (strlen (groups[i]) == STRLEN ("connection"))
connection_tag = groups[i];
else
connection_groups = g_slist_prepend (connection_groups, groups[i]);
} else
g_free (groups[i]);
}
g_free (groups);
if (connection_tag) {
/* We want the group "connection" checked at last, so that
* all other "connection.\+" have preference. Those other
* groups are checked in order of appearance. */
connection_groups = g_slist_prepend (connection_groups, connection_tag);
}
if (connection_groups) {
guint len = g_slist_length (connection_groups);
GSList *iter;
connection_infos = g_new0 (ConnectionInfo, len + 1);
for (iter = connection_groups; iter; iter = iter->next) {
ConnectionInfo *connection_info;
char *value;
nm_assert (len >= 1);
connection_info = &connection_infos[--len];
connection_info->group_name = iter->data;
value = g_key_file_get_value (keyfile, iter->data, "match-device", NULL);
if (value) {
connection_info->match_device.has = TRUE;
connection_info->match_device.spec = nm_match_spec_split (value);
g_free (value);
}
connection_info->stop_match = nm_config_keyfile_get_boolean (keyfile, iter->data, "stop-match", FALSE);
}
g_slist_free (connection_groups);
}
return connection_infos;
}
/************************************************************************/
static gboolean
_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b)
{
@@ -324,6 +435,7 @@ static void
finalize (GObject *gobject)
{
NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (gobject);
guint i;
g_free (priv->config_main_file);
g_free (priv->config_description);
@@ -340,6 +452,14 @@ finalize (GObject *gobject)
g_slist_free_full (priv->ignore_carrier, g_free);
g_slist_free_full (priv->assume_ipv6ll_only, g_free);
if (priv->connection_infos) {
for (i = 0; priv->connection_infos[i].group_name; i++) {
g_free (priv->connection_infos[i].group_name);
g_slist_free_full (priv->connection_infos[i].match_device.spec, g_free);
}
g_free (priv->connection_infos);
}
g_key_file_unref (priv->keyfile);
G_OBJECT_CLASS (nm_config_data_parent_class)->finalize (gobject);
@@ -357,6 +477,8 @@ constructed (GObject *object)
NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (self);
char *interval;
priv->connection_infos = _get_connection_infos (priv->keyfile);
priv->connectivity.uri = g_key_file_get_value (priv->keyfile, "connectivity", "uri", NULL);
priv->connectivity.response = g_key_file_get_value (priv->keyfile, "connectivity", "response", NULL);

View File

@@ -94,6 +94,10 @@ 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);
char *nm_config_data_get_connection_default (const NMConfigData *self,
const char *property,
NMDevice *device);
G_END_DECLS
#endif /* NM_CONFIG_DATA_H */

View File

@@ -13,3 +13,27 @@ response=Hello
[extra-section]
extra-key=some value
[connection]
ipv4.route-metric=50
ipv6.ip6_privacy=0
dummy.test1=no
dummy.test2=no
[connection.dev51]
match-device=mac:00:00:00:00:00:51
stop-match=yes
ipv4.route-metric=51
dummy.test1=yes
[connection.dev52]
match-device=mac:00:00:00:00:00:52
ipv4.route-metric=52
[connection.public]
match-device=interface-name:wlan1
# match-wifi is not yet implemented. Just an idea what could be useful.
match-wifi=ssid:*[Ss]tarbucks*|*University*
ipv6.ip6_privacy=2

View File

@@ -94,6 +94,9 @@ test_config_simple (void)
GError *error = NULL;
const char **plugins;
char *value;
gs_unref_object NMDevice *dev50 = nm_test_device_new ("00:00:00:00:00:50");
gs_unref_object NMDevice *dev51 = nm_test_device_new ("00:00:00:00:00:51");
gs_unref_object NMDevice *dev52 = nm_test_device_new ("00:00:00:00:00:52");
config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir", NULL);
@@ -122,6 +125,54 @@ test_config_simple (void)
g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND);
g_clear_error (&error);
value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection", "ipv6.ip6_privacy", NULL);
g_assert_cmpstr (value, ==, "0");
g_free (value);
value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection.dev51", "ipv4.route-metric", NULL);
g_assert_cmpstr (value, ==, "51");
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv6.route-metric", NULL);
g_assert_cmpstr (value, ==, NULL);
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", NULL);
g_assert_cmpstr (value, ==, "50");
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", dev50);
g_assert_cmpstr (value, ==, "50");
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", dev51);
g_assert_cmpstr (value, ==, "51");
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", dev52);
g_assert_cmpstr (value, ==, "52");
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test1", dev51);
g_assert_cmpstr (value, ==, "yes");
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test1", dev50);
g_assert_cmpstr (value, ==, "no");
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test2", dev51);
g_assert_cmpstr (value, ==, NULL);
g_free (value);
value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test2", dev50);
g_assert_cmpstr (value, ==, "no");
g_free (value);
g_object_unref (config);
}