diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c3c36ed81..b6acd6fb1 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/config.h.meson b/config.h.meson
index 9ae88b26b..d41304db6 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -280,3 +280,8 @@
/* Define to 1 if you have history support from -lreadline. */
#mesondefine HAVE_READLINE_HISTORY
+/* Define if NBFT support is enabled */
+#mesondefine WITH_NBFT
+
+/* Define to 1 if dlvsym() is available */
+#mesondefine HAVE_DLVSYM
diff --git a/contrib/alpine/REQUIRED_PACKAGES b/contrib/alpine/REQUIRED_PACKAGES
index 358214fd0..6f0e86fdd 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 fa73b21ad..d91e54d27 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 1bb7ec6b3..38a2cbbf4 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 638da502d..788e9ead5 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 3864620f8..4881d110d 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/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..e13cd9da0 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 '))
@@ -929,6 +932,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..6b02e0668 100644
--- a/src/nm-initrd-generator/meson.build
+++ b/src/nm-initrd-generator/meson.build
@@ -6,6 +6,7 @@ libnmi_core = static_library(
'nmi-cmdline-reader.c',
'nmi-dt-reader.c',
'nmi-ibft-reader.c',
+ 'nmi-nbft-reader.c',
),
include_directories: [
src_inc,
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..a6cd30f10 100644
--- a/src/nm-initrd-generator/nmi-cmdline-reader.c
+++ b/src/nm-initrd-generator/nmi-cmdline-reader.c
@@ -1451,8 +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;
+ guint64 dhcp_timeout = 90;
+ guint64 dhcp_num_tries = 1;
+ gboolean nvmf_nonbft = FALSE;
+ gboolean have_dracut_nbft = FALSE;
reader = reader_new();
@@ -1469,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")) {
@@ -1561,7 +1566,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 && !have_dracut_nbft) {
+ 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..bb2db7003
--- /dev/null
+++ b/src/nm-initrd-generator/nmi-nbft-reader.c
@@ -0,0 +1,414 @@
+/* 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
+
+#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 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 char *
+format_conn_name(const char *table_name, struct nbft_info_hfi *hfi, gboolean is_vlan)
+{
+ 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);
+}
+
+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;
+}
+
+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;
+
+ connection = nm_simple_connection_new();
+
+ s_connection = nm_setting_connection_new();
+ g_object_set(s_connection,
+ NM_SETTING_CONNECTION_TYPE,
+ is_vlan ? NM_SETTING_VLAN_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME,
+ NM_SETTING_CONNECTION_ID,
+ conn_name,
+ 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);
+
+ 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();
+ 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;
+ }
+ 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);
+ 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",
+ hfi->tcp_info.ipaddr,
+ prefix,
+ error->message);
+ 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)) {
+ 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;
+ }
+
+ g_ptr_array_add(a, g_steal_pointer(&connection));
+}
+
+NMConnection **
+nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname)
+{
+ 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;
+
+ 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;
+
+ /* 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);
+ if (ret) {
+ _LOGW(LOGD_CORE, "Error parsing NBFT table %s: %m", entry_path);
+ continue;
+ }
+
+ 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'",
+ entry_path,
+ (*hfi)->index,
+ (*hfi)->transport);
+ continue;
+ }
+ parse_hfi(a, *hfi, entry_name, hostname);
+ }
+
+ _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(g_steal_pointer(&a), FALSE);
+}
+
+#else /* WITH_NBFT */
+
+NMConnection **
+nmi_nbft_reader_parse(const char *sysfs_dir, char **hostname)
+{
+ return NULL;
+}
+
+#endif
diff --git a/tools/nm-in-container b/tools/nm-in-container
index 0e3f31da7..365b8fcf8 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 \\