From 1cb0635d0801c63d7cbb3413035296cf37d61e79 Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Wed, 20 Nov 2024 18:19:45 +0100 Subject: [PATCH 1/6] initrd: add new NBFT parser The NVMe Boot Firmware Table (NBFT) is a mechanism of passing context from a pre-OS Boot environment to an OS runtime, as defined by the NVM Express Boot Specification. Exposed as an ACPI table it contains network interface definitions along with NVMe subsystem and namespace data structures. This adds new nm-initrd-generator parser that uses libnvme NBFT parser implementation. Signed-off-by: Tomas Bzatek --- config.h.meson | 2 + man/nm-initrd-generator.xml | 1 + meson.build | 8 + meson_options.txt | 1 + src/nm-initrd-generator/meson.build | 19 +- src/nm-initrd-generator/nm-initrd-generator.h | 2 + src/nm-initrd-generator/nmi-cmdline-reader.c | 12 + src/nm-initrd-generator/nmi-nbft-reader.c | 293 ++++++++++++++++++ 8 files changed, 332 insertions(+), 6 deletions(-) create mode 100644 src/nm-initrd-generator/nmi-nbft-reader.c diff --git a/config.h.meson b/config.h.meson index 9ae88b26bf..2bd93ce224 100644 --- a/config.h.meson +++ b/config.h.meson @@ -280,3 +280,5 @@ /* Define to 1 if you have history support from -lreadline. */ #mesondefine HAVE_READLINE_HISTORY +/* Define if NBFT support is enabled */ +#mesondefine WITH_NBFT diff --git a/man/nm-initrd-generator.xml b/man/nm-initrd-generator.xml index 2d3d875532..312edff2ee 100644 --- a/man/nm-initrd-generator.xml +++ b/man/nm-initrd-generator.xml @@ -154,6 +154,7 @@ + diff --git a/meson.build b/meson.build index 40386a0b4e..a88012c6b5 100644 --- a/meson.build +++ b/meson.build @@ -929,6 +929,14 @@ if python.found() config_h.set_quoted('TEST_NM_PYTHON', python_path) endif +# libnvme (NBFT support) +enable_nbft = get_option('nbft') +if enable_nbft + libnvme_dep = dependency('libnvme', version: '>= 1.5', required: false) + assert(libnvme_dep.found(), 'NBFT support was requested, but the libnvme library is not available. Use -Dnbft=false to build without it.') +endif +config_h.set10('WITH_NBFT', enable_nbft) + data_conf = configuration_data() data_conf.set('DISTRO_NETWORK_SERVICE', (enable_ifcfg_rh ? 'network.service' : '')) data_conf.set('NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT', config_default_logging_audit) diff --git a/meson_options.txt b/meson_options.txt index fe696aaf16..18d8bfc44c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -45,6 +45,7 @@ option('nmtui', type: 'boolean', value: true, description: 'Build nmtui') option('nm_cloud_setup', type: 'boolean', value: true, description: 'Build nm-cloud-setup, a tool for automatically configuring networking in cloud') option('bluez5_dun', type: 'boolean', value: false, description: 'enable Bluez5 DUN support') option('ebpf', type: 'combo', choices: ['auto', 'true', 'false'], description: 'Enable eBPF support') +option('nbft', type: 'boolean', value: true, description: 'Enable NBFT support in the initrd generator') # configuration plugins option('config_plugins_default', type: 'string', value: '', description: 'Default configuration option for main.plugins setting, used as fallback if the configuration option is unset') diff --git a/src/nm-initrd-generator/meson.build b/src/nm-initrd-generator/meson.build index 080fe7e42f..0e9689f321 100644 --- a/src/nm-initrd-generator/meson.build +++ b/src/nm-initrd-generator/meson.build @@ -1,19 +1,28 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +libnmi_deps = [ + libnm_core_public_dep, +] + +if enable_nbft + libnmi_deps += [ + libnvme_dep + ] +endif + libnmi_core = static_library( 'nmi-core', sources: files( 'nmi-cmdline-reader.c', 'nmi-dt-reader.c', 'nmi-ibft-reader.c', + 'nmi-nbft-reader.c', ), include_directories: [ src_inc, top_inc, ], - dependencies: [ - libnm_core_public_dep, - ], + dependencies: libnmi_deps, ) executable( @@ -23,9 +32,7 @@ executable( src_inc, top_inc, ], - dependencies: [ - libnm_core_public_dep, - ], + dependencies: libnmi_deps, link_with: [ libnmi_core, libnm_core_aux_intern, diff --git a/src/nm-initrd-generator/nm-initrd-generator.h b/src/nm-initrd-generator/nm-initrd-generator.h index c2baad4e55..a73ce361f9 100644 --- a/src/nm-initrd-generator/nm-initrd-generator.h +++ b/src/nm-initrd-generator/nm-initrd-generator.h @@ -41,6 +41,8 @@ nmi_ibft_update_connection_from_nic(NMConnection *connection, GHashTable *nic, G NMConnection *nmi_dt_reader_parse(const char *sysfs_dir); +NMConnection **nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname); + GHashTable *nmi_cmdline_reader_parse(const char *etc_connections_dir, const char *sysfs_dir, const char *const *argv, diff --git a/src/nm-initrd-generator/nmi-cmdline-reader.c b/src/nm-initrd-generator/nmi-cmdline-reader.c index d6dc1fcb7c..d7fd8667ba 100644 --- a/src/nm-initrd-generator/nmi-cmdline-reader.c +++ b/src/nm-initrd-generator/nmi-cmdline-reader.c @@ -1453,6 +1453,7 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, int i; guint64 dhcp_timeout = 90; guint64 dhcp_num_tries = 1; + gboolean nvmf_nonbft = FALSE; reader = reader_new(); @@ -1561,7 +1562,18 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, reader_parse_dns_backend(reader, argument); } else if (nm_streq(tag, "rd.net.dns-resolve-mode")) { reader_parse_dns_resolve_mode(reader, argument); + } else if (nm_streq(tag, "rd.nvmf.nonbft")) + nvmf_nonbft = TRUE; + } + + if (!nvmf_nonbft) { + NMConnection **nbft_connections, **c; + + nbft_connections = nmi_nbft_reader_parse(sysfs_dir, &reader->hostname); + for (c = nbft_connections; c && *c; c++) { + reader_add_connection(reader, nm_connection_get_id(*c), *c); } + g_free(nbft_connections); } for (i = 0; i < reader->vlan_parents->len; i++) { diff --git a/src/nm-initrd-generator/nmi-nbft-reader.c b/src/nm-initrd-generator/nmi-nbft-reader.c new file mode 100644 index 0000000000..782c6acca9 --- /dev/null +++ b/src/nm-initrd-generator/nmi-nbft-reader.c @@ -0,0 +1,293 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-initrd-generator.h" + +#if WITH_NBFT + +#include + +#include "libnm-log-core/nm-logging.h" +#include "libnm-core-intern/nm-core-internal.h" + +/*****************************************************************************/ + +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "nbft-reader: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static inline gboolean +is_valid_addr(int family, const char *addr) +{ + return (addr && strlen(addr) > 0 && !nm_streq(addr, "0.0.0.0") && !nm_streq(addr, "::") + && nm_utils_ipaddr_valid(family, addr)); +} + +static NMConnection * +parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) +{ + gs_unref_object NMConnection *connection; + NMSetting *s_connection; + NMSetting *s_wired; + gs_free char *hwaddr = NULL; + gs_free char *ifname = NULL; + gs_unref_object NMSetting *s_ip4 = NULL; + gs_unref_object NMSetting *s_ip6 = NULL; + nm_auto_unref_ip_address NMIPAddress *ipaddr = NULL; + gs_free_error GError *error = NULL; + int family = AF_UNSPEC; + NMIPAddr addr_bin; + + /* Pre-checks */ + if (!nm_inet_parse_bin_full(family, FALSE, hfi->tcp_info.ipaddr, &family, &addr_bin)) { + _LOGW(LOGD_CORE, "NBFT: Malformed IP address: '%s'", hfi->tcp_info.ipaddr); + return NULL; + } + + ifname = g_strdup_printf("NBFT connection %d", iface_idx); + hwaddr = g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x", + hfi->tcp_info.mac_addr[0], + hfi->tcp_info.mac_addr[1], + hfi->tcp_info.mac_addr[2], + hfi->tcp_info.mac_addr[3], + hfi->tcp_info.mac_addr[4], + hfi->tcp_info.mac_addr[5]); + + /* Create new connection */ + connection = nm_simple_connection_new(); + + s_connection = nm_setting_connection_new(); + g_object_set(s_connection, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_ID, + ifname, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, + NMI_AUTOCONNECT_PRIORITY_FIRMWARE, + NULL); + nm_connection_add_setting(connection, s_connection); + + /* MAC address */ + s_wired = nm_setting_wired_new(); + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL); + nm_connection_add_setting(connection, s_wired); + + /* TODO: hfi->tcp_info.vlan */ + + /* IP addresses */ + s_ip4 = nm_setting_ip4_config_new(); + s_ip6 = nm_setting_ip6_config_new(); + + switch (family) { + /* IPv4 */ + case AF_INET: + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + if (is_valid_addr(AF_INET, hfi->tcp_info.dhcp_server_ipaddr)) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL); + if (hfi->tcp_info.host_name && strlen(hfi->tcp_info.host_name) > 0) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + hfi->tcp_info.host_name, + NULL); + } + } else { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + ipaddr = nm_ip_address_new_binary(AF_INET, + &addr_bin, + hfi->tcp_info.subnet_mask_prefix, + &error); + if (!ipaddr) { + _LOGW(LOGD_CORE, + "Cannot parse IP %s/%u: %s", + hfi->tcp_info.ipaddr, + hfi->tcp_info.subnet_mask_prefix, + error->message); + return NULL; + } + nm_setting_ip_config_add_address(NM_SETTING_IP_CONFIG(s_ip4), ipaddr); + if (is_valid_addr(AF_INET, hfi->tcp_info.gateway_ipaddr)) { + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_GATEWAY, + hfi->tcp_info.gateway_ipaddr, + NULL); + } + if (is_valid_addr(AF_INET, hfi->tcp_info.primary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip4), + hfi->tcp_info.primary_dns_ipaddr); + } + if (is_valid_addr(AF_INET, hfi->tcp_info.secondary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip4), + hfi->tcp_info.secondary_dns_ipaddr); + } + } + break; + + /* IPv6 */ + case AF_INET6: + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + if (is_valid_addr(AF_INET6, hfi->tcp_info.dhcp_server_ipaddr)) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + if (hfi->tcp_info.host_name && strlen(hfi->tcp_info.host_name) > 0) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + hfi->tcp_info.host_name, + NULL); + } + } else { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NULL); + /* FIXME: buggy firmware implementations may report prefix=0 for v6 addresses, + * reported as https://github.com/timberland-sig/edk2/issues/37 + */ + ipaddr = nm_ip_address_new_binary(AF_INET6, + &addr_bin, + hfi->tcp_info.subnet_mask_prefix, + &error); + if (!ipaddr) { + _LOGW(LOGD_CORE, + "Cannot parse IP %s/%u: %s", + hfi->tcp_info.ipaddr, + prefix, + error->message); + return NULL; + } + nm_setting_ip_config_add_address(NM_SETTING_IP_CONFIG(s_ip6), ipaddr); + if (is_valid_addr(AF_INET6, hfi->tcp_info.gateway_ipaddr)) { + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_GATEWAY, + hfi->tcp_info.gateway_ipaddr, + NULL); + } + if (is_valid_addr(AF_INET6, hfi->tcp_info.primary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip6), + hfi->tcp_info.primary_dns_ipaddr); + } + if (is_valid_addr(AF_INET6, hfi->tcp_info.secondary_dns_ipaddr)) { + nm_setting_ip_config_add_dns(NM_SETTING_IP_CONFIG(s_ip6), + hfi->tcp_info.secondary_dns_ipaddr); + } + } + break; + default: + g_warn_if_reached(); + } + + nm_connection_add_setting(connection, g_steal_pointer(&s_ip4)); + nm_connection_add_setting(connection, g_steal_pointer(&s_ip6)); + + /* Hostname */ + if (hfi->tcp_info.host_name && strlen(hfi->tcp_info.host_name) > 0) { + g_free(*hostname); + *hostname = g_strdup(hfi->tcp_info.host_name); + } + + /* TODO: translate the following HFI struct members? + * hfi->tcp_info.pci_sbdf + * hfi->tcp_info.ip_origin + * hfi->tcp_info.dhcp_server_ipaddr + * hfi->tcp_info.this_hfi_is_default_route + * hfi->tcp_info.dhcp_override + */ + + if (!nm_connection_normalize(connection, NULL, NULL, &error)) { + _LOGW(LOGD_CORE, "Generated an invalid connection: %s", error->message); + return NULL; + } + return g_steal_pointer(&connection); +} + +NMConnection ** +nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) +{ + GPtrArray *a; + gs_free char *path = NULL; + gs_free_error GError *error = NULL; + GDir *dir; + const char *entry_name; + int idx = 1; + + g_return_val_if_fail(sysfs_dir != NULL, NULL); + path = g_build_filename(sysfs_dir, "firmware", "acpi", "tables", NULL); + + dir = g_dir_open(path, 0, NULL); + if (!dir) + return NULL; + + a = g_ptr_array_new(); + + while ((entry_name = g_dir_read_name(dir))) { + gs_free char *entry_path = NULL; + struct nbft_info *nbft; + struct nbft_info_hfi **hfi; + int ret; + + if (!g_str_has_prefix(entry_name, "NBFT")) + continue; + + entry_path = g_build_filename(path, entry_name, NULL); + ret = nvme_nbft_read(&nbft, entry_path); + if (ret) { + _LOGW(LOGD_CORE, "Error parsing NBFT table %s: %m", entry_path); + continue; + } + + for (hfi = nbft->hfi_list; hfi && *hfi; hfi++, idx++) { + NMConnection *connection; + + if (!nm_streq((*hfi)->transport, "tcp")) { + _LOGW(LOGD_CORE, + "NBFT table %s, HFI descriptor %d: unsupported transport type '%s'", + entry_path, + (*hfi)->index, + (*hfi)->transport); + continue; + } + + connection = parse_hfi(*hfi, idx, hostname); + if (connection) + g_ptr_array_add(a, connection); + } + + nvme_nbft_free(nbft); + } + + g_dir_close(dir); + g_ptr_array_add(a, NULL); /* trailing NULL-delimiter */ + return (NMConnection **) g_ptr_array_free(a, FALSE); +} + +#else /* WITH_NBFT */ + +NMConnection ** +nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) +{ + return NULL; +} + +#endif From 8b7c6f8b9052b17e4c2392e1ac559ee9e9f31a49 Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Mon, 2 Dec 2024 14:52:54 +0100 Subject: [PATCH 2/6] initrd: avoid dynamic linking of libnvme, use dlopen() instead As suggested during the review process, NBFT is niche and most users won't need it. So keep the initrd generator light and only open libnvme when any NBFT table is found. In a typical dracut host-only scenario the nbft dracut module will be pulled in only when NBFT is present in the system, packing in nvme-cli and libnvme in the initramfs image. Signed-off-by: Tomas Bzatek --- config.h.meson | 3 ++ meson.build | 3 ++ src/nm-initrd-generator/meson.build | 18 +++----- src/nm-initrd-generator/nmi-nbft-reader.c | 53 +++++++++++++++++++---- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/config.h.meson b/config.h.meson index 2bd93ce224..d41304db60 100644 --- a/config.h.meson +++ b/config.h.meson @@ -282,3 +282,6 @@ /* Define if NBFT support is enabled */ #mesondefine WITH_NBFT + +/* Define to 1 if dlvsym() is available */ +#mesondefine HAVE_DLVSYM diff --git a/meson.build b/meson.build index a88012c6b5..e13cd9da07 100644 --- a/meson.build +++ b/meson.build @@ -137,6 +137,9 @@ config_h.set10('HAVE_DECL_REALLOCARRAY', cc.has_function('reallocarray', prefix: config_h.set10('HAVE_DECL_EXPLICIT_BZERO', cc.has_function('explicit_bzero', prefix: '#include ')) config_h.set10('HAVE_DECL_MEMFD_CREATE', cc.has_function('memfd_create', prefix: '#include ')) +config_h.set10('HAVE_DLVSYM', cc.has_function('dlvsym', prefix: '''#define _GNU_SOURCE + #include ''')) + # types config_h.set('SIZEOF_PID_T', cc.sizeof('pid_t', prefix : '#include ')) config_h.set('SIZEOF_UID_T', cc.sizeof('uid_t', prefix : '#include ')) diff --git a/src/nm-initrd-generator/meson.build b/src/nm-initrd-generator/meson.build index 0e9689f321..6b02e0668d 100644 --- a/src/nm-initrd-generator/meson.build +++ b/src/nm-initrd-generator/meson.build @@ -1,15 +1,5 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -libnmi_deps = [ - libnm_core_public_dep, -] - -if enable_nbft - libnmi_deps += [ - libnvme_dep - ] -endif - libnmi_core = static_library( 'nmi-core', sources: files( @@ -22,7 +12,9 @@ libnmi_core = static_library( src_inc, top_inc, ], - dependencies: libnmi_deps, + dependencies: [ + libnm_core_public_dep, + ], ) executable( @@ -32,7 +24,9 @@ executable( src_inc, top_inc, ], - dependencies: libnmi_deps, + dependencies: [ + libnm_core_public_dep, + ], link_with: [ libnmi_core, libnm_core_aux_intern, diff --git a/src/nm-initrd-generator/nmi-nbft-reader.c b/src/nm-initrd-generator/nmi-nbft-reader.c index 782c6acca9..09e5eb12df 100644 --- a/src/nm-initrd-generator/nmi-nbft-reader.c +++ b/src/nm-initrd-generator/nmi-nbft-reader.c @@ -10,6 +10,7 @@ #if WITH_NBFT #include +#include #include "libnm-log-core/nm-logging.h" #include "libnm-core-intern/nm-core-internal.h" @@ -32,6 +33,34 @@ is_valid_addr(int family, const char *addr) && nm_utils_ipaddr_valid(family, addr)); } +static int (*_nvme_nbft_read)(struct nbft_info **nbft, const char *filename); +static void (*_nvme_nbft_free)(struct nbft_info *nbft); + +static void * +load_libnvme(void) +{ + void *handle; + + handle = dlopen("libnvme.so.1", RTLD_LAZY); + if (!handle) + return NULL; + +#if HAVE_DLVSYM + _nvme_nbft_read = dlvsym(handle, "nvme_nbft_read", "LIBNVME_1_5"); + _nvme_nbft_free = dlvsym(handle, "nvme_nbft_free", "LIBNVME_1_5"); +#else + /* no dlvsym() in musl */ + _nvme_nbft_read = dlsym(handle, "nvme_nbft_read"); + _nvme_nbft_free = dlsym(handle, "nvme_nbft_free"); +#endif + + if (!_nvme_nbft_read || !_nvme_nbft_free) { + dlclose(handle); + return NULL; + } + return handle; +} + static NMConnection * parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) { @@ -225,12 +254,13 @@ parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) NMConnection ** nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) { - GPtrArray *a; - gs_free char *path = NULL; - gs_free_error GError *error = NULL; - GDir *dir; - const char *entry_name; - int idx = 1; + nm_auto_unref_ptrarray GPtrArray *a = NULL; + gs_free char *path = NULL; + gs_free_error GError *error = NULL; + GDir *dir; + void *libnvme_handle = NULL; + const char *entry_name; + int idx = 1; g_return_val_if_fail(sysfs_dir != NULL, NULL); path = g_build_filename(sysfs_dir, "firmware", "acpi", "tables", NULL); @@ -250,8 +280,14 @@ nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) if (!g_str_has_prefix(entry_name, "NBFT")) continue; + /* attempt to load libnvme only on the first table match, saving some I/O */ + if (!libnvme_handle && !(libnvme_handle = load_libnvme())) { + g_dir_close(dir); + return NULL; + } + entry_path = g_build_filename(path, entry_name, NULL); - ret = nvme_nbft_read(&nbft, entry_path); + ret = _nvme_nbft_read(&nbft, entry_path); if (ret) { _LOGW(LOGD_CORE, "Error parsing NBFT table %s: %m", entry_path); continue; @@ -274,10 +310,11 @@ nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) g_ptr_array_add(a, connection); } - nvme_nbft_free(nbft); + _nvme_nbft_free(nbft); } g_dir_close(dir); + dlclose(libnvme_handle); g_ptr_array_add(a, NULL); /* trailing NULL-delimiter */ return (NMConnection **) g_ptr_array_free(a, FALSE); } From d38cbfb3d1ec43d953f484b39fcc6c6128e7bf3b Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Tue, 10 Dec 2024 19:22:14 +0100 Subject: [PATCH 3/6] initrd: VLAN support for the NBFT parser Creates additional connections for VLANs, which are in fact separate HFI records in the NBFT table. Uses MAC address for linking parent interface as the interface naming is defined by an external service. Signed-off-by: Tomas Bzatek --- src/nm-initrd-generator/nmi-nbft-reader.c | 164 ++++++++++++++++------ 1 file changed, 122 insertions(+), 42 deletions(-) diff --git a/src/nm-initrd-generator/nmi-nbft-reader.c b/src/nm-initrd-generator/nmi-nbft-reader.c index 09e5eb12df..cf2c3c1626 100644 --- a/src/nm-initrd-generator/nmi-nbft-reader.c +++ b/src/nm-initrd-generator/nmi-nbft-reader.c @@ -61,45 +61,57 @@ load_libnvme(void) return handle; } -static NMConnection * -parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) +static char * +format_conn_name(const char *table_name, struct nbft_info_hfi *hfi, gboolean is_vlan) { - gs_unref_object NMConnection *connection; - NMSetting *s_connection; - NMSetting *s_wired; - gs_free char *hwaddr = NULL; - gs_free char *ifname = NULL; - gs_unref_object NMSetting *s_ip4 = NULL; - gs_unref_object NMSetting *s_ip6 = NULL; - nm_auto_unref_ip_address NMIPAddress *ipaddr = NULL; - gs_free_error GError *error = NULL; - int family = AF_UNSPEC; - NMIPAddr addr_bin; + if (is_vlan) { + nm_assert(hfi->tcp_info.vlan > 0); + return g_strdup_printf("%s connection HFI %d VLAN %d", + table_name, + hfi->index, + hfi->tcp_info.vlan); + } else + return g_strdup_printf("%s connection HFI %d", table_name, hfi->index); +} - /* Pre-checks */ - if (!nm_inet_parse_bin_full(family, FALSE, hfi->tcp_info.ipaddr, &family, &addr_bin)) { - _LOGW(LOGD_CORE, "NBFT: Malformed IP address: '%s'", hfi->tcp_info.ipaddr); - return NULL; +static NMConnection * +find_conn_for_wired_mac(GPtrArray *a, const char *hwaddr) +{ + guint i; + + for (i = 0; i < a->len; i++) { + NMConnection *con = a->pdata[i]; + NMSettingWired *s_wired; + + if (!nm_connection_is_type(con, NM_SETTING_WIRED_SETTING_NAME)) + continue; + s_wired = nm_connection_get_setting_wired(con); + if (!s_wired) + continue; + if (nm_streq(hwaddr, nm_setting_wired_get_mac_address(s_wired))) + return con; } + return NULL; +} - ifname = g_strdup_printf("NBFT connection %d", iface_idx); - hwaddr = g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x", - hfi->tcp_info.mac_addr[0], - hfi->tcp_info.mac_addr[1], - hfi->tcp_info.mac_addr[2], - hfi->tcp_info.mac_addr[3], - hfi->tcp_info.mac_addr[4], - hfi->tcp_info.mac_addr[5]); +static NMConnection * +create_wired_conn(struct nbft_info_hfi *hfi, + const char *conn_name, + const char *hwaddr, + gboolean is_vlan) +{ + NMConnection *connection; + NMSetting *s_connection; + NMSetting *s_wired; - /* Create new connection */ connection = nm_simple_connection_new(); s_connection = nm_setting_connection_new(); g_object_set(s_connection, NM_SETTING_CONNECTION_TYPE, - NM_SETTING_WIRED_SETTING_NAME, + is_vlan ? NM_SETTING_VLAN_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_CONNECTION_ID, - ifname, + conn_name, NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, NMI_AUTOCONNECT_PRIORITY_FIRMWARE, NULL); @@ -110,7 +122,80 @@ parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL); nm_connection_add_setting(connection, s_wired); - /* TODO: hfi->tcp_info.vlan */ + return connection; +} + +static void +parse_hfi(GPtrArray *a, struct nbft_info_hfi *hfi, const char *table_name, char **hostname) +{ + gs_unref_object NMConnection *connection = NULL; + NMConnection *parent_connection; + NMSetting *s_vlan; + gs_free char *hwaddr = NULL; + gs_free char *conn_name = NULL; + gs_unref_object NMSetting *s_ip4 = NULL; + gs_unref_object NMSetting *s_ip6 = NULL; + nm_auto_unref_ip_address NMIPAddress *ipaddr = NULL; + guint prefix; + gs_free_error GError *error = NULL; + int family = AF_UNSPEC; + NMIPAddr addr_bin; + + /* Pre-checks */ + if (!nm_inet_parse_bin_full(family, FALSE, hfi->tcp_info.ipaddr, &family, &addr_bin)) { + _LOGW(LOGD_CORE, "NBFT: Malformed IP address: '%s'", hfi->tcp_info.ipaddr); + return; + } + + /* MAC address */ + hwaddr = g_strdup_printf("%02X:%02X:%02X:%02X:%02X:%02X", + hfi->tcp_info.mac_addr[0], + hfi->tcp_info.mac_addr[1], + hfi->tcp_info.mac_addr[2], + hfi->tcp_info.mac_addr[3], + hfi->tcp_info.mac_addr[4], + hfi->tcp_info.mac_addr[5]); + + /* First check if we need VLANs */ + if (hfi->tcp_info.vlan > 0) { + parent_connection = find_conn_for_wired_mac(a, hwaddr); + if (!parent_connection) { + /* Create new parent wired connection */ + conn_name = format_conn_name(table_name, hfi, FALSE); + parent_connection = create_wired_conn(hfi, conn_name, hwaddr, FALSE); + + s_ip4 = nm_setting_ip4_config_new(); + s_ip6 = nm_setting_ip6_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + nm_connection_add_setting(parent_connection, g_steal_pointer(&s_ip4)); + nm_connection_add_setting(parent_connection, g_steal_pointer(&s_ip6)); + + if (!nm_connection_normalize(parent_connection, NULL, NULL, &error)) { + _LOGW(LOGD_CORE, "Generated an invalid connection: %s", error->message); + g_object_unref(parent_connection); + return; + } + g_ptr_array_add(a, parent_connection); + } + + conn_name = format_conn_name(table_name, hfi, TRUE); + connection = create_wired_conn(hfi, conn_name, hwaddr, TRUE); + + s_vlan = nm_setting_vlan_new(); + g_object_set(s_vlan, NM_SETTING_VLAN_ID, hfi->tcp_info.vlan, NULL); + nm_connection_add_setting(connection, s_vlan); + } else { + /* No VLANS */ + conn_name = format_conn_name(table_name, hfi, FALSE); + connection = create_wired_conn(hfi, conn_name, hwaddr, FALSE); + } /* IP addresses */ s_ip4 = nm_setting_ip4_config_new(); @@ -149,7 +234,7 @@ parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) hfi->tcp_info.ipaddr, hfi->tcp_info.subnet_mask_prefix, error->message); - return NULL; + return; } nm_setting_ip_config_add_address(NM_SETTING_IP_CONFIG(s_ip4), ipaddr); if (is_valid_addr(AF_INET, hfi->tcp_info.gateway_ipaddr)) { @@ -204,7 +289,7 @@ parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) hfi->tcp_info.ipaddr, prefix, error->message); - return NULL; + return; } nm_setting_ip_config_add_address(NM_SETTING_IP_CONFIG(s_ip6), ipaddr); if (is_valid_addr(AF_INET6, hfi->tcp_info.gateway_ipaddr)) { @@ -246,9 +331,10 @@ parse_hfi(struct nbft_info_hfi *hfi, int iface_idx, char **hostname) if (!nm_connection_normalize(connection, NULL, NULL, &error)) { _LOGW(LOGD_CORE, "Generated an invalid connection: %s", error->message); - return NULL; + return; } - return g_steal_pointer(&connection); + + g_ptr_array_add(a, g_steal_pointer(&connection)); } NMConnection ** @@ -260,7 +346,6 @@ nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) GDir *dir; void *libnvme_handle = NULL; const char *entry_name; - int idx = 1; g_return_val_if_fail(sysfs_dir != NULL, NULL); path = g_build_filename(sysfs_dir, "firmware", "acpi", "tables", NULL); @@ -293,9 +378,7 @@ nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) continue; } - for (hfi = nbft->hfi_list; hfi && *hfi; hfi++, idx++) { - NMConnection *connection; - + for (hfi = nbft->hfi_list; hfi && *hfi; hfi++) { if (!nm_streq((*hfi)->transport, "tcp")) { _LOGW(LOGD_CORE, "NBFT table %s, HFI descriptor %d: unsupported transport type '%s'", @@ -304,10 +387,7 @@ nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) (*hfi)->transport); continue; } - - connection = parse_hfi(*hfi, idx, hostname); - if (connection) - g_ptr_array_add(a, connection); + parse_hfi(a, *hfi, entry_name, hostname); } _nvme_nbft_free(nbft); @@ -316,7 +396,7 @@ nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname) g_dir_close(dir); dlclose(libnvme_handle); g_ptr_array_add(a, NULL); /* trailing NULL-delimiter */ - return (NMConnection **) g_ptr_array_free(a, FALSE); + return (NMConnection **) g_ptr_array_free(g_steal_pointer(&a), FALSE); } #else /* WITH_NBFT */ From 81839c0da82ea8f196fc87ec75f37e566f3c52c2 Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Wed, 2 Apr 2025 17:45:33 +0200 Subject: [PATCH 4/6] initrd: workaround zero v6 prefixes for NBFT HFIs Some firmware implementations incorrectly report v6 address prefix of zero. Let's use /64 as a sane workaround. Signed-off-by: Tomas Bzatek --- src/nm-initrd-generator/nmi-nbft-reader.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/nm-initrd-generator/nmi-nbft-reader.c b/src/nm-initrd-generator/nmi-nbft-reader.c index cf2c3c1626..bb2db70030 100644 --- a/src/nm-initrd-generator/nmi-nbft-reader.c +++ b/src/nm-initrd-generator/nmi-nbft-reader.c @@ -276,13 +276,17 @@ parse_hfi(GPtrArray *a, struct nbft_info_hfi *hfi, const char *table_name, char NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); - /* FIXME: buggy firmware implementations may report prefix=0 for v6 addresses, - * reported as https://github.com/timberland-sig/edk2/issues/37 - */ - ipaddr = nm_ip_address_new_binary(AF_INET6, - &addr_bin, - hfi->tcp_info.subnet_mask_prefix, - &error); + prefix = hfi->tcp_info.subnet_mask_prefix; + if (prefix == 0) { + /* Buggy firmware implementations may report prefix=0 for v6 addresses, + * let's fall back to /64. + */ + _LOGW(LOGD_CORE, + "NBFT: Invalid IPv6 prefix %d, using /64 as a fallback.", + hfi->tcp_info.subnet_mask_prefix); + prefix = 64; + } + ipaddr = nm_ip_address_new_binary(AF_INET6, &addr_bin, prefix, &error); if (!ipaddr) { _LOGW(LOGD_CORE, "Cannot parse IP %s/%u: %s", From f0d6b339bf87b91ed2e09e0eca8a7f6b7ceea82a Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Tue, 15 Apr 2025 15:43:19 +0200 Subject: [PATCH 5/6] initrd: skip NBFT parsing when already defined on the cmdline Skip the internal NBFT table parsing when nbft interfaces are already defined on the cmdline, e.g. from the original 95nvmf dracut module. Signed-off-by: Tomas Bzatek --- src/nm-initrd-generator/nmi-cmdline-reader.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/nm-initrd-generator/nmi-cmdline-reader.c b/src/nm-initrd-generator/nmi-cmdline-reader.c index d7fd8667ba..a6cd30f10e 100644 --- a/src/nm-initrd-generator/nmi-cmdline-reader.c +++ b/src/nm-initrd-generator/nmi-cmdline-reader.c @@ -1451,9 +1451,10 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, gs_unref_ptrarray GPtrArray *routes = NULL; gs_unref_ptrarray GPtrArray *znets = NULL; int i; - guint64 dhcp_timeout = 90; - guint64 dhcp_num_tries = 1; - gboolean nvmf_nonbft = FALSE; + guint64 dhcp_timeout = 90; + guint64 dhcp_num_tries = 1; + gboolean nvmf_nonbft = FALSE; + gboolean have_dracut_nbft = FALSE; reader = reader_new(); @@ -1470,7 +1471,10 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, /* pass */ } else if (nm_streq(tag, "net.ifnames")) net_ifnames = !nm_streq(argument, "0"); - else if (nm_streq(tag, "rd.peerdns")) + else if (nm_streq(tag, "ifname")) { + if (NM_STR_HAS_PREFIX(argument, "nbft")) + have_dracut_nbft = TRUE; + } else if (nm_streq(tag, "rd.peerdns")) reader->ignore_auto_dns = !_nm_utils_ascii_str_to_bool(argument, TRUE); else if (nm_streq(tag, "rd.net.timeout.dhcp")) { if (nm_streq0(argument, "infinity")) { @@ -1566,7 +1570,7 @@ nmi_cmdline_reader_parse(const char *etc_connections_dir, nvmf_nonbft = TRUE; } - if (!nvmf_nonbft) { + if (!nvmf_nonbft && !have_dracut_nbft) { NMConnection **nbft_connections, **c; nbft_connections = nmi_nbft_reader_parse(sysfs_dir, &reader->hostname); From ec917dc670c8f8bd73bc238c583b91ee9bb61d63 Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Fri, 22 Nov 2024 16:49:47 +0100 Subject: [PATCH 6/6] ci: add libnvme dependency Required for the nm-initrd-generator NBFT support. Signed-off-by: Tomas Bzatek --- .gitlab-ci.yml | 10 +++++----- contrib/alpine/REQUIRED_PACKAGES | 1 + contrib/debian/REQUIRED_PACKAGES | 1 + contrib/fedora/REQUIRED_PACKAGES | 1 + contrib/fedora/rpm/NetworkManager.spec | 1 + contrib/scripts/nm-ci-run.sh | 7 +++++++ tools/nm-in-container | 1 + 7 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3c36ed81d..b6acd6fb1e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,11 +60,11 @@ variables: # # This is done by running `ci-fairy generate-template` and possibly bumping # ".default_tag". - ALPINE_TAG: 'tag-77ec3d923fd6' - CENTOS_TAG: 'tag-7a677f4838e1' - DEBIAN_TAG: 'tag-ecad19904683' - FEDORA_TAG: 'tag-7a677f4838e1' - UBUNTU_TAG: 'tag-ecad19904683' + ALPINE_TAG: 'tag-57edf560bf4f' + CENTOS_TAG: 'tag-7ea4f50c8578' + DEBIAN_TAG: 'tag-1601ce2572c5' + FEDORA_TAG: 'tag-7ea4f50c8578' + UBUNTU_TAG: 'tag-1601ce2572c5' ALPINE_EXEC: 'bash .gitlab-ci/alpine-install.sh' CENTOS_EXEC: 'bash .gitlab-ci/fedora-install.sh' diff --git a/contrib/alpine/REQUIRED_PACKAGES b/contrib/alpine/REQUIRED_PACKAGES index 358214fd0f..6f0e86fddd 100755 --- a/contrib/alpine/REQUIRED_PACKAGES +++ b/contrib/alpine/REQUIRED_PACKAGES @@ -24,6 +24,7 @@ apk add \ 'jansson-dev' \ 'libgudev-dev' \ 'libndp-dev' \ + 'libnvme-dev' \ 'libnl3-dev' \ 'libpsl-dev' \ 'libsoup-dev' \ diff --git a/contrib/debian/REQUIRED_PACKAGES b/contrib/debian/REQUIRED_PACKAGES index fa73b21add..d91e54d274 100755 --- a/contrib/debian/REQUIRED_PACKAGES +++ b/contrib/debian/REQUIRED_PACKAGES @@ -56,6 +56,7 @@ install \ libndp-dev \ libnewt-dev \ libnss3-dev \ + libnvme-dev \ libpolkit-gobject-1-dev \ libpsl-dev \ libreadline-dev \ diff --git a/contrib/fedora/REQUIRED_PACKAGES b/contrib/fedora/REQUIRED_PACKAGES index 1bb7ec6b30..38a2cbbf43 100755 --- a/contrib/fedora/REQUIRED_PACKAGES +++ b/contrib/fedora/REQUIRED_PACKAGES @@ -66,6 +66,7 @@ install \ jq \ libcurl-devel \ libndp-devel \ + libnvme-devel \ libselinux-devel \ libtool \ libuuid-devel \ diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index 638da502df..788e9ead57 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -307,6 +307,7 @@ BuildRequires: libubsan BuildRequires: firewalld-filesystem BuildRequires: iproute BuildRequires: iproute-tc +BuildRequires: libnvme-devel >= 1.5 Provides: %{name}-dispatcher%{?_isa} = %{epoch}:%{version}-%{release} diff --git a/contrib/scripts/nm-ci-run.sh b/contrib/scripts/nm-ci-run.sh index 3864620f82..4881d110d3 100755 --- a/contrib/scripts/nm-ci-run.sh +++ b/contrib/scripts/nm-ci-run.sh @@ -54,10 +54,15 @@ _WITH_WERROR=1 _WITH_LIBTEAM="true" _WITH_DOCS="true" _WITH_SYSTEMD_LOGIND="true" +_WITH_NBFT="true" if [ $IS_ALPINE = 1 ]; then _WITH_SYSTEMD_LOGIND="false" fi +if ! pkgconf 'libnvme >= 1.5'; then + _WITH_NBFT="false" +fi + if [ -z "${NMTST_SEED_RAND+x}" ]; then NMTST_SEED_RAND="$SRANDOM" if [ -z "$NMTST_SEED_RAND" ]; then @@ -181,6 +186,8 @@ meson setup build \ -D ifcfg_rh=false \ -D ifupdown=true \ \ + -D nbft=$_WITH_NBFT \ + \ #end export NM_TEST_CLIENT_CHECK_L10N=1 diff --git a/tools/nm-in-container b/tools/nm-in-container index 0e3f31da79..365b8fcf8d 100755 --- a/tools/nm-in-container +++ b/tools/nm-in-container @@ -224,6 +224,7 @@ RUN dnf install -y \\ libasan \\ libcurl-devel \\ libndp-devel \\ + libnvme-devel \\ libpsl-devel \\ libselinux-devel \\ libtool \\