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
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;
}