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:
@@ -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
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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 */
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user