initrd: rework parsing of ip method from "ip="

Dracut supports several options for the "ip=" method.

NetworkManager interprets and handles them in a certain way that aims to
give a similar behavior. But as such it maps different settings ("auth6"
and "dhcp6") to exactly the same behavior.

Add _parse_ip_method() function to normalize these keys, and map their
aliases to the keyword that nm-initrd-generator handles. The advantage
is that you see now in _parse_ip_method() which methods are mapped to
the same behavior, and the later (more complex) code only deals with the
normalized kinds.

Also, use the same validation code at all 3 places where IP methods
can appear, that is

  ip=<method>
  ip=<ifname>:<method>[:...]
  ip=<client-ip>:...:<method>[:...]

Also, dracut supports specifying multiple methods and concatenate them
with comma. nm-initrd-generator only did partly, for example,
`ip=dhcp,dhcp6" would have worked, but only because the code failed
to recognize the string and fell back to the default behavior. It would
not have worked as `ip=<ifname>:dhcp,dhcp6[:...]`. Not all combinations
make sense, but some do. So let _parse_ip_method() detect and handle
them. Currently, they mostly map to "auto", but in the future it might
make sense that `ip=dhcp,local6` is a distinct kind.

Try to tighten up the parsing. It's fine to be forgiving and flexible
about what we parse, but bogus values should not silently be
accepted. However, in order to keep previous behavior, `ip=bogus`
and `ip=<client-ip>:...:<bogus-method>[:...]` explicitly map invalid
method to "auto".
This commit is contained in:
Thomas Haller 2021-07-23 12:30:20 +02:00
parent 6fa7f2e06c
commit 8b25221689
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
2 changed files with 139 additions and 25 deletions

View file

@ -394,6 +394,120 @@ reader_read_all_connections_from_fw(Reader *reader, const char *sysfs_dir)
reader_add_connection(reader, "ofw", dt_connection);
}
#define _strv_is_same_unordered(strv, ...) \
nm_strv_is_same_unordered(NM_CAST_STRV_CC(strv), -1, NM_MAKE_STRV(__VA_ARGS__), -1)
static void
_strv_remove(const char **strv, const char *needle)
{
gssize idx;
gsize len;
gsize i;
idx = nm_utils_strv_find_first((char **) strv, -1, needle);
if (idx < 0)
return;
/* Remove element at idx, by shifting the remaining ones
* (including the terminating NULL). */
len = NM_PTRARRAY_LEN(strv);
for (i = idx; i < len; i++)
strv[i] = strv[i + 1];
}
static const char *
_parse_ip_method(const char *kind)
{
const char *const KINDS[] = {
"none",
"dhcp",
"dhcp6",
"link6",
"auto",
"ibft",
};
gs_free char * kind_to_free = NULL;
gs_free const char **strv = NULL;
gsize i;
kind = nm_strstrip_avoid_copy_a(300, kind, &kind_to_free);
if (nm_str_is_empty(kind)) {
/* Dracut defaults empty/missing to "dhcp". We treat them differently, as it
* depends on whether we have IP addresses too.
* https://github.com/dracutdevs/dracut/blob/3cc9f1c10c67dcdb5254e0eb69f19e9ab22abf20/modules.d/35network-legacy/parse-ip-opts.sh#L62 */
return "auto";
}
for (i = 0; i < G_N_ELEMENTS(KINDS); i++) {
if (nm_streq(kind, KINDS[i]))
return KINDS[i];
}
/* the following are (currently) treated as aliases. */
if (nm_streq(kind, "fw"))
return "ibft";
if (nm_streq(kind, "single-dhcp"))
return "dhcp";
if (nm_streq(kind, "off"))
return "none";
if (nm_streq(kind, "auto6"))
return "dhcp6";
if (NM_IN_STRSET(kind, "on", "any"))
return "auto";
if (!strchr(kind, ','))
return NULL;
/* dracut also supports combinations, separated by comma. We don't
* support arbitrary combinations, but accept specific subsets. */
strv = nm_utils_strsplit_set_full(kind, ",", NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
if (!strv)
return NULL;
/* first normalize the strv array by replacing all entries by their
* normalized kind. */
for (i = 0; strv[i]; i++) {
strv[i] = _parse_ip_method(strv[i]);
if (!strv[i]) {
/* Unknown key. Not recognized. */
return NULL;
}
}
/* sort list and remove duplicates. */
nm_utils_strv_sort(strv, -1);
_nm_utils_strv_cleanup_const(strv, TRUE, TRUE);
if (nm_utils_strv_find_first((char **) strv, -1, "auto") >= 0) {
/* if "auto" is present, then "dhcp4", "dhcp6", and "local6" is implied. */
_strv_remove(strv, "dhcp4");
_strv_remove(strv, "dhcp6");
_strv_remove(strv, "local6");
} else if (nm_utils_strv_find_first((char **) strv, -1, "dhcp6") >= 0) {
/* if "dhcp6" is present, then "local6" is implied. */
_strv_remove(strv, "local6");
}
if (strv[0] && !strv[1]) {
/* there is only one value left. It's good. */
return strv[0];
}
/* only certain combinations are allowed... those are listed
* and mapped to a canonical value.
*
* For the moment, these map all to "auto". This might be revisited
* in the future to add new kinds like "dhcp+local6". */
if (_strv_is_same_unordered(strv, "dhcp", "dhcp6"))
return "auto";
if (_strv_is_same_unordered(strv, "dhcp", "local6"))
return "auto";
/* undetected. */
return NULL;
}
static void
reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
{
@ -403,7 +517,8 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
gs_unref_hashtable GHashTable *ibft = NULL;
const char * tmp;
const char * tmp2;
const char * kind = NULL;
const char * tmp3;
const char * kind;
const char * client_ip = NULL;
const char * peer = NULL;
const char * gateway_ip = NULL;
@ -432,24 +547,17 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
tmp = get_word(&argument, ':');
if (!*argument) {
/* ip={dhcp|on|any|dhcp6|auto6|link6|ibft} */
kind = tmp;
kind = _parse_ip_method(tmp);
if (!kind) {
/* invalid method. We treat it as "auto". */
kind = "auto";
}
} else {
tmp2 = get_word(&argument, ':');
if (NM_IN_STRSET(tmp2,
"none",
"off",
"dhcp",
"single-dhcp",
"on"
"any",
"dhcp6",
"auto",
"auto6",
"link6",
"ibft")) {
if (!nm_str_is_empty(tmp2) && (tmp3 = _parse_ip_method(tmp2))) {
/* <ifname>:{none|off|dhcp|on|any|dhcp6|auto|auto6|link6|ibft} */
iface_spec = tmp;
kind = tmp2;
kind = tmp3;
} else {
/* <client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<kind> */
client_ip = tmp;
@ -466,7 +574,12 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
netmask = get_word(&argument, ':');
client_hostname = get_word(&argument, ':');
iface_spec = get_word(&argument, ':');
kind = get_word(&argument, ':');
tmp2 = get_word(&argument, ':');
kind = _parse_ip_method(tmp2);
if (!kind) {
/* invalid method. We treat that as "auto". */
kind = "auto";
}
}
if (client_hostname && !nm_sd_hostname_is_valid(client_hostname, FALSE))
@ -495,7 +608,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
}
}
if (iface_spec == NULL && NM_IN_STRSET(kind, "fw", "ibft")) {
if (iface_spec == NULL && nm_streq(kind, "ibft")) {
reader_read_all_connections_from_fw(reader, sysfs_dir);
return;
}
@ -592,7 +705,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
}
/* Dynamic IP configuration configured explicitly. */
if (NM_IN_STRSET(kind, "none", "off")) {
if (nm_streq(kind, "none")) {
if (nm_setting_ip_config_get_num_addresses(s_ip6) == 0) {
g_object_set(s_ip6,
NM_SETTING_IP_CONFIG_METHOD,
@ -605,7 +718,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
NULL);
}
} else if (NM_IN_STRSET(kind, "dhcp", "single-dhcp")) {
} else if (nm_streq(kind, "dhcp")) {
g_object_set(s_ip4,
NM_SETTING_IP_CONFIG_METHOD,
NM_SETTING_IP4_CONFIG_METHOD_AUTO,
@ -618,7 +731,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
NM_SETTING_IP6_CONFIG_METHOD_AUTO,
NULL);
}
} else if (NM_IN_STRSET(kind, "auto6", "dhcp6")) {
} else if (nm_streq(kind, "dhcp6")) {
g_object_set(s_ip6,
NM_SETTING_IP_CONFIG_METHOD,
NM_SETTING_IP6_CONFIG_METHOD_AUTO,
@ -631,7 +744,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
NULL);
}
} else if (nm_streq0(kind, "link6")) {
} else if (nm_streq(kind, "link6")) {
g_object_set(s_ip6,
NM_SETTING_IP_CONFIG_METHOD,
NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
@ -644,7 +757,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
NULL);
}
} else if (NM_IN_STRSET(kind, "fw", "ibft")) {
} else if (nm_streq(kind, "ibft")) {
NMSettingWired *s_wired;
const char * mac = NULL;
const char * ifname;
@ -684,6 +797,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
}
}
} else {
nm_assert(nm_streq(kind, "auto"));
clear_ip4_required_timeout = FALSE;
}

View file

@ -130,7 +130,7 @@ static void
test_dhcp_with_hostname(void)
{
gs_unref_hashtable GHashTable *connections = NULL;
const char *const * ARGV = NM_MAKE_STRV("ip=::::host1::dhcp");
const char *const * ARGV = NM_MAKE_STRV("ip=::::host1::dhcp,dhcp6");
NMConnection * connection;
NMSettingConnection * s_con;
NMSettingWired * s_wired;
@ -181,7 +181,7 @@ test_dhcp_with_hostname(void)
static void
test_dhcp_with_mtu(void)
{
const char *const *ARGV0 = NM_MAKE_STRV("ip=:dhcp:1499");
const char *const *ARGV0 = NM_MAKE_STRV("ip=:dhcp6,dhcp:1499");
const char *const *ARGV1 = NM_MAKE_STRV("ip=::::::dhcp:1499");
const char *const *ARGV[] = {ARGV0, ARGV1};
guint i;
@ -290,7 +290,7 @@ test_dhcp_timeout(void)
static void
test_if_auto_with_mtu(void)
{
const char *const *ARGV = NM_MAKE_STRV("ip=eth0:auto:1666");
const char *const *ARGV = NM_MAKE_STRV("ip=eth0:dhcp,dhcp6:1666");
gs_unref_object NMConnection *connection = NULL;
NMSettingConnection * s_con;
NMSettingWired * s_wired;