From 4d4680443742f6abcc14099f6dfade318a6aa583 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 22 Mar 2019 20:13:15 +0100 Subject: [PATCH] ifcfg-rh: add support for routing rules as "ROUTING_RULE_#" keys initscripts support rule-* and rule6-* files for that. Up until now, we ignored these files for the most part, except if a user configured such files, the profile could not contain any static routes (or specify a route-table setting). This also worked together with the dispatcher script "examples/dispatcher/10-ifcfg-rh-routes.sh". We cannot now start taking over that file format for rules. It might break existing setups, because we can never fully understand all rules as they are understood by iproute2. Also, if a user has a rule/rule6 file and uses NetworkManager successfully today, then clearly there is a script in place to make that work. We must not break that when adding rules support. Hence, store routing rules as numbered "ROUTING_RULE_#" and "ROUTING_RULE6_#" keys. Note that we use different keys for IPv4 and IPv6. The main reason is that the string format is mostly compatible with iproute2. That means, you can take the value and pass it to `ip rule add`. However, `ip rule add` only accepts IPv4 rules. For IPv6 rules, the user needs to call `ip -6 rule add`. If we would use the same key for IPv4 and IPv6, then it would be hard to write a script to do this. Also, nm_ip_routing_rule_from_string() does take the address family as hint in this case. This makes ROUTING_RULE_1="pref 1" ROUTING_RULE6_1="pref 1" automatically determine that address families. Otherwise, such abbreviated forms would be not valid. --- Makefile.am | 1 + .../plugins/ifcfg-rh/nms-ifcfg-rh-reader.c | 98 +++++++++++++++++-- .../plugins/ifcfg-rh/nms-ifcfg-rh-writer.c | 59 +++++++++++ src/settings/plugins/ifcfg-rh/shvar.c | 77 ++++++++++++++- src/settings/plugins/ifcfg-rh/shvar.h | 8 ++ .../ifcfg-Test_Write_Routing_Rules.cexpected | 19 ++++ .../plugins/ifcfg-rh/tests/test-ifcfg-rh.c | 96 ++++++++++++++++++ 7 files changed, 346 insertions(+), 12 deletions(-) create mode 100644 src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected diff --git a/Makefile.am b/Makefile.am index a8353656da..de3d25e308 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2729,6 +2729,7 @@ EXTRA_DIST += \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected \ + src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected \ diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c index de45c084ef..f9722b8f37 100644 --- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -2083,7 +2083,7 @@ make_ip6_setting (shvarFile *ifcfg, g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL); g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, - svGetValueBoolean (ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), NULL); + svGetValueBoolean (ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), NULL); /* Read static IP addresses. * Read them even for AUTO and DHCP methods - in this case the addresses are @@ -4316,6 +4316,84 @@ parse_ethtool_option (const char *value, } } +static GPtrArray * +read_routing_rules_parse (shvarFile *ifcfg, + gboolean routes_read) +{ + gs_unref_ptrarray GPtrArray *arr = NULL; + gs_free const char **keys = NULL; + guint i, len; + + keys = svGetKeysSorted (ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6, &len); + if (len == 0) + return NULL; + + if (!routes_read) { + PARSE_WARNING ("'rule-' or 'rule6-' files are present; Policy routing rules (ROUTING_RULE*) settings are ignored"); + return NULL; + } + + arr = g_ptr_array_new_full (len, (GDestroyNotify) nm_ip_routing_rule_unref); + for (i = 0; i < len; i++) { + const char *key = keys[i]; + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + gs_free_error GError *local = NULL; + gs_free char *value_to_free = NULL; + const char *value; + gboolean key_is_ipv4; + + key_is_ipv4 = (key[NM_STRLEN ("ROUTING_RULE")] == '_'); + nm_assert ( key_is_ipv4 == NM_STR_HAS_PREFIX (key, "ROUTING_RULE_")); + nm_assert (!key_is_ipv4 == NM_STR_HAS_PREFIX (key, "ROUTING_RULE6_")); + + value = svGetValueStr (ifcfg, key, &value_to_free); + if (!value) + continue; + + rule = nm_ip_routing_rule_from_string (value, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (key_is_ipv4 + ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6), + NULL, + &local); + if (!rule) { + PARSE_WARNING ("invalid routing rule %s=\"%s\": %s", key, value, local->message); + continue; + } + + g_ptr_array_add (arr, g_steal_pointer (&rule)); + } + + if (arr->len == 0) + return NULL; + + return g_steal_pointer (&arr); +} + +static void +read_routing_rules (shvarFile *ifcfg, + gboolean routes_read, + NMSettingIPConfig *s_ip4, + NMSettingIPConfig *s_ip6) +{ + gs_unref_ptrarray GPtrArray *routing_rules = NULL; + guint i; + + routing_rules = read_routing_rules_parse (ifcfg, routes_read); + if (!routing_rules) + return; + + for (i = 0; i < routing_rules->len; i++) { + NMIPRoutingRule *rule = routing_rules->pdata[i]; + + nm_setting_ip_config_add_routing_rule ( (nm_ip_routing_rule_get_addr_family (rule) == AF_INET) + ? s_ip4 + : s_ip6, + rule); + } +} + static void parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection) { @@ -5817,8 +5895,7 @@ connection_from_file_full (const char *filename, error); if (!s_ip6) return NULL; - else - nm_connection_add_setting (connection, s_ip6); + nm_connection_add_setting (connection, s_ip6); s_ip4 = make_ip4_setting (main_ifcfg, network_ifcfg, @@ -5827,12 +5904,15 @@ connection_from_file_full (const char *filename, error); if (!s_ip4) return NULL; - else { - read_aliases (NM_SETTING_IP_CONFIG (s_ip4), - !has_ip4_defroute && !nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (s_ip4)), - filename); - nm_connection_add_setting (connection, s_ip4); - } + read_aliases (NM_SETTING_IP_CONFIG (s_ip4), + !has_ip4_defroute && !nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (s_ip4)), + filename); + nm_connection_add_setting (connection, s_ip4); + + read_routing_rules (main_ifcfg, + !has_complex_routes_v4 && !has_complex_routes_v6, + NM_SETTING_IP_CONFIG (s_ip4), + NM_SETTING_IP_CONFIG (s_ip6)); s_sriov = make_sriov_setting (main_ifcfg); if (s_sriov) diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c index f6b8f8c21a..a57e6c3a60 100644 --- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c @@ -2954,6 +2954,52 @@ write_ip6_setting (NMConnection *connection, return TRUE; } +static void +write_ip_routing_rules (NMConnection *connection, + shvarFile *ifcfg, + gboolean route_ignore) +{ + gsize idx; + int is_ipv4; + + svUnsetAll (ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6); + + if (route_ignore) + return; + + idx = 0; + + for (is_ipv4 = 1; is_ipv4 >= 0; is_ipv4--) { + const int addr_family = is_ipv4 ? AF_INET : AF_INET6; + NMSettingIPConfig *s_ip; + guint i, num; + + s_ip = nm_connection_get_setting_ip_config (connection, addr_family); + if (!s_ip) + continue; + + num = nm_setting_ip_config_get_num_routing_rules (s_ip); + for (i = 0; i < num; i++) { + NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i); + gs_free const char *s = NULL; + char key[64]; + + s = nm_ip_routing_rule_to_string (rule, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE, + NULL, + NULL); + if (!s) + continue; + + if (is_ipv4) + numbered_tag (key, "ROUTING_RULE_", ++idx); + else + numbered_tag (key, "ROUTING_RULE6_", ++idx); + svSetValueStr (ifcfg, key, s); + } + } +} + static char * escape_id (const char *id) { @@ -3176,6 +3222,15 @@ do_write_construct (NMConnection *connection, has_complex_routes_v4 ? "" : "6"); return FALSE; } + if ( ( s_ip4 + && nm_setting_ip_config_get_num_routing_rules (s_ip4) > 0) + || ( s_ip6 + && nm_setting_ip_config_get_num_routing_rules (s_ip6) > 0)) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "Cannot configure routing rules on a connection that has an associated 'rule%s-' file", + has_complex_routes_v4 ? "" : "6"); + return FALSE; + } route_ignore = TRUE; } else route_ignore = FALSE; @@ -3193,6 +3248,10 @@ do_write_construct (NMConnection *connection, error)) return FALSE; + write_ip_routing_rules (connection, + ifcfg, + route_ignore); + write_connection_setting (s_con, ifcfg); NM_SET_OUT (out_ifcfg, g_steal_pointer (&ifcfg)); diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c index f3d58e26c7..02aba1c57f 100644 --- a/src/settings/plugins/ifcfg-rh/shvar.c +++ b/src/settings/plugins/ifcfg-rh/shvar.c @@ -879,10 +879,25 @@ _is_all_digits (const char *str) #define IS_NUMBERED_TAG(key, tab_name) \ ({ \ - const char *_key = (key); \ + const char *_key2 = (key); \ \ - ( (strncmp (_key, tab_name, NM_STRLEN (tab_name)) == 0) \ - && _is_all_digits (&_key[NM_STRLEN (tab_name)])); \ + ( (strncmp (_key2, tab_name, NM_STRLEN (tab_name)) == 0) \ + && _is_all_digits (&_key2[NM_STRLEN (tab_name)])); \ + }) + +#define IS_NUMBERED_TAG_PARSE(key, tab_name, out_idx) \ + ({ \ + const char *_key = (key); \ + gint64 _idx; \ + gboolean _good = FALSE; \ + gint64 *_out_idx = (out_idx); \ + \ + if ( IS_NUMBERED_TAG (_key, ""tab_name"") \ + && (_idx = _nm_utils_ascii_str_to_int64 (&_key[NM_STRLEN (tab_name)], 10, 0, G_MAXINT64, -1)) != -1) { \ + NM_SET_OUT (_out_idx, _idx); \ + _good = TRUE; \ + } \ + _good; \ }) static gboolean @@ -919,10 +934,30 @@ _svKeyMatchesType (const char *key, SvKeyType match_key_type) if (IS_NUMBERED_TAG (key, "SRIOV_VF")) return TRUE; } + if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) { + if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", NULL)) + return TRUE; + } + if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) { + if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", NULL)) + return TRUE; + } return FALSE; } +gint64 +svNumberedParseKey (const char *key) +{ + gint64 idx; + + if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", &idx)) + return idx; + if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", &idx)) + return idx; + return -1; +} + GHashTable * svGetKeys (shvarFile *s, SvKeyType match_key_type) { @@ -947,6 +982,42 @@ svGetKeys (shvarFile *s, SvKeyType match_key_type) return keys; } +static int +_get_keys_sorted_cmp (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + const char *k_a = *((const char *const*) a); + const char *k_b = *((const char *const*) b); + gint64 n_a; + gint64 n_b; + + n_a = svNumberedParseKey (k_a); + n_b = svNumberedParseKey (k_b); + NM_CMP_DIRECT (n_a, n_b); + NM_CMP_RETURN (strcmp (k_a, k_b)); + nm_assert_not_reached (); + return 0; +} + +const char ** +svGetKeysSorted (shvarFile *s, + SvKeyType match_key_type, + guint *out_len) +{ + gs_unref_hashtable GHashTable *keys_hash = NULL; + + keys_hash = svGetKeys (s, match_key_type); + if (!keys_hash) { + NM_SET_OUT (out_len, 0); + return NULL; + } + return (const char **) nm_utils_hash_keys_to_array (keys_hash, + _get_keys_sorted_cmp, + NULL, + out_len); +} + /*****************************************************************************/ const char * diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h index 622bb474b1..b38a855760 100644 --- a/src/settings/plugins/ifcfg-rh/shvar.h +++ b/src/settings/plugins/ifcfg-rh/shvar.h @@ -40,6 +40,8 @@ typedef enum { SV_KEY_TYPE_TC = (1LL << 3), SV_KEY_TYPE_USER = (1LL << 4), SV_KEY_TYPE_SRIOV_VF = (1LL << 5), + SV_KEY_TYPE_ROUTING_RULE4 = (1LL << 6), + SV_KEY_TYPE_ROUTING_RULE6 = (1LL << 7), } SvKeyType; const char *svFileGetName (const shvarFile *s); @@ -67,8 +69,14 @@ char *svGetValueStr_cp (shvarFile *s, const char *key); int svParseBoolean (const char *value, int def); +gint64 svNumberedParseKey (const char *key); + GHashTable *svGetKeys (shvarFile *s, SvKeyType match_key_type); +const char **svGetKeysSorted (shvarFile *s, + SvKeyType match_key_type, + guint *out_len); + /* return TRUE if resolves to any truth value (e.g. "yes", "y", "true") * return FALSE if resolves to any non-truth value (e.g. "no", "n", "false") * return otherwise diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected new file mode 100644 index 0000000000..0c2fa035cf --- /dev/null +++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected @@ -0,0 +1,19 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=stable-privacy +ROUTING_RULE_1="priority 10 from 0.0.0.0/0 table 1" +ROUTING_RULE_2="priority 10 to 192.167.8.0/24 table 2" +ROUTING_RULE6_3="priority 10 from ::/0 table 10" +ROUTING_RULE6_4="priority 10 to 1:2:3::5/24 table 22" +ROUTING_RULE6_5="priority 10 to 1:3:3::5 table 55" +NAME="Test Write Routing Rules" +UUID=${UUID} +ONBOOT=yes diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c index 99ffae403b..09e2c0e4b5 100644 --- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c @@ -4465,6 +4465,101 @@ test_write_wired_dhcp (void) nmtst_assert_connection_equals (connection, TRUE, reread, FALSE); } +static NMIPRoutingRule * +_ip_routing_rule_new (int addr_family, + const char *str) +{ + NMIPRoutingRuleAsStringFlags flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE; + gs_free_error GError *local = NULL; + NMIPRoutingRule *rule; + + if (addr_family != AF_UNSPEC) { + if (addr_family == AF_INET) + flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET; + else { + g_assert (addr_family == AF_INET6); + flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6; + } + } + + rule = nm_ip_routing_rule_from_string (str, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | flags, + NULL, + nmtst_get_rand_bool () ? &local : NULL); + nmtst_assert_success (rule, local); + + if (addr_family != AF_UNSPEC) + g_assert_cmpint (nm_ip_routing_rule_get_addr_family (rule), ==, addr_family); + return rule; +} + +static void +_ip_routing_rule_add_to_setting (NMSettingIPConfig *s_ip, + const char *str) +{ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + + rule = _ip_routing_rule_new (nm_setting_ip_config_get_addr_family (s_ip), str); + nm_setting_ip_config_add_routing_rule (s_ip, rule); +} + +static void +test_write_routing_rules (void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection *s_con; + NMSettingWired *s_wired; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + + connection = nm_simple_connection_new (); + + s_con = (NMSettingConnection *) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, "Test Write Routing Rules", + NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (), + NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME, + NULL); + + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + + g_object_set (s_ip4, + NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip6)); + + g_object_set (s_ip6, + NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + + _ip_routing_rule_add_to_setting (s_ip4, "pref 10 from 0.0.0.0/0 table 1"); + _ip_routing_rule_add_to_setting (s_ip4, "priority 10 to 192.167.8.0/24 table 2"); + _ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 table 10"); + _ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 to 1:2:3::5/24 table 22"); + _ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 to 1:3:3::5/128 table 55"); + + nmtst_assert_connection_verifies (connection); + + _writer_new_connec_exp (connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR"/ifcfg-Test_Write_Routing_Rules.cexpected", + &testfile); + reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL); + nmtst_assert_connection_equals (connection, TRUE, reread, FALSE); +} + static void test_write_wired_match (void) { @@ -10200,6 +10295,7 @@ int main (int argc, char **argv) g_test_add_func (TPATH "wired/write-dhcp-plus-ip", test_write_wired_dhcp_plus_ip); g_test_add_func (TPATH "wired/write/dhcp-8021x-peap-mschapv2", test_write_wired_dhcp_8021x_peap_mschapv2); g_test_add_func (TPATH "wired/write/match", test_write_wired_match); + g_test_add_func (TPATH "wired/write/routing-rules", test_write_routing_rules); #define _add_test_write_wired_8021x_tls(testpath, scheme, flags) \ nmtst_add_test_func (testpath, test_write_wired_8021x_tls, GINT_TO_POINTER (scheme), GINT_TO_POINTER (flags))