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