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