From 37785a57e02806f97012f2e4787771605ab6af78 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 25 Mar 2025 11:53:16 +0100 Subject: [PATCH 1/8] platform: use consistent naming for ethtool functions For unknown reasons (wrong copy and paste?) the getter functions had a "link" in the name. Remove it. --- src/core/devices/nm-device.c | 20 +++++++++--------- src/libnm-platform/nm-platform.c | 36 ++++++++++++++------------------ src/libnm-platform/nm-platform.h | 19 +++++++---------- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index e7984c578a..2bb69e9ed3 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -2746,7 +2746,7 @@ _ethtool_features_set(NMDevice *self, if (nm_setting_ethtool_init_features(s_ethtool, ethtool_state->requested) == 0) return; - features = nm_platform_ethtool_get_link_features(platform, ethtool_state->ifindex); + features = nm_platform_ethtool_get_features(platform, ethtool_state->ifindex); if (!features) { _LOGW(LOGD_DEVICE, "ethtool: failure setting offload features (cannot read features)"); return; @@ -2865,9 +2865,9 @@ _ethtool_coalesce_set(NMDevice *self, continue; if (!has_old) { - if (!nm_platform_ethtool_get_link_coalesce(platform, - ethtool_state->ifindex, - &coalesce_old)) { + if (!nm_platform_ethtool_get_coalesce(platform, + ethtool_state->ifindex, + &coalesce_old)) { _LOGW(LOGD_DEVICE, "ethtool: failure getting coalesce settings (cannot read)"); return; } @@ -2946,7 +2946,7 @@ _ethtool_ring_set(NMDevice *self, nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)); if (!has_old) { - if (!nm_platform_ethtool_get_link_ring(platform, ethtool_state->ifindex, &ring_old)) { + if (!nm_platform_ethtool_get_ring(platform, ethtool_state->ifindex, &ring_old)) { _LOGW(LOGD_DEVICE, "ethtool: failure setting ring options (cannot read existing setting)"); return; @@ -3042,9 +3042,9 @@ _ethtool_channels_set(NMDevice *self, nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)); if (!has_old) { - if (!nm_platform_ethtool_get_link_channels(platform, - ethtool_state->ifindex, - &channels_old)) { + if (!nm_platform_ethtool_get_channels(platform, + ethtool_state->ifindex, + &channels_old)) { _LOGW(LOGD_DEVICE, "ethtool: failure setting channels options (cannot read existing setting)"); return; @@ -3161,7 +3161,7 @@ _ethtool_pause_set(NMDevice *self, nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)); if (!has_old) { - if (!nm_platform_ethtool_get_link_pause(platform, ethtool_state->ifindex, &pause_old)) { + if (!nm_platform_ethtool_get_pause(platform, ethtool_state->ifindex, &pause_old)) { _LOGW(LOGD_DEVICE, "ethtool: failure setting pause options (cannot read " "existing setting)"); @@ -3247,7 +3247,7 @@ _ethtool_eee_set(NMDevice *self, nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)); if (!has_old) { - if (!nm_platform_ethtool_get_link_eee(platform, ethtool_state->ifindex, &eee_old)) { + if (!nm_platform_ethtool_get_eee(platform, ethtool_state->ifindex, &eee_old)) { _LOGW(LOGD_DEVICE, "ethtool: failure setting eee options (cannot read " "existing setting)"); diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 160e3c0ade..496bb4f5ee 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -3595,7 +3595,7 @@ nm_platform_ethtool_get_link_settings(NMPlatform *self, /*****************************************************************************/ NMEthtoolFeatureStates * -nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex) +nm_platform_ethtool_get_features(NMPlatform *self, int ifindex) { _CHECK_SELF_NETNS(self, klass, netns, NULL); @@ -3640,9 +3640,7 @@ nm_platform_ethtool_set_fec_mode(NMPlatform *self, int ifindex, uint32_t fec_mod } gboolean -nm_platform_ethtool_get_link_coalesce(NMPlatform *self, - int ifindex, - NMEthtoolCoalesceState *coalesce) +nm_platform_ethtool_get_coalesce(NMPlatform *self, int ifindex, NMEthtoolCoalesceState *coalesce) { _CHECK_SELF_NETNS(self, klass, netns, FALSE); @@ -3665,7 +3663,7 @@ nm_platform_ethtool_set_coalesce(NMPlatform *self, } gboolean -nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring) +nm_platform_ethtool_get_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring) { _CHECK_SELF_NETNS(self, klass, netns, FALSE); @@ -3686,9 +3684,7 @@ nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingS } gboolean -nm_platform_ethtool_get_link_channels(NMPlatform *self, - int ifindex, - NMEthtoolChannelsState *channels) +nm_platform_ethtool_get_channels(NMPlatform *self, int ifindex, NMEthtoolChannelsState *channels) { _CHECK_SELF_NETNS(self, klass, netns, FALSE); @@ -3711,7 +3707,7 @@ nm_platform_ethtool_set_channels(NMPlatform *self, } gboolean -nm_platform_ethtool_get_link_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause) +nm_platform_ethtool_get_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause) { _CHECK_SELF_NETNS(self, klass, netns, FALSE); @@ -3721,17 +3717,6 @@ nm_platform_ethtool_get_link_pause(NMPlatform *self, int ifindex, NMEthtoolPause return nmp_utils_ethtool_get_pause(ifindex, pause); } -gboolean -nm_platform_ethtool_get_link_eee(NMPlatform *self, int ifindex, NMEthtoolEEEState *eee) -{ - _CHECK_SELF_NETNS(self, klass, netns, FALSE); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(eee, FALSE); - - return nmp_utils_ethtool_get_eee(ifindex, eee); -} - gboolean nm_platform_ethtool_set_pause(NMPlatform *self, int ifindex, const NMEthtoolPauseState *pause) { @@ -3742,6 +3727,17 @@ nm_platform_ethtool_set_pause(NMPlatform *self, int ifindex, const NMEthtoolPaus return nmp_utils_ethtool_set_pause(ifindex, pause); } +gboolean +nm_platform_ethtool_get_eee(NMPlatform *self, int ifindex, NMEthtoolEEEState *eee) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(eee, FALSE); + + return nmp_utils_ethtool_get_eee(ifindex, eee); +} + gboolean nm_platform_ethtool_set_eee(NMPlatform *self, int ifindex, const NMEthtoolEEEState *eee) { diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 4c783ac2e9..4ddc224d59 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -2631,7 +2631,7 @@ gboolean nm_platform_ethtool_get_link_settings(NMPlatform *self, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex); -NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex); +NMEthtoolFeatureStates *nm_platform_ethtool_get_features(NMPlatform *self, int ifindex); gboolean nm_platform_ethtool_set_features( NMPlatform *self, int ifindex, @@ -2639,22 +2639,20 @@ gboolean nm_platform_ethtool_set_features( const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, gboolean do_set /* or reset */); -gboolean nm_platform_ethtool_get_link_coalesce(NMPlatform *self, - int ifindex, - NMEthtoolCoalesceState *coalesce); +gboolean +nm_platform_ethtool_get_coalesce(NMPlatform *self, int ifindex, NMEthtoolCoalesceState *coalesce); gboolean nm_platform_ethtool_set_coalesce(NMPlatform *self, int ifindex, const NMEthtoolCoalesceState *coalesce); -gboolean nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring); +gboolean nm_platform_ethtool_get_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring); gboolean nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring); -gboolean nm_platform_ethtool_get_link_channels(NMPlatform *self, - int ifindex, - NMEthtoolChannelsState *channels); +gboolean +nm_platform_ethtool_get_channels(NMPlatform *self, int ifindex, NMEthtoolChannelsState *channels); gboolean nm_platform_ethtool_set_channels(NMPlatform *self, int ifindex, @@ -2664,10 +2662,9 @@ gboolean nm_platform_ethtool_get_fec_mode(NMPlatform *self, int ifindex, uint32_ gboolean nm_platform_ethtool_set_fec_mode(NMPlatform *self, int ifindex, uint32_t fec_mode); -gboolean -nm_platform_ethtool_get_link_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause); +gboolean nm_platform_ethtool_get_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause); -gboolean nm_platform_ethtool_get_link_eee(NMPlatform *self, int ifindex, NMEthtoolEEEState *eee); +gboolean nm_platform_ethtool_get_eee(NMPlatform *self, int ifindex, NMEthtoolEEEState *eee); gboolean nm_platform_ethtool_set_pause(NMPlatform *self, int ifindex, const NMEthtoolPauseState *pause); From 88efe48916f6d23a7f3bbcfb288c9c8dee34d9fd Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 13 Mar 2025 14:31:30 +0100 Subject: [PATCH 2/8] libnm-setting: remove unused include --- src/libnmc-setting/nm-meta-setting-desc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c index e109f228e9..9b11cd4a17 100644 --- a/src/libnmc-setting/nm-meta-setting-desc.c +++ b/src/libnmc-setting/nm-meta-setting-desc.c @@ -11,7 +11,6 @@ #include #include #include -#include #include "libnm-core-aux-intern/nm-common-macros.h" #include "libnm-glib-aux/nm-enum-utils.h" From e8a3cd611ea8aae3106208ad17f1214c4d5e5b93 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 19 Mar 2025 13:37:10 +0100 Subject: [PATCH 3/8] platform: move ethtool ioctl functions to a separate file We're going to replace most of the ioctl-based ethtool functions with a netlink-based equivalent. Move the ioctl ones to a separate file so that it's easier to see what still needs to be converted. Also add a common prefix to the function names. --- src/core/platform/tests/test-link.c | 13 +- src/libnm-platform/devlink/nm-devlink.c | 3 +- src/libnm-platform/meson.build | 1 + src/libnm-platform/nm-linux-platform.c | 15 +- src/libnm-platform/nm-platform-utils.c | 1765 ---------------------- src/libnm-platform/nm-platform-utils.h | 57 +- src/libnm-platform/nm-platform.c | 37 +- src/libnm-platform/nmp-ethtool-ioctl.c | 1781 +++++++++++++++++++++++ src/libnm-platform/nmp-ethtool-ioctl.h | 67 + src/libnm-platform/nmp-object.c | 3 +- 10 files changed, 1888 insertions(+), 1854 deletions(-) create mode 100644 src/libnm-platform/nmp-ethtool-ioctl.c create mode 100644 src/libnm-platform/nmp-ethtool-ioctl.h diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c index 4cadfe4d48..4a08d565a0 100644 --- a/src/core/platform/tests/test-link.c +++ b/src/core/platform/tests/test-link.c @@ -15,6 +15,7 @@ #include "libnm-base/nm-ethtool-base.h" #include "libnm-platform/nmp-object.h" #include "libnm-platform/nmp-netns.h" +#include "libnm-platform/nmp-ethtool-ioctl.h" #include "libnm-platform/nm-platform-utils.h" #include "test-common.h" @@ -3183,10 +3184,10 @@ test_netns_general(gpointer fixture, gconstpointer test_data) * Work around that and skip asserts that are known to fail. */ ethtool_support = nmtstp_run_command("ethtool -i dummy1_ > /dev/null") == 0; if (ethtool_support) { - g_assert(nmp_utils_ethtool_get_driver_info( + g_assert(nmp_ethtool_ioctl_get_driver_info( nmtstp_link_get_typed(platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, &driver_info)); - g_assert(nmp_utils_ethtool_get_driver_info( + g_assert(nmp_ethtool_ioctl_get_driver_info( nmtstp_link_get_typed(platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex, &driver_info)); g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0); @@ -3197,10 +3198,10 @@ test_netns_general(gpointer fixture, gconstpointer test_data) g_assert(nm_platform_netns_push(platform_2, &netns_tmp)); if (ethtool_support) { - g_assert(nmp_utils_ethtool_get_driver_info( + g_assert(nmp_ethtool_ioctl_get_driver_info( nmtstp_link_get_typed(platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, &driver_info)); - g_assert(nmp_utils_ethtool_get_driver_info( + g_assert(nmp_ethtool_ioctl_get_driver_info( nmtstp_link_get_typed(platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex, &driver_info)); g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0); @@ -4031,7 +4032,7 @@ test_ethtool_features_get(void) _LOGT(">>> ethtool-features-get RUN %u (do-set=%s", i_run, do_set ? "set" : "reset"); - features = nmp_utils_ethtool_get_features(IFINDEX); + features = nmp_ethtool_ioctl_get_features(IFINDEX); g_ptr_array_add(gfree_keeper, features); ethtool_features_dump(features); @@ -4044,7 +4045,7 @@ test_ethtool_features_get(void) features = gfree_keeper->pdata[i_run * 2 - 1]; } - nmp_utils_ethtool_set_features(IFINDEX, features, requested, do_set); + nmp_ethtool_ioctl_set_features(IFINDEX, features, requested, do_set); } } diff --git a/src/libnm-platform/devlink/nm-devlink.c b/src/libnm-platform/devlink/nm-devlink.c index f06697cf59..97749cdafe 100644 --- a/src/libnm-platform/devlink/nm-devlink.c +++ b/src/libnm-platform/devlink/nm-devlink.c @@ -14,6 +14,7 @@ #include "libnm-platform/nm-netlink.h" #include "libnm-platform/nm-platform.h" #include "libnm-platform/nm-platform-utils.h" +#include "libnm-platform/nmp-ethtool-ioctl.h" #define _NMLOG_PREFIX_NAME "devlink" #define _NMLOG_DOMAIN LOGD_PLATFORM | LOGD_DEVICE @@ -118,7 +119,7 @@ nm_devlink_get_dev_identifier(NMDevlink *self, char **out_bus, char **out_addr, return FALSE; } - if (!nmp_utils_ethtool_get_driver_info(self->ifindex, ðtool_driver_info)) { + if (!nmp_ethtool_ioctl_get_driver_info(self->ifindex, ðtool_driver_info)) { g_set_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, diff --git a/src/libnm-platform/meson.build b/src/libnm-platform/meson.build index 7b6ad04266..e43d8758bc 100644 --- a/src/libnm-platform/meson.build +++ b/src/libnm-platform/meson.build @@ -12,6 +12,7 @@ libnm_platform = static_library( 'nmp-netns.c', 'nmp-object.c', 'nmp-plobj.c', + 'nmp-ethtool-ioctl.c', 'devlink/nm-devlink.c', 'wifi/nm-wifi-utils-nl80211.c', 'wifi/nm-wifi-utils.c', diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index a3939fc705..59cb540aa1 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -41,6 +41,7 @@ #include "libnm-platform/nm-netlink.h" #include "libnm-platform/nm-platform-utils.h" #include "libnm-platform/nmp-netns.h" +#include "libnm-platform/nmp-ethtool-ioctl.h" #include "libnm-platform/devlink/nm-devlink.h" #include "libnm-platform/wifi/nm-wifi-utils-wext.h" #include "libnm-platform/wifi/nm-wifi-utils.h" @@ -1320,7 +1321,7 @@ _linktype_get_type(NMPlatform *platform, NMPUtilsEthtoolDriverInfo driver_info; /* Fallback OVS detection for kernel <= 3.16 */ - if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) { + if (nmp_ethtool_ioctl_get_driver_info(ifindex, &driver_info)) { if (nm_streq(driver_info.driver, "openvswitch")) return NM_LINK_TYPE_OPENVSWITCH; @@ -8966,8 +8967,8 @@ link_supports_carrier_detect(NMPlatform *platform, int ifindex) * us whether the device actually supports carrier detection in the first * place. We assume any device that does implements one of these two APIs. */ - return nmp_utils_ethtool_supports_carrier_detect(ifindex) - || nmp_utils_mii_supports_carrier_detect(ifindex); + return nmp_ethtool_ioctl_supports_carrier_detect(ifindex) + || nmp_mii_ioctl_supports_carrier_detect(ifindex); } static gboolean @@ -8985,7 +8986,7 @@ link_supports_vlans(NMPlatform *platform, int ifindex) if (!nm_platform_netns_push(platform, &netns)) return FALSE; - return nmp_utils_ethtool_supports_vlans(ifindex); + return nmp_ethtool_ioctl_supports_vlans(ifindex); } static gboolean @@ -9063,7 +9064,7 @@ link_get_permanent_address_ethtool(NMPlatform *platform, int ifindex, NMPLinkAdd if (!nm_platform_netns_push(platform, &netns)) return FALSE; - if (!nmp_utils_ethtool_get_permanent_address(ifindex, buffer, &len)) + if (!nmp_ethtool_ioctl_get_permanent_address(ifindex, buffer, &len)) return FALSE; nm_assert(len <= _NM_UTILS_HWADDR_LEN_MAX); memcpy(out_address->data, buffer, len); @@ -10391,7 +10392,7 @@ link_get_wake_on_lan(NMPlatform *platform, int ifindex) return FALSE; if (type == NM_LINK_TYPE_ETHERNET) - return nmp_utils_ethtool_get_wake_on_lan(ifindex); + return nmp_ethtool_ioctl_get_wake_on_lan(ifindex); else if (type == NM_LINK_TYPE_WIFI) { WIFI_GET_WIFI_DATA(wifi_data, platform, ifindex, FALSE); @@ -10416,7 +10417,7 @@ link_get_driver_info(NMPlatform *platform, if (!nm_platform_netns_push(platform, &netns)) return FALSE; - if (!nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) + if (!nmp_ethtool_ioctl_get_driver_info(ifindex, &driver_info)) return FALSE; NM_SET_OUT(out_driver_name, g_strdup(driver_info.driver)); NM_SET_OUT(out_driver_version, g_strdup(driver_info.version)); diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c index 15aac11c2b..d35ff6feba 100644 --- a/src/libnm-platform/nm-platform-utils.c +++ b/src/libnm-platform/nm-platform-utils.c @@ -12,14 +12,12 @@ #include #include #include -#include #include #include #include #include #include -#include "libnm-base/nm-ethtool-base.h" #include "libnm-log-core/nm-logging.h" #include "libnm-glib-aux/nm-time-utils.h" @@ -60,1453 +58,6 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_platform_link_duplex_type_to_string, NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_FULL, "full"), NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_HALF, "half"), ); -/*****************************************************************************/ - -typedef struct { - int fd; - const int ifindex; - char ifname[IFNAMSIZ]; -} SocketHandle; - -#define SOCKET_HANDLE_INIT(_ifindex) \ - { \ - .fd = -1, \ - .ifindex = (_ifindex), \ - } - -static void -_nm_auto_socket_handle(SocketHandle *shandle) -{ - if (shandle->fd >= 0) - nm_close(shandle->fd); -} - -#define nm_auto_socket_handle nm_auto(_nm_auto_socket_handle) - -/*****************************************************************************/ - -typedef enum { - IOCTL_CALL_DATA_TYPE_NONE, - IOCTL_CALL_DATA_TYPE_IFRDATA, - IOCTL_CALL_DATA_TYPE_IFRU, -} IoctlCallDataType; - -static int -_ioctl_call(const char *log_ioctl_type, - const char *log_subtype, - unsigned long int ioctl_request, - int ifindex, - int *inout_fd, - char *inout_ifname, - IoctlCallDataType edata_type, - gpointer edata, - gsize edata_size, - struct ifreq *out_ifreq) -{ - nm_auto_close int fd_close = -1; - int fd; - int r; - gpointer edata_backup = NULL; - gs_free gpointer edata_backup_free = NULL; - guint try_count; - char known_ifnames[2][IFNAMSIZ]; - const char *failure_reason = NULL; - struct ifreq ifr; - - nm_assert(ifindex > 0); - nm_assert(NM_IN_SET(edata_type, - IOCTL_CALL_DATA_TYPE_NONE, - IOCTL_CALL_DATA_TYPE_IFRDATA, - IOCTL_CALL_DATA_TYPE_IFRU)); - nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_NONE || edata_size == 0); - nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRDATA || edata_size > 0); - nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRU - || (edata_size > 0 && edata_size <= sizeof(ifr.ifr_ifru))); - nm_assert(edata_size == 0 || edata); - - /* open a file descriptor (or use the one provided). */ - if (inout_fd && *inout_fd >= 0) - fd = *inout_fd; - else { - fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (fd < 0) { - r = -NM_ERRNO_NATIVE(errno); - failure_reason = "failed creating socket or ioctl"; - goto out; - } - if (inout_fd) - *inout_fd = fd; - else - fd_close = fd; - } - - /* resolve the ifindex to name (or use the one provided). */ - if (inout_ifname && inout_ifname[0]) - nm_utils_ifname_cpy(known_ifnames[0], inout_ifname); - else { - if (!nmp_utils_if_indextoname(ifindex, known_ifnames[0])) { - failure_reason = "cannot resolve ifindex"; - r = -ENODEV; - goto out; - } - if (inout_ifname) - nm_utils_ifname_cpy(inout_ifname, known_ifnames[0]); - } - - /* we might need to retry the request. Backup edata so that we can - * restore it on retry. */ - if (edata_size > 0) - edata_backup = nm_memdup_maybe_a(500, edata, edata_size, &edata_backup_free); - - try_count = 0; - -again: -{ - const char *ifname = known_ifnames[try_count % 2]; - - nm_assert(ifindex > 0); - nm_assert(ifname && nm_utils_ifname_valid_kernel(ifname, NULL)); - nm_assert(fd >= 0); - - memset(&ifr, 0, sizeof(ifr)); - nm_utils_ifname_cpy(ifr.ifr_name, ifname); - if (edata_type == IOCTL_CALL_DATA_TYPE_IFRDATA) - ifr.ifr_data = edata; - else if (edata_type == IOCTL_CALL_DATA_TYPE_IFRU) - memcpy(&ifr.ifr_ifru, edata, NM_MIN(edata_size, sizeof(ifr.ifr_ifru))); - - if (ioctl(fd, ioctl_request, &ifr) < 0) { - r = -NM_ERRNO_NATIVE(errno); - nm_log_trace(LOGD_PLATFORM, - "%s[%d]: %s, %s: failed: %s", - log_ioctl_type, - ifindex, - log_subtype, - ifname, - nm_strerror_native(-r)); - } else { - r = 0; - nm_log_trace(LOGD_PLATFORM, - "%s[%d]: %s, %s: success", - log_ioctl_type, - ifindex, - log_subtype, - ifname); - } -} - - try_count++; - - /* resolve the name again to see whether the ifindex still has the same name. */ - if (!nmp_utils_if_indextoname(ifindex, known_ifnames[try_count % 2])) { - /* we could not find the ifindex again. Probably the device just got - * removed. - * - * In both cases we return the error code we got from ioctl above. - * Either it failed because the device was gone already or it still - * managed to complete the call. In both cases, the error code is good. */ - failure_reason = - "cannot resolve ifindex after ioctl call. Probably the device was just removed"; - goto out; - } - - /* check whether the ifname changed in the meantime. If yes, would render the result - * invalid. Note that this cannot detect every race regarding renames, for example: - * - * - if_indextoname(#10) gives eth0 - * - rename(#10) => eth0_tmp - * - rename(#11) => eth0 - * - ioctl(eth0) (wrongly fetching #11, formerly eth1) - * - rename(#11) => eth_something - * - rename(#10) => eth0 - * - if_indextoname(#10) gives eth0 - */ - if (!nm_streq(known_ifnames[0], known_ifnames[1])) { - gboolean retry; - - /* we detected a possible(!) rename. - * - * For getters it's straight forward to just retry the call. - * - * For setters we also always retry. If our previous call operated on the right device, - * calling it again should have no bad effect (just setting the same thing more than once). - * - * The only potential bad thing is if there was a race involving swapping names, and we just - * set the ioctl option on the wrong device. But then the bad thing already happenned and - * we cannot detect it (nor do anything about it). At least, we can retry and set the - * option on the right interface. */ - retry = (try_count < 5); - - nm_log_trace(LOGD_PLATFORM, - "%s[%d]: %s: rename detected from \"%s\" to \"%s\". %s", - log_ioctl_type, - ifindex, - log_subtype, - known_ifnames[(try_count - 1) % 2], - known_ifnames[try_count % 2], - retry ? "Retry" : "No retry"); - if (inout_ifname) - nm_utils_ifname_cpy(inout_ifname, known_ifnames[try_count % 2]); - if (retry) { - if (edata_size > 0) - memcpy(edata, edata_backup, edata_size); - goto again; - } - } - -out: - if (failure_reason) { - nm_log_trace(LOGD_PLATFORM, - "%s[%d]: %s: %s: %s", - log_ioctl_type, - ifindex, - log_subtype, - failure_reason, - r < 0 ? nm_strerror_native(-r) : "assume success"); - } - if (r >= 0) - NM_SET_OUT(out_ifreq, ifr); - return r; -} - -/****************************************************************************** - * ethtool - *****************************************************************************/ - -static NM_UTILS_ENUM2STR_DEFINE(_ethtool_cmd_to_string, - guint32, - NM_UTILS_ENUM2STR(ETHTOOL_GCOALESCE, "ETHTOOL_GCOALESCE"), - NM_UTILS_ENUM2STR(ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO"), - NM_UTILS_ENUM2STR(ETHTOOL_GFEATURES, "ETHTOOL_GFEATURES"), - NM_UTILS_ENUM2STR(ETHTOOL_GLINK, "ETHTOOL_GLINK"), - NM_UTILS_ENUM2STR(ETHTOOL_GLINKSETTINGS, "ETHTOOL_GLINKSETTINGS"), - NM_UTILS_ENUM2STR(ETHTOOL_GPERMADDR, "ETHTOOL_GPERMADDR"), - NM_UTILS_ENUM2STR(ETHTOOL_GRINGPARAM, "ETHTOOL_GRINGPARAM"), - NM_UTILS_ENUM2STR(ETHTOOL_GPAUSEPARAM, "ETHTOOL_GPAUSEPARAM"), - NM_UTILS_ENUM2STR(ETHTOOL_GSET, "ETHTOOL_GSET"), - NM_UTILS_ENUM2STR(ETHTOOL_GSSET_INFO, "ETHTOOL_GSSET_INFO"), - NM_UTILS_ENUM2STR(ETHTOOL_GSTATS, "ETHTOOL_GSTATS"), - NM_UTILS_ENUM2STR(ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"), - NM_UTILS_ENUM2STR(ETHTOOL_GWOL, "ETHTOOL_GWOL"), - NM_UTILS_ENUM2STR(ETHTOOL_SCOALESCE, "ETHTOOL_SCOALESCE"), - NM_UTILS_ENUM2STR(ETHTOOL_SFEATURES, "ETHTOOL_SFEATURES"), - NM_UTILS_ENUM2STR(ETHTOOL_SLINKSETTINGS, "ETHTOOL_SLINKSETTINGS"), - NM_UTILS_ENUM2STR(ETHTOOL_SRINGPARAM, "ETHTOOL_SRINGPARAM"), - NM_UTILS_ENUM2STR(ETHTOOL_SPAUSEPARAM, "ETHTOOL_SPAUSEPARAM"), - NM_UTILS_ENUM2STR(ETHTOOL_SSET, "ETHTOOL_SSET"), - NM_UTILS_ENUM2STR(ETHTOOL_SWOL, "ETHTOOL_SWOL"), ); - -static const char * -_ethtool_edata_to_string(gpointer edata, gsize edata_size, char *sbuf, gsize sbuf_len) -{ - nm_assert(edata); - nm_assert(edata_size >= sizeof(guint32)); - nm_assert((((intptr_t) edata) % _nm_alignof(guint32)) == 0); - - return _ethtool_cmd_to_string(*((guint32 *) edata), sbuf, sbuf_len); -} - -/*****************************************************************************/ - -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) -#define ethtool_cmd_speed(pedata) ((pedata)->speed) - -#define ethtool_cmd_speed_set(pedata, speed) \ - G_STMT_START \ - { \ - (pedata)->speed = (guint16) (speed); \ - } \ - G_STMT_END -#endif - -static int -_ethtool_call_handle(SocketHandle *shandle, gpointer edata, gsize edata_size) -{ - char sbuf[50]; - - return _ioctl_call("ethtool", - _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)), - SIOCETHTOOL, - shandle->ifindex, - &shandle->fd, - shandle->ifname, - IOCTL_CALL_DATA_TYPE_IFRDATA, - edata, - edata_size, - NULL); -} - -static int -_ethtool_call_once(int ifindex, gpointer edata, gsize edata_size) -{ - char sbuf[50]; - - return _ioctl_call("ethtool", - _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)), - SIOCETHTOOL, - ifindex, - NULL, - NULL, - IOCTL_CALL_DATA_TYPE_IFRDATA, - edata, - edata_size, - NULL); -} - -/*****************************************************************************/ - -static struct ethtool_gstrings * -ethtool_get_stringset(SocketHandle *shandle, int stringset_id) -{ - struct { - struct ethtool_sset_info info; - guint32 sentinel; - } sset_info = { - .info.cmd = ETHTOOL_GSSET_INFO, - .info.reserved = 0, - .info.sset_mask = (1ULL << stringset_id), - }; - const guint32 *pdata; - gs_free struct ethtool_gstrings *gstrings = NULL; - gsize gstrings_len; - guint32 i, len; - - if (_ethtool_call_handle(shandle, &sset_info, sizeof(sset_info)) < 0) - return NULL; - if (!sset_info.info.sset_mask) - return NULL; - - pdata = (guint32 *) sset_info.info.data; - - len = *pdata; - - gstrings_len = sizeof(*gstrings) + (len * ETH_GSTRING_LEN); - gstrings = g_malloc0(gstrings_len); - gstrings->cmd = ETHTOOL_GSTRINGS; - gstrings->string_set = stringset_id; - gstrings->len = len; - if (gstrings->len > 0) { - if (_ethtool_call_handle(shandle, gstrings, gstrings_len) < 0) - return NULL; - for (i = 0; i < gstrings->len; i++) { - /* ensure NUL terminated */ - gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0'; - } - } - - return g_steal_pointer(&gstrings); -} - -static int -ethtool_gstrings_find(const struct ethtool_gstrings *gstrings, const char *needle) -{ - guint32 i; - - /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. - * that means, we cannot possibly request longer names. */ - nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN); - - for (i = 0; i < gstrings->len; i++) { - if (nm_streq((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle)) - return i; - } - return -1; -} - -static int -ethtool_get_stringset_index(SocketHandle *shandle, int stringset_id, const char *needle) -{ - gs_free struct ethtool_gstrings *gstrings = NULL; - - /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. - * that means, we cannot possibly request longer names. */ - nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN); - - gstrings = ethtool_get_stringset(shandle, stringset_id); - if (gstrings) - return ethtool_gstrings_find(gstrings, needle); - return -1; -} - -/*****************************************************************************/ - -static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = { -#define ETHT_FEAT(eid, ...) \ - { \ - .ethtool_id = eid, \ - .n_kernel_names = NM_NARG(__VA_ARGS__), \ - .kernel_names = ((const char *const[]) {__VA_ARGS__}), \ - } - - /* the order does only matter for one thing: if it happens that more than one NMEthtoolID - * reference the same kernel-name, then the one that is mentioned *later* will win in - * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which - * refer to multiple features ("feature-tso"), while also having more specific ids - * ("feature-tx-tcp-segmentation"). */ - - /* names from ethtool utility, which are aliases for multiple features. */ - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_SG, "tx-scatter-gather", "tx-scatter-gather-fraglist"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TSO, - "tx-tcp-segmentation", - "tx-tcp-ecn-segmentation", - "tx-tcp-mangleid-segmentation", - "tx-tcp6-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX, - "tx-checksum-ipv4", - "tx-checksum-ip-generic", - "tx-checksum-ipv6", - "tx-checksum-fcoe-crc", - "tx-checksum-sctp"), - - /* names from ethtool utility, which are aliases for one feature. */ - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"), - - /* names of features, as known by kernel. */ - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD, "macsec-hw-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST, "rx-gro-list"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING, "rx-udp-gro-forwarding"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD, "tls-hw-rx-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST, "tx-gso-list"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION, - "tx-tunnel-remcsum-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"), - ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"), -}; - -/* the number of kernel features that we handle. It essentially is the sum of all - * kernel_names. So, all ethtool-ids that reference exactly one kernel-name - * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases - * for multiple kernel-names. */ -#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u) - -static void -_ASSERT_ethtool_feature_infos(void) -{ -#if NM_MORE_ASSERTS > 10 - guint i, k, n; - bool found[_NM_ETHTOOL_ID_FEATURE_NUM] = {}; - - G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM); - - n = 0; - for (i = 0; i < G_N_ELEMENTS(_ethtool_feature_infos); i++) { - NMEthtoolFeatureState kstate; - const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i]; - - g_assert(inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST); - g_assert(inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST); - g_assert(inf->n_kernel_names > 0); - - for (k = 0; k < i; k++) - g_assert(inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id); - - g_assert(!found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)]); - found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)] = TRUE; - - kstate.idx_kernel_name = inf->n_kernel_names - 1; - g_assert((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1)); - - n += inf->n_kernel_names; - for (k = 0; k < inf->n_kernel_names; k++) { - const char *name = inf->kernel_names[k]; - - g_assert(!nm_strv_contains(inf->kernel_names, k, name)); - - /* these offload features are only informational and cannot be set from user-space - * (NETIF_F_NEVER_CHANGE). We should not track them in _ethtool_feature_infos. */ - g_assert(!nm_streq(name, "netns-local")); - g_assert(!nm_streq(name, "tx-lockless")); - g_assert(!nm_streq(name, "vlan-challenged")); - } - } - - for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) - g_assert(found[i]); - - g_assert(n == N_ETHTOOL_KERNEL_FEATURES); -#endif -} - -static NMEthtoolFeatureStates * -ethtool_get_features(SocketHandle *shandle) -{ - gs_free NMEthtoolFeatureStates *states = NULL; - gs_free struct ethtool_gstrings *ss_features = NULL; - - _ASSERT_ethtool_feature_infos(); - - ss_features = ethtool_get_stringset(shandle, ETH_SS_FEATURES); - if (!ss_features) - return NULL; - - if (ss_features->len > 0) { - gs_free struct ethtool_gfeatures *gfeatures_free = NULL; - struct ethtool_gfeatures *gfeatures; - gsize gfeatures_len; - guint idx; - const NMEthtoolFeatureState *states_list0 = NULL; - const NMEthtoolFeatureState *const *states_plist0 = NULL; - guint states_plist_n = 0; - - gfeatures_len = sizeof(struct ethtool_gfeatures) - + (NM_DIV_ROUND_UP(ss_features->len, 32u) * sizeof(gfeatures->features[0])); - gfeatures = nm_malloc0_maybe_a(300, gfeatures_len, &gfeatures_free); - gfeatures->cmd = ETHTOOL_GFEATURES; - gfeatures->size = NM_DIV_ROUND_UP(ss_features->len, 32u); - if (_ethtool_call_handle(shandle, gfeatures, gfeatures_len) < 0) - return NULL; - - for (idx = 0; idx < G_N_ELEMENTS(_ethtool_feature_infos); idx++) { - const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx]; - guint idx_kernel_name; - - for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) { - NMEthtoolFeatureState *kstate; - const char *kernel_name = info->kernel_names[idx_kernel_name]; - int i_feature; - guint i_block; - guint32 i_flag; - - i_feature = ethtool_gstrings_find(ss_features, kernel_name); - if (i_feature < 0) - continue; - - i_block = ((guint) i_feature) / 32u; - i_flag = (guint32) (1u << (((guint) i_feature) % 32u)); - - if (!states) { - states = g_malloc0( - sizeof(NMEthtoolFeatureStates) - + (N_ETHTOOL_KERNEL_FEATURES * sizeof(NMEthtoolFeatureState)) - + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)) - * sizeof(NMEthtoolFeatureState *))); - states_list0 = &states->states_list[0]; - states_plist0 = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES]; - states->n_ss_features = ss_features->len; - } - - nm_assert(states->n_states < N_ETHTOOL_KERNEL_FEATURES); - kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states]; - states->n_states++; - - kstate->info = info; - kstate->idx_ss_features = i_feature; - kstate->idx_kernel_name = idx_kernel_name; - kstate->available = !!(gfeatures->features[i_block].available & i_flag); - kstate->requested = !!(gfeatures->features[i_block].requested & i_flag); - kstate->active = !!(gfeatures->features[i_block].active & i_flag); - kstate->never_changed = !!(gfeatures->features[i_block].never_changed & i_flag); - - nm_assert(states_plist_n - < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)); - - if (!states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) - states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)] = - &states_plist0[states_plist_n]; - ((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate; - states_plist_n++; - } - - if (states && states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) { - nm_assert(states_plist_n - < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)); - nm_assert(!states_plist0[states_plist_n]); - states_plist_n++; - } - } - } - - return g_steal_pointer(&states); -} - -NMEthtoolFeatureStates * -nmp_utils_ethtool_get_features(int ifindex) -{ - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - NMEthtoolFeatureStates *features; - - g_return_val_if_fail(ifindex > 0, 0); - - features = ethtool_get_features(&shandle); - - if (!features) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting features", - ifindex, - "get-features"); - return NULL; - } - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel features", - ifindex, - "get-features"); - return features; -} - -static const char * -_ethtool_feature_state_to_string(char *buf, - gsize buf_size, - const NMEthtoolFeatureState *s, - const char *prefix) -{ - int l; - - l = g_snprintf(buf, - buf_size, - "%s %s%s", - prefix ?: "", - ONOFF(s->active), - (!s->available || s->never_changed) - ? ", [fixed]" - : ((s->requested != s->active) - ? (s->requested ? ", [requested on]" : ", [requested off]") - : "")); - nm_assert(l < buf_size); - return buf; -} - -gboolean -nmp_utils_ethtool_set_features( - int ifindex, - const NMEthtoolFeatureStates *features, - const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, - gboolean do_set /* or reset */) -{ - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - gs_free struct ethtool_sfeatures *sfeatures_free = NULL; - struct ethtool_sfeatures *sfeatures; - gsize sfeatures_len; - int r; - guint i, j; - struct { - const NMEthtoolFeatureState *f_state; - NMOptionBool requested; - } set_states[N_ETHTOOL_KERNEL_FEATURES]; - guint set_states_n = 0; - gboolean success = TRUE; - - g_return_val_if_fail(ifindex > 0, 0); - g_return_val_if_fail(features, 0); - g_return_val_if_fail(requested, 0); - - nm_assert(features->n_states <= N_ETHTOOL_KERNEL_FEATURES); - - for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) { - const NMEthtoolFeatureState *const *states_indexed; - - if (requested[i] == NM_OPTION_BOOL_DEFAULT) - continue; - - if (!(states_indexed = features->states_indexed[i])) { - if (do_set) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: set feature %s: skip (not found)", - ifindex, - "set-features", - nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname); - success = FALSE; - } - continue; - } - - for (j = 0; states_indexed[j]; j++) { - const NMEthtoolFeatureState *s = states_indexed[j]; - char sbuf[255]; - - if (set_states_n >= G_N_ELEMENTS(set_states)) - g_return_val_if_reached(FALSE); - - if (s->never_changed) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as " - "never changed)", - ifindex, - "set-features", - do_set ? "set" : "reset", - nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, - s->info->kernel_names[s->idx_kernel_name], - ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active), - _ethtool_feature_state_to_string(sbuf, - sizeof(sbuf), - s, - do_set ? " currently:" : " before:")); - continue; - } - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: %s feature %s (%s): %s, %s", - ifindex, - "set-features", - do_set ? "set" : "reset", - nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, - s->info->kernel_names[s->idx_kernel_name], - ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active), - _ethtool_feature_state_to_string(sbuf, - sizeof(sbuf), - s, - do_set ? " currently:" : " before:")); - - if (do_set && (!s->available || s->never_changed) - && (s->active != (requested[i] == NM_OPTION_BOOL_TRUE))) { - /* we request to change a flag which kernel reported as fixed. - * While the ethtool operation will silently succeed, mark the request - * as failure. */ - success = FALSE; - } - - set_states[set_states_n].f_state = s; - set_states[set_states_n].requested = requested[i]; - set_states_n++; - } - } - - if (set_states_n == 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: no feature requested", - ifindex, - "set-features"); - return TRUE; - } - - sfeatures_len = - sizeof(struct ethtool_sfeatures) - + (NM_DIV_ROUND_UP(features->n_ss_features, 32U) * sizeof(sfeatures->features[0])); - sfeatures = nm_malloc0_maybe_a(300, sfeatures_len, &sfeatures_free); - sfeatures->cmd = ETHTOOL_SFEATURES; - sfeatures->size = NM_DIV_ROUND_UP(features->n_ss_features, 32U); - - for (i = 0; i < set_states_n; i++) { - const NMEthtoolFeatureState *s = set_states[i].f_state; - guint i_block; - guint32 i_flag; - gboolean is_requested; - - i_block = s->idx_ss_features / 32u; - i_flag = (guint32) (1u << (s->idx_ss_features % 32u)); - - sfeatures->features[i_block].valid |= i_flag; - - if (do_set) - is_requested = (set_states[i].requested == NM_OPTION_BOOL_TRUE); - else - is_requested = s->active; - - if (is_requested) - sfeatures->features[i_block].requested |= i_flag; - else - sfeatures->features[i_block].requested &= ~i_flag; - } - - r = _ethtool_call_handle(&shandle, sfeatures, sfeatures_len); - if (r < 0) { - success = FALSE; - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting features (%s)", - ifindex, - "set-features", - nm_strerror_native(-r)); - return FALSE; - } - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: %s", - ifindex, - "set-features", - success ? "successfully setting features" - : "at least some of the features were not successfully set"); - return success; -} - -gboolean -nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce) -{ - struct ethtool_coalesce eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(coalesce, FALSE); - - eth_data.cmd = ETHTOOL_GCOALESCE; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting coalesce settings", - ifindex, - "get-coalesce"); - return FALSE; - } - - *coalesce = (NMEthtoolCoalesceState) { - .s = { - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)] = - eth_data.rx_coalesce_usecs, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)] = - eth_data.rx_max_coalesced_frames, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)] = - eth_data.rx_coalesce_usecs_irq, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)] = - eth_data.rx_max_coalesced_frames_irq, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)] = - eth_data.tx_coalesce_usecs, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)] = - eth_data.tx_max_coalesced_frames, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)] = - eth_data.tx_coalesce_usecs_irq, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)] = - eth_data.tx_max_coalesced_frames_irq, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)] = - eth_data.stats_block_coalesce_usecs, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)] = - eth_data.use_adaptive_rx_coalesce, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)] = - eth_data.use_adaptive_tx_coalesce, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)] = - eth_data.pkt_rate_low, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)] = - eth_data.rx_coalesce_usecs_low, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)] = - eth_data.rx_max_coalesced_frames_low, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)] = - eth_data.tx_coalesce_usecs_low, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)] = - eth_data.tx_max_coalesced_frames_low, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)] = - eth_data.pkt_rate_high, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)] = - eth_data.rx_coalesce_usecs_high, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)] = - eth_data.rx_max_coalesced_frames_high, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)] = - eth_data.tx_coalesce_usecs_high, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)] = - eth_data.tx_max_coalesced_frames_high, - [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)] = - eth_data.rate_sample_interval, - }}; - return TRUE; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel coalesce settings", - ifindex, - "get-coalesce"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce) -{ - struct ethtool_coalesce eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(coalesce, FALSE); - - eth_data = (struct ethtool_coalesce) { - .cmd = ETHTOOL_SCOALESCE, - .rx_coalesce_usecs = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)], - .rx_max_coalesced_frames = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)], - .rx_coalesce_usecs_irq = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)], - .rx_max_coalesced_frames_irq = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)], - .tx_coalesce_usecs = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)], - .tx_max_coalesced_frames = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)], - .tx_coalesce_usecs_irq = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)], - .tx_max_coalesced_frames_irq = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)], - .stats_block_coalesce_usecs = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)], - .use_adaptive_rx_coalesce = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)], - .use_adaptive_tx_coalesce = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)], - .pkt_rate_low = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)], - .rx_coalesce_usecs_low = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)], - .rx_max_coalesced_frames_low = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)], - .tx_coalesce_usecs_low = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)], - .tx_max_coalesced_frames_low = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)], - .pkt_rate_high = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)], - .rx_coalesce_usecs_high = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)], - .rx_max_coalesced_frames_high = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)], - .tx_coalesce_usecs_high = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)], - .tx_max_coalesced_frames_high = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)], - .rate_sample_interval = - coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)], - }; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting coalesce settings", - ifindex, - "set-coalesce"); - return FALSE; - } - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: set kernel coalesce settings", - ifindex, - "set-coalesce"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring) -{ - struct ethtool_ringparam eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(ring, FALSE); - - eth_data.cmd = ETHTOOL_GRINGPARAM; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting ring settings", - ifindex, - "get-ring"); - return FALSE; - } - - *ring = (NMEthtoolRingState) { - .rx_pending = eth_data.rx_pending, - .rx_jumbo_pending = eth_data.rx_jumbo_pending, - .rx_mini_pending = eth_data.rx_mini_pending, - .tx_pending = eth_data.tx_pending, - }; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel ring settings", - ifindex, - "get-ring"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring) -{ - struct ethtool_ringparam eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(ring, FALSE); - - eth_data = (struct ethtool_ringparam) { - .cmd = ETHTOOL_SRINGPARAM, - .rx_pending = ring->rx_pending, - .rx_jumbo_pending = ring->rx_jumbo_pending, - .rx_mini_pending = ring->rx_mini_pending, - .tx_pending = ring->tx_pending, - }; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting ring settings", - ifindex, - "set-ring"); - return FALSE; - } - - nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel ring settings", ifindex, "set-ring"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_get_channels(int ifindex, NMEthtoolChannelsState *channels) -{ - struct ethtool_channels eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(channels, FALSE); - - eth_data.cmd = ETHTOOL_GCHANNELS; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting channels settings", - ifindex, - "get-channels"); - return FALSE; - } - - *channels = (NMEthtoolChannelsState) { - .rx = eth_data.rx_count, - .tx = eth_data.tx_count, - .other = eth_data.other_count, - .combined = eth_data.combined_count, - }; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel channels settings", - ifindex, - "get-channels"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_set_channels(int ifindex, const NMEthtoolChannelsState *channels) -{ - struct ethtool_channels eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(channels, FALSE); - - eth_data = (struct ethtool_channels) { - .cmd = ETHTOOL_SCHANNELS, - .rx_count = channels->rx, - .tx_count = channels->tx, - .other_count = channels->other, - .combined_count = channels->combined, - }; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting channels settings", - ifindex, - "set-channels"); - return FALSE; - } - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: set kernel channels settings", - ifindex, - "set-channels"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_get_pause(int ifindex, NMEthtoolPauseState *pause) -{ - struct ethtool_pauseparam eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(pause, FALSE); - - eth_data.cmd = ETHTOOL_GPAUSEPARAM; - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting pause settings", - ifindex, - "get-pause"); - return FALSE; - } - - *pause = (NMEthtoolPauseState) { - .autoneg = eth_data.autoneg == 1, - .rx = eth_data.rx_pause == 1, - .tx = eth_data.tx_pause == 1, - }; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel pause settings", - ifindex, - "get-pause"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_get_eee(int ifindex, NMEthtoolEEEState *eee) -{ - struct ethtool_eee eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(eee, FALSE); - - eth_data.cmd = ETHTOOL_GEEE; - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting eee settings", - ifindex, - "get-eee"); - return FALSE; - } - - *eee = (NMEthtoolEEEState) { - .enabled = eth_data.eee_enabled == 1, - }; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel eee settings", - ifindex, - "get-eee"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_set_pause(int ifindex, const NMEthtoolPauseState *pause) -{ - struct ethtool_pauseparam eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(pause, FALSE); - - eth_data = (struct ethtool_pauseparam) { - .cmd = ETHTOOL_SPAUSEPARAM, - .autoneg = pause->autoneg ? 1 : 0, - .rx_pause = pause->rx ? 1 : 0, - .tx_pause = pause->tx ? 1 : 0, - }; - - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting pause settings", - ifindex, - "set-pause"); - return FALSE; - } - nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel puase settings", ifindex, "set-pause"); - return TRUE; -} - -gboolean -nmp_utils_ethtool_set_eee(int ifindex, const NMEthtoolEEEState *eee) -{ - struct ethtool_eee eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(eee, FALSE); - - eth_data.cmd = ETHTOOL_GEEE; - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting eee settings", - ifindex, - "get-eee"); - return FALSE; - } - - eth_data.cmd = ETHTOOL_SEEE, eth_data.eee_enabled = eee->enabled ? 1 : 0; - - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting eee settings", - ifindex, - "set-eee"); - return FALSE; - } - nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel eee settings", ifindex, "set-eee"); - return TRUE; -} -/*****************************************************************************/ - -gboolean -nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data) -{ - struct ethtool_drvinfo *drvinfo; - - G_STATIC_ASSERT_EXPR(sizeof(*data) == sizeof(*drvinfo)); - G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, driver) - == offsetof(struct ethtool_drvinfo, driver)); - G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, version) - == offsetof(struct ethtool_drvinfo, version)); - G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, fw_version) - == offsetof(struct ethtool_drvinfo, fw_version)); - G_STATIC_ASSERT_EXPR(sizeof(data->driver) == sizeof(drvinfo->driver)); - G_STATIC_ASSERT_EXPR(sizeof(data->version) == sizeof(drvinfo->version)); - G_STATIC_ASSERT_EXPR(sizeof(data->fw_version) == sizeof(drvinfo->fw_version)); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(data, FALSE); - - drvinfo = (struct ethtool_drvinfo *) data; - *drvinfo = (struct ethtool_drvinfo) { - .cmd = ETHTOOL_GDRVINFO, - }; - return _ethtool_call_once(ifindex, drvinfo, sizeof(*drvinfo)) >= 0; -} - -gboolean -nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length) -{ - struct { - struct ethtool_perm_addr e; - guint8 _extra_data[_NM_UTILS_HWADDR_LEN_MAX + 1]; - } edata = { - .e.cmd = ETHTOOL_GPERMADDR, - .e.size = _NM_UTILS_HWADDR_LEN_MAX, - }; - const guint8 *pdata; - - guint i; - - g_return_val_if_fail(ifindex > 0, FALSE); - - if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0) - return FALSE; - - if (edata.e.size > _NM_UTILS_HWADDR_LEN_MAX) - return FALSE; - if (edata.e.size < 1) - return FALSE; - - pdata = (const guint8 *) edata.e.data; - - if (NM_IN_SET(pdata[0], 0, 0xFF)) { - /* Some drivers might return a permanent address of all zeros. - * Reject that (rh#1264024) - * - * Some drivers return a permanent address of all ones. Reject that too */ - for (i = 1; i < edata.e.size; i++) { - if (pdata[0] != pdata[i]) - goto not_all_0or1; - } - return FALSE; - } - -not_all_0or1: - memcpy(buf, pdata, edata.e.size); - *length = edata.e.size; - return TRUE; -} - -gboolean -nmp_utils_ethtool_supports_carrier_detect(int ifindex) -{ - struct ethtool_cmd edata = {.cmd = ETHTOOL_GLINK}; - - g_return_val_if_fail(ifindex > 0, FALSE); - - /* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we - * assume the device supports carrier-detect, otherwise we assume it - * doesn't. - */ - return _ethtool_call_once(ifindex, &edata, sizeof(edata)) >= 0; -} - -gboolean -nmp_utils_ethtool_supports_vlans(int ifindex) -{ - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - gs_free struct ethtool_gfeatures *features_free = NULL; - struct ethtool_gfeatures *features; - gsize features_len; - int idx, block, bit, size; - - g_return_val_if_fail(ifindex > 0, FALSE); - - idx = ethtool_get_stringset_index(&shandle, ETH_SS_FEATURES, "vlan-challenged"); - if (idx < 0) { - nm_log_dbg(LOGD_PLATFORM, - "ethtool[%d]: vlan-challenged ethtool feature does not exist?", - ifindex); - return FALSE; - } - - block = idx / 32; - bit = idx % 32; - size = block + 1; - - features_len = sizeof(*features) + (size * sizeof(struct ethtool_get_features_block)); - features = nm_malloc0_maybe_a(300, features_len, &features_free); - features->cmd = ETHTOOL_GFEATURES; - features->size = size; - - if (_ethtool_call_handle(&shandle, features, features_len) < 0) - return FALSE; - - return !(features->features[block].active & (1 << bit)); -} - -int -nmp_utils_ethtool_get_peer_ifindex(int ifindex) -{ - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - gsize stats_len; - gs_free struct ethtool_stats *stats_free = NULL; - struct ethtool_stats *stats; - int peer_ifindex_stat; - - g_return_val_if_fail(ifindex > 0, 0); - - peer_ifindex_stat = ethtool_get_stringset_index(&shandle, ETH_SS_STATS, "peer_ifindex"); - if (peer_ifindex_stat < 0) { - nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex); - return FALSE; - } - - stats_len = sizeof(*stats) + (peer_ifindex_stat + 1) * sizeof(guint64); - stats = nm_malloc0_maybe_a(300, stats_len, &stats_free); - stats->cmd = ETHTOOL_GSTATS; - stats->n_stats = peer_ifindex_stat + 1; - if (_ethtool_call_handle(&shandle, stats, stats_len) < 0) - return 0; - - return stats->data[peer_ifindex_stat]; -} - -gboolean -nmp_utils_ethtool_get_wake_on_lan(int ifindex) -{ - struct ethtool_wolinfo wol = { - .cmd = ETHTOOL_GWOL, - }; - - g_return_val_if_fail(ifindex > 0, FALSE); - - if (_ethtool_call_once(ifindex, &wol, sizeof(wol)) < 0) - return FALSE; - - return wol.wolopts != 0; -} - -gboolean -nmp_utils_ethtool_get_link_settings(int ifindex, - gboolean *out_autoneg, - guint32 *out_speed, - NMPlatformLinkDuplexType *out_duplex) -{ - struct ethtool_cmd edata = { - .cmd = ETHTOOL_GSET, - }; - - g_return_val_if_fail(ifindex > 0, FALSE); - - if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0) - return FALSE; - - NM_SET_OUT(out_autoneg, (edata.autoneg == AUTONEG_ENABLE)); - - if (out_speed) { - guint32 speed; - - speed = ethtool_cmd_speed(&edata); - if (speed == G_MAXUINT16 || speed == G_MAXUINT32) - speed = 0; - - *out_speed = speed; - } - - if (out_duplex) { - switch (edata.duplex) { - case DUPLEX_HALF: - *out_duplex = NM_PLATFORM_LINK_DUPLEX_HALF; - break; - case DUPLEX_FULL: - *out_duplex = NM_PLATFORM_LINK_DUPLEX_FULL; - break; - default: /* DUPLEX_UNKNOWN */ - *out_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN; - break; - } - } - - return TRUE; -} - -#define ADVERTISED_INVALID 0 - -static guint32 -get_baset_mode(guint32 speed, NMPlatformLinkDuplexType duplex) -{ - if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN) - return ADVERTISED_INVALID; - - if (duplex == NM_PLATFORM_LINK_DUPLEX_HALF) { - switch (speed) { - case 10: - return ADVERTISED_10baseT_Half; - case 100: - return ADVERTISED_100baseT_Half; - case 1000: - return ADVERTISED_1000baseT_Half; - default: - return ADVERTISED_INVALID; - } - } else { - switch (speed) { - case 10: - return ADVERTISED_10baseT_Full; - case 100: - return ADVERTISED_100baseT_Full; - case 1000: - return ADVERTISED_1000baseT_Full; - case 10000: - return ADVERTISED_10000baseT_Full; - default: - return ADVERTISED_INVALID; - } - } -} - -static gboolean -platform_link_duplex_type_to_native(NMPlatformLinkDuplexType duplex_type, guint8 *out_native) -{ - switch (duplex_type) { - case NM_PLATFORM_LINK_DUPLEX_HALF: - *out_native = DUPLEX_HALF; - return TRUE; - case NM_PLATFORM_LINK_DUPLEX_FULL: - *out_native = DUPLEX_FULL; - return TRUE; - case NM_PLATFORM_LINK_DUPLEX_UNKNOWN: - return FALSE; - default: - g_return_val_if_reached(FALSE); - } -} - const guint8 _nmp_link_mode_all_advertised_modes_bits[] = { ETHTOOL_LINK_MODE_10baseT_Half_BIT, ETHTOOL_LINK_MODE_10baseT_Full_BIT, @@ -1597,322 +148,6 @@ const guint32 _nmp_link_mode_all_advertised_modes[] = { 0x0ffffbffu, }; -static NMOptionBool -set_link_settings_new(SocketHandle *shandle, - gboolean autoneg, - guint32 speed, - NMPlatformLinkDuplexType duplex) -{ - struct ethtool_link_settings edata0; - gs_free struct ethtool_link_settings *edata = NULL; - gsize edata_size; - guint nwords; - guint i; - - edata0 = (struct ethtool_link_settings) { - .cmd = ETHTOOL_GLINKSETTINGS, - .link_mode_masks_nwords = 0, - }; - - /* perform the handshake to find the size of masks */ - if (_ethtool_call_handle(shandle, &edata0, sizeof(edata0)) < 0 - || edata0.link_mode_masks_nwords >= 0) { - /* new API not supported */ - return NM_OPTION_BOOL_DEFAULT; - } - - nwords = -edata0.link_mode_masks_nwords; - edata_size = sizeof(*edata) + sizeof(guint32) * nwords * 3; - edata = g_malloc0(edata_size); - edata->cmd = ETHTOOL_GLINKSETTINGS; - edata->link_mode_masks_nwords = nwords; - - /* retrieve first current settings */ - if (_ethtool_call_handle(shandle, edata, edata_size) < 0) - return FALSE; - - /* then change the needed ones */ - edata->cmd = ETHTOOL_SLINKSETTINGS; - - { - const guint32 *v_map_supported = &edata->link_mode_masks[0]; - guint32 *v_map_advertising = &edata->link_mode_masks[nwords]; - guint32 *v_map_lp_advertising = &edata->link_mode_masks[2 * nwords]; - - memcpy(v_map_advertising, v_map_supported, sizeof(guint32) * nwords); - (void) v_map_lp_advertising; - - if (speed != 0) { - guint32 mode; - - mode = get_baset_mode(speed, duplex); - - if (mode == ADVERTISED_INVALID) { - if (!autoneg) - goto set_autoneg; - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised", - shandle->ifindex, - speed, - nm_platform_link_duplex_type_to_string(duplex)); - return FALSE; - } - - if (!(v_map_supported[0] & mode)) { - if (!autoneg) - goto set_autoneg; - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: device does not support %uBASE-T %s duplex mode", - shandle->ifindex, - speed, - nm_platform_link_duplex_type_to_string(duplex)); - return FALSE; - } - - for (i = 0; i < (guint) G_N_ELEMENTS(_nmp_link_mode_all_advertised_modes); i++) - v_map_advertising[i] &= ~_nmp_link_mode_all_advertised_modes[i]; - v_map_advertising[0] |= mode; - } - } - -set_autoneg: - if (autoneg) - edata->autoneg = AUTONEG_ENABLE; - else { - edata->autoneg = AUTONEG_DISABLE; - - if (speed) - edata->speed = speed; - - platform_link_duplex_type_to_native(duplex, &edata->duplex); - } - - return _ethtool_call_handle(shandle, edata, edata_size) >= 0; -} - -gboolean -nmp_utils_ethtool_set_link_settings(int ifindex, - gboolean autoneg, - guint32 speed, - NMPlatformLinkDuplexType duplex) -{ - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - struct ethtool_cmd edata = { - .cmd = ETHTOOL_GSET, - }; - NMOptionBool ret; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail((speed && duplex != NM_PLATFORM_LINK_DUPLEX_UNKNOWN) - || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), - FALSE); - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: set link: autoneg=%d, speed=%d, duplex=%s", - ifindex, - autoneg, - speed, - nm_platform_link_duplex_type_to_string(duplex)); - - ret = set_link_settings_new(&shandle, autoneg, speed, duplex); - if (ret != NM_OPTION_BOOL_DEFAULT) - return ret; - - /* new ETHTOOL_GLINKSETTINGS API not supported, fall back to GSET */ - - /* retrieve first current settings */ - if (_ethtool_call_handle(&shandle, &edata, sizeof(edata)) < 0) - return FALSE; - - /* then change the needed ones */ - edata.cmd = ETHTOOL_SSET; - - edata.advertising = edata.supported; - if (speed != 0) { - guint32 mode; - - mode = get_baset_mode(speed, duplex); - - if (mode == ADVERTISED_INVALID) { - if (!autoneg) - goto set_autoneg; - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised", - ifindex, - speed, - nm_platform_link_duplex_type_to_string(duplex)); - return FALSE; - } - if (!(edata.supported & mode)) { - if (!autoneg) - goto set_autoneg; - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: device does not support %uBASE-T %s duplex mode", - ifindex, - speed, - nm_platform_link_duplex_type_to_string(duplex)); - return FALSE; - } - edata.advertising &= ~_nmp_link_mode_all_advertised_modes[0]; - edata.advertising |= mode; - } - -set_autoneg: - if (autoneg) - edata.autoneg = AUTONEG_ENABLE; - else { - edata.autoneg = AUTONEG_DISABLE; - - if (speed) - ethtool_cmd_speed_set(&edata, speed); - - platform_link_duplex_type_to_native(duplex, &edata.duplex); - } - - return _ethtool_call_handle(&shandle, &edata, sizeof(edata)) >= 0; -} - -gboolean -nmp_utils_ethtool_set_wake_on_lan(int ifindex, - _NMSettingWiredWakeOnLan wol, - const char *wol_password) -{ - struct ethtool_wolinfo wol_info = { - .cmd = ETHTOOL_SWOL, - .wolopts = 0, - }; - - g_return_val_if_fail(ifindex > 0, FALSE); - - if (wol == _NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE) - return TRUE; - - nm_log_dbg(LOGD_PLATFORM, - "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'", - ifindex, - (unsigned) wol, - wol_password); - - if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_PHY)) - wol_info.wolopts |= WAKE_PHY; - if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST)) - wol_info.wolopts |= WAKE_UCAST; - if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST)) - wol_info.wolopts |= WAKE_MCAST; - if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST)) - wol_info.wolopts |= WAKE_BCAST; - if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_ARP)) - wol_info.wolopts |= WAKE_ARP; - if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) - wol_info.wolopts |= WAKE_MAGIC; - - if (wol_password) { - if (!_nm_utils_hwaddr_aton_exact(wol_password, wol_info.sopass, ETH_ALEN)) { - nm_log_dbg(LOGD_PLATFORM, - "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'", - ifindex, - wol_password); - return FALSE; - } - wol_info.wolopts |= WAKE_MAGICSECURE; - } - - return _ethtool_call_once(ifindex, &wol_info, sizeof(wol_info)) >= 0; -} - -gboolean -nmp_utils_ethtool_get_fec_mode(int ifindex, uint32_t *fec_mode) -{ - int r; - struct ethtool_fecparam fec_param = { - .cmd = ETHTOOL_GFECPARAM, - .fec = 0, - }; - - g_return_val_if_fail(ifindex > 0, FALSE); - - if (_ethtool_call_once(ifindex, &fec_param, sizeof(fec_param)) >= 0) { - nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: get FEC options 0x%x", ifindex, fec_param.fec); - *fec_mode = fec_param.fec; - return TRUE; - } else { - r = -NM_ERRNO_NATIVE(errno); - nm_log_dbg(LOGD_PLATFORM, - "ethtool[%d]: ETHTOOL_GFECPARAM failure get fec mode: (%s)", - ifindex, - nm_strerror_native(-r)); - return FALSE; - } -} - -gboolean -nmp_utils_ethtool_set_fec_mode(int ifindex, uint32_t fec_mode) -{ - struct ethtool_fecparam fec_param = { - .cmd = ETHTOOL_SFECPARAM, - .fec = fec_mode, - }; - - g_return_val_if_fail(ifindex > 0, FALSE); - - nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: setting FEC options 0x%x", ifindex, fec_mode); - - return _ethtool_call_once(ifindex, &fec_param, sizeof(fec_param)) >= 0; -} - -/****************************************************************************** - * mii - *****************************************************************************/ - -gboolean -nmp_utils_mii_supports_carrier_detect(int ifindex) -{ - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - int r; - struct ifreq ifr; - struct mii_ioctl_data *mii; - - g_return_val_if_fail(ifindex > 0, FALSE); - - r = _ioctl_call("mii", - "SIOCGMIIPHY", - SIOCGMIIPHY, - shandle.ifindex, - &shandle.fd, - shandle.ifname, - IOCTL_CALL_DATA_TYPE_NONE, - NULL, - 0, - &ifr); - if (r < 0) - return FALSE; - - /* If we can read the BMSR register, we assume that the card supports MII link detection */ - mii = (struct mii_ioctl_data *) &ifr.ifr_ifru; - mii->reg_num = MII_BMSR; - - r = _ioctl_call("mii", - "SIOCGMIIREG", - SIOCGMIIREG, - shandle.ifindex, - &shandle.fd, - shandle.ifname, - IOCTL_CALL_DATA_TYPE_IFRU, - mii, - sizeof(*mii), - &ifr); - if (r < 0) - return FALSE; - - mii = (struct mii_ioctl_data *) &ifr.ifr_ifru; - nm_log_trace(LOGD_PLATFORM, - "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", - ifindex, - shandle.ifname, - mii->val_out); - return TRUE; -} - /****************************************************************************** * udev *****************************************************************************/ diff --git a/src/libnm-platform/nm-platform-utils.h b/src/libnm-platform/nm-platform-utils.h index f47bcd492b..4477d7ed6c 100644 --- a/src/libnm-platform/nm-platform-utils.h +++ b/src/libnm-platform/nm-platform-utils.h @@ -8,72 +8,17 @@ #include "libnm-base/nm-base.h" #include "libnm-platform/nmp-base.h" +#include "libnm-platform/nm-platform.h" /*****************************************************************************/ -const char *nmp_utils_ethtool_get_driver(int ifindex); -gboolean nmp_utils_ethtool_supports_carrier_detect(int ifindex); -gboolean nmp_utils_ethtool_supports_vlans(int ifindex); -int nmp_utils_ethtool_get_peer_ifindex(int ifindex); -gboolean nmp_utils_ethtool_get_wake_on_lan(int ifindex); -gboolean nmp_utils_ethtool_set_wake_on_lan(int ifindex, - _NMSettingWiredWakeOnLan wol, - const char *wol_password); - const char *nm_platform_link_duplex_type_to_string(NMPlatformLinkDuplexType duplex); extern const guint8 _nmp_link_mode_all_advertised_modes_bits[79]; extern const guint32 _nmp_link_mode_all_advertised_modes[3]; -gboolean nmp_utils_ethtool_get_link_settings(int ifindex, - gboolean *out_autoneg, - guint32 *out_speed, - NMPlatformLinkDuplexType *out_duplex); -gboolean nmp_utils_ethtool_set_link_settings(int ifindex, - gboolean autoneg, - guint32 speed, - NMPlatformLinkDuplexType duplex); - -gboolean nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length); - -gboolean nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data); - -NMEthtoolFeatureStates *nmp_utils_ethtool_get_features(int ifindex); - -gboolean nmp_utils_ethtool_set_features( - int ifindex, - const NMEthtoolFeatureStates *features, - const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, - gboolean do_set /* or reset */); - -gboolean nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce); - -gboolean nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce); - -gboolean nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring); - -gboolean nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring); - -gboolean nmp_utils_ethtool_get_channels(int ifindex, NMEthtoolChannelsState *channels); - -gboolean nmp_utils_ethtool_set_channels(int ifindex, const NMEthtoolChannelsState *channels); - -gboolean nmp_utils_ethtool_get_pause(int ifindex, NMEthtoolPauseState *pause); - -gboolean nmp_utils_ethtool_set_pause(int ifindex, const NMEthtoolPauseState *pause); - -gboolean nmp_utils_ethtool_get_eee(int ifindex, NMEthtoolEEEState *eee); - -gboolean nmp_utils_ethtool_set_eee(int ifindex, const NMEthtoolEEEState *eee); - -gboolean nmp_utils_ethtool_get_fec_mode(int ifindex, uint32_t *fec_mode); - -gboolean nmp_utils_ethtool_set_fec_mode(int ifindex, uint32_t fec_mode); - /*****************************************************************************/ -gboolean nmp_utils_mii_supports_carrier_detect(int ifindex); - struct udev_device; const char *nmp_utils_udev_get_driver(struct udev_device *udevice); diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 496bb4f5ee..0a0eeafb1d 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -31,6 +31,7 @@ #include "libnm-log-core/nm-logging.h" #include "libnm-platform/nm-platform-utils.h" #include "libnm-platform/nmp-netns.h" +#include "libnm-platform/nmp-ethtool-ioctl.h" #include "libnm-udev-aux/nm-udev-utils.h" #include "nm-platform-private.h" #include "nmp-object.h" @@ -3230,7 +3231,7 @@ nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_pee if (!nm_platform_netns_push(self, &netns)) return FALSE; - peer_ifindex = nmp_utils_ethtool_get_peer_ifindex(plink->ifindex); + peer_ifindex = nmp_ethtool_ioctl_get_peer_ifindex(plink->ifindex); if (peer_ifindex <= 0) return FALSE; @@ -3561,7 +3562,7 @@ nm_platform_ethtool_set_wake_on_lan(NMPlatform *self, g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_wake_on_lan(ifindex, wol, wol_password); + return nmp_ethtool_ioctl_set_wake_on_lan(ifindex, wol, wol_password); } gboolean @@ -3575,7 +3576,7 @@ nm_platform_ethtool_set_link_settings(NMPlatform *self, g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_link_settings(ifindex, autoneg, speed, duplex); + return nmp_ethtool_ioctl_set_link_settings(ifindex, autoneg, speed, duplex); } gboolean @@ -3589,7 +3590,7 @@ nm_platform_ethtool_get_link_settings(NMPlatform *self, g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_get_link_settings(ifindex, out_autoneg, out_speed, out_duplex); + return nmp_ethtool_ioctl_get_link_settings(ifindex, out_autoneg, out_speed, out_duplex); } /*****************************************************************************/ @@ -3601,7 +3602,7 @@ nm_platform_ethtool_get_features(NMPlatform *self, int ifindex) g_return_val_if_fail(ifindex > 0, NULL); - return nmp_utils_ethtool_get_features(ifindex); + return nmp_ethtool_ioctl_get_features(ifindex); } gboolean @@ -3616,7 +3617,7 @@ nm_platform_ethtool_set_features( g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_features(ifindex, features, requested, do_set); + return nmp_ethtool_ioctl_set_features(ifindex, features, requested, do_set); } gboolean @@ -3626,7 +3627,7 @@ nm_platform_ethtool_get_fec_mode(NMPlatform *self, int ifindex, uint32_t *fec_mo g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_get_fec_mode(ifindex, fec_mode); + return nmp_ethtool_ioctl_get_fec_mode(ifindex, fec_mode); } gboolean @@ -3636,7 +3637,7 @@ nm_platform_ethtool_set_fec_mode(NMPlatform *self, int ifindex, uint32_t fec_mod g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_fec_mode(ifindex, fec_mode); + return nmp_ethtool_ioctl_set_fec_mode(ifindex, fec_mode); } gboolean @@ -3647,7 +3648,7 @@ nm_platform_ethtool_get_coalesce(NMPlatform *self, int ifindex, NMEthtoolCoalesc g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(coalesce, FALSE); - return nmp_utils_ethtool_get_coalesce(ifindex, coalesce); + return nmp_ethtool_ioctl_get_coalesce(ifindex, coalesce); } gboolean @@ -3659,7 +3660,7 @@ nm_platform_ethtool_set_coalesce(NMPlatform *self, g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_coalesce(ifindex, coalesce); + return nmp_ethtool_ioctl_set_coalesce(ifindex, coalesce); } gboolean @@ -3670,7 +3671,7 @@ nm_platform_ethtool_get_ring(NMPlatform *self, int ifindex, NMEthtoolRingState * g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(ring, FALSE); - return nmp_utils_ethtool_get_ring(ifindex, ring); + return nmp_ethtool_ioctl_get_ring(ifindex, ring); } gboolean @@ -3680,7 +3681,7 @@ nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingS g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_ring(ifindex, ring); + return nmp_ethtool_ioctl_set_ring(ifindex, ring); } gboolean @@ -3691,7 +3692,7 @@ nm_platform_ethtool_get_channels(NMPlatform *self, int ifindex, NMEthtoolChannel g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(channels, FALSE); - return nmp_utils_ethtool_get_channels(ifindex, channels); + return nmp_ethtool_ioctl_get_channels(ifindex, channels); } gboolean @@ -3703,7 +3704,7 @@ nm_platform_ethtool_set_channels(NMPlatform *self, g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_channels(ifindex, channels); + return nmp_ethtool_ioctl_set_channels(ifindex, channels); } gboolean @@ -3714,7 +3715,7 @@ nm_platform_ethtool_get_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(pause, FALSE); - return nmp_utils_ethtool_get_pause(ifindex, pause); + return nmp_ethtool_ioctl_get_pause(ifindex, pause); } gboolean @@ -3724,7 +3725,7 @@ nm_platform_ethtool_set_pause(NMPlatform *self, int ifindex, const NMEthtoolPaus g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_pause(ifindex, pause); + return nmp_ethtool_ioctl_set_pause(ifindex, pause); } gboolean @@ -3735,7 +3736,7 @@ nm_platform_ethtool_get_eee(NMPlatform *self, int ifindex, NMEthtoolEEEState *ee g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(eee, FALSE); - return nmp_utils_ethtool_get_eee(ifindex, eee); + return nmp_ethtool_ioctl_get_eee(ifindex, eee); } gboolean @@ -3745,7 +3746,7 @@ nm_platform_ethtool_set_eee(NMPlatform *self, int ifindex, const NMEthtoolEEESta g_return_val_if_fail(ifindex > 0, FALSE); - return nmp_utils_ethtool_set_eee(ifindex, eee); + return nmp_ethtool_ioctl_set_eee(ifindex, eee); } /*****************************************************************************/ diff --git a/src/libnm-platform/nmp-ethtool-ioctl.c b/src/libnm-platform/nmp-ethtool-ioctl.c new file mode 100644 index 0000000000..0e90f8182c --- /dev/null +++ b/src/libnm-platform/nmp-ethtool-ioctl.c @@ -0,0 +1,1781 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nmp-ethtool-ioctl.h" + +#include "libnm-platform/nm-platform.h" +#include "libnm-platform/nm-platform-utils.h" +#include "libnm-log-core/nm-logging.h" +#include "libnm-base/nm-ethtool-base.h" + +#include +#include +#include +#include +#include +#include + +#define ONOFF(bool_val) ((bool_val) ? "on" : "off") + +/*****************************************************************************/ + +typedef struct { + int fd; + const int ifindex; + char ifname[IFNAMSIZ]; +} SocketHandle; + +#define SOCKET_HANDLE_INIT(_ifindex) \ + { \ + .fd = -1, \ + .ifindex = (_ifindex), \ + } + +static void +_nm_auto_socket_handle(SocketHandle *shandle) +{ + if (shandle->fd >= 0) + nm_close(shandle->fd); +} + +#define nm_auto_socket_handle nm_auto(_nm_auto_socket_handle) + +/*****************************************************************************/ + +typedef enum { + IOCTL_CALL_DATA_TYPE_NONE, + IOCTL_CALL_DATA_TYPE_IFRDATA, + IOCTL_CALL_DATA_TYPE_IFRU, +} IoctlCallDataType; + +static int +_ioctl_call(const char *log_ioctl_type, + const char *log_subtype, + unsigned long int ioctl_request, + int ifindex, + int *inout_fd, + char *inout_ifname, + IoctlCallDataType edata_type, + gpointer edata, + gsize edata_size, + struct ifreq *out_ifreq) +{ + nm_auto_close int fd_close = -1; + int fd; + int r; + gpointer edata_backup = NULL; + gs_free gpointer edata_backup_free = NULL; + guint try_count; + char known_ifnames[2][IFNAMSIZ]; + const char *failure_reason = NULL; + struct ifreq ifr; + + nm_assert(ifindex > 0); + nm_assert(NM_IN_SET(edata_type, + IOCTL_CALL_DATA_TYPE_NONE, + IOCTL_CALL_DATA_TYPE_IFRDATA, + IOCTL_CALL_DATA_TYPE_IFRU)); + nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_NONE || edata_size == 0); + nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRDATA || edata_size > 0); + nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRU + || (edata_size > 0 && edata_size <= sizeof(ifr.ifr_ifru))); + nm_assert(edata_size == 0 || edata); + + /* open a file descriptor (or use the one provided). */ + if (inout_fd && *inout_fd >= 0) + fd = *inout_fd; + else { + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + r = -NM_ERRNO_NATIVE(errno); + failure_reason = "failed creating socket or ioctl"; + goto out; + } + if (inout_fd) + *inout_fd = fd; + else + fd_close = fd; + } + + /* resolve the ifindex to name (or use the one provided). */ + if (inout_ifname && inout_ifname[0]) + nm_utils_ifname_cpy(known_ifnames[0], inout_ifname); + else { + if (!nmp_utils_if_indextoname(ifindex, known_ifnames[0])) { + failure_reason = "cannot resolve ifindex"; + r = -ENODEV; + goto out; + } + if (inout_ifname) + nm_utils_ifname_cpy(inout_ifname, known_ifnames[0]); + } + + /* we might need to retry the request. Backup edata so that we can + * restore it on retry. */ + if (edata_size > 0) + edata_backup = nm_memdup_maybe_a(500, edata, edata_size, &edata_backup_free); + + try_count = 0; + +again: +{ + const char *ifname = known_ifnames[try_count % 2]; + + nm_assert(ifindex > 0); + nm_assert(ifname && nm_utils_ifname_valid_kernel(ifname, NULL)); + nm_assert(fd >= 0); + + memset(&ifr, 0, sizeof(ifr)); + nm_utils_ifname_cpy(ifr.ifr_name, ifname); + if (edata_type == IOCTL_CALL_DATA_TYPE_IFRDATA) + ifr.ifr_data = edata; + else if (edata_type == IOCTL_CALL_DATA_TYPE_IFRU) + memcpy(&ifr.ifr_ifru, edata, NM_MIN(edata_size, sizeof(ifr.ifr_ifru))); + + if (ioctl(fd, ioctl_request, &ifr) < 0) { + r = -NM_ERRNO_NATIVE(errno); + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s, %s: failed: %s", + log_ioctl_type, + ifindex, + log_subtype, + ifname, + nm_strerror_native(-r)); + } else { + r = 0; + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s, %s: success", + log_ioctl_type, + ifindex, + log_subtype, + ifname); + } +} + + try_count++; + + /* resolve the name again to see whether the ifindex still has the same name. */ + if (!nmp_utils_if_indextoname(ifindex, known_ifnames[try_count % 2])) { + /* we could not find the ifindex again. Probably the device just got + * removed. + * + * In both cases we return the error code we got from ioctl above. + * Either it failed because the device was gone already or it still + * managed to complete the call. In both cases, the error code is good. */ + failure_reason = + "cannot resolve ifindex after ioctl call. Probably the device was just removed"; + goto out; + } + + /* check whether the ifname changed in the meantime. If yes, would render the result + * invalid. Note that this cannot detect every race regarding renames, for example: + * + * - if_indextoname(#10) gives eth0 + * - rename(#10) => eth0_tmp + * - rename(#11) => eth0 + * - ioctl(eth0) (wrongly fetching #11, formerly eth1) + * - rename(#11) => eth_something + * - rename(#10) => eth0 + * - if_indextoname(#10) gives eth0 + */ + if (!nm_streq(known_ifnames[0], known_ifnames[1])) { + gboolean retry; + + /* we detected a possible(!) rename. + * + * For getters it's straight forward to just retry the call. + * + * For setters we also always retry. If our previous call operated on the right device, + * calling it again should have no bad effect (just setting the same thing more than once). + * + * The only potential bad thing is if there was a race involving swapping names, and we just + * set the ioctl option on the wrong device. But then the bad thing already happenned and + * we cannot detect it (nor do anything about it). At least, we can retry and set the + * option on the right interface. */ + retry = (try_count < 5); + + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s: rename detected from \"%s\" to \"%s\". %s", + log_ioctl_type, + ifindex, + log_subtype, + known_ifnames[(try_count - 1) % 2], + known_ifnames[try_count % 2], + retry ? "Retry" : "No retry"); + if (inout_ifname) + nm_utils_ifname_cpy(inout_ifname, known_ifnames[try_count % 2]); + if (retry) { + if (edata_size > 0) + memcpy(edata, edata_backup, edata_size); + goto again; + } + } + +out: + if (failure_reason) { + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s: %s: %s", + log_ioctl_type, + ifindex, + log_subtype, + failure_reason, + r < 0 ? nm_strerror_native(-r) : "assume success"); + } + if (r >= 0) + NM_SET_OUT(out_ifreq, ifr); + return r; +} + +/****************************************************************************** + * ethtool + *****************************************************************************/ + +static NM_UTILS_ENUM2STR_DEFINE(_ethtool_cmd_to_string, + guint32, + NM_UTILS_ENUM2STR(ETHTOOL_GCOALESCE, "ETHTOOL_GCOALESCE"), + NM_UTILS_ENUM2STR(ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO"), + NM_UTILS_ENUM2STR(ETHTOOL_GFEATURES, "ETHTOOL_GFEATURES"), + NM_UTILS_ENUM2STR(ETHTOOL_GLINK, "ETHTOOL_GLINK"), + NM_UTILS_ENUM2STR(ETHTOOL_GLINKSETTINGS, "ETHTOOL_GLINKSETTINGS"), + NM_UTILS_ENUM2STR(ETHTOOL_GPERMADDR, "ETHTOOL_GPERMADDR"), + NM_UTILS_ENUM2STR(ETHTOOL_GRINGPARAM, "ETHTOOL_GRINGPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_GPAUSEPARAM, "ETHTOOL_GPAUSEPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_GSET, "ETHTOOL_GSET"), + NM_UTILS_ENUM2STR(ETHTOOL_GSSET_INFO, "ETHTOOL_GSSET_INFO"), + NM_UTILS_ENUM2STR(ETHTOOL_GSTATS, "ETHTOOL_GSTATS"), + NM_UTILS_ENUM2STR(ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"), + NM_UTILS_ENUM2STR(ETHTOOL_GWOL, "ETHTOOL_GWOL"), + NM_UTILS_ENUM2STR(ETHTOOL_SCOALESCE, "ETHTOOL_SCOALESCE"), + NM_UTILS_ENUM2STR(ETHTOOL_SFEATURES, "ETHTOOL_SFEATURES"), + NM_UTILS_ENUM2STR(ETHTOOL_SLINKSETTINGS, "ETHTOOL_SLINKSETTINGS"), + NM_UTILS_ENUM2STR(ETHTOOL_SRINGPARAM, "ETHTOOL_SRINGPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_SPAUSEPARAM, "ETHTOOL_SPAUSEPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_SSET, "ETHTOOL_SSET"), + NM_UTILS_ENUM2STR(ETHTOOL_SWOL, "ETHTOOL_SWOL"), ); + +static const char * +_ethtool_edata_to_string(gpointer edata, gsize edata_size, char *sbuf, gsize sbuf_len) +{ + nm_assert(edata); + nm_assert(edata_size >= sizeof(guint32)); + nm_assert((((intptr_t) edata) % _nm_alignof(guint32)) == 0); + + return _ethtool_cmd_to_string(*((guint32 *) edata), sbuf, sbuf_len); +} + +/*****************************************************************************/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) +#define ethtool_cmd_speed(pedata) ((pedata)->speed) + +#define ethtool_cmd_speed_set(pedata, speed) \ + G_STMT_START \ + { \ + (pedata)->speed = (guint16) (speed); \ + } \ + G_STMT_END +#endif + +static int +_ethtool_call_handle(SocketHandle *shandle, gpointer edata, gsize edata_size) +{ + char sbuf[50]; + + return _ioctl_call("ethtool", + _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)), + SIOCETHTOOL, + shandle->ifindex, + &shandle->fd, + shandle->ifname, + IOCTL_CALL_DATA_TYPE_IFRDATA, + edata, + edata_size, + NULL); +} + +static int +_ethtool_call_once(int ifindex, gpointer edata, gsize edata_size) +{ + char sbuf[50]; + + return _ioctl_call("ethtool", + _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)), + SIOCETHTOOL, + ifindex, + NULL, + NULL, + IOCTL_CALL_DATA_TYPE_IFRDATA, + edata, + edata_size, + NULL); +} + +/*****************************************************************************/ + +static struct ethtool_gstrings * +ethtool_get_stringset(SocketHandle *shandle, int stringset_id) +{ + struct { + struct ethtool_sset_info info; + guint32 sentinel; + } sset_info = { + .info.cmd = ETHTOOL_GSSET_INFO, + .info.reserved = 0, + .info.sset_mask = (1ULL << stringset_id), + }; + const guint32 *pdata; + gs_free struct ethtool_gstrings *gstrings = NULL; + gsize gstrings_len; + guint32 i, len; + + if (_ethtool_call_handle(shandle, &sset_info, sizeof(sset_info)) < 0) + return NULL; + if (!sset_info.info.sset_mask) + return NULL; + + pdata = (guint32 *) sset_info.info.data; + + len = *pdata; + + gstrings_len = sizeof(*gstrings) + (len * ETH_GSTRING_LEN); + gstrings = g_malloc0(gstrings_len); + gstrings->cmd = ETHTOOL_GSTRINGS; + gstrings->string_set = stringset_id; + gstrings->len = len; + if (gstrings->len > 0) { + if (_ethtool_call_handle(shandle, gstrings, gstrings_len) < 0) + return NULL; + for (i = 0; i < gstrings->len; i++) { + /* ensure NUL terminated */ + gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0'; + } + } + + return g_steal_pointer(&gstrings); +} + +static int +ethtool_gstrings_find(const struct ethtool_gstrings *gstrings, const char *needle) +{ + guint32 i; + + /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. + * that means, we cannot possibly request longer names. */ + nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN); + + for (i = 0; i < gstrings->len; i++) { + if (nm_streq((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle)) + return i; + } + return -1; +} + +static int +ethtool_get_stringset_index(SocketHandle *shandle, int stringset_id, const char *needle) +{ + gs_free struct ethtool_gstrings *gstrings = NULL; + + /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. + * that means, we cannot possibly request longer names. */ + nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN); + + gstrings = ethtool_get_stringset(shandle, stringset_id); + if (gstrings) + return ethtool_gstrings_find(gstrings, needle); + return -1; +} + +/*****************************************************************************/ + +static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = { +#define ETHT_FEAT(eid, ...) \ + { \ + .ethtool_id = eid, \ + .n_kernel_names = NM_NARG(__VA_ARGS__), \ + .kernel_names = ((const char *const[]) {__VA_ARGS__}), \ + } + + /* the order does only matter for one thing: if it happens that more than one NMEthtoolID + * reference the same kernel-name, then the one that is mentioned *later* will win in + * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which + * refer to multiple features ("feature-tso"), while also having more specific ids + * ("feature-tx-tcp-segmentation"). */ + + /* names from ethtool utility, which are aliases for multiple features. */ + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_SG, "tx-scatter-gather", "tx-scatter-gather-fraglist"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TSO, + "tx-tcp-segmentation", + "tx-tcp-ecn-segmentation", + "tx-tcp-mangleid-segmentation", + "tx-tcp6-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX, + "tx-checksum-ipv4", + "tx-checksum-ip-generic", + "tx-checksum-ipv6", + "tx-checksum-fcoe-crc", + "tx-checksum-sctp"), + + /* names from ethtool utility, which are aliases for one feature. */ + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"), + + /* names of features, as known by kernel. */ + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD, "macsec-hw-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST, "rx-gro-list"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING, "rx-udp-gro-forwarding"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD, "tls-hw-rx-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST, "tx-gso-list"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION, + "tx-tunnel-remcsum-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"), +}; + +/* the number of kernel features that we handle. It essentially is the sum of all + * kernel_names. So, all ethtool-ids that reference exactly one kernel-name + * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases + * for multiple kernel-names. */ +#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u) + +static void +_ASSERT_ethtool_feature_infos(void) +{ +#if NM_MORE_ASSERTS > 10 + guint i, k, n; + bool found[_NM_ETHTOOL_ID_FEATURE_NUM] = {}; + + G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM); + + n = 0; + for (i = 0; i < G_N_ELEMENTS(_ethtool_feature_infos); i++) { + NMEthtoolFeatureState kstate; + const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i]; + + g_assert(inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST); + g_assert(inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST); + g_assert(inf->n_kernel_names > 0); + + for (k = 0; k < i; k++) + g_assert(inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id); + + g_assert(!found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)]); + found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)] = TRUE; + + kstate.idx_kernel_name = inf->n_kernel_names - 1; + g_assert((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1)); + + n += inf->n_kernel_names; + for (k = 0; k < inf->n_kernel_names; k++) { + const char *name = inf->kernel_names[k]; + + g_assert(!nm_strv_contains(inf->kernel_names, k, name)); + + /* these offload features are only informational and cannot be set from user-space + * (NETIF_F_NEVER_CHANGE). We should not track them in _ethtool_feature_infos. */ + g_assert(!nm_streq(name, "netns-local")); + g_assert(!nm_streq(name, "tx-lockless")); + g_assert(!nm_streq(name, "vlan-challenged")); + } + } + + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) + g_assert(found[i]); + + g_assert(n == N_ETHTOOL_KERNEL_FEATURES); +#endif +} + +static NMEthtoolFeatureStates * +ethtool_get_features(SocketHandle *shandle) +{ + gs_free NMEthtoolFeatureStates *states = NULL; + gs_free struct ethtool_gstrings *ss_features = NULL; + + _ASSERT_ethtool_feature_infos(); + + ss_features = ethtool_get_stringset(shandle, ETH_SS_FEATURES); + if (!ss_features) + return NULL; + + if (ss_features->len > 0) { + gs_free struct ethtool_gfeatures *gfeatures_free = NULL; + struct ethtool_gfeatures *gfeatures; + gsize gfeatures_len; + guint idx; + const NMEthtoolFeatureState *states_list0 = NULL; + const NMEthtoolFeatureState *const *states_plist0 = NULL; + guint states_plist_n = 0; + + gfeatures_len = sizeof(struct ethtool_gfeatures) + + (NM_DIV_ROUND_UP(ss_features->len, 32u) * sizeof(gfeatures->features[0])); + gfeatures = nm_malloc0_maybe_a(300, gfeatures_len, &gfeatures_free); + gfeatures->cmd = ETHTOOL_GFEATURES; + gfeatures->size = NM_DIV_ROUND_UP(ss_features->len, 32u); + if (_ethtool_call_handle(shandle, gfeatures, gfeatures_len) < 0) + return NULL; + + for (idx = 0; idx < G_N_ELEMENTS(_ethtool_feature_infos); idx++) { + const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx]; + guint idx_kernel_name; + + for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) { + NMEthtoolFeatureState *kstate; + const char *kernel_name = info->kernel_names[idx_kernel_name]; + int i_feature; + guint i_block; + guint32 i_flag; + + i_feature = ethtool_gstrings_find(ss_features, kernel_name); + if (i_feature < 0) + continue; + + i_block = ((guint) i_feature) / 32u; + i_flag = (guint32) (1u << (((guint) i_feature) % 32u)); + + if (!states) { + states = g_malloc0( + sizeof(NMEthtoolFeatureStates) + + (N_ETHTOOL_KERNEL_FEATURES * sizeof(NMEthtoolFeatureState)) + + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)) + * sizeof(NMEthtoolFeatureState *))); + states_list0 = &states->states_list[0]; + states_plist0 = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES]; + states->n_ss_features = ss_features->len; + } + + nm_assert(states->n_states < N_ETHTOOL_KERNEL_FEATURES); + kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states]; + states->n_states++; + + kstate->info = info; + kstate->idx_ss_features = i_feature; + kstate->idx_kernel_name = idx_kernel_name; + kstate->available = !!(gfeatures->features[i_block].available & i_flag); + kstate->requested = !!(gfeatures->features[i_block].requested & i_flag); + kstate->active = !!(gfeatures->features[i_block].active & i_flag); + kstate->never_changed = !!(gfeatures->features[i_block].never_changed & i_flag); + + nm_assert(states_plist_n + < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)); + + if (!states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) + states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)] = + &states_plist0[states_plist_n]; + ((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate; + states_plist_n++; + } + + if (states && states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) { + nm_assert(states_plist_n + < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)); + nm_assert(!states_plist0[states_plist_n]); + states_plist_n++; + } + } + } + + return g_steal_pointer(&states); +} + +NMEthtoolFeatureStates * +nmp_ethtool_ioctl_get_features(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + NMEthtoolFeatureStates *features; + + g_return_val_if_fail(ifindex > 0, 0); + + features = ethtool_get_features(&shandle); + + if (!features) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting features", + ifindex, + "get-features"); + return NULL; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel features", + ifindex, + "get-features"); + return features; +} + +static const char * +_ethtool_feature_state_to_string(char *buf, + gsize buf_size, + const NMEthtoolFeatureState *s, + const char *prefix) +{ + int l; + + l = g_snprintf(buf, + buf_size, + "%s %s%s", + prefix ?: "", + ONOFF(s->active), + (!s->available || s->never_changed) + ? ", [fixed]" + : ((s->requested != s->active) + ? (s->requested ? ", [requested on]" : ", [requested off]") + : "")); + nm_assert(l < buf_size); + return buf; +} + +gboolean +nmp_ethtool_ioctl_set_features( + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + gs_free struct ethtool_sfeatures *sfeatures_free = NULL; + struct ethtool_sfeatures *sfeatures; + gsize sfeatures_len; + int r; + guint i, j; + struct { + const NMEthtoolFeatureState *f_state; + NMOptionBool requested; + } set_states[N_ETHTOOL_KERNEL_FEATURES]; + guint set_states_n = 0; + gboolean success = TRUE; + + g_return_val_if_fail(ifindex > 0, 0); + g_return_val_if_fail(features, 0); + g_return_val_if_fail(requested, 0); + + nm_assert(features->n_states <= N_ETHTOOL_KERNEL_FEATURES); + + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) { + const NMEthtoolFeatureState *const *states_indexed; + + if (requested[i] == NM_OPTION_BOOL_DEFAULT) + continue; + + if (!(states_indexed = features->states_indexed[i])) { + if (do_set) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: set feature %s: skip (not found)", + ifindex, + "set-features", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname); + success = FALSE; + } + continue; + } + + for (j = 0; states_indexed[j]; j++) { + const NMEthtoolFeatureState *s = states_indexed[j]; + char sbuf[255]; + + if (set_states_n >= G_N_ELEMENTS(set_states)) + g_return_val_if_reached(FALSE); + + if (s->never_changed) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as " + "never changed)", + ifindex, + "set-features", + do_set ? "set" : "reset", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + s->info->kernel_names[s->idx_kernel_name], + ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active), + _ethtool_feature_state_to_string(sbuf, + sizeof(sbuf), + s, + do_set ? " currently:" : " before:")); + continue; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: %s feature %s (%s): %s, %s", + ifindex, + "set-features", + do_set ? "set" : "reset", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + s->info->kernel_names[s->idx_kernel_name], + ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active), + _ethtool_feature_state_to_string(sbuf, + sizeof(sbuf), + s, + do_set ? " currently:" : " before:")); + + if (do_set && (!s->available || s->never_changed) + && (s->active != (requested[i] == NM_OPTION_BOOL_TRUE))) { + /* we request to change a flag which kernel reported as fixed. + * While the ethtool operation will silently succeed, mark the request + * as failure. */ + success = FALSE; + } + + set_states[set_states_n].f_state = s; + set_states[set_states_n].requested = requested[i]; + set_states_n++; + } + } + + if (set_states_n == 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: no feature requested", + ifindex, + "set-features"); + return TRUE; + } + + sfeatures_len = + sizeof(struct ethtool_sfeatures) + + (NM_DIV_ROUND_UP(features->n_ss_features, 32U) * sizeof(sfeatures->features[0])); + sfeatures = nm_malloc0_maybe_a(300, sfeatures_len, &sfeatures_free); + sfeatures->cmd = ETHTOOL_SFEATURES; + sfeatures->size = NM_DIV_ROUND_UP(features->n_ss_features, 32U); + + for (i = 0; i < set_states_n; i++) { + const NMEthtoolFeatureState *s = set_states[i].f_state; + guint i_block; + guint32 i_flag; + gboolean is_requested; + + i_block = s->idx_ss_features / 32u; + i_flag = (guint32) (1u << (s->idx_ss_features % 32u)); + + sfeatures->features[i_block].valid |= i_flag; + + if (do_set) + is_requested = (set_states[i].requested == NM_OPTION_BOOL_TRUE); + else + is_requested = s->active; + + if (is_requested) + sfeatures->features[i_block].requested |= i_flag; + else + sfeatures->features[i_block].requested &= ~i_flag; + } + + r = _ethtool_call_handle(&shandle, sfeatures, sfeatures_len); + if (r < 0) { + success = FALSE; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting features (%s)", + ifindex, + "set-features", + nm_strerror_native(-r)); + return FALSE; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: %s", + ifindex, + "set-features", + success ? "successfully setting features" + : "at least some of the features were not successfully set"); + return success; +} + +gboolean +nmp_ethtool_ioctl_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce) +{ + struct ethtool_coalesce eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(coalesce, FALSE); + + eth_data.cmd = ETHTOOL_GCOALESCE; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting coalesce settings", + ifindex, + "get-coalesce"); + return FALSE; + } + + *coalesce = (NMEthtoolCoalesceState) { + .s = { + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)] = + eth_data.rx_coalesce_usecs, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)] = + eth_data.rx_max_coalesced_frames, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)] = + eth_data.rx_coalesce_usecs_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)] = + eth_data.rx_max_coalesced_frames_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)] = + eth_data.tx_coalesce_usecs, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)] = + eth_data.tx_max_coalesced_frames, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)] = + eth_data.tx_coalesce_usecs_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)] = + eth_data.tx_max_coalesced_frames_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)] = + eth_data.stats_block_coalesce_usecs, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)] = + eth_data.use_adaptive_rx_coalesce, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)] = + eth_data.use_adaptive_tx_coalesce, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)] = + eth_data.pkt_rate_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)] = + eth_data.rx_coalesce_usecs_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)] = + eth_data.rx_max_coalesced_frames_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)] = + eth_data.tx_coalesce_usecs_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)] = + eth_data.tx_max_coalesced_frames_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)] = + eth_data.pkt_rate_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)] = + eth_data.rx_coalesce_usecs_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)] = + eth_data.rx_max_coalesced_frames_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)] = + eth_data.tx_coalesce_usecs_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)] = + eth_data.tx_max_coalesced_frames_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)] = + eth_data.rate_sample_interval, + }}; + return TRUE; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel coalesce settings", + ifindex, + "get-coalesce"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce) +{ + struct ethtool_coalesce eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(coalesce, FALSE); + + eth_data = (struct ethtool_coalesce) { + .cmd = ETHTOOL_SCOALESCE, + .rx_coalesce_usecs = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)], + .rx_max_coalesced_frames = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)], + .rx_coalesce_usecs_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)], + .rx_max_coalesced_frames_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)], + .tx_coalesce_usecs = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)], + .tx_max_coalesced_frames = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)], + .tx_coalesce_usecs_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)], + .tx_max_coalesced_frames_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)], + .stats_block_coalesce_usecs = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)], + .use_adaptive_rx_coalesce = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)], + .use_adaptive_tx_coalesce = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)], + .pkt_rate_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)], + .rx_coalesce_usecs_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)], + .rx_max_coalesced_frames_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)], + .tx_coalesce_usecs_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)], + .tx_max_coalesced_frames_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)], + .pkt_rate_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)], + .rx_coalesce_usecs_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)], + .rx_max_coalesced_frames_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)], + .tx_coalesce_usecs_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)], + .tx_max_coalesced_frames_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)], + .rate_sample_interval = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)], + }; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting coalesce settings", + ifindex, + "set-coalesce"); + return FALSE; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: set kernel coalesce settings", + ifindex, + "set-coalesce"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_get_ring(int ifindex, NMEthtoolRingState *ring) +{ + struct ethtool_ringparam eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ring, FALSE); + + eth_data.cmd = ETHTOOL_GRINGPARAM; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting ring settings", + ifindex, + "get-ring"); + return FALSE; + } + + *ring = (NMEthtoolRingState) { + .rx_pending = eth_data.rx_pending, + .rx_jumbo_pending = eth_data.rx_jumbo_pending, + .rx_mini_pending = eth_data.rx_mini_pending, + .tx_pending = eth_data.tx_pending, + }; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel ring settings", + ifindex, + "get-ring"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_set_ring(int ifindex, const NMEthtoolRingState *ring) +{ + struct ethtool_ringparam eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ring, FALSE); + + eth_data = (struct ethtool_ringparam) { + .cmd = ETHTOOL_SRINGPARAM, + .rx_pending = ring->rx_pending, + .rx_jumbo_pending = ring->rx_jumbo_pending, + .rx_mini_pending = ring->rx_mini_pending, + .tx_pending = ring->tx_pending, + }; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting ring settings", + ifindex, + "set-ring"); + return FALSE; + } + + nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel ring settings", ifindex, "set-ring"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_get_channels(int ifindex, NMEthtoolChannelsState *channels) +{ + struct ethtool_channels eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(channels, FALSE); + + eth_data.cmd = ETHTOOL_GCHANNELS; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting channels settings", + ifindex, + "get-channels"); + return FALSE; + } + + *channels = (NMEthtoolChannelsState) { + .rx = eth_data.rx_count, + .tx = eth_data.tx_count, + .other = eth_data.other_count, + .combined = eth_data.combined_count, + }; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel channels settings", + ifindex, + "get-channels"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_set_channels(int ifindex, const NMEthtoolChannelsState *channels) +{ + struct ethtool_channels eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(channels, FALSE); + + eth_data = (struct ethtool_channels) { + .cmd = ETHTOOL_SCHANNELS, + .rx_count = channels->rx, + .tx_count = channels->tx, + .other_count = channels->other, + .combined_count = channels->combined, + }; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting channels settings", + ifindex, + "set-channels"); + return FALSE; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: set kernel channels settings", + ifindex, + "set-channels"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_get_pause(int ifindex, NMEthtoolPauseState *pause) +{ + struct ethtool_pauseparam eth_data; + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(pause, FALSE); + + eth_data.cmd = ETHTOOL_GPAUSEPARAM; + if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting pause settings", + ifindex, + "get-pause"); + return FALSE; + } + + *pause = (NMEthtoolPauseState) { + .autoneg = eth_data.autoneg == 1, + .rx = eth_data.rx_pause == 1, + .tx = eth_data.tx_pause == 1, + }; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel pause settings", + ifindex, + "get-pause"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_set_pause(int ifindex, const NMEthtoolPauseState *pause) +{ + struct ethtool_pauseparam eth_data; + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(pause, FALSE); + + eth_data = (struct ethtool_pauseparam) { + .cmd = ETHTOOL_SPAUSEPARAM, + .autoneg = pause->autoneg ? 1 : 0, + .rx_pause = pause->rx ? 1 : 0, + .tx_pause = pause->tx ? 1 : 0, + }; + + if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting pause settings", + ifindex, + "set-pause"); + return FALSE; + } + nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel puase settings", ifindex, "set-pause"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_get_eee(int ifindex, NMEthtoolEEEState *eee) +{ + struct ethtool_eee eth_data; + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(eee, FALSE); + + eth_data.cmd = ETHTOOL_GEEE; + if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting eee settings", + ifindex, + "get-eee"); + return FALSE; + } + + *eee = (NMEthtoolEEEState) { + .enabled = eth_data.eee_enabled == 1, + }; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel eee settings", + ifindex, + "get-eee"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_set_eee(int ifindex, const NMEthtoolEEEState *eee) +{ + struct ethtool_eee eth_data; + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(eee, FALSE); + + eth_data.cmd = ETHTOOL_GEEE; + if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting eee settings", + ifindex, + "get-eee"); + return FALSE; + } + + eth_data.cmd = ETHTOOL_SEEE, eth_data.eee_enabled = eee->enabled ? 1 : 0; + + if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting eee settings", + ifindex, + "set-eee"); + return FALSE; + } + nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel eee settings", ifindex, "set-eee"); + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_get_fec_mode(int ifindex, uint32_t *fec_mode) +{ + int r; + struct ethtool_fecparam fec_param = { + .cmd = ETHTOOL_GFECPARAM, + .fec = 0, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (_ethtool_call_once(ifindex, &fec_param, sizeof(fec_param)) >= 0) { + nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: get FEC options 0x%x", ifindex, fec_param.fec); + *fec_mode = fec_param.fec; + return TRUE; + } else { + r = -NM_ERRNO_NATIVE(errno); + nm_log_dbg(LOGD_PLATFORM, + "ethtool[%d]: ETHTOOL_GFECPARAM failure get fec mode: (%s)", + ifindex, + nm_strerror_native(-r)); + return FALSE; + } +} + +gboolean +nmp_ethtool_ioctl_set_fec_mode(int ifindex, uint32_t fec_mode) +{ + struct ethtool_fecparam fec_param = { + .cmd = ETHTOOL_SFECPARAM, + .fec = fec_mode, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: setting FEC options 0x%x", ifindex, fec_mode); + + return _ethtool_call_once(ifindex, &fec_param, sizeof(fec_param)) >= 0; +} + +/*****************************************************************************/ + +gboolean +nmp_ethtool_ioctl_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data) +{ + struct ethtool_drvinfo *drvinfo; + + G_STATIC_ASSERT_EXPR(sizeof(*data) == sizeof(*drvinfo)); + G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, driver) + == offsetof(struct ethtool_drvinfo, driver)); + G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, version) + == offsetof(struct ethtool_drvinfo, version)); + G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, fw_version) + == offsetof(struct ethtool_drvinfo, fw_version)); + G_STATIC_ASSERT_EXPR(sizeof(data->driver) == sizeof(drvinfo->driver)); + G_STATIC_ASSERT_EXPR(sizeof(data->version) == sizeof(drvinfo->version)); + G_STATIC_ASSERT_EXPR(sizeof(data->fw_version) == sizeof(drvinfo->fw_version)); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(data, FALSE); + + drvinfo = (struct ethtool_drvinfo *) data; + *drvinfo = (struct ethtool_drvinfo) { + .cmd = ETHTOOL_GDRVINFO, + }; + return _ethtool_call_once(ifindex, drvinfo, sizeof(*drvinfo)) >= 0; +} + +gboolean +nmp_ethtool_ioctl_get_permanent_address(int ifindex, guint8 *buf, size_t *length) +{ + struct { + struct ethtool_perm_addr e; + guint8 _extra_data[_NM_UTILS_HWADDR_LEN_MAX + 1]; + } edata = { + .e.cmd = ETHTOOL_GPERMADDR, + .e.size = _NM_UTILS_HWADDR_LEN_MAX, + }; + const guint8 *pdata; + + guint i; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0) + return FALSE; + + if (edata.e.size > _NM_UTILS_HWADDR_LEN_MAX) + return FALSE; + if (edata.e.size < 1) + return FALSE; + + pdata = (const guint8 *) edata.e.data; + + if (NM_IN_SET(pdata[0], 0, 0xFF)) { + /* Some drivers might return a permanent address of all zeros. + * Reject that (rh#1264024) + * + * Some drivers return a permanent address of all ones. Reject that too */ + for (i = 1; i < edata.e.size; i++) { + if (pdata[0] != pdata[i]) + goto not_all_0or1; + } + return FALSE; + } + +not_all_0or1: + memcpy(buf, pdata, edata.e.size); + *length = edata.e.size; + return TRUE; +} + +gboolean +nmp_ethtool_ioctl_supports_carrier_detect(int ifindex) +{ + struct ethtool_cmd edata = {.cmd = ETHTOOL_GLINK}; + + g_return_val_if_fail(ifindex > 0, FALSE); + + /* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we + * assume the device supports carrier-detect, otherwise we assume it + * doesn't. + */ + return _ethtool_call_once(ifindex, &edata, sizeof(edata)) >= 0; +} + +gboolean +nmp_ethtool_ioctl_supports_vlans(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + gs_free struct ethtool_gfeatures *features_free = NULL; + struct ethtool_gfeatures *features; + gsize features_len; + int idx, block, bit, size; + + g_return_val_if_fail(ifindex > 0, FALSE); + + idx = ethtool_get_stringset_index(&shandle, ETH_SS_FEATURES, "vlan-challenged"); + if (idx < 0) { + nm_log_dbg(LOGD_PLATFORM, + "ethtool[%d]: vlan-challenged ethtool feature does not exist?", + ifindex); + return FALSE; + } + + block = idx / 32; + bit = idx % 32; + size = block + 1; + + features_len = sizeof(*features) + (size * sizeof(struct ethtool_get_features_block)); + features = nm_malloc0_maybe_a(300, features_len, &features_free); + features->cmd = ETHTOOL_GFEATURES; + features->size = size; + + if (_ethtool_call_handle(&shandle, features, features_len) < 0) + return FALSE; + + return !(features->features[block].active & (1 << bit)); +} + +int +nmp_ethtool_ioctl_get_peer_ifindex(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + gsize stats_len; + gs_free struct ethtool_stats *stats_free = NULL; + struct ethtool_stats *stats; + int peer_ifindex_stat; + + g_return_val_if_fail(ifindex > 0, 0); + + peer_ifindex_stat = ethtool_get_stringset_index(&shandle, ETH_SS_STATS, "peer_ifindex"); + if (peer_ifindex_stat < 0) { + nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex); + return FALSE; + } + + stats_len = sizeof(*stats) + (peer_ifindex_stat + 1) * sizeof(guint64); + stats = nm_malloc0_maybe_a(300, stats_len, &stats_free); + stats->cmd = ETHTOOL_GSTATS; + stats->n_stats = peer_ifindex_stat + 1; + if (_ethtool_call_handle(&shandle, stats, stats_len) < 0) + return 0; + + return stats->data[peer_ifindex_stat]; +} + +gboolean +nmp_ethtool_ioctl_get_wake_on_lan(int ifindex) +{ + struct ethtool_wolinfo wol = { + .cmd = ETHTOOL_GWOL, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (_ethtool_call_once(ifindex, &wol, sizeof(wol)) < 0) + return FALSE; + + return wol.wolopts != 0; +} + +gboolean +nmp_ethtool_ioctl_set_wake_on_lan(int ifindex, + _NMSettingWiredWakeOnLan wol, + const char *wol_password) +{ + struct ethtool_wolinfo wol_info = { + .cmd = ETHTOOL_SWOL, + .wolopts = 0, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (wol == _NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE) + return TRUE; + + nm_log_dbg(LOGD_PLATFORM, + "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'", + ifindex, + (unsigned) wol, + wol_password); + + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_PHY)) + wol_info.wolopts |= WAKE_PHY; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST)) + wol_info.wolopts |= WAKE_UCAST; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST)) + wol_info.wolopts |= WAKE_MCAST; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST)) + wol_info.wolopts |= WAKE_BCAST; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_ARP)) + wol_info.wolopts |= WAKE_ARP; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) + wol_info.wolopts |= WAKE_MAGIC; + + if (wol_password) { + if (!_nm_utils_hwaddr_aton_exact(wol_password, wol_info.sopass, ETH_ALEN)) { + nm_log_dbg(LOGD_PLATFORM, + "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'", + ifindex, + wol_password); + return FALSE; + } + wol_info.wolopts |= WAKE_MAGICSECURE; + } + + return _ethtool_call_once(ifindex, &wol_info, sizeof(wol_info)) >= 0; +} + +/*****************************************************************************/ + +gboolean +nmp_ethtool_ioctl_get_link_settings(int ifindex, + gboolean *out_autoneg, + guint32 *out_speed, + NMPlatformLinkDuplexType *out_duplex) +{ + struct ethtool_cmd edata = { + .cmd = ETHTOOL_GSET, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0) + return FALSE; + + NM_SET_OUT(out_autoneg, (edata.autoneg == AUTONEG_ENABLE)); + + if (out_speed) { + guint32 speed; + + speed = ethtool_cmd_speed(&edata); + if (speed == G_MAXUINT16 || speed == G_MAXUINT32) + speed = 0; + + *out_speed = speed; + } + + if (out_duplex) { + switch (edata.duplex) { + case DUPLEX_HALF: + *out_duplex = NM_PLATFORM_LINK_DUPLEX_HALF; + break; + case DUPLEX_FULL: + *out_duplex = NM_PLATFORM_LINK_DUPLEX_FULL; + break; + default: /* DUPLEX_UNKNOWN */ + *out_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN; + break; + } + } + + return TRUE; +} + +#define ADVERTISED_INVALID 0 + +static guint32 +get_baset_mode(guint32 speed, NMPlatformLinkDuplexType duplex) +{ + if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN) + return ADVERTISED_INVALID; + + if (duplex == NM_PLATFORM_LINK_DUPLEX_HALF) { + switch (speed) { + case 10: + return ADVERTISED_10baseT_Half; + case 100: + return ADVERTISED_100baseT_Half; + case 1000: + return ADVERTISED_1000baseT_Half; + default: + return ADVERTISED_INVALID; + } + } else { + switch (speed) { + case 10: + return ADVERTISED_10baseT_Full; + case 100: + return ADVERTISED_100baseT_Full; + case 1000: + return ADVERTISED_1000baseT_Full; + case 10000: + return ADVERTISED_10000baseT_Full; + default: + return ADVERTISED_INVALID; + } + } +} + +static gboolean +platform_link_duplex_type_to_native(NMPlatformLinkDuplexType duplex_type, guint8 *out_native) +{ + switch (duplex_type) { + case NM_PLATFORM_LINK_DUPLEX_HALF: + *out_native = DUPLEX_HALF; + return TRUE; + case NM_PLATFORM_LINK_DUPLEX_FULL: + *out_native = DUPLEX_FULL; + return TRUE; + case NM_PLATFORM_LINK_DUPLEX_UNKNOWN: + return FALSE; + default: + g_return_val_if_reached(FALSE); + } +} + +static NMOptionBool +set_link_settings_new(SocketHandle *shandle, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex) +{ + struct ethtool_link_settings edata0; + gs_free struct ethtool_link_settings *edata = NULL; + gsize edata_size; + guint nwords; + guint i; + + edata0 = (struct ethtool_link_settings) { + .cmd = ETHTOOL_GLINKSETTINGS, + .link_mode_masks_nwords = 0, + }; + + /* perform the handshake to find the size of masks */ + if (_ethtool_call_handle(shandle, &edata0, sizeof(edata0)) < 0 + || edata0.link_mode_masks_nwords >= 0) { + /* new API not supported */ + return NM_OPTION_BOOL_DEFAULT; + } + + nwords = -edata0.link_mode_masks_nwords; + edata_size = sizeof(*edata) + sizeof(guint32) * nwords * 3; + edata = g_malloc0(edata_size); + edata->cmd = ETHTOOL_GLINKSETTINGS; + edata->link_mode_masks_nwords = nwords; + + /* retrieve first current settings */ + if (_ethtool_call_handle(shandle, edata, edata_size) < 0) + return FALSE; + + /* then change the needed ones */ + edata->cmd = ETHTOOL_SLINKSETTINGS; + + { + const guint32 *v_map_supported = &edata->link_mode_masks[0]; + guint32 *v_map_advertising = &edata->link_mode_masks[nwords]; + guint32 *v_map_lp_advertising = &edata->link_mode_masks[2 * nwords]; + + memcpy(v_map_advertising, v_map_supported, sizeof(guint32) * nwords); + (void) v_map_lp_advertising; + + if (speed != 0) { + guint32 mode; + + mode = get_baset_mode(speed, duplex); + + if (mode == ADVERTISED_INVALID) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised", + shandle->ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + + if (!(v_map_supported[0] & mode)) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: device does not support %uBASE-T %s duplex mode", + shandle->ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + + for (i = 0; i < (guint) G_N_ELEMENTS(_nmp_link_mode_all_advertised_modes); i++) + v_map_advertising[i] &= ~_nmp_link_mode_all_advertised_modes[i]; + v_map_advertising[0] |= mode; + } + } + +set_autoneg: + if (autoneg) + edata->autoneg = AUTONEG_ENABLE; + else { + edata->autoneg = AUTONEG_DISABLE; + + if (speed) + edata->speed = speed; + + platform_link_duplex_type_to_native(duplex, &edata->duplex); + } + + return _ethtool_call_handle(shandle, edata, edata_size) >= 0; +} + +gboolean +nmp_ethtool_ioctl_set_link_settings(int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + struct ethtool_cmd edata = { + .cmd = ETHTOOL_GSET, + }; + NMOptionBool ret; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail((speed && duplex != NM_PLATFORM_LINK_DUPLEX_UNKNOWN) + || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), + FALSE); + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: set link: autoneg=%d, speed=%d, duplex=%s", + ifindex, + autoneg, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + + ret = set_link_settings_new(&shandle, autoneg, speed, duplex); + if (ret != NM_OPTION_BOOL_DEFAULT) + return ret; + + /* new ETHTOOL_GLINKSETTINGS API not supported, fall back to GSET */ + + /* retrieve first current settings */ + if (_ethtool_call_handle(&shandle, &edata, sizeof(edata)) < 0) + return FALSE; + + /* then change the needed ones */ + edata.cmd = ETHTOOL_SSET; + + edata.advertising = edata.supported; + if (speed != 0) { + guint32 mode; + + mode = get_baset_mode(speed, duplex); + + if (mode == ADVERTISED_INVALID) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised", + ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + if (!(edata.supported & mode)) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: device does not support %uBASE-T %s duplex mode", + ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + edata.advertising &= ~_nmp_link_mode_all_advertised_modes[0]; + edata.advertising |= mode; + } + +set_autoneg: + if (autoneg) + edata.autoneg = AUTONEG_ENABLE; + else { + edata.autoneg = AUTONEG_DISABLE; + + if (speed) + ethtool_cmd_speed_set(&edata, speed); + + platform_link_duplex_type_to_native(duplex, &edata.duplex); + } + + return _ethtool_call_handle(&shandle, &edata, sizeof(edata)) >= 0; +} + +gboolean +nmp_mii_ioctl_supports_carrier_detect(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + int r; + struct ifreq ifr; + struct mii_ioctl_data *mii; + + g_return_val_if_fail(ifindex > 0, FALSE); + + r = _ioctl_call("mii", + "SIOCGMIIPHY", + SIOCGMIIPHY, + shandle.ifindex, + &shandle.fd, + shandle.ifname, + IOCTL_CALL_DATA_TYPE_NONE, + NULL, + 0, + &ifr); + if (r < 0) + return FALSE; + + /* If we can read the BMSR register, we assume that the card supports MII link detection */ + mii = (struct mii_ioctl_data *) &ifr.ifr_ifru; + mii->reg_num = MII_BMSR; + + r = _ioctl_call("mii", + "SIOCGMIIREG", + SIOCGMIIREG, + shandle.ifindex, + &shandle.fd, + shandle.ifname, + IOCTL_CALL_DATA_TYPE_IFRU, + mii, + sizeof(*mii), + &ifr); + if (r < 0) + return FALSE; + + mii = (struct mii_ioctl_data *) &ifr.ifr_ifru; + nm_log_trace(LOGD_PLATFORM, + "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", + ifindex, + shandle.ifname, + mii->val_out); + return TRUE; +} diff --git a/src/libnm-platform/nmp-ethtool-ioctl.h b/src/libnm-platform/nmp-ethtool-ioctl.h new file mode 100644 index 0000000000..534c798240 --- /dev/null +++ b/src/libnm-platform/nmp-ethtool-ioctl.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef __NMP_ETHTOOL_IOCTL_H__ +#define __NMP_ETHTOOL_IOCTL_H__ + +#include "libnm-platform/nmp-base.h" +#include "libnm-platform/nm-netlink.h" + +gboolean nmp_ethtool_ioctl_supports_carrier_detect(int ifindex); + +gboolean nmp_ethtool_ioctl_supports_vlans(int ifindex); + +int nmp_ethtool_ioctl_get_peer_ifindex(int ifindex); + +gboolean nmp_ethtool_ioctl_get_wake_on_lan(int ifindex); + +gboolean nmp_ethtool_ioctl_set_wake_on_lan(int ifindex, + _NMSettingWiredWakeOnLan wol, + const char *wol_password); + +gboolean nmp_ethtool_ioctl_get_link_settings(int ifindex, + gboolean *out_autoneg, + guint32 *out_speed, + NMPlatformLinkDuplexType *out_duplex); +gboolean nmp_ethtool_ioctl_set_link_settings(int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex); + +gboolean nmp_ethtool_ioctl_get_permanent_address(int ifindex, guint8 *buf, size_t *length); + +gboolean nmp_ethtool_ioctl_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data); + +NMEthtoolFeatureStates *nmp_ethtool_ioctl_get_features(int ifindex); + +gboolean nmp_ethtool_ioctl_set_features( + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */); + +gboolean nmp_ethtool_ioctl_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce); + +gboolean nmp_ethtool_ioctl_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce); + +gboolean nmp_ethtool_ioctl_get_ring(int ifindex, NMEthtoolRingState *ring); + +gboolean nmp_ethtool_ioctl_set_ring(int ifindex, const NMEthtoolRingState *ring); + +gboolean nmp_ethtool_ioctl_get_channels(int ifindex, NMEthtoolChannelsState *channels); + +gboolean nmp_ethtool_ioctl_set_channels(int ifindex, const NMEthtoolChannelsState *channels); + +gboolean nmp_ethtool_ioctl_get_pause(int ifindex, NMEthtoolPauseState *pause); + +gboolean nmp_ethtool_ioctl_set_pause(int ifindex, const NMEthtoolPauseState *pause); + +gboolean nmp_ethtool_ioctl_get_eee(int ifindex, NMEthtoolEEEState *eee); + +gboolean nmp_ethtool_ioctl_set_eee(int ifindex, const NMEthtoolEEEState *eee); + +gboolean nmp_ethtool_ioctl_get_fec_mode(int ifindex, uint32_t *fec_mode); + +gboolean nmp_ethtool_ioctl_set_fec_mode(int ifindex, uint32_t fec_mode); + +gboolean nmp_mii_ioctl_supports_carrier_detect(int ifindex); + +#endif /* __NMP_ETHTOOL_IOCTL_H__ */ diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c index f41cc95ab6..5c3f2ce4aa 100644 --- a/src/libnm-platform/nmp-object.c +++ b/src/libnm-platform/nmp-object.c @@ -16,6 +16,7 @@ #include "libnm-platform/nm-platform-utils.h" #include "libnm-platform/wifi/nm-wifi-utils.h" #include "libnm-platform/wpan/nm-wpan-utils.h" +#include "libnm-platform/nmp-ethtool-ioctl.h" /*****************************************************************************/ @@ -647,7 +648,7 @@ _link_get_driver(struct udev_device *udevice, const char *kind, int ifindex) if (ifindex > 0) { NMPUtilsEthtoolDriverInfo driver_info; - if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) { + if (nmp_ethtool_ioctl_get_driver_info(ifindex, &driver_info)) { if (driver_info.driver[0]) return g_intern_string(driver_info.driver); } From 79ba228c598d078e1a0728f1ee543230b2b9a545 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 19 Mar 2025 13:47:53 +0100 Subject: [PATCH 4/8] platform: add ethtool netlink implementation Introduce some basic infrastructure to perform ethtool operations via netlink. As a proof of concept, implement the pause settings. Netlink has some advantages over ioctl(): - it can be easily extended with new attributes; - it can return descriptive error messages via the extended ack mechanism. For example, when setting the ring parameters to a value outside the allowed range, userspace receives error code -EINVAL and message "requested ring size exceeds maximum". ioctl() gets only -EINVAL, which is shared among many error reasons; - since it's possible to specify an ifindex in the request, there are no race conditions when the interface name changes; New ethtool API is available only via netlink; however it makes sense to start using netlink also for the old API that NM is already using (pause, eee, rings, etc.) over ioctl() because of the advantages described above. --- src/libnm-platform/meson.build | 1 + src/libnm-platform/nmp-ethtool.c | 319 +++++++++++++++++++++++++++++++ src/libnm-platform/nmp-ethtool.h | 17 ++ 3 files changed, 337 insertions(+) create mode 100644 src/libnm-platform/nmp-ethtool.c create mode 100644 src/libnm-platform/nmp-ethtool.h diff --git a/src/libnm-platform/meson.build b/src/libnm-platform/meson.build index e43d8758bc..35015b022d 100644 --- a/src/libnm-platform/meson.build +++ b/src/libnm-platform/meson.build @@ -12,6 +12,7 @@ libnm_platform = static_library( 'nmp-netns.c', 'nmp-object.c', 'nmp-plobj.c', + 'nmp-ethtool.c', 'nmp-ethtool-ioctl.c', 'devlink/nm-devlink.c', 'wifi/nm-wifi-utils-nl80211.c', diff --git a/src/libnm-platform/nmp-ethtool.c b/src/libnm-platform/nmp-ethtool.c new file mode 100644 index 0000000000..30cc1895e7 --- /dev/null +++ b/src/libnm-platform/nmp-ethtool.c @@ -0,0 +1,319 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nmp-ethtool.h" + +#include "libnm-platform/nm-platform.h" +#include "libnm-log-core/nm-logging.h" + +enum { + ETHTOOL_A_HEADER_UNSPEC, + ETHTOOL_A_HEADER_DEV_INDEX, /* u32 */ + ETHTOOL_A_HEADER_DEV_NAME, /* string */ + ETHTOOL_A_HEADER_FLAGS, /* u32 - ETHTOOL_FLAG_* */ + ETHTOOL_A_HEADER_PHY_INDEX, /* u32 */ + + /* add new constants above here */ + __ETHTOOL_A_HEADER_CNT, + ETHTOOL_A_HEADER_MAX = __ETHTOOL_A_HEADER_CNT - 1 +}; + +enum { + ETHTOOL_MSG_USER_NONE = 0, + ETHTOOL_MSG_STRSET_GET = 1, + ETHTOOL_MSG_LINKINFO_GET, + ETHTOOL_MSG_LINKINFO_SET, + ETHTOOL_MSG_LINKMODES_GET, + ETHTOOL_MSG_LINKMODES_SET, + ETHTOOL_MSG_LINKSTATE_GET, + ETHTOOL_MSG_DEBUG_GET, + ETHTOOL_MSG_DEBUG_SET, + ETHTOOL_MSG_WOL_GET, + ETHTOOL_MSG_WOL_SET, + ETHTOOL_MSG_FEATURES_GET, + ETHTOOL_MSG_FEATURES_SET, + ETHTOOL_MSG_PRIVFLAGS_GET, + ETHTOOL_MSG_PRIVFLAGS_SET, + ETHTOOL_MSG_RINGS_GET, + ETHTOOL_MSG_RINGS_SET, + ETHTOOL_MSG_CHANNELS_GET, + ETHTOOL_MSG_CHANNELS_SET, + ETHTOOL_MSG_COALESCE_GET, + ETHTOOL_MSG_COALESCE_SET, + ETHTOOL_MSG_PAUSE_GET, + ETHTOOL_MSG_PAUSE_SET, + ETHTOOL_MSG_EEE_GET, + ETHTOOL_MSG_EEE_SET, + ETHTOOL_MSG_TSINFO_GET, + ETHTOOL_MSG_CABLE_TEST_ACT, + ETHTOOL_MSG_CABLE_TEST_TDR_ACT, + ETHTOOL_MSG_TUNNEL_INFO_GET, + ETHTOOL_MSG_FEC_GET, + ETHTOOL_MSG_FEC_SET, + + /* add new constants above here */ + __ETHTOOL_MSG_USER_CNT, + ETHTOOL_MSG_USER_MAX = __ETHTOOL_MSG_USER_CNT - 1 +}; + +#define ETHTOOL_GENL_VERSION 1 + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "ethtool" +#define _NMLOG(_level, ...) \ + G_STMT_START \ + { \ + int _ifindex = ifindex; \ + \ + nm_log((_level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "%s[%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + _ifindex _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +#define CB_RESULT_PENDING 0 +#define CB_RESULT_OK 1 + +static int +ack_cb_handler(const struct nl_msg *msg, void *data) +{ + int *result = data; + *result = CB_RESULT_OK; + return NL_STOP; +} + +static int +finish_cb_handler(const struct nl_msg *msg, void *data) +{ + int *result = data; + *result = CB_RESULT_OK; + return NL_SKIP; +} + +static int +err_cb_handler(const struct sockaddr_nl *nla, const struct nlmsgerr *err, void *data) +{ + void **args = data; + int *result = args[0]; + char **err_msg = args[1]; + const char *extack_msg = NULL; + + *result = err->error; + nlmsg_parse_error(nlmsg_undata(err), &extack_msg); + + if (err_msg) + *err_msg = g_strdup(extack_msg ?: nm_strerror(err->error)); + + return NL_SKIP; +} + +static int +ethtool_send_and_recv(struct nl_sock *sock, + int ifindex, + struct nl_msg *msg, + int (*valid_handler)(const struct nl_msg *, void *), + void *valid_data, + char **err_msg, + const char *log_prefix) +{ + int nle; + int cb_result = CB_RESULT_PENDING; + void *err_arg[] = {&cb_result, err_msg}; + const struct nl_cb cb = { + .err_cb = err_cb_handler, + .err_arg = err_arg, + .finish_cb = finish_cb_handler, + .finish_arg = &cb_result, + .ack_cb = ack_cb_handler, + .ack_arg = &cb_result, + .valid_cb = valid_handler, + .valid_arg = valid_data, + }; + + g_return_val_if_fail(msg, -ENOMEM); + + if (err_msg) + *err_msg = NULL; + + nle = nl_send_auto(sock, msg); + if (nle < 0) + goto out; + + while (cb_result == CB_RESULT_PENDING) { + nle = nl_recvmsgs(sock, &cb); + if (nle < 0 && nle != -EAGAIN) { + break; + } + } + +out: + if (nle < 0 && err_msg && *err_msg == NULL) + *err_msg = strdup(nm_strerror(nle)); + + if (nle >= 0 && cb_result < 0) + nle = cb_result; + + if (nle < 0) { + _LOGT("%s: netlink error: %d (%s)", log_prefix, nle, err_msg && *err_msg ? *err_msg : ""); + } + + return nle; +} + +static struct nl_msg * +ethtool_create_msg(guint16 family_id, + int ifindex, + guint8 cmd, + int header_attr, + const char *log_prefix) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nlattr *nest_header; + + if (family_id == 0) { + _LOGT("%s: ethtool genl family not found", log_prefix); + return NULL; + } + + msg = nlmsg_alloc(nlmsg_total_size(GENL_HDRLEN) + 200); + + if (!genlmsg_put(msg, + NL_AUTO_PORT, + NL_AUTO_SEQ, + family_id, + 0, + NLM_F_REQUEST, + cmd, + ETHTOOL_GENL_VERSION)) + goto nla_put_failure; + + nest_header = nla_nest_start(msg, header_attr); + NLA_PUT_U32(msg, ETHTOOL_A_HEADER_DEV_INDEX, (guint32) ifindex); + NLA_NEST_END(msg, nest_header); + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +/*****************************************************************************/ +/* PAUSE */ +/*****************************************************************************/ + +enum { + ETHTOOL_A_PAUSE_UNSPEC, + ETHTOOL_A_PAUSE_HEADER, + ETHTOOL_A_PAUSE_AUTONEG, + ETHTOOL_A_PAUSE_RX, + ETHTOOL_A_PAUSE_TX, + + __ETHTOOL_A_PAUSE_CNT, + ETHTOOL_A_PAUSE_MAX = (__ETHTOOL_A_PAUSE_CNT - 1) +}; + +static int +ethtool_parse_pause(const struct nl_msg *msg, void *data) +{ + NMEthtoolPauseState *pause = data; + static const struct nla_policy policy[] = { + [ETHTOOL_A_PAUSE_AUTONEG] = {.type = NLA_U8}, + [ETHTOOL_A_PAUSE_RX] = {.type = NLA_U8}, + [ETHTOOL_A_PAUSE_TX] = {.type = NLA_U8}, + }; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb[G_N_ELEMENTS(policy)]; + + *pause = (NMEthtoolPauseState) {}; + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), policy) < 0) + return NL_SKIP; + + if (tb[ETHTOOL_A_PAUSE_AUTONEG]) + pause->autoneg = !!nla_get_u8(tb[ETHTOOL_A_PAUSE_AUTONEG]); + if (tb[ETHTOOL_A_PAUSE_RX]) + pause->rx = !!nla_get_u8(tb[ETHTOOL_A_PAUSE_RX]); + if (tb[ETHTOOL_A_PAUSE_TX]) + pause->tx = !!nla_get_u8(tb[ETHTOOL_A_PAUSE_TX]); + + return NL_OK; +} + +gboolean +nmp_ethtool_get_pause(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + NMEthtoolPauseState *pause) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *err_msg = NULL; + int r; + + g_return_val_if_fail(pause, FALSE); + + _LOGT("get-pause: start"); + *pause = (NMEthtoolPauseState) {}; + + msg = ethtool_create_msg(family_id, + ifindex, + ETHTOOL_MSG_PAUSE_GET, + ETHTOOL_A_PAUSE_HEADER, + "get-pause"); + if (!msg) + return FALSE; + + r = ethtool_send_and_recv(genl_sock, + ifindex, + msg, + ethtool_parse_pause, + pause, + &err_msg, + "get-pause"); + if (r < 0) + return FALSE; + + _LOGT("get-pause: autoneg %d rx %d tx %d", pause->autoneg, pause->rx, pause->tx); + + return TRUE; +} + +gboolean +nmp_ethtool_set_pause(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + const NMEthtoolPauseState *pause) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *err_msg = NULL; + int r; + + g_return_val_if_fail(pause, FALSE); + + _LOGT("set-pause: autoneg %d rx %d tx %d", pause->autoneg, pause->rx, pause->tx); + + msg = ethtool_create_msg(family_id, + ifindex, + ETHTOOL_MSG_PAUSE_SET, + ETHTOOL_A_PAUSE_HEADER, + "set-pause"); + if (!msg) + return FALSE; + + NLA_PUT_U8(msg, ETHTOOL_A_PAUSE_AUTONEG, pause->autoneg); + NLA_PUT_U8(msg, ETHTOOL_A_PAUSE_RX, pause->rx); + NLA_PUT_U8(msg, ETHTOOL_A_PAUSE_TX, pause->tx); + + r = ethtool_send_and_recv(genl_sock, ifindex, msg, NULL, NULL, &err_msg, "set-pause"); + if (r < 0) + return FALSE; + + _LOGT("set-pause: succeeded"); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} diff --git a/src/libnm-platform/nmp-ethtool.h b/src/libnm-platform/nmp-ethtool.h new file mode 100644 index 0000000000..36b4e0b075 --- /dev/null +++ b/src/libnm-platform/nmp-ethtool.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef __NMP_ETHTOOL_H__ +#define __NMP_ETHTOOL_H__ + +#include "libnm-platform/nmp-base.h" +#include "libnm-platform/nm-netlink.h" + +gboolean nmp_ethtool_get_pause(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + NMEthtoolPauseState *pause); +gboolean nmp_ethtool_set_pause(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + const NMEthtoolPauseState *pause); + +#endif /* __NMP_ETHTOOL_H__ */ From 62c841afcfa6984b16a1956b2697245755b0c5b6 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 24 Mar 2025 20:57:58 +0100 Subject: [PATCH 5/8] platform: use the new ethtool-netlink API for pause settings --- src/libnm-platform/nm-linux-platform.c | 28 +++++++++++++ src/libnm-platform/nm-platform.c | 5 ++- src/libnm-platform/nm-platform.h | 3 ++ src/libnm-platform/nmp-ethtool-ioctl.c | 58 -------------------------- src/libnm-platform/nmp-ethtool-ioctl.h | 4 -- 5 files changed, 34 insertions(+), 64 deletions(-) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 59cb540aa1..8746de7f8f 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -41,6 +41,7 @@ #include "libnm-platform/nm-netlink.h" #include "libnm-platform/nm-platform-utils.h" #include "libnm-platform/nmp-netns.h" +#include "libnm-platform/nmp-ethtool.h" #include "libnm-platform/nmp-ethtool-ioctl.h" #include "libnm-platform/devlink/nm-devlink.h" #include "libnm-platform/wifi/nm-wifi-utils-wext.h" @@ -11832,6 +11833,30 @@ mptcp_addrs_dump(NMPlatform *platform) /*****************************************************************************/ +static gboolean +ethtool_get_pause(NMPlatform *platform, int ifindex, NMEthtoolPauseState *pause) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return nmp_ethtool_get_pause(priv->sk_genl_sync, + genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_ETHTOOL), + ifindex, + pause); +} + +static gboolean +ethtool_set_pause(NMPlatform *platform, int ifindex, const NMEthtoolPauseState *pause) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return nmp_ethtool_set_pause(priv->sk_genl_sync, + genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_ETHTOOL), + ifindex, + pause); +} + +/*****************************************************************************/ + static void cache_update_link_udev(NMPlatform *platform, int ifindex, struct udev_device *udevice) { @@ -12329,4 +12354,7 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass) platform_class->genl_get_family_id = genl_get_family_id; platform_class->mptcp_addr_update = mptcp_addr_update; platform_class->mptcp_addrs_dump = mptcp_addrs_dump; + + platform_class->ethtool_set_pause = ethtool_set_pause; + platform_class->ethtool_get_pause = ethtool_get_pause; } diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 0a0eeafb1d..cfd1dab597 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -3715,7 +3715,7 @@ nm_platform_ethtool_get_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(pause, FALSE); - return nmp_ethtool_ioctl_get_pause(ifindex, pause); + return klass->ethtool_get_pause(self, ifindex, pause); } gboolean @@ -3724,8 +3724,9 @@ nm_platform_ethtool_set_pause(NMPlatform *self, int ifindex, const NMEthtoolPaus _CHECK_SELF_NETNS(self, klass, netns, FALSE); g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(pause, FALSE); - return nmp_ethtool_ioctl_set_pause(ifindex, pause); + return klass->ethtool_set_pause(self, ifindex, pause); } gboolean diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 4ddc224d59..aacd323cf5 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -1346,6 +1346,9 @@ typedef struct { GPtrArray *(*mptcp_addrs_dump)(NMPlatform *self); + gboolean (*ethtool_get_pause)(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause); + gboolean (*ethtool_set_pause)(NMPlatform *self, int ifindex, const NMEthtoolPauseState *pause); + } NMPlatformClass; /* NMPlatform signals diff --git a/src/libnm-platform/nmp-ethtool-ioctl.c b/src/libnm-platform/nmp-ethtool-ioctl.c index 0e90f8182c..c44d67c642 100644 --- a/src/libnm-platform/nmp-ethtool-ioctl.c +++ b/src/libnm-platform/nmp-ethtool-ioctl.c @@ -1090,64 +1090,6 @@ nmp_ethtool_ioctl_set_channels(int ifindex, const NMEthtoolChannelsState *channe return TRUE; } -gboolean -nmp_ethtool_ioctl_get_pause(int ifindex, NMEthtoolPauseState *pause) -{ - struct ethtool_pauseparam eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(pause, FALSE); - - eth_data.cmd = ETHTOOL_GPAUSEPARAM; - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting pause settings", - ifindex, - "get-pause"); - return FALSE; - } - - *pause = (NMEthtoolPauseState) { - .autoneg = eth_data.autoneg == 1, - .rx = eth_data.rx_pause == 1, - .tx = eth_data.tx_pause == 1, - }; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel pause settings", - ifindex, - "get-pause"); - return TRUE; -} - -gboolean -nmp_ethtool_ioctl_set_pause(int ifindex, const NMEthtoolPauseState *pause) -{ - struct ethtool_pauseparam eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(pause, FALSE); - - eth_data = (struct ethtool_pauseparam) { - .cmd = ETHTOOL_SPAUSEPARAM, - .autoneg = pause->autoneg ? 1 : 0, - .rx_pause = pause->rx ? 1 : 0, - .tx_pause = pause->tx ? 1 : 0, - }; - - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting pause settings", - ifindex, - "set-pause"); - return FALSE; - } - nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel puase settings", ifindex, "set-pause"); - return TRUE; -} - gboolean nmp_ethtool_ioctl_get_eee(int ifindex, NMEthtoolEEEState *eee) { diff --git a/src/libnm-platform/nmp-ethtool-ioctl.h b/src/libnm-platform/nmp-ethtool-ioctl.h index 534c798240..4d446c218f 100644 --- a/src/libnm-platform/nmp-ethtool-ioctl.h +++ b/src/libnm-platform/nmp-ethtool-ioctl.h @@ -50,10 +50,6 @@ gboolean nmp_ethtool_ioctl_get_channels(int ifindex, NMEthtoolChannelsState *cha gboolean nmp_ethtool_ioctl_set_channels(int ifindex, const NMEthtoolChannelsState *channels); -gboolean nmp_ethtool_ioctl_get_pause(int ifindex, NMEthtoolPauseState *pause); - -gboolean nmp_ethtool_ioctl_set_pause(int ifindex, const NMEthtoolPauseState *pause); - gboolean nmp_ethtool_ioctl_get_eee(int ifindex, NMEthtoolEEEState *eee); gboolean nmp_ethtool_ioctl_set_eee(int ifindex, const NMEthtoolEEEState *eee); From 3580dfe517aa7ec031b1d93e876f4469c3b1fb28 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 24 Mar 2025 21:16:47 +0100 Subject: [PATCH 6/8] platform: replace EEE ethtool ioctl calls with netlink --- src/libnm-platform/nm-linux-platform.c | 24 ++++++ src/libnm-platform/nm-platform.c | 5 +- src/libnm-platform/nm-platform.h | 2 + src/libnm-platform/nmp-ethtool-ioctl.c | 60 -------------- src/libnm-platform/nmp-ethtool-ioctl.h | 4 - src/libnm-platform/nmp-ethtool.c | 105 +++++++++++++++++++++++++ src/libnm-platform/nmp-ethtool.h | 9 +++ 7 files changed, 143 insertions(+), 66 deletions(-) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 8746de7f8f..5fe0ff036d 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -11855,6 +11855,28 @@ ethtool_set_pause(NMPlatform *platform, int ifindex, const NMEthtoolPauseState * pause); } +static gboolean +ethtool_get_eee(NMPlatform *platform, int ifindex, NMEthtoolEEEState *eee) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return nmp_ethtool_get_eee(priv->sk_genl_sync, + genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_ETHTOOL), + ifindex, + eee); +} + +static gboolean +ethtool_set_eee(NMPlatform *platform, int ifindex, const NMEthtoolEEEState *eee) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return nmp_ethtool_set_eee(priv->sk_genl_sync, + genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_ETHTOOL), + ifindex, + eee); +} + /*****************************************************************************/ static void @@ -12357,4 +12379,6 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass) platform_class->ethtool_set_pause = ethtool_set_pause; platform_class->ethtool_get_pause = ethtool_get_pause; + platform_class->ethtool_set_eee = ethtool_set_eee; + platform_class->ethtool_get_eee = ethtool_get_eee; } diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index cfd1dab597..47db1c40be 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -3737,7 +3737,7 @@ nm_platform_ethtool_get_eee(NMPlatform *self, int ifindex, NMEthtoolEEEState *ee g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(eee, FALSE); - return nmp_ethtool_ioctl_get_eee(ifindex, eee); + return klass->ethtool_get_eee(self, ifindex, eee); } gboolean @@ -3746,8 +3746,9 @@ nm_platform_ethtool_set_eee(NMPlatform *self, int ifindex, const NMEthtoolEEESta _CHECK_SELF_NETNS(self, klass, netns, FALSE); g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(eee, FALSE); - return nmp_ethtool_ioctl_set_eee(ifindex, eee); + return klass->ethtool_set_eee(self, ifindex, eee); } /*****************************************************************************/ diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index aacd323cf5..405a2d012d 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -1348,6 +1348,8 @@ typedef struct { gboolean (*ethtool_get_pause)(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause); gboolean (*ethtool_set_pause)(NMPlatform *self, int ifindex, const NMEthtoolPauseState *pause); + gboolean (*ethtool_get_eee)(NMPlatform *self, int ifindex, NMEthtoolEEEState *eee); + gboolean (*ethtool_set_eee)(NMPlatform *self, int ifindex, const NMEthtoolEEEState *eee); } NMPlatformClass; diff --git a/src/libnm-platform/nmp-ethtool-ioctl.c b/src/libnm-platform/nmp-ethtool-ioctl.c index c44d67c642..bbc9d5e760 100644 --- a/src/libnm-platform/nmp-ethtool-ioctl.c +++ b/src/libnm-platform/nmp-ethtool-ioctl.c @@ -1090,66 +1090,6 @@ nmp_ethtool_ioctl_set_channels(int ifindex, const NMEthtoolChannelsState *channe return TRUE; } -gboolean -nmp_ethtool_ioctl_get_eee(int ifindex, NMEthtoolEEEState *eee) -{ - struct ethtool_eee eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(eee, FALSE); - - eth_data.cmd = ETHTOOL_GEEE; - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting eee settings", - ifindex, - "get-eee"); - return FALSE; - } - - *eee = (NMEthtoolEEEState) { - .enabled = eth_data.eee_enabled == 1, - }; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel eee settings", - ifindex, - "get-eee"); - return TRUE; -} - -gboolean -nmp_ethtool_ioctl_set_eee(int ifindex, const NMEthtoolEEEState *eee) -{ - struct ethtool_eee eth_data; - nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(eee, FALSE); - - eth_data.cmd = ETHTOOL_GEEE; - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting eee settings", - ifindex, - "get-eee"); - return FALSE; - } - - eth_data.cmd = ETHTOOL_SEEE, eth_data.eee_enabled = eee->enabled ? 1 : 0; - - if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_eee)) != 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting eee settings", - ifindex, - "set-eee"); - return FALSE; - } - nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel eee settings", ifindex, "set-eee"); - return TRUE; -} - gboolean nmp_ethtool_ioctl_get_fec_mode(int ifindex, uint32_t *fec_mode) { diff --git a/src/libnm-platform/nmp-ethtool-ioctl.h b/src/libnm-platform/nmp-ethtool-ioctl.h index 4d446c218f..97c2ff14e6 100644 --- a/src/libnm-platform/nmp-ethtool-ioctl.h +++ b/src/libnm-platform/nmp-ethtool-ioctl.h @@ -50,10 +50,6 @@ gboolean nmp_ethtool_ioctl_get_channels(int ifindex, NMEthtoolChannelsState *cha gboolean nmp_ethtool_ioctl_set_channels(int ifindex, const NMEthtoolChannelsState *channels); -gboolean nmp_ethtool_ioctl_get_eee(int ifindex, NMEthtoolEEEState *eee); - -gboolean nmp_ethtool_ioctl_set_eee(int ifindex, const NMEthtoolEEEState *eee); - gboolean nmp_ethtool_ioctl_get_fec_mode(int ifindex, uint32_t *fec_mode); gboolean nmp_ethtool_ioctl_set_fec_mode(int ifindex, uint32_t fec_mode); diff --git a/src/libnm-platform/nmp-ethtool.c b/src/libnm-platform/nmp-ethtool.c index 30cc1895e7..d502c4f946 100644 --- a/src/libnm-platform/nmp-ethtool.c +++ b/src/libnm-platform/nmp-ethtool.c @@ -317,3 +317,108 @@ nmp_ethtool_set_pause(struct nl_sock *genl_sock, nla_put_failure: g_return_val_if_reached(FALSE); } + +/*****************************************************************************/ +/* EEE */ +/*****************************************************************************/ + +enum { + ETHTOOL_A_EEE_UNSPEC, + ETHTOOL_A_EEE_HEADER, /* nest - _A_HEADER_* */ + ETHTOOL_A_EEE_MODES_OURS, /* bitset */ + ETHTOOL_A_EEE_MODES_PEER, /* bitset */ + ETHTOOL_A_EEE_ACTIVE, /* u8 */ + ETHTOOL_A_EEE_ENABLED, /* u8 */ + + /* add new constants above here */ + __ETHTOOL_A_EEE_CNT, + ETHTOOL_A_EEE_MAX = (__ETHTOOL_A_EEE_CNT - 1) +}; + +static int +ethtool_parse_eee(const struct nl_msg *msg, void *data) +{ + NMEthtoolEEEState *eee = data; + static const struct nla_policy policy[] = { + [ETHTOOL_A_EEE_ENABLED] = {.type = NLA_U8}, + }; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb[G_N_ELEMENTS(policy)]; + + *eee = (NMEthtoolEEEState) {}; + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), policy) < 0) + return NL_SKIP; + + if (tb[ETHTOOL_A_EEE_ENABLED]) + eee->enabled = !!nla_get_u8(tb[ETHTOOL_A_EEE_ENABLED]); + + return NL_OK; +} + +gboolean +nmp_ethtool_get_eee(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + NMEthtoolEEEState *eee) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *err_msg = NULL; + int r; + + g_return_val_if_fail(eee, FALSE); + + _LOGT("get-eee: start"); + *eee = (NMEthtoolEEEState) {}; + + msg = ethtool_create_msg(family_id, + ifindex, + ETHTOOL_MSG_EEE_GET, + ETHTOOL_A_EEE_HEADER, + "get-eee"); + if (!msg) + return FALSE; + + r = ethtool_send_and_recv(genl_sock, ifindex, msg, ethtool_parse_eee, eee, &err_msg, "get-eee"); + if (r < 0) + return FALSE; + + _LOGT("get-eee: enabled %d", eee->enabled); + + return TRUE; +} + +gboolean +nmp_ethtool_set_eee(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + const NMEthtoolEEEState *eee) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *err_msg = NULL; + int r; + + g_return_val_if_fail(eee, FALSE); + + _LOGT("set-eee: enabled %d", eee->enabled); + + msg = ethtool_create_msg(family_id, + ifindex, + ETHTOOL_MSG_EEE_SET, + ETHTOOL_A_EEE_HEADER, + "set-eee"); + if (!msg) + return FALSE; + + NLA_PUT_U8(msg, ETHTOOL_A_EEE_ENABLED, eee->enabled); + + r = ethtool_send_and_recv(genl_sock, ifindex, msg, NULL, NULL, &err_msg, "set-eee"); + if (r < 0) + return FALSE; + + _LOGT("set-eee: succeeded"); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} diff --git a/src/libnm-platform/nmp-ethtool.h b/src/libnm-platform/nmp-ethtool.h index 36b4e0b075..73e4b34826 100644 --- a/src/libnm-platform/nmp-ethtool.h +++ b/src/libnm-platform/nmp-ethtool.h @@ -14,4 +14,13 @@ gboolean nmp_ethtool_set_pause(struct nl_sock *genl_sock, int ifindex, const NMEthtoolPauseState *pause); +gboolean nmp_ethtool_get_eee(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + NMEthtoolEEEState *eee); +gboolean nmp_ethtool_set_eee(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + const NMEthtoolEEEState *eee); + #endif /* __NMP_ETHTOOL_H__ */ From 250475c0fd118cfa9305ef738cd43f09d24a1734 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 24 Mar 2025 21:36:22 +0100 Subject: [PATCH 7/8] platform: replace ring ethtool ioctl calls with netlink --- src/libnm-platform/nm-linux-platform.c | 24 +++++ src/libnm-platform/nm-platform.c | 5 +- src/libnm-platform/nm-platform.h | 3 +- src/libnm-platform/nmp-ethtool-ioctl.c | 60 ----------- src/libnm-platform/nmp-ethtool-ioctl.h | 4 - src/libnm-platform/nmp-ethtool.c | 135 +++++++++++++++++++++++++ src/libnm-platform/nmp-ethtool.h | 9 ++ 7 files changed, 173 insertions(+), 67 deletions(-) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 5fe0ff036d..ff994ff5d8 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -11877,6 +11877,28 @@ ethtool_set_eee(NMPlatform *platform, int ifindex, const NMEthtoolEEEState *eee) eee); } +static gboolean +ethtool_get_ring(NMPlatform *platform, int ifindex, NMEthtoolRingState *ring) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return nmp_ethtool_get_ring(priv->sk_genl_sync, + genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_ETHTOOL), + ifindex, + ring); +} + +static gboolean +ethtool_set_ring(NMPlatform *platform, int ifindex, const NMEthtoolRingState *ring) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return nmp_ethtool_set_ring(priv->sk_genl_sync, + genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_ETHTOOL), + ifindex, + ring); +} + /*****************************************************************************/ static void @@ -12381,4 +12403,6 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass) platform_class->ethtool_get_pause = ethtool_get_pause; platform_class->ethtool_set_eee = ethtool_set_eee; platform_class->ethtool_get_eee = ethtool_get_eee; + platform_class->ethtool_set_ring = ethtool_set_ring; + platform_class->ethtool_get_ring = ethtool_get_ring; } diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 47db1c40be..fd966d2ce7 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -3671,7 +3671,7 @@ nm_platform_ethtool_get_ring(NMPlatform *self, int ifindex, NMEthtoolRingState * g_return_val_if_fail(ifindex > 0, FALSE); g_return_val_if_fail(ring, FALSE); - return nmp_ethtool_ioctl_get_ring(ifindex, ring); + return klass->ethtool_get_ring(self, ifindex, ring); } gboolean @@ -3680,8 +3680,9 @@ nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingS _CHECK_SELF_NETNS(self, klass, netns, FALSE); g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ring, FALSE); - return nmp_ethtool_ioctl_set_ring(ifindex, ring); + return klass->ethtool_set_ring(self, ifindex, ring); } gboolean diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 405a2d012d..e4ccb9b1cb 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -1350,7 +1350,8 @@ typedef struct { gboolean (*ethtool_set_pause)(NMPlatform *self, int ifindex, const NMEthtoolPauseState *pause); gboolean (*ethtool_get_eee)(NMPlatform *self, int ifindex, NMEthtoolEEEState *eee); gboolean (*ethtool_set_eee)(NMPlatform *self, int ifindex, const NMEthtoolEEEState *eee); - + gboolean (*ethtool_get_ring)(NMPlatform *self, int ifindex, NMEthtoolRingState *ring); + gboolean (*ethtool_set_ring)(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring); } NMPlatformClass; /* NMPlatform signals diff --git a/src/libnm-platform/nmp-ethtool-ioctl.c b/src/libnm-platform/nmp-ethtool-ioctl.c index bbc9d5e760..830b02f6cc 100644 --- a/src/libnm-platform/nmp-ethtool-ioctl.c +++ b/src/libnm-platform/nmp-ethtool-ioctl.c @@ -967,66 +967,6 @@ nmp_ethtool_ioctl_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coales return TRUE; } -gboolean -nmp_ethtool_ioctl_get_ring(int ifindex, NMEthtoolRingState *ring) -{ - struct ethtool_ringparam eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(ring, FALSE); - - eth_data.cmd = ETHTOOL_GRINGPARAM; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure getting ring settings", - ifindex, - "get-ring"); - return FALSE; - } - - *ring = (NMEthtoolRingState) { - .rx_pending = eth_data.rx_pending, - .rx_jumbo_pending = eth_data.rx_jumbo_pending, - .rx_mini_pending = eth_data.rx_mini_pending, - .tx_pending = eth_data.tx_pending, - }; - - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: retrieved kernel ring settings", - ifindex, - "get-ring"); - return TRUE; -} - -gboolean -nmp_ethtool_ioctl_set_ring(int ifindex, const NMEthtoolRingState *ring) -{ - struct ethtool_ringparam eth_data; - - g_return_val_if_fail(ifindex > 0, FALSE); - g_return_val_if_fail(ring, FALSE); - - eth_data = (struct ethtool_ringparam) { - .cmd = ETHTOOL_SRINGPARAM, - .rx_pending = ring->rx_pending, - .rx_jumbo_pending = ring->rx_jumbo_pending, - .rx_mini_pending = ring->rx_mini_pending, - .tx_pending = ring->tx_pending, - }; - - if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { - nm_log_trace(LOGD_PLATFORM, - "ethtool[%d]: %s: failure setting ring settings", - ifindex, - "set-ring"); - return FALSE; - } - - nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel ring settings", ifindex, "set-ring"); - return TRUE; -} - gboolean nmp_ethtool_ioctl_get_channels(int ifindex, NMEthtoolChannelsState *channels) { diff --git a/src/libnm-platform/nmp-ethtool-ioctl.h b/src/libnm-platform/nmp-ethtool-ioctl.h index 97c2ff14e6..cb93e9cf55 100644 --- a/src/libnm-platform/nmp-ethtool-ioctl.h +++ b/src/libnm-platform/nmp-ethtool-ioctl.h @@ -42,10 +42,6 @@ gboolean nmp_ethtool_ioctl_get_coalesce(int ifindex, NMEthtoolCoalesceState *coa gboolean nmp_ethtool_ioctl_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce); -gboolean nmp_ethtool_ioctl_get_ring(int ifindex, NMEthtoolRingState *ring); - -gboolean nmp_ethtool_ioctl_set_ring(int ifindex, const NMEthtoolRingState *ring); - gboolean nmp_ethtool_ioctl_get_channels(int ifindex, NMEthtoolChannelsState *channels); gboolean nmp_ethtool_ioctl_set_channels(int ifindex, const NMEthtoolChannelsState *channels); diff --git a/src/libnm-platform/nmp-ethtool.c b/src/libnm-platform/nmp-ethtool.c index d502c4f946..ed028f6a1c 100644 --- a/src/libnm-platform/nmp-ethtool.c +++ b/src/libnm-platform/nmp-ethtool.c @@ -422,3 +422,138 @@ nmp_ethtool_set_eee(struct nl_sock *genl_sock, nla_put_failure: g_return_val_if_reached(FALSE); } + +/*****************************************************************************/ +/* RINGS */ +/*****************************************************************************/ + +enum { + ETHTOOL_A_RINGS_UNSPEC, + ETHTOOL_A_RINGS_HEADER, /* nest - _A_HEADER_* */ + ETHTOOL_A_RINGS_RX_MAX, /* u32 */ + ETHTOOL_A_RINGS_RX_MINI_MAX, /* u32 */ + ETHTOOL_A_RINGS_RX_JUMBO_MAX, /* u32 */ + ETHTOOL_A_RINGS_TX_MAX, /* u32 */ + ETHTOOL_A_RINGS_RX, /* u32 */ + ETHTOOL_A_RINGS_RX_MINI, /* u32 */ + ETHTOOL_A_RINGS_RX_JUMBO, /* u32 */ + ETHTOOL_A_RINGS_TX, /* u32 */ + + /* add new constants above here */ + __ETHTOOL_A_RINGS_CNT, + ETHTOOL_A_RINGS_MAX = (__ETHTOOL_A_RINGS_CNT - 1) +}; + +static int +ethtool_parse_ring(const struct nl_msg *msg, void *data) +{ + NMEthtoolRingState *ring = data; + static const struct nla_policy policy[] = { + [ETHTOOL_A_RINGS_RX] = {.type = NLA_U32}, + [ETHTOOL_A_RINGS_RX_MINI] = {.type = NLA_U32}, + [ETHTOOL_A_RINGS_RX_JUMBO] = {.type = NLA_U32}, + [ETHTOOL_A_RINGS_TX] = {.type = NLA_U32}, + }; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb[G_N_ELEMENTS(policy)]; + + *ring = (NMEthtoolRingState) {}; + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), policy) < 0) + return NL_SKIP; + + if (tb[ETHTOOL_A_RINGS_RX]) + ring->rx_pending = nla_get_u32(tb[ETHTOOL_A_RINGS_RX]); + if (tb[ETHTOOL_A_RINGS_RX_MINI]) + ring->rx_mini_pending = nla_get_u32(tb[ETHTOOL_A_RINGS_RX_MINI]); + if (tb[ETHTOOL_A_RINGS_RX_JUMBO]) + ring->rx_jumbo_pending = nla_get_u32(tb[ETHTOOL_A_RINGS_RX_JUMBO]); + if (tb[ETHTOOL_A_RINGS_TX]) + ring->tx_pending = nla_get_u32(tb[ETHTOOL_A_RINGS_TX]); + + return NL_OK; +} + +gboolean +nmp_ethtool_get_ring(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + NMEthtoolRingState *ring) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *err_msg = NULL; + int r; + + g_return_val_if_fail(ring, FALSE); + + _LOGT("get-ring: start"); + *ring = (NMEthtoolRingState) {}; + + msg = ethtool_create_msg(family_id, + ifindex, + ETHTOOL_MSG_RINGS_GET, + ETHTOOL_A_RINGS_HEADER, + "get-ring"); + if (!msg) + return FALSE; + + r = ethtool_send_and_recv(genl_sock, + ifindex, + msg, + ethtool_parse_ring, + ring, + &err_msg, + "get-ring"); + if (r < 0) + return FALSE; + + _LOGT("get-ring: rx %u rx-mini %u rx-jumbo %u tx %u", + ring->rx_pending, + ring->rx_mini_pending, + ring->rx_jumbo_pending, + ring->tx_pending); + + return TRUE; +} + +gboolean +nmp_ethtool_set_ring(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + const NMEthtoolRingState *ring) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *err_msg = NULL; + int r; + + g_return_val_if_fail(ring, FALSE); + + _LOGT("set-ring: rx %u rx-mini %u rx-jumbo %u tx %u", + ring->rx_pending, + ring->rx_mini_pending, + ring->rx_jumbo_pending, + ring->tx_pending); + + msg = ethtool_create_msg(family_id, + ifindex, + ETHTOOL_MSG_RINGS_SET, + ETHTOOL_A_RINGS_HEADER, + "set-ring"); + if (!msg) + return FALSE; + + NLA_PUT_U32(msg, ETHTOOL_A_RINGS_RX, ring->rx_pending); + NLA_PUT_U32(msg, ETHTOOL_A_RINGS_RX_MINI, ring->rx_mini_pending); + NLA_PUT_U32(msg, ETHTOOL_A_RINGS_RX_JUMBO, ring->rx_jumbo_pending); + NLA_PUT_U32(msg, ETHTOOL_A_RINGS_TX, ring->tx_pending); + + r = ethtool_send_and_recv(genl_sock, ifindex, msg, NULL, NULL, &err_msg, "set-ring"); + if (r < 0) + return FALSE; + + _LOGT("set-ring: succeeded"); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} diff --git a/src/libnm-platform/nmp-ethtool.h b/src/libnm-platform/nmp-ethtool.h index 73e4b34826..88bb95b687 100644 --- a/src/libnm-platform/nmp-ethtool.h +++ b/src/libnm-platform/nmp-ethtool.h @@ -23,4 +23,13 @@ gboolean nmp_ethtool_set_eee(struct nl_sock *genl_sock, int ifindex, const NMEthtoolEEEState *eee); +gboolean nmp_ethtool_get_ring(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + NMEthtoolRingState *ring); +gboolean nmp_ethtool_set_ring(struct nl_sock *genl_sock, + guint16 family_id, + int ifindex, + const NMEthtoolRingState *ring); + #endif /* __NMP_ETHTOOL_H__ */ From 3fb86182ebac63305c968d06e4ef50512613c441 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 25 Mar 2025 11:18:12 +0100 Subject: [PATCH 8/8] README: document the required kernel version Add a new section in README.md that describes the required Linux kernel version. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f536295a9f..0a56d783c4 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,14 @@ contrib/fedora/rpm/NetworkManager.conf for how to enable debug logging in NetworkManager. +Requirements +------------ + +NetworkManager requires: + +- Linux kernel >= 5.6 for some ethtool options (pause, eee, ring) + + Documentation -------------