Search code examples
cnetlink

How to get LINKMODES using <ethtool_netlink.h>


How to use the netlink interface for ethtool to get linkmodes (speed,duplex,...)

I've tried this:

#include <errno.h>
#include <linux/ethtool_netlink.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/netlink.h>
#include <stdio.h>

static int parse_cb(struct nl_msg* msg, void* arg __attribute__((unused))) {
    int err                    = 0;
    struct genlmsghdr* genlhdr = nlmsg_data(nlmsg_hdr(msg));
    struct nlattr* tb[ETHTOOL_A_LINKMODES_MAX + 1];

    err = nla_parse(tb, ETHTOOL_A_LINKMODES_MAX, genlmsg_attrdata(genlhdr, 0), genlmsg_attrlen(genlhdr, 0), NULL);
    if (0 != err) {
        nl_perror(err, "Failed parsing attributes");
        return NL_SKIP;
    }

    if (!tb[ETHTOOL_A_LINKMODES_SPEED]) {
        fprintf(stderr, "Attribute not found\n");
        return NL_SKIP;
    }

    printf("Link speed: %d\n", nla_get_u32(tb[ETHTOOL_A_LINKMODES_SPEED]));

    return NL_OK;
}

static int send_request(struct nl_sock* sk, int fam) {
    void* hdr          = NULL;
    int err            = 0;
    struct nl_msg* msg = NULL;

    msg = nlmsg_alloc();
    if (NULL == msg) {
        fprintf(stderr, "Failed to create message\n");
        return -ENOMEM;
    }

    hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, fam, 0, NLM_F_DUMP, ETHTOOL_MSG_LINKMODES_GET, ETHTOOL_GENL_VERSION);
    if (NULL == hdr) {
        fprintf(stderr, "Failed to put message\n");
        return -EMSGSIZE;
    }

    // Optionally set the interface
    // err = nla_put_string(msg, ETHTOOL_A_HEADER_DEV_NAME, "enp0s0");
    // if (err < 0) {
    //  return -err;
    // }

    err = nl_send_auto(sk, msg);
    err = err >= 0 ? 0 : err;

    nlmsg_free(msg);

    return err;
}

int main(void) {
    struct nl_sock* sock = NULL;
    int err              = 0;
    int fam              = 0;

    sock = nl_socket_alloc();
    if (NULL == sock) {
        fprintf(stderr, "Failed to allocate unicast nl_sock\n");
        return -1;
    }

    err = nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, parse_cb, NULL);
    if (0 != err) {
        nl_perror(err, "Failed to add custom cb");
    }

    err = genl_connect(sock);
    if (0 != err) {
        nl_perror(err, "Failed to connect socket");
        goto free_socket;
    }

    fam = genl_ctrl_resolve(sock, ETHTOOL_GENL_NAME);
    if (fam < 0) {
        nl_perror(err, "failed to resolve generic netlink family");
        goto free_socket;
    }

    err = send_request(sock, fam);
    if (0 != err) {
        nl_perror(err, "failed to send message");
    }

    err = nl_recvmsgs_default(sock);
    if (err < 0 && err != -NLE_INTR && err != -NLE_AGAIN) {
        nl_perror(err, "Failed to receive messages");
    }

free_socket:
    nl_close(sock);
    nl_socket_free(sock);

    return err;
}

But I get the error message Failed to receive messages: Invalid input data or parameter.

Same thing if I specify the interface (commented code).

It looks like something could be wrong with the message I am sending. I cannot seem to find good examples of how to use the ethtool netlink interface


Solution

  • You have to attach the ETHTOOL_A_LINKMODES_HEADER, even if you leave it empty. And you must set it's NLA_F_NESTED bit. Something like this (my code is C++, but modifying this to be C is trivial):

    Status Ethtool::sendRequest(int deviceIdx) {
        void *hdr = NULL;
        int err = 0;
        struct nl_msg *msg = nlmsg_alloc();
        if (NULL == msg) {
            throw std::runtime_error("Failed to create message\n");
        }
    
        SCOPE_EXIT { nlmsg_free(msg); };
    
        hdr = genlmsg_put(msg,
                          NL_AUTO_PORT,
                          NL_AUTO_SEQ,
                          ethtoolFamilyId_,
                          0, // no protocol-specific header
                          NLM_F_REQUEST | NLM_F_ACK,
                          ETHTOOL_MSG_LINKMODES_GET,
                          ETHTOOL_GENL_VERSION);
        if (NULL == hdr) {
            throw std::runtime_error("Failed to put message\n");
        }
    
        // Create nested ethtool header
        auto ethtoolHdr = nla_nest_start(msg, NLA_F_NESTED | ETHTOOL_A_LINKMODES_HEADER);
        if (NULL == ethtoolHdr) {
            throw std::runtime_error("Failed to start nested message\n");
        }
    
        err = nla_put_u32(msg, ETHTOOL_A_HEADER_DEV_INDEX, deviceIdx);
        if (err < 0) {
            throw std::runtime_error("Failed to put device index\n");
        }
    
        err = nla_put_u32(msg, ETHTOOL_A_HEADER_FLAGS, ETHTOOL_FLAG_COMPACT_BITSETS);
        if (err < 0) {
            throw std::runtime_error("Failed to put flags\n");
        }
    
        err = nla_nest_end(msg, ethtoolHdr);
        if (err < 0) {
            throw std::runtime_error("Failed to end nested message");
        }
    
        err = nl_send_auto(ethtoolSock_, msg);
        if (err < 0) {
            throw std::runtime_error("Failed to send message\n");
        }
    
        err = nl_recvmsgs_default(ethtoolSock_);
        if (err < 0) {
            throw std::runtime_error("Failed to receive message");
        }
    
        return Status::success();
    }
    

    And the parsing function would look like this:

    int Ethtool::parseCb(nl_msg *msg, void *arg) {
        Ethtool *ethtool = reinterpret_cast<Ethtool *>(arg);
        int err = 0;
        genlmsghdr *genlhdr = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg)));
        nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1];
        nlattr *headerTb[ETHTOOL_A_HEADER_MAX + 1];
    
        err = nla_parse(tb,
                        ETHTOOL_A_LINKMODES_MAX,
                        genlmsg_attrdata(genlhdr, 0),
                        genlmsg_attrlen(genlhdr, 0),
                        NULL);
        if (0 != err) {
            nl_perror(err, "Failed parsing attributes");
            return NL_SKIP;
        }
    
        if (!tb[ETHTOOL_A_LINKMODES_HEADER]) {
            std::cerr << "Header attribute not found\n";
            return NL_SKIP;
        }
    
        err = nla_parse_nested(headerTb, ETHTOOL_A_HEADER_MAX, tb[ETHTOOL_A_LINKMODES_HEADER], NULL);
        if (0 != err) {
            std::cerr << "Failed parsing nested header attributes" << nl_geterror(err) << std::endl;
            return NL_SKIP;
        }
    
        if (!headerTb[ETHTOOL_A_HEADER_DEV_INDEX]) {
            std::cerr << "Port index attribute not found\n";
            return NL_SKIP;
        }
        uint32_t deviceIdx = nla_get_u32(headerTb[ETHTOOL_A_HEADER_DEV_INDEX]);
    
        if (!tb[ETHTOOL_A_LINKMODES_SPEED]) {
            std::cerr << "Speed attribute not found\n";
            return NL_SKIP;
        }
    
        // Autoneg
        if (!tb[ETHTOOL_A_LINKMODES_AUTONEG]) {
            std::cerr << "Autoneg attribute not found\n";
            return NL_SKIP;
        }
    
        ethtool->portInfo_[deviceIdx].autoneg = nla_get_u8(tb[ETHTOOL_A_LINKMODES_AUTONEG]);
        ethtool->portInfo_[deviceIdx].speed = nla_get_u32(tb[ETHTOOL_A_LINKMODES_SPEED]);
    
        return NL_OK;
    }