blob: 216f5b94623656cd2048e466d7a526239e18da53 [file] [log] [blame]
/*
* msgbuff.c - netlink message buffer
*
* Data structures and code for flexible message buffer abstraction.
*/
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include "../internal.h"
#include "netlink.h"
#include "msgbuff.h"
#define MAX_MSG_SIZE (4 << 20) /* 4 MB */
/**
* msgbuff_realloc() - reallocate buffer if needed
* @msgbuff: message buffer
* @new_size: requested minimum size (add MNL_SOCKET_BUFFER_SIZE if zero)
*
* Make sure allocated buffer has size at least @new_size. If @new_size is
* shorter than current size, do nothing. If @new_size is 0, grow buffer by
* MNL_SOCKET_BUFFER_SIZE. Fail if new size would exceed MAX_MSG_SIZE.
*
* Return: 0 on success or negative error code
*/
int msgbuff_realloc(struct nl_msg_buff *msgbuff, unsigned int new_size)
{
unsigned int nlhdr_off, genlhdr_off, payload_off;
unsigned int old_size = msgbuff->size;
char *nbuff;
nlhdr_off = (char *)msgbuff->nlhdr - msgbuff->buff;
genlhdr_off = (char *)msgbuff->genlhdr - msgbuff->buff;
payload_off = (char *)msgbuff->payload - msgbuff->buff;
if (!new_size)
new_size = old_size + MNL_SOCKET_BUFFER_SIZE;
if (new_size <= old_size)
return 0;
if (new_size > MAX_MSG_SIZE)
return -EMSGSIZE;
nbuff = realloc(msgbuff->buff, new_size);
if (!nbuff) {
msgbuff->buff = NULL;
msgbuff->size = 0;
msgbuff->left = 0;
return -ENOMEM;
}
if (nbuff != msgbuff->buff) {
if (new_size > old_size)
memset(nbuff + old_size, '\0', new_size - old_size);
msgbuff->nlhdr = (struct nlmsghdr *)(nbuff + nlhdr_off);
msgbuff->genlhdr = (struct genlmsghdr *)(nbuff + genlhdr_off);
msgbuff->payload = nbuff + payload_off;
msgbuff->buff = nbuff;
}
msgbuff->size = new_size;
msgbuff->left += (new_size - old_size);
return 0;
}
/**
* msgbuff_append() - add contents of another message buffer
* @dest: target message buffer
* @src: source message buffer
*
* Append contents of @src at the end of @dest. Fail if target buffer cannot
* be reallocated to sufficient size.
*
* Return: 0 on success or negative error code.
*/
int msgbuff_append(struct nl_msg_buff *dest, struct nl_msg_buff *src)
{
unsigned int src_len = mnl_nlmsg_get_payload_len(src->nlhdr);
unsigned int dest_len = MNL_ALIGN(msgbuff_len(dest));
int ret;
src_len -= GENL_HDRLEN;
ret = msgbuff_realloc(dest, dest_len + src_len);
if (ret < 0)
return ret;
memcpy(mnl_nlmsg_get_payload_tail(dest->nlhdr), src->payload, src_len);
msgbuff_reset(dest, dest_len + src_len);
return 0;
}
/**
* ethnla_put - write a netlink attribute to message buffer
* @msgbuff: message buffer
* @type: attribute type
* @len: attribute payload length
* @data: attribute payload
*
* Appends a netlink attribute with header to message buffer, reallocates
* if needed. This is mostly used via specific ethnla_put_* wrappers for
* basic data types.
*
* Return: false on success, true on error (reallocation failed)
*/
bool ethnla_put(struct nl_msg_buff *msgbuff, uint16_t type, size_t len,
const void *data)
{
struct nlmsghdr *nlhdr = msgbuff->nlhdr;
while (!mnl_attr_put_check(nlhdr, msgbuff->left, type, len, data)) {
int ret = msgbuff_realloc(msgbuff, 0);
if (ret < 0)
return true;
}
return false;
}
/**
* ethnla_nest_start - start a nested attribute
* @msgbuff: message buffer
* @type: nested attribute type (NLA_F_NESTED is added automatically)
*
* Return: pointer to the nest attribute or null of error
*/
struct nlattr *ethnla_nest_start(struct nl_msg_buff *msgbuff, uint16_t type)
{
struct nlmsghdr *nlhdr = msgbuff->nlhdr;
struct nlattr *attr;
do {
attr = mnl_attr_nest_start_check(nlhdr, msgbuff->left, type);
if (attr)
return attr;
} while (msgbuff_realloc(msgbuff, 0) == 0);
return NULL;
}
/**
* ethnla_fill_header() - write standard ethtool request header to message
* @msgbuff: message buffer
* @type: attribute type for header nest
* @devname: device name (NULL to omit)
* @flags: request flags (omitted if 0)
*
* Return: pointer to the nest attribute or null of error
*/
bool ethnla_fill_header(struct nl_msg_buff *msgbuff, uint16_t type,
const char *devname, uint32_t flags)
{
struct nlattr *nest;
nest = ethnla_nest_start(msgbuff, type);
if (!nest)
return true;
if ((devname &&
ethnla_put_strz(msgbuff, ETHTOOL_A_HEADER_DEV_NAME, devname)) ||
(flags &&
ethnla_put_u32(msgbuff, ETHTOOL_A_HEADER_FLAGS, flags)))
goto err;
ethnla_nest_end(msgbuff, nest);
return false;
err:
ethnla_nest_cancel(msgbuff, nest);
return true;
}
/**
* __msg_init() - init a genetlink message, fill netlink and genetlink header
* @msgbuff: message buffer
* @family: genetlink family
* @cmd: genetlink command (genlmsghdr::cmd)
* @flags: netlink flags (nlmsghdr::nlmsg_flags)
* @version: genetlink family version (genlmsghdr::version)
*
* Initialize a new genetlink message, fill netlink and genetlink header and
* set pointers in struct nl_msg_buff.
*
* Return: 0 on success or negative error code.
*/
int __msg_init(struct nl_msg_buff *msgbuff, int family, int cmd,
unsigned int flags, int version)
{
struct nlmsghdr *nlhdr;
struct genlmsghdr *genlhdr;
int ret;
ret = msgbuff_realloc(msgbuff, MNL_SOCKET_BUFFER_SIZE);
if (ret < 0)
return ret;
memset(msgbuff->buff, '\0', NLMSG_HDRLEN + GENL_HDRLEN);
nlhdr = mnl_nlmsg_put_header(msgbuff->buff);
nlhdr->nlmsg_type = family;
nlhdr->nlmsg_flags = flags;
msgbuff->nlhdr = nlhdr;
genlhdr = mnl_nlmsg_put_extra_header(nlhdr, sizeof(*genlhdr));
genlhdr->cmd = cmd;
genlhdr->version = version;
msgbuff->genlhdr = genlhdr;
msgbuff->payload = mnl_nlmsg_get_payload_offset(nlhdr, GENL_HDRLEN);
return 0;
}
/**
* msg_init() - init an ethtool netlink message
* @msgbuff: message buffer
* @cmd: genetlink command (genlmsghdr::cmd)
* @flags: netlink flags (nlmsghdr::nlmsg_flags)
*
* Initialize a new ethtool netlink message, fill netlink and genetlink header
* and set pointers in struct nl_msg_buff.
*
* Return: 0 on success or negative error code.
*/
int msg_init(struct nl_context *nlctx, struct nl_msg_buff *msgbuff, int cmd,
unsigned int flags)
{
return __msg_init(msgbuff, nlctx->ethnl_fam, cmd, flags,
ETHTOOL_GENL_VERSION);
}
/**
* msgbuff_init() - initialize a message buffer
* @msgbuff: message buffer
*
* Initialize a message buffer structure before first use. Buffer length is
* set to zero and the buffer is not allocated until the first call to
* msgbuff_reallocate().
*/
void msgbuff_init(struct nl_msg_buff *msgbuff)
{
memset(msgbuff, '\0', sizeof(*msgbuff));
}
/**
* msg_done() - destroy a message buffer
* @msgbuff: message buffer
*
* Free the buffer and reset size and remaining size.
*/
void msgbuff_done(struct nl_msg_buff *msgbuff)
{
free(msgbuff->buff);
msgbuff->buff = NULL;
msgbuff->size = 0;
msgbuff->left = 0;
}