ifcfg-rh: support route options

For IPv4 we support both the legacy and the new route file format. In
the legacy format, option are appended to the "ip route" command
arguments:

  203.0.113.0/24 metric 3 via 198.51.100.1 dev eth2 cwnd 14 mtu lock 1500

This is backwards compatible with initscripts. In the new format, a
OPTIONSx= variable is added to represent the options in the same
format understood by iproute2:

 ADDRESS0=203.0.113.0
 NETMASK0=255.255.255.0
 GATEWAY0=198.51.100.1
 METRIC0=3
 OPTIONS0="cwnd 14 mtu lock 1500"

initscripts do not support this variable at the moment (but the
changes needed to support it are trivial).

By default the new format is used, unless the route file is already in
the legacy format.

For IPv6 only the legacy format is supported, as before.
This commit is contained in:
Beniamino Galvani 2017-02-16 14:30:16 +01:00
parent cdfa625102
commit 40e1fd9531
7 changed files with 295 additions and 36 deletions

View file

@ -615,7 +615,7 @@ nm_setting_ip4_config_class_init (NMSettingIP4ConfigClass *ip4_class)
* ---end---
* ---ifcfg-rh---
* property: routes
* variable: ADDRESS1, NETMASK1, GATEWAY1, METRIC1, ...
* variable: ADDRESS1, NETMASK1, GATEWAY1, METRIC1, OPTIONS1, ...
* description: List of static routes. They are not stored in ifcfg-* file,
* but in route-* file instead.
* ---end---

View file

@ -435,6 +435,134 @@ read_full_ip4_address (shvarFile *ifcfg,
return FALSE;
}
/*
* Use looser syntax to comprise all the possibilities.
* The validity must be checked after the match.
*/
#define IPV4_ADDR_REGEX "(?:[0-9]{1,3}\\.){3}[0-9]{1,3}"
#define IPV6_ADDR_REGEX "[0-9A-Fa-f:.]+"
/*
* NOTE: The regexes below don't describe all variants allowed by 'ip route add',
* namely destination IP without 'to' keyword is recognized just at line start.
*/
static gboolean
parse_route_options (NMIPRoute *route, int family, const char *line, GError **error)
{
GRegex *regex = NULL;
GMatchInfo *match_info = NULL;
gboolean success = FALSE;
char *metrics[] = { NM_IP_ROUTE_ATTRIBUTE_WINDOW, NM_IP_ROUTE_ATTRIBUTE_CWND,
NM_IP_ROUTE_ATTRIBUTE_INITCWND , NM_IP_ROUTE_ATTRIBUTE_INITRWND,
NM_IP_ROUTE_ATTRIBUTE_MTU , NULL };
char buffer[1024];
int i;
g_return_val_if_fail (family == AF_INET || family == AF_INET6, FALSE);
for (i = 0; metrics[i]; i++) {
nm_sprintf_buf (buffer, "(?:\\s|^)%s\\s+(lock\\s+)?(\\d+)(?:$|\\s)", metrics[i]);
regex = g_regex_new (buffer, 0, 0, NULL);
g_regex_match (regex, line, 0, &match_info);
if (g_match_info_matches (match_info)) {
gs_free char *lock = g_match_info_fetch (match_info, 1);
gs_free char *str = g_match_info_fetch (match_info, 2);
gint64 num = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXUINT32, -1);
if (num == -1) {
g_match_info_free (match_info);
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid route %s '%s'", metrics[i], str);
goto out;
}
nm_ip_route_set_attribute (route, metrics[i],
g_variant_new_uint32 (num));
if (lock && lock[0]) {
nm_sprintf_buf (buffer, "lock-%s", metrics[i]);
nm_ip_route_set_attribute (route, buffer,
g_variant_new_boolean (TRUE));
}
}
g_clear_pointer (&regex, g_regex_unref);
g_clear_pointer (&match_info, g_match_info_free);
}
/* tos */
regex = g_regex_new ("(?:\\s|^)tos\\s+(\\S+)(?:$|\\s)", 0, 0, NULL);
g_regex_match (regex, line, 0, &match_info);
if (g_match_info_matches (match_info)) {
gs_free char *str = g_match_info_fetch (match_info, 1);
gs_free_error GError *local_error = NULL;
gint64 num = _nm_utils_ascii_str_to_int64 (str, 0, 0, G_MAXUINT8, -1);
if (num == -1) {
g_match_info_free (match_info);
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid route %s '%s'", "tos", str);
goto out;
}
nm_ip_route_set_attribute (route, NM_IP_ROUTE_ATTRIBUTE_TOS,
g_variant_new_byte ((guchar) num));
}
g_clear_pointer (&regex, g_regex_unref);
g_clear_pointer (&match_info, g_match_info_free);
/* from */
if (family == AF_INET6) {
regex = g_regex_new ("(?:\\s|^)from\\s+(" IPV6_ADDR_REGEX "(?:/\\d{1,3})?)(?:$|\\s)", 0, 0, NULL);
g_regex_match (regex, line, 0, &match_info);
if (g_match_info_matches (match_info)) {
gs_free char *str = g_match_info_fetch (match_info, 1);
gs_free_error GError *local_error = NULL;
GVariant *variant = g_variant_new_string (str);
if (!nm_ip_route_attribute_validate (NM_IP_ROUTE_ATTRIBUTE_SRC, variant, family, NULL, &local_error)) {
g_match_info_free (match_info);
g_variant_unref (variant);
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid route from '%s': %s", str, local_error->message);
goto out;
}
nm_ip_route_set_attribute (route, NM_IP_ROUTE_ATTRIBUTE_SRC, variant);
}
g_clear_pointer (&regex, g_regex_unref);
g_clear_pointer (&match_info, g_match_info_free);
}
if (family == AF_INET)
regex = g_regex_new ("(?:\\s|^)src\\s+(" IPV4_ADDR_REGEX ")(?:$|\\s)", 0, 0, NULL);
else
regex = g_regex_new ("(?:\\s|^)src\\s+(" IPV6_ADDR_REGEX ")(?:$|\\s)", 0, 0, NULL);
g_regex_match (regex, line, 0, &match_info);
if (g_match_info_matches (match_info)) {
gs_free char *str = g_match_info_fetch (match_info, 1);
gs_free_error GError *local_error = NULL;
GVariant *variant = g_variant_new_string (str);
if (!nm_ip_route_attribute_validate (NM_IP_ROUTE_ATTRIBUTE_PREF_SRC, variant, family,
NULL, &local_error)) {
g_match_info_free (match_info);
g_variant_unref (variant);
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid route src '%s': %s", str, local_error->message);
goto out;
}
nm_ip_route_set_attribute (route, NM_IP_ROUTE_ATTRIBUTE_PREF_SRC, variant);
}
success = TRUE;
out:
if (regex)
g_regex_unref (regex);
if (match_info)
g_match_info_free (match_info);
return success;
}
/* Returns TRUE on missing route or valid route */
static gboolean
read_one_ip4_route (shvarFile *ifcfg,
@ -507,10 +635,20 @@ read_one_ip4_route (shvarFile *ifcfg,
metric = -1;
*out_route = nm_ip_route_new_binary (AF_INET, &dest, prefix, &next_hop, metric, error);
if (*out_route)
return TRUE;
if (!*out_route)
return FALSE;
return FALSE;
/* Options */
nm_clear_g_free (&value);
value = svGetValueStr_cp (ifcfg, numbered_tag (tag, "OPTIONS", which));
if (value) {
if (!parse_route_options (*out_route, AF_INET, value, error)) {
g_clear_pointer (out_route, nm_ip_route_unref);
return FALSE;
}
}
return TRUE;
}
static gboolean
@ -636,6 +774,12 @@ read_route_file_legacy (const char *filename, NMSettingIPConfig *s_ip4, GError *
route = nm_ip_route_new (AF_INET, dest, prefix_int, next_hop, metric_int, error);
if (!route)
goto error;
if (!parse_route_options (route, AF_INET, *iter, error)) {
nm_ip_route_unref (route);
goto error;
}
if (!nm_setting_ip_config_add_route (s_ip4, route))
PARSE_WARNING ("duplicate IP4 route");
nm_ip_route_unref (route);
@ -729,13 +873,6 @@ error:
return success;
}
/* IPv6 address is very complex to describe completely by a regular expression,
* so don't try to, rather use looser syntax to comprise all possibilities
* NOTE: The regexes below don't describe all variants allowed by 'ip route add',
* namely destination IP without 'to' keyword is recognized just at line start.
*/
#define IPV6_ADDR_REGEX "[0-9A-Fa-f:.]+"
static gboolean
read_route6_file (const char *filename, NMSettingIPConfig *s_ip6, GError **error)
{
@ -757,6 +894,7 @@ read_route6_file (const char *filename, NMSettingIPConfig *s_ip6, GError **error
const char *pattern_via = "via\\s+(" IPV6_ADDR_REGEX ")"; /* IPv6 of gateway */
const char *pattern_metric = "metric\\s+(\\d+)"; /* metric */
g_return_val_if_fail (filename != NULL, FALSE);
g_return_val_if_fail (s_ip6 != NULL, FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
@ -862,6 +1000,12 @@ read_route6_file (const char *filename, NMSettingIPConfig *s_ip6, GError **error
g_free (next_hop);
if (!route)
goto error;
if (!parse_route_options (route, AF_INET6, *iter, error)) {
nm_ip_route_unref (route);
goto error;
}
if (!nm_setting_ip_config_add_route (s_ip6, route))
PARSE_WARNING ("duplicate IP6 route");
nm_ip_route_unref (route);

View file

@ -1867,6 +1867,61 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
}
}
static char *
get_route_attributes_string (NMIPRoute *route, int family)
{
gs_strfreev char **names = NULL;
GVariant *attr, *lock;
GString *str;
int i;
names = nm_ip_route_get_attribute_names (route);
if (!names || !names[0])
return NULL;
str = g_string_new ("");
for (i = 0; names[i]; i++) {
attr = nm_ip_route_get_attribute (route, names[i]);
if (!nm_ip_route_attribute_validate (names[i], attr, family, NULL, NULL))
continue;
if (NM_IN_STRSET (names[i], NM_IP_ROUTE_ATTRIBUTE_WINDOW,
NM_IP_ROUTE_ATTRIBUTE_CWND,
NM_IP_ROUTE_ATTRIBUTE_INITCWND,
NM_IP_ROUTE_ATTRIBUTE_INITRWND,
NM_IP_ROUTE_ATTRIBUTE_MTU)) {
char lock_name[256];
nm_sprintf_buf (lock_name, "lock-%s", names[i]);
lock = nm_ip_route_get_attribute (route, lock_name);
g_string_append_printf (str,
"%s %s%u",
names[i],
(lock && g_variant_get_boolean (lock)) ? "lock " : "",
g_variant_get_uint32 (attr));
} else if (strstr (names[i], "lock-")) {
/* handled above */
} else if (nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_TOS)) {
g_string_append_printf (str, "%s %u", names[i], (unsigned) g_variant_get_byte (attr));
} else if ( nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_PREF_SRC)
|| nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_SRC)) {
char *arg = nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_PREF_SRC) ? "src" : "from";
g_string_append_printf (str, "%s %s", arg, g_variant_get_string (attr, NULL));
} else {
_LOGW ("unknown route option '%s'", names[i]);
continue;
}
if (names[i + 1])
g_string_append_c (str, ' ');
}
return g_string_free (str, FALSE);
}
static gboolean
write_route_file_legacy (const char *filename, NMSettingIPConfig *s_ip4, GError **error)
{
@ -1891,6 +1946,8 @@ write_route_file_legacy (const char *filename, NMSettingIPConfig *s_ip4, GError
route_items = g_malloc0 (sizeof (char*) * (num + 1));
for (i = 0; i < num; i++) {
gs_free char *options = NULL;
route = nm_setting_ip_config_get_route (s_ip4, i);
dest = nm_ip_route_get_dest (route);
@ -1898,10 +1955,19 @@ write_route_file_legacy (const char *filename, NMSettingIPConfig *s_ip4, GError
next_hop = nm_ip_route_get_next_hop (route);
metric = nm_ip_route_get_metric (route);
if (metric == -1)
route_items[i] = g_strdup_printf ("%s/%u via %s\n", dest, prefix, next_hop);
else
route_items[i] = g_strdup_printf ("%s/%u via %s metric %u\n", dest, prefix, next_hop, (guint32) metric);
options = get_route_attributes_string (route, AF_INET);
if (metric == -1) {
route_items[i] = g_strdup_printf ("%s/%u via %s%s%s\n",
dest, prefix, next_hop,
options ? " " : "",
options ?: "");
} else {
route_items[i] = g_strdup_printf ("%s/%u via %s metric %u%s%s\n",
dest, prefix, next_hop, (guint32) metric,
options ? " " : "",
options ?: "");
}
}
route_items[num] = NULL;
route_contents = g_strjoinv (NULL, route_items);
@ -1960,7 +2026,7 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
{
NMSettingIPConfig *s_ip4;
const char *value;
char *addr_key, *prefix_key, *netmask_key, *gw_key, *metric_key, *tmp;
char *addr_key, *prefix_key, *netmask_key, *gw_key, *metric_key, *options_key, *tmp;
char *route_path = NULL;
gint32 j;
guint32 i, n, num;
@ -2217,13 +2283,17 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
netmask_key = g_strdup_printf ("NETMASK%d", i);
gw_key = g_strdup_printf ("GATEWAY%d", i);
metric_key = g_strdup_printf ("METRIC%d", i);
options_key = g_strdup_printf ("OPTIONS%d", i);
if (i >= num) {
svUnsetValue (routefile, addr_key);
svUnsetValue (routefile, netmask_key);
svUnsetValue (routefile, gw_key);
svUnsetValue (routefile, metric_key);
svUnsetValue (routefile, options_key);
} else {
gs_free char *options = NULL;
route = nm_setting_ip_config_get_route (s_ip4, i);
svSetValueStr (routefile, addr_key, nm_ip_route_get_dest (route));
@ -2244,12 +2314,17 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
svSetValueStr (routefile, metric_key, tmp);
g_free (tmp);
}
options = get_route_attributes_string (route, AF_INET);
if (options)
svSetValueStr (routefile, options_key, options);
}
g_free (addr_key);
g_free (netmask_key);
g_free (gw_key);
g_free (metric_key);
g_free (options_key);
}
if (!svWriteFile (routefile, 0644, error)) {
svCloseFile (routefile);
@ -2371,15 +2446,13 @@ write_ip4_aliases (NMConnection *connection, char *base_ifcfg_path)
static gboolean
write_route6_file (const char *filename, NMSettingIPConfig *s_ip6, GError **error)
{
char **route_items;
gs_free char *route_contents = NULL;
nm_auto_free_gstring GString *contents = NULL;
NMIPRoute *route;
guint32 i, num;
g_return_val_if_fail (filename != NULL, FALSE);
g_return_val_if_fail (s_ip6 != NULL, FALSE);
g_return_val_if_fail (error != NULL, FALSE);
g_return_val_if_fail (*error == NULL, FALSE);
g_return_val_if_fail (filename, FALSE);
g_return_val_if_fail (s_ip6, FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
num = nm_setting_ip_config_get_num_routes (s_ip6);
if (num == 0) {
@ -2387,28 +2460,33 @@ write_route6_file (const char *filename, NMSettingIPConfig *s_ip6, GError **erro
return TRUE;
}
route_items = g_malloc0 (sizeof (char*) * (num + 1));
contents = g_string_new ("");
for (i = 0; i < num; i++) {
gs_free char *options = NULL;
route = nm_setting_ip_config_get_route (s_ip6, i);
options = get_route_attributes_string (route, AF_INET6);
if (nm_ip_route_get_metric (route) == -1) {
route_items[i] = g_strdup_printf ("%s/%u via %s\n",
nm_ip_route_get_dest (route),
nm_ip_route_get_prefix (route),
nm_ip_route_get_next_hop (route));
} else {
route_items[i] = g_strdup_printf ("%s/%u via %s metric %u\n",
g_string_append_printf (contents, "%s/%u via %s%s%s",
nm_ip_route_get_dest (route),
nm_ip_route_get_prefix (route),
nm_ip_route_get_next_hop (route),
(guint32) nm_ip_route_get_metric (route));
options ? " " : "",
options ?: "");
} else {
g_string_append_printf (contents, "%s/%u via %s metric %u%s%s",
nm_ip_route_get_dest (route),
nm_ip_route_get_prefix (route),
nm_ip_route_get_next_hop (route),
(unsigned) nm_ip_route_get_metric (route),
options ? " " : "",
options ?: "");
}
g_string_append (contents, "\n");
}
route_items[num] = NULL;
route_contents = g_strjoinv (NULL, route_items);
g_strfreev (route_items);
if (!g_file_set_contents (filename, route_contents, -1, NULL)) {
if (!g_file_set_contents (filename, contents->str, -1, NULL)) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
"Writing route6 file '%s' failed", filename);
return FALSE;

View file

@ -6,3 +6,4 @@ ADDRESS1=44.55.66.77
NETMASK1=255.255.255.255
GATEWAY1=192.168.1.7
METRIC1=3
OPTIONS1="mtu lock 9000 cwnd 12 src 1.1.1.1 tos 0x28 window 30000 initcwnd lock 13 initrwnd 14"

View file

@ -3,5 +3,5 @@
21.31.41.0/24 via 9.9.9.9 metric 1
via 8.8.8.8 to 32.42.52.62
43.53.0.0/16 metric 3 via 7.7.7.7 dev eth2
43.53.0.0/16 metric 3 via 7.7.7.7 dev eth2 cwnd 14 mtu lock 9000 initrwnd 20 window lock 10000 initcwnd lock 42 src 1.2.3.4

View file

@ -5,3 +5,5 @@ default via dead::beaf
# routes without "via" are valid
abbe::cafe/64 metric 777
aaaa::cccc/64 from 1111::2222/48 via 3333::4444 src 5555::6666 mtu lock 1450 cwnd 13

View file

@ -1127,6 +1127,15 @@ test_read_wired_static_routes (void)
g_assert_cmpint (nm_ip_route_get_prefix (ip4_route), ==, 32);
g_assert_cmpstr (nm_ip_route_get_next_hop (ip4_route), ==, "192.168.1.7");
g_assert_cmpint (nm_ip_route_get_metric (ip4_route), ==, 3);
nmtst_assert_route_attribute_byte (ip4_route, NM_IP_ROUTE_ATTRIBUTE_TOS, 0x28);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 30000);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 12);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 13);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 14);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000);
nmtst_assert_route_attribute_boolean (ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
nmtst_assert_route_attribute_boolean (ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, TRUE);
nmtst_assert_route_attribute_string (ip4_route, NM_IP_ROUTE_ATTRIBUTE_PREF_SRC, "1.1.1.1");
g_object_unref (connection);
}
@ -1188,6 +1197,14 @@ test_read_wired_static_routes_legacy (void)
g_assert_cmpint (nm_ip_route_get_prefix (ip4_route), ==, 16);
g_assert_cmpstr (nm_ip_route_get_next_hop (ip4_route), ==, "7.7.7.7");
g_assert_cmpint (nm_ip_route_get_metric (ip4_route), ==, 3);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_WINDOW, 10000);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 14);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITCWND, 42);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_INITRWND, 20);
nmtst_assert_route_attribute_uint32 (ip4_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 9000);
nmtst_assert_route_attribute_boolean (ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW, TRUE);
nmtst_assert_route_attribute_boolean (ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
nmtst_assert_route_attribute_string (ip4_route, NM_IP_ROUTE_ATTRIBUTE_PREF_SRC, "1.2.3.4");
g_object_unref (connection);
}
@ -1328,7 +1345,7 @@ test_read_wired_ipv6_manual (void)
g_assert_cmpint (nm_ip_address_get_prefix (ip6_addr), ==, 96);
/* Routes */
g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip6), ==, 2);
g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip6), ==, 3);
/* Route #1 */
ip6_route = nm_setting_ip_config_get_route (s_ip6, 0);
g_assert (ip6_route);
@ -1343,6 +1360,17 @@ test_read_wired_ipv6_manual (void)
g_assert_cmpint (nm_ip_route_get_prefix (ip6_route), ==, 64);
g_assert_cmpstr (nm_ip_route_get_next_hop (ip6_route), ==, NULL);
g_assert_cmpint (nm_ip_route_get_metric (ip6_route), ==, 777);
/* Route #3 */
ip6_route = nm_setting_ip_config_get_route (s_ip6, 2);
g_assert (ip6_route);
g_assert_cmpstr (nm_ip_route_get_dest (ip6_route), ==, "aaaa::cccc");
g_assert_cmpint (nm_ip_route_get_prefix (ip6_route), ==, 64);
g_assert_cmpstr (nm_ip_route_get_next_hop (ip6_route), ==, "3333::4444");
nmtst_assert_route_attribute_uint32 (ip6_route, NM_IP_ROUTE_ATTRIBUTE_CWND, 13);
nmtst_assert_route_attribute_uint32 (ip6_route, NM_IP_ROUTE_ATTRIBUTE_MTU, 1450);
nmtst_assert_route_attribute_boolean (ip6_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
nmtst_assert_route_attribute_string (ip6_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1111::2222/48");
nmtst_assert_route_attribute_string (ip6_route, NM_IP_ROUTE_ATTRIBUTE_PREF_SRC, "5555::6666");
/* DNS Addresses */
g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip6), ==, 2);
@ -3867,6 +3895,12 @@ test_write_wired_static (void)
route6 = nm_ip_route_new (AF_INET6, "::", 128, "2222:aaaa::9999", 1, &error);
g_assert_no_error (error);
nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_TOS, g_variant_new_byte (0xb8));
nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32 (100));
nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32 (1280));
nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, g_variant_new_boolean (TRUE));
nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string ("2222::bbbb/32"));
nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_PREF_SRC, g_variant_new_string ("::42"));
nm_setting_ip_config_add_route (s_ip6, route6);
nm_ip_route_unref (route6);