From 1cb0635d0801c63d7cbb3413035296cf37d61e79 Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Wed, 20 Nov 2024 18:19:45 +0100 Subject: [PATCH] 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 9ae88b26b..2bd93ce22 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 2d3d87553..312edff2e 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 40386a0b4..a88012c6b 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 fe696aaf1..18d8bfc44 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 080fe7e42..0e9689f32 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 c2baad4e5..a73ce361f 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 d6dc1fcb7..d7fd8667b 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 000000000..782c6acca --- /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