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.
This commit is contained in:
@@ -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',
|
||||
|
319
src/libnm-platform/nmp-ethtool.c
Normal file
319
src/libnm-platform/nmp-ethtool.c
Normal file
@@ -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);
|
||||
}
|
17
src/libnm-platform/nmp-ethtool.h
Normal file
17
src/libnm-platform/nmp-ethtool.h
Normal file
@@ -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__ */
|
Reference in New Issue
Block a user