blob: 5450c9b3bb36d5b86d80b5c1c6b8fc2541b5014e [file] [log] [blame]
/*
* nlsock.c - netlink socket
*
* Data structure and code for netlink socket abstraction.
*/
#include <stdint.h>
#include <errno.h>
#include "../internal.h"
#include "nlsock.h"
#include "netlink.h"
#include "prettymsg.h"
#define NLSOCK_RECV_BUFFSIZE 65536
static void ctrl_msg_summary(const struct nlmsghdr *nlhdr)
{
const struct nlmsgerr *nlerr;
switch (nlhdr->nlmsg_type) {
case NLMSG_NOOP:
printf(" noop\n");
break;
case NLMSG_ERROR:
printf(" error");
if (nlhdr->nlmsg_len < NLMSG_HDRLEN + sizeof(*nlerr)) {
printf(" malformed\n");
break;
}
nlerr = mnl_nlmsg_get_payload(nlhdr);
printf(" errno=%d\n", nlerr->error);
break;
case NLMSG_DONE:
printf(" done\n");
break;
case NLMSG_OVERRUN:
printf(" overrun\n");
break;
default:
printf(" type %u\n", nlhdr->nlmsg_type);
break;
}
}
static void genl_msg_summary(const struct nlmsghdr *nlhdr, int ethnl_fam,
bool outgoing, bool pretty)
{
if (nlhdr->nlmsg_type == ethnl_fam) {
const struct pretty_nlmsg_desc *msg_desc;
const struct genlmsghdr *ghdr;
unsigned int n_desc;
printf(" ethool");
if (nlhdr->nlmsg_len < NLMSG_HDRLEN + GENL_HDRLEN) {
printf(" malformed\n");
return;
}
ghdr = mnl_nlmsg_get_payload(nlhdr);
msg_desc = outgoing ? ethnl_umsg_desc : ethnl_kmsg_desc;
n_desc = outgoing ? ethnl_umsg_n_desc : ethnl_kmsg_n_desc;
if (ghdr->cmd < n_desc && msg_desc[ghdr->cmd].name)
printf(" %s", msg_desc[ghdr->cmd].name);
else
printf(" cmd %u", ghdr->cmd);
fputc('\n', stdout);
if (pretty)
pretty_print_genlmsg(nlhdr, msg_desc, n_desc, 0);
return;
}
if (nlhdr->nlmsg_type == GENL_ID_CTRL) {
printf(" genl-ctrl\n");
if (pretty)
pretty_print_genlmsg(nlhdr, genlctrl_msg_desc,
genlctrl_msg_n_desc, 0);
} else {
fputc('\n', stdout);
if (pretty)
pretty_print_genlmsg(nlhdr, NULL, 0, 0);
}
}
static void rtnl_msg_summary(const struct nlmsghdr *nlhdr, bool pretty)
{
unsigned int type = nlhdr->nlmsg_type;
if (type < rtnl_msg_n_desc && rtnl_msg_desc[type].name)
printf(" %s\n", rtnl_msg_desc[type].name);
else
printf(" type %u\n", type);
if (pretty)
pretty_print_rtnlmsg(nlhdr, 0);
}
static void debug_msg_summary(const struct nlmsghdr *nlhdr, int ethnl_fam,
int nl_fam, bool outgoing, bool pretty)
{
printf(" msg length %u", nlhdr->nlmsg_len);
if (nlhdr->nlmsg_type < NLMSG_MIN_TYPE) {
ctrl_msg_summary(nlhdr);
return;
}
switch(nl_fam) {
case NETLINK_GENERIC:
genl_msg_summary(nlhdr, ethnl_fam, outgoing, pretty);
break;
case NETLINK_ROUTE:
rtnl_msg_summary(nlhdr, pretty);
break;
default:
fputc('\n', stdout);
break;
}
}
static void debug_msg(struct nl_socket *nlsk, const void *msg, unsigned int len,
bool outgoing)
{
const char *dirlabel = outgoing ? "sending" : "received";
uint32_t debug = nlsk->nlctx->ctx->debug;
const struct nlmsghdr *nlhdr = msg;
bool summary, dump, pretty;
const char *nl_fam_label;
int left = len;
summary = debug_on(debug, DEBUG_NL_MSGS);
dump = debug_on(debug,
outgoing ? DEBUG_NL_DUMP_SND : DEBUG_NL_DUMP_RCV);
pretty = debug_on(debug, DEBUG_NL_PRETTY_MSG);
if (!summary && !dump)
return;
switch(nlsk->nl_fam) {
case NETLINK_GENERIC:
nl_fam_label = "genetlink";
break;
case NETLINK_ROUTE:
nl_fam_label = "rtnetlink";
break;
default:
nl_fam_label = "netlink";
break;
}
printf("%s %s packet (%u bytes):\n", dirlabel, nl_fam_label, len);
while (nlhdr && left > 0 && mnl_nlmsg_ok(nlhdr, left)) {
if (summary)
debug_msg_summary(nlhdr, nlsk->nlctx->ethnl_fam,
nlsk->nl_fam, outgoing, pretty);
if (dump)
mnl_nlmsg_fprintf(stdout, nlhdr, nlhdr->nlmsg_len,
GENL_HDRLEN);
nlhdr = mnl_nlmsg_next(nlhdr, &left);
}
}
/**
* nlsock_process_ack() - process NLMSG_ERROR message from kernel
* @nlhdr: pointer to netlink header
* @len: length of received data (from nlhdr to end of buffer)
* @suppress_nlerr: 0 show all errors, 1 silence -EOPNOTSUPP, 2 silence all
*
* Return: error code extracted from the message
*/
static int nlsock_process_ack(struct nlmsghdr *nlhdr, unsigned long len,
unsigned int suppress_nlerr, bool pretty)
{
const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
DECLARE_ATTR_TB_INFO(tb);
unsigned int err_offset = 0;
unsigned int tlv_offset;
struct nlmsgerr *nlerr;
bool silent;
if ((len < NLMSG_HDRLEN + sizeof(*nlerr)) || (len < nlhdr->nlmsg_len))
return -EFAULT;
nlerr = mnl_nlmsg_get_payload(nlhdr);
silent = suppress_nlerr >= 2 ||
(suppress_nlerr && nlerr->error == -EOPNOTSUPP);
if (silent || !(nlhdr->nlmsg_flags & NLM_F_ACK_TLVS))
goto tlv_done;
tlv_offset = sizeof(*nlerr);
if (!(nlhdr->nlmsg_flags & NLM_F_CAPPED))
tlv_offset += MNL_ALIGN(mnl_nlmsg_get_payload_len(&nlerr->msg));
if (mnl_attr_parse(nlhdr, tlv_offset, attr_cb, &tb_info) < 0)
goto tlv_done;
if (tb[NLMSGERR_ATTR_MSG]) {
const char *msg = mnl_attr_get_str(tb[NLMSGERR_ATTR_MSG]);
fprintf(stderr, "netlink %s: %s",
nlerr->error ? "error" : "warning", msg);
if (!pretty && tb[NLMSGERR_ATTR_OFFS])
fprintf(stderr, " (offset %u)",
mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]));
fputc('\n', stderr);
}
if (tb[NLMSGERR_ATTR_OFFS])
err_offset = mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]);
tlv_done:
if (nlerr->error && !silent) {
errno = -nlerr->error;
perror("netlink error");
}
if (pretty && !(nlhdr->nlmsg_flags & NLM_F_CAPPED) &&
nlhdr->nlmsg_len >= NLMSG_HDRLEN + nlerr->msg.nlmsg_len) {
fprintf(stderr, "offending message%s:\n",
err_offset ? " and attribute" : "");
pretty_print_genlmsg(&nlerr->msg, ethnl_umsg_desc,
ethnl_umsg_n_desc, err_offset);
}
return nlerr->error;
}
/**
* nlsock_process_reply() - process reply packet(s) from kernel
* @nlsk: netlink socket to read from
* @reply_cb: callback to process each message
* @data: pointer passed as argument to @reply_cb callback
*
* Read packets from kernel and pass reply messages to @reply_cb callback
* until an error is encountered or NLMSG_ERR message is received. In the
* latter case, return value is the error code extracted from it.
*
* Return: 0 on success or negative error code
*/
int nlsock_process_reply(struct nl_socket *nlsk, mnl_cb_t reply_cb, void *data)
{
struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
struct nlmsghdr *nlhdr;
ssize_t len;
char *buff;
int ret;
ret = msgbuff_realloc(msgbuff, NLSOCK_RECV_BUFFSIZE);
if (ret < 0)
return ret;
buff = msgbuff->buff;
do {
len = mnl_socket_recvfrom(nlsk->sk, buff, msgbuff->size);
if (len <= 0)
return (len ? -EFAULT : 0);
debug_msg(nlsk, buff, len, false);
if (len < NLMSG_HDRLEN)
return -EFAULT;
nlhdr = (struct nlmsghdr *)buff;
if (nlhdr->nlmsg_type == NLMSG_ERROR) {
unsigned int suppress = nlsk->nlctx->suppress_nlerr;
bool pretty;
pretty = debug_on(nlsk->nlctx->ctx->debug,
DEBUG_NL_PRETTY_MSG);
return nlsock_process_ack(nlhdr, len, suppress, pretty);
}
msgbuff->nlhdr = nlhdr;
msgbuff->genlhdr = mnl_nlmsg_get_payload(nlhdr);
msgbuff->payload =
mnl_nlmsg_get_payload_offset(nlhdr, GENL_HDRLEN);
ret = mnl_cb_run(buff, len, nlsk->seq, nlsk->port, reply_cb,
data);
} while (ret > 0);
return ret;
}
int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
uint16_t hdr_attrtype, u32 flags)
{
unsigned int nlm_flags = NLM_F_REQUEST | NLM_F_ACK;
struct nl_context *nlctx = nlsk->nlctx;
const char *devname = nlctx->ctx->devname;
int ret;
if (devname && !strcmp(devname, WILDCARD_DEVNAME)) {
devname = NULL;
nlm_flags |= NLM_F_DUMP;
}
nlctx->is_dump = !devname;
ret = msg_init(nlctx, &nlsk->msgbuff, nlcmd, nlm_flags);
if (ret < 0)
return ret;
if (ethnla_fill_header_phy(&nlsk->msgbuff, hdr_attrtype, devname,
nlctx->ctx->phy_index, flags))
return -EMSGSIZE;
return 0;
}
/**
* nlsock_prep_filtered_dump_request() - Initialize a filtered DUMP request
* @nlsk: netlink socket
* @nlcmd: netlink command
* @hdr_attrtype: netlink command header attribute
* @flags: netlink command header flags
*
* Prepare a DUMP request that may include the device index as a filtering
* attribute in the header.
*
* Return: 0 on success, or a negative number on error
*/
int nlsock_prep_filtered_dump_request(struct nl_socket *nlsk,
unsigned int nlcmd, uint16_t hdr_attrtype,
u32 flags)
{
struct nl_context *nlctx = nlsk->nlctx;
const char *devname = nlctx->ctx->devname;
unsigned int nlm_flags;
int ret;
nlctx->is_dump = true;
nlm_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
if (devname && !strcmp(devname, WILDCARD_DEVNAME))
devname = NULL;
ret = msg_init(nlctx, &nlsk->msgbuff, nlcmd, nlm_flags);
if (ret < 0)
return ret;
if (ethnla_fill_header(&nlsk->msgbuff, hdr_attrtype, devname, flags))
return -EMSGSIZE;
return 0;
}
#ifndef TEST_ETHTOOL
/**
* nlsock_sendmsg() - send a netlink message to kernel
* @nlsk: netlink socket
* @altbuff: alternative message buffer; if null, use buffer embedded in @nlsk
*
* Return: sent size or negative error code
*/
ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *altbuff)
{
struct nl_msg_buff *msgbuff = altbuff ?: &nlsk->msgbuff;
struct nlmsghdr *nlhdr = msgbuff->nlhdr;
nlhdr->nlmsg_seq = ++nlsk->seq;
debug_msg(nlsk, msgbuff->buff, nlhdr->nlmsg_len, true);
return mnl_socket_sendto(nlsk->sk, nlhdr, nlhdr->nlmsg_len);
}
#endif
/**
* nlsock_send_get_request() - send request and process reply
* @nlsk: netlink socket
* @cb: callback to process reply message(s)
*
* This simple helper only handles the most common case when the embedded
* message buffer is sent and @cb takes netlink context (struct nl_context)
* as last argument.
*/
int nlsock_send_get_request(struct nl_socket *nlsk, mnl_cb_t cb)
{
int ret;
ret = nlsock_sendmsg(nlsk, NULL);
if (ret < 0)
goto err;
ret = nlsock_process_reply(nlsk, cb, nlsk->nlctx);
if (ret == 0)
return 0;
err:
return nlsk->nlctx->exit_code ?: 1;
}
/**
* nlsock_init() - allocate and initialize netlink socket
* @nlctx: netlink context
* @__nlsk: store pointer to the allocated socket here
* @nl_fam: netlink family (e.g. NETLINK_GENERIC or NETLINK_ROUTE)
*
* Allocate and initialize netlink socket and its embedded message buffer.
* Cleans up on error, caller is responsible for destroying the socket with
* nlsock_done() on success.
*
* Return: 0 on success or negative error code
*/
int nlsock_init(struct nl_context *nlctx, struct nl_socket **__nlsk, int nl_fam)
{
struct nl_socket *nlsk;
int val;
int ret;
nlsk = calloc(1, sizeof(*nlsk));
if (!nlsk)
return -ENOMEM;
nlsk->nlctx = nlctx;
msgbuff_init(&nlsk->msgbuff);
ret = -ECONNREFUSED;
nlsk->sk = mnl_socket_open(nl_fam);
if (!nlsk->sk)
goto out_msgbuff;
val = 1;
mnl_socket_setsockopt(nlsk->sk, NETLINK_EXT_ACK, &val, sizeof(val));
ret = mnl_socket_bind(nlsk->sk, 0, MNL_SOCKET_AUTOPID);
if (ret < 0)
goto out_close;
nlsk->port = mnl_socket_get_portid(nlsk->sk);
nlsk->nl_fam = nl_fam;
*__nlsk = nlsk;
return 0;
out_close:
if (nlsk->sk)
mnl_socket_close(nlsk->sk);
out_msgbuff:
msgbuff_done(&nlsk->msgbuff);
free(nlsk);
return ret;
}
/**
* nlsock_done() - destroy a netlink socket
* @nlsk: netlink socket
*
* Close the socket and free the structure and related data.
*/
void nlsock_done(struct nl_socket *nlsk)
{
if (!nlsk)
return;
if (nlsk->sk)
mnl_socket_close(nlsk->sk);
msgbuff_done(&nlsk->msgbuff);
memset(nlsk, '\0', sizeof(*nlsk));
free(nlsk);
}