diff options
Diffstat (limited to 'src/core/initrd/nmi-ibft-reader.c')
| -rw-r--r-- | src/core/initrd/nmi-ibft-reader.c | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/src/core/initrd/nmi-ibft-reader.c b/src/core/initrd/nmi-ibft-reader.c new file mode 100644 index 00000000..9895cda4 --- /dev/null +++ b/src/core/initrd/nmi-ibft-reader.c @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2018 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-initrd-generator.h" + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <sys/wait.h> +#include <sys/inotify.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <linux/if_ether.h> + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +#define _NMLOG(level, domain, ...) \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "ibft-reader: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static GHashTable * +load_one_nic(const char *sysfs_dir, const char *dir_name) +{ + gs_free char *nic_path = g_build_filename(sysfs_dir, dir_name, NULL); + GDir * nic_dir; + const char * entry_name; + char * content; + gs_free_error GError *error = NULL; + GHashTable * nic; + + g_return_val_if_fail(sysfs_dir != NULL, FALSE); + + nic_dir = g_dir_open(nic_path, 0, &error); + if (!nic_dir) { + _LOGW(LOGD_CORE, "Can't open %s: %s", nic_path, error->message); + return NULL; + } + + nic = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + while ((entry_name = g_dir_read_name(nic_dir))) { + gs_free char *entry_path = g_build_filename(nic_path, entry_name, NULL); + + if (!g_file_test(entry_path, G_FILE_TEST_IS_REGULAR)) + continue; + + if (!g_file_get_contents(entry_path, &content, NULL, &error)) { + _LOGW(LOGD_CORE, "Can't read %s: %s", entry_path, error->message); + g_clear_error(&error); + continue; + } + + g_strchomp(content); + if (!g_hash_table_insert(nic, g_strdup(entry_name), content)) + _LOGW(LOGD_CORE, "Duplicate iBFT entry: %s", entry_name); + } + + g_dir_close(nic_dir); + + return nic; +} + +GHashTable * +nmi_ibft_read(const char *sysfs_dir) +{ + gs_free char *ibft_path = NULL; + GDir * ibft_dir; + const char * dir_name; + GHashTable * ibft, *nic; + char * mac; + gs_free_error GError *error = NULL; + + g_return_val_if_fail(sysfs_dir != NULL, FALSE); + + ibft_path = g_build_filename(sysfs_dir, "firmware", "ibft", NULL); + + ibft = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_hash_table_unref); + + if (!g_file_test(ibft_path, G_FILE_TEST_IS_DIR)) + nm_utils_modprobe(NULL, FALSE, "iscsi_ibft", NULL); + if (!g_file_test(ibft_path, G_FILE_TEST_IS_DIR)) + return ibft; + + ibft_dir = g_dir_open(ibft_path, 0, &error); + if (!ibft_dir) { + _LOGW(LOGD_CORE, "Unable to open iBFT firmware directory: %s", error->message); + return ibft; + } + + while ((dir_name = g_dir_read_name(ibft_dir))) { + if (!g_str_has_prefix(dir_name, "ethernet")) + continue; + + nic = load_one_nic(ibft_path, dir_name); + mac = g_hash_table_lookup(nic, "mac"); + + if (!mac) { + _LOGW(LOGD_CORE, "Ignoring an iBFT record without a MAC address"); + g_hash_table_unref(nic); + continue; + } + + mac = g_ascii_strup(mac, -1); + if (!g_hash_table_insert(ibft, mac, nic)) + _LOGW(LOGD_CORE, "Duplicate iBFT record for %s", mac); + } + + g_dir_close(ibft_dir); + + return ibft; +} + +static gboolean +ip_setting_add_from_block(GHashTable *nic, NMConnection *connection, GError **error) +{ + NMSettingIPConfig *s_ip = NULL; + NMSettingIPConfig *s_ip4 = NULL; + NMSettingIPConfig *s_ip6 = NULL; + NMIPAddress * addr; + const char * s_ipaddr = NULL; + const char * s_prefix = NULL; + const char * s_gateway = NULL; + const char * s_dns1 = NULL; + const char * s_dns2 = NULL; + const char * s_origin = NULL; + const char * method = NULL; + int family; + gint64 prefix; + + s_ipaddr = (const char *) g_hash_table_lookup(nic, "ip-addr"); + s_prefix = (const char *) g_hash_table_lookup(nic, "prefix-len"); + s_gateway = (const char *) g_hash_table_lookup(nic, "gateway"); + s_dns1 = (const char *) g_hash_table_lookup(nic, "primary-dns"); + s_dns2 = (const char *) g_hash_table_lookup(nic, "secondary-dns"); + s_origin = (const char *) g_hash_table_lookup(nic, "origin"); + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (!s_ip4) { + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, (NMSetting *) s_ip4); + } + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if (!s_ip6) { + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, (NMSetting *) s_ip6); + + g_object_set(s_ip6, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + NULL); + } + + family = get_ip_address_family(s_ipaddr, FALSE); + if (family == AF_UNSPEC) + family = get_ip_address_family(s_gateway, FALSE); + + switch (family) { + case AF_INET: + s_ip = s_ip4; + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NULL); + break; + case AF_INET6: + s_ip = s_ip6; + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL); + break; + default: + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid IP address '%s'.", + s_ipaddr); + return FALSE; + } + + if ((nm_streq0(s_origin, "3") && family == AF_INET) + || (nm_streq0(s_origin, "4") && family == AF_INET)) { + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + } else if (nm_streq0(s_origin, "3") && family == AF_INET6) { + method = NM_SETTING_IP6_CONFIG_METHOD_DHCP; + } else if (nm_streq0(s_origin, "4") && family == AF_INET6) { + method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + } else if (family == AF_INET) { + method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + } else if (family == AF_INET6) { + method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + } else { + g_return_val_if_reached(FALSE); + } + g_object_set(s_ip, + NM_SETTING_IP_CONFIG_METHOD, + method, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + + if (s_gateway && !nm_utils_ipaddr_is_valid(family, s_gateway)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid IP gateway '%s'.", + s_gateway); + return FALSE; + } + + if (s_dns1 && !nm_utils_ipaddr_is_valid(family, s_dns1)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid DNS1 address '%s'.", + s_dns1); + return FALSE; + } + + if (s_dns2 && !nm_utils_ipaddr_is_valid(family, s_dns2)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid DNS2 address '%s'.", + s_dns2); + return FALSE; + } + + if (s_ipaddr) { + prefix = _nm_utils_ascii_str_to_int64(s_prefix, 10, 0, 128, -1); + if (prefix == -1) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid IP prefix '%s'.", + s_prefix); + return FALSE; + } + + addr = nm_ip_address_new(family, s_ipaddr, prefix, error); + if (!addr) { + g_prefix_error(error, "iBFT: "); + return FALSE; + } + + nm_setting_ip_config_add_address(s_ip, addr); + nm_ip_address_unref(addr); + + g_object_set(s_ip, NM_SETTING_IP_CONFIG_GATEWAY, s_gateway, NULL); + } + + if (s_dns1) + nm_setting_ip_config_add_dns(s_ip, s_dns1); + if (s_dns2) + nm_setting_ip_config_add_dns(s_ip, s_dns2); + + return TRUE; +} + +static gboolean +connection_setting_add(GHashTable * nic, + NMConnection *connection, + const char * type, + const char * prefix, + GError ** error) +{ + NMSetting * s_con; + char * id, *uuid; + const char *s_index, *s_hwaddr, *s_ipaddr, *s_vlanid; + + s_index = (const char *) g_hash_table_lookup(nic, "index"); + s_hwaddr = (const char *) g_hash_table_lookup(nic, "mac"); + s_ipaddr = (const char *) g_hash_table_lookup(nic, "ip-addr"); + s_vlanid = (const char *) g_hash_table_lookup(nic, "vlan"); + + if (!s_hwaddr) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: missing MAC address"); + return FALSE; + } + + id = g_strdup_printf("iBFT%s%s Connection%s%s", + prefix ? " " : "", + prefix ? prefix : "", + s_index ? " " : "", + s_index ? s_index : ""); + + uuid = _nm_utils_uuid_generate_from_strings("ibft", + s_hwaddr, + s_vlanid ? "V" : "v", + s_vlanid ? s_vlanid : "", + s_ipaddr ? "A" : "DHCP", + s_ipaddr ? s_ipaddr : "", + NULL); + + s_con = (NMSetting *) nm_connection_get_setting_connection(connection); + if (!s_con) { + s_con = nm_setting_connection_new(); + nm_connection_add_setting(connection, s_con); + } + + g_object_set(s_con, + NM_SETTING_CONNECTION_TYPE, + type, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_ID, + id, + NM_SETTING_CONNECTION_INTERFACE_NAME, + NULL, + NULL); + + g_free(uuid); + g_free(id); + + return TRUE; +} + +static gboolean +is_ibft_vlan_device(GHashTable *nic) +{ + const char *s_vlan_id; + + g_assert(nic); + + s_vlan_id = (const char *) g_hash_table_lookup(nic, "vlan"); + + if (s_vlan_id) { + /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it + * means "no VLAN". + */ + if (_nm_utils_ascii_str_to_int64(s_vlan_id, 10, 1, 4095, -1) != -1) + return TRUE; + } + + return FALSE; +} + +static gboolean +vlan_setting_add_from_block(GHashTable *nic, NMConnection *connection, GError **error) +{ + NMSetting * s_vlan = NULL; + const char *vlan_id_str = NULL; + gint64 vlan_id = -1; + + g_assert(nic); + g_assert(connection); + + /* This won't fail since this function shouldn't be called unless the + * iBFT VLAN ID exists and is > 0. + */ + vlan_id_str = (const char *) g_hash_table_lookup(nic, "vlan"); + g_assert(vlan_id_str); + + /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it means "no VLAN" */ + vlan_id = _nm_utils_ascii_str_to_int64(vlan_id_str, 10, 1, 4095, -1); + if (vlan_id == -1) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Invalid VLAN_ID '%s'", + vlan_id_str); + return FALSE; + } + + s_vlan = (NMSetting *) nm_connection_get_setting_vlan(connection); + if (!s_vlan) { + s_vlan = nm_setting_vlan_new(); + nm_connection_add_setting(connection, s_vlan); + } + + g_object_set(s_vlan, NM_SETTING_VLAN_ID, (guint32) vlan_id, NULL); + + return TRUE; +} + +static gboolean +wired_setting_add_from_block(GHashTable *nic, NMConnection *connection, GError **error) +{ + NMSetting * s_wired = NULL; + const char *hwaddr = NULL; + + g_assert(nic); + g_assert(connection); + + hwaddr = (const char *) g_hash_table_lookup(nic, "mac"); + if (!hwaddr) { + g_set_error_literal(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: missing MAC address"); + return FALSE; + } + + if (!nm_utils_hwaddr_valid(hwaddr, ETH_ALEN)) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "iBFT: invalid MAC address '%s'.", + hwaddr); + return FALSE; + } + + s_wired = (NMSetting *) nm_connection_get_setting_wired(connection); + if (!s_wired) { + s_wired = nm_setting_wired_new(); + nm_connection_add_setting(connection, s_wired); + } + + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL); + + return TRUE; +} + +gboolean +nmi_ibft_update_connection_from_nic(NMConnection *connection, GHashTable *nic, GError **error) +{ + gboolean is_vlan = FALSE; + + g_assert(nic); + + is_vlan = is_ibft_vlan_device(nic); + if (is_vlan && !vlan_setting_add_from_block(nic, connection, error)) + return FALSE; + + /* Always have a wired setting; for VLAN it defines the parent */ + if (!wired_setting_add_from_block(nic, connection, error)) + return FALSE; + + if (!ip_setting_add_from_block(nic, connection, error)) + return FALSE; + + if (!connection_setting_add(nic, + connection, + is_vlan ? NM_SETTING_VLAN_SETTING_NAME + : NM_SETTING_WIRED_SETTING_NAME, + is_vlan ? "VLAN" : NULL, + error)) + return FALSE; + + if (!nm_connection_normalize(connection, NULL, NULL, error)) + return FALSE; + + return TRUE; +} |