/* * msgbuff.c - netlink message buffer * * Data structures and code for flexible message buffer abstraction. */ #include #include #include #include #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; }