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 <tbzatek@redhat.com>
This commit is contained in:

committed by
Beniamino Galvani

parent
03f30ac5cf
commit
1cb0635d08
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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)
|
||||
|
@@ -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')
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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++) {
|
||||
|
293
src/nm-initrd-generator/nmi-nbft-reader.c
Normal file
293
src/nm-initrd-generator/nmi-nbft-reader.c
Normal file
@@ -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 <libnvme.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 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
|
Reference in New Issue
Block a user