summary refs log tree commit diff
path: root/src/libnm-platform/nmp-ethtool.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-platform/nmp-ethtool.c')
-rw-r--r--src/libnm-platform/nmp-ethtool.c559
1 files changed, 559 insertions, 0 deletions
diff --git a/src/libnm-platform/nmp-ethtool.c b/src/libnm-platform/nmp-ethtool.c
new file mode 100644
index 00000000..ed028f6a
--- /dev/null
+++ b/src/libnm-platform/nmp-ethtool.c
@@ -0,0 +1,559 @@
+/* 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);
+}
+
+/*****************************************************************************/
+/* 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);
+}
+
+/*****************************************************************************/
+/* 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);
+}