merge: branch 'nbft-parser-1'

initrd: add new NBFT parser

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2077
This commit is contained in:
Beniamino Galvani
2025-04-28 16:44:24 +00:00
15 changed files with 471 additions and 8 deletions

View File

@@ -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'

View File

@@ -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

View File

@@ -24,6 +24,7 @@ apk add \
'jansson-dev' \
'libgudev-dev' \
'libndp-dev' \
'libnvme-dev' \
'libnl3-dev' \
'libpsl-dev' \
'libsoup-dev' \

View File

@@ -56,6 +56,7 @@ install \
libndp-dev \
libnewt-dev \
libnss3-dev \
libnvme-dev \
libpolkit-gobject-1-dev \
libpsl-dev \
libreadline-dev \

View File

@@ -66,6 +66,7 @@ install \
jq \
libcurl-devel \
libndp-devel \
libnvme-devel \
libselinux-devel \
libtool \
libuuid-devel \

View File

@@ -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}

View File

@@ -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

View File

@@ -154,6 +154,7 @@
<member><option>net.ifnames</option></member>
<member><option>rd.peerdns</option></member>
<member><option>rd.iscsi.ibft</option></member>
<member><option>rd.nvmf.nonbft</option></member>
<member><option>rd.bootif</option></member>
<member><option>rd.neednet</option></member>
<member><option>rd.ethtool</option></member>

View File

@@ -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 <string.h>'))
config_h.set10('HAVE_DECL_MEMFD_CREATE', cc.has_function('memfd_create', prefix: '#include <sys/mman.h>'))
config_h.set10('HAVE_DLVSYM', cc.has_function('dlvsym', prefix: '''#define _GNU_SOURCE
#include <dlfcn.h>'''))
# types
config_h.set('SIZEOF_PID_T', cc.sizeof('pid_t', prefix : '#include <sys/types.h>'))
config_h.set('SIZEOF_UID_T', cc.sizeof('uid_t', prefix : '#include <sys/types.h>'))
@@ -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)

View File

@@ -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')

View File

@@ -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,

View File

@@ -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,

View File

@@ -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++) {

View File

@@ -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 <libnvme.h>
#include <dlfcn.h>
#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

View File

@@ -224,6 +224,7 @@ RUN dnf install -y \\
libasan \\
libcurl-devel \\
libndp-devel \\
libnvme-devel \\
libpsl-devel \\
libselinux-devel \\
libtool \\