diff options
Diffstat (limited to 'sys/netlink/netlink_message_writer.c')
-rw-r--r-- | sys/netlink/netlink_message_writer.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/sys/netlink/netlink_message_writer.c b/sys/netlink/netlink_message_writer.c new file mode 100644 index 000000000000..8c5b3ec14058 --- /dev/null +++ b/sys/netlink/netlink_message_writer.c @@ -0,0 +1,399 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/rmlock.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/syslog.h> + +#include <netlink/netlink.h> +#include <netlink/netlink_ctl.h> +#include <netlink/netlink_linux.h> +#include <netlink/netlink_var.h> + +#define DEBUG_MOD_NAME nl_writer +#define DEBUG_MAX_LEVEL LOG_DEBUG3 +#include <netlink/netlink_debug.h> +_DECLARE_DEBUG(LOG_INFO); + +static bool +nlmsg_get_buf(struct nl_writer *nw, size_t len, bool waitok) +{ + const int mflag = waitok ? M_WAITOK : M_NOWAIT; + + MPASS(nw->buf == NULL); + + NL_LOG(LOG_DEBUG3, "Setting up nw %p len %zu %s", nw, len, + waitok ? "wait" : "nowait"); + + nw->buf = nl_buf_alloc(len, mflag); + if (__predict_false(nw->buf == NULL)) + return (false); + nw->hdr = NULL; + nw->malloc_flag = mflag; + nw->num_messages = 0; + nw->enomem = false; + + return (true); +} + +static bool +nl_send_one(struct nl_writer *nw) +{ + + return (nl_send(nw, nw->nlp)); +} + +bool +_nl_writer_unicast(struct nl_writer *nw, size_t size, struct nlpcb *nlp, + bool waitok) +{ + *nw = (struct nl_writer){ + .nlp = nlp, + .cb = nl_send_one, + }; + + return (nlmsg_get_buf(nw, size, waitok)); +} + +bool +_nl_writer_group(struct nl_writer *nw, size_t size, uint16_t protocol, + uint16_t group_id, int priv, bool waitok) +{ + *nw = (struct nl_writer){ + .group.proto = protocol, + .group.id = group_id, + .group.priv = priv, + .cb = nl_send_group, + }; + + return (nlmsg_get_buf(nw, size, waitok)); +} + +void +_nlmsg_ignore_limit(struct nl_writer *nw) +{ + nw->ignore_limit = true; +} + +bool +_nlmsg_flush(struct nl_writer *nw) +{ + bool result; + + if (__predict_false(nw->hdr != NULL)) { + /* Last message has not been completed, skip it. */ + int completed_len = (char *)nw->hdr - nw->buf->data; + /* Send completed messages */ + nw->buf->datalen -= nw->buf->datalen - completed_len; + nw->hdr = NULL; + } + + if (nw->buf->datalen == 0) { + MPASS(nw->num_messages == 0); + nl_buf_free(nw->buf); + nw->buf = NULL; + return (true); + } + + result = nw->cb(nw); + nw->num_messages = 0; + + if (!result) { + NL_LOG(LOG_DEBUG, "nw %p flush with %p() failed", nw, nw->cb); + } + + return (result); +} + +/* + * Flushes previous data and allocates new underlying storage + * sufficient for holding at least @required_len bytes. + * Return true on success. + */ +bool +_nlmsg_refill_buffer(struct nl_writer *nw, size_t required_len) +{ + struct nl_buf *new; + size_t completed_len, new_len, last_len; + + MPASS(nw->buf != NULL); + + if (nw->enomem) + return (false); + + NL_LOG(LOG_DEBUG3, "no space at offset %u/%u (want %zu), trying to " + "reclaim", nw->buf->datalen, nw->buf->buflen, required_len); + + /* Calculate new buffer size and allocate it. */ + completed_len = (nw->hdr != NULL) ? + (char *)nw->hdr - nw->buf->data : nw->buf->datalen; + if (completed_len > 0 && required_len < NLMBUFSIZE) { + /* We already ran out of space, use largest effective size. */ + new_len = max(nw->buf->buflen, NLMBUFSIZE); + } else { + if (nw->buf->buflen < NLMBUFSIZE) + /* XXXGL: does this happen? */ + new_len = NLMBUFSIZE; + else + new_len = nw->buf->buflen * 2; + while (new_len < required_len) + new_len *= 2; + } + + new = nl_buf_alloc(new_len, nw->malloc_flag | M_ZERO); + if (__predict_false(new == NULL)) { + nw->enomem = true; + NL_LOG(LOG_DEBUG, "getting new buf failed, setting ENOMEM"); + return (false); + } + + /* Copy last (unfinished) header to the new storage. */ + last_len = nw->buf->datalen - completed_len; + if (last_len > 0) { + memcpy(new->data, nw->hdr, last_len); + new->datalen = last_len; + } + + NL_LOG(LOG_DEBUG2, "completed: %zu bytes, copied: %zu bytes", + completed_len, last_len); + + if (completed_len > 0) { + nlmsg_flush(nw); + MPASS(nw->buf == NULL); + } else + nl_buf_free(nw->buf); + nw->buf = new; + nw->hdr = (last_len > 0) ? (struct nlmsghdr *)new->data : NULL; + NL_LOG(LOG_DEBUG2, "switched buffer: used %u/%u bytes", + new->datalen, new->buflen); + + return (true); +} + +bool +_nlmsg_add(struct nl_writer *nw, uint32_t portid, uint32_t seq, uint16_t type, + uint16_t flags, uint32_t len) +{ + struct nl_buf *nb = nw->buf; + struct nlmsghdr *hdr; + size_t required_len; + + MPASS(nw->hdr == NULL); + + required_len = NETLINK_ALIGN(len + sizeof(struct nlmsghdr)); + if (__predict_false(nb->datalen + required_len > nb->buflen)) { + if (!nlmsg_refill_buffer(nw, required_len)) + return (false); + nb = nw->buf; + } + + hdr = (struct nlmsghdr *)(&nb->data[nb->datalen]); + + hdr->nlmsg_len = len; + hdr->nlmsg_type = type; + hdr->nlmsg_flags = flags; + hdr->nlmsg_seq = seq; + hdr->nlmsg_pid = portid; + + nw->hdr = hdr; + nb->datalen += sizeof(struct nlmsghdr); + + return (true); +} + +bool +_nlmsg_end(struct nl_writer *nw) +{ + struct nl_buf *nb = nw->buf; + + MPASS(nw->hdr != NULL); + + if (nw->enomem) { + NL_LOG(LOG_DEBUG, "ENOMEM when dumping message"); + nlmsg_abort(nw); + return (false); + } + + nw->hdr->nlmsg_len = nb->data + nb->datalen - (char *)nw->hdr; + NL_LOG(LOG_DEBUG2, "wrote msg len: %u type: %d: flags: 0x%X seq: %u pid: %u", + nw->hdr->nlmsg_len, nw->hdr->nlmsg_type, nw->hdr->nlmsg_flags, + nw->hdr->nlmsg_seq, nw->hdr->nlmsg_pid); + nw->hdr = NULL; + nw->num_messages++; + return (true); +} + +void +_nlmsg_abort(struct nl_writer *nw) +{ + struct nl_buf *nb = nw->buf; + + if (nw->hdr != NULL) { + nb->datalen = (char *)nw->hdr - nb->data; + nw->hdr = NULL; + } +} + +void +nlmsg_ack(struct nlpcb *nlp, int error, struct nlmsghdr *hdr, + struct nl_pstate *npt) +{ + struct nlmsgerr *errmsg; + int payload_len; + uint32_t flags = nlp->nl_flags; + struct nl_writer *nw = npt->nw; + bool cap_ack; + + payload_len = sizeof(struct nlmsgerr); + + /* + * The only case when we send the full message in the + * reply is when there is an error and NETLINK_CAP_ACK + * is not set. + */ + cap_ack = (error == 0) || (flags & NLF_CAP_ACK); + if (!cap_ack) + payload_len += hdr->nlmsg_len - sizeof(struct nlmsghdr); + payload_len = NETLINK_ALIGN(payload_len); + + uint16_t nl_flags = cap_ack ? NLM_F_CAPPED : 0; + if ((npt->err_msg || npt->err_off) && nlp->nl_flags & NLF_EXT_ACK) + nl_flags |= NLM_F_ACK_TLVS; + + NL_LOG(LOG_DEBUG3, "acknowledging message type %d seq %d", + hdr->nlmsg_type, hdr->nlmsg_seq); + + if (!nlmsg_add(nw, nlp->nl_port, hdr->nlmsg_seq, NLMSG_ERROR, nl_flags, payload_len)) + goto enomem; + + errmsg = nlmsg_reserve_data(nw, payload_len, struct nlmsgerr); + errmsg->error = error; + /* In case of error copy the whole message, else just the header */ + memcpy(&errmsg->msg, hdr, cap_ack ? sizeof(*hdr) : hdr->nlmsg_len); + + if (npt->err_msg != NULL && nlp->nl_flags & NLF_EXT_ACK) + nlattr_add_string(nw, NLMSGERR_ATTR_MSG, npt->err_msg); + if (npt->err_off != 0 && nlp->nl_flags & NLF_EXT_ACK) + nlattr_add_u32(nw, NLMSGERR_ATTR_OFFS, npt->err_off); + if (npt->cookie != NULL) + nlattr_add_raw(nw, npt->cookie); + + if (nlmsg_end(nw)) + return; +enomem: + NLP_LOG(LOG_DEBUG, nlp, "error allocating ack data for message %d seq %u", + hdr->nlmsg_type, hdr->nlmsg_seq); + nlmsg_abort(nw); +} + +bool +_nlmsg_end_dump(struct nl_writer *nw, int error, struct nlmsghdr *hdr) +{ + if (!nlmsg_add(nw, hdr->nlmsg_pid, hdr->nlmsg_seq, NLMSG_DONE, 0, sizeof(int))) { + NL_LOG(LOG_DEBUG, "Error finalizing table dump"); + return (false); + } + /* Save operation result */ + int *perror = nlmsg_reserve_object(nw, int); + NL_LOG(LOG_DEBUG2, "record error=%d at off %d (%p)", error, + nw->buf->datalen, perror); + *perror = error; + nlmsg_end(nw); + nw->suppress_ack = true; + + return (true); +} + +/* + * KPI functions. + */ + +u_int +nlattr_save_offset(const struct nl_writer *nw) +{ + return (nw->buf->datalen - ((char *)nw->hdr - nw->buf->data)); +} + +void * +nlmsg_reserve_data_raw(struct nl_writer *nw, size_t sz) +{ + struct nl_buf *nb = nw->buf; + void *data; + + sz = NETLINK_ALIGN(sz); + if (__predict_false(nb->datalen + sz > nb->buflen)) { + if (!nlmsg_refill_buffer(nw, sz)) + return (NULL); + nb = nw->buf; + } + + data = &nb->data[nb->datalen]; + bzero(data, sz); + nb->datalen += sz; + + return (data); +} + +bool +nlattr_add(struct nl_writer *nw, uint16_t attr_type, uint16_t attr_len, + const void *data) +{ + struct nl_buf *nb = nw->buf; + struct nlattr *nla; + size_t required_len; + + KASSERT(attr_len <= UINT16_MAX - sizeof(struct nlattr), + ("%s: invalid attribute length %u", __func__, attr_len)); + + required_len = NLA_ALIGN(attr_len + sizeof(struct nlattr)); + if (__predict_false(nb->datalen + required_len > nb->buflen)) { + if (!nlmsg_refill_buffer(nw, required_len)) + return (false); + nb = nw->buf; + } + + nla = (struct nlattr *)(&nb->data[nb->datalen]); + + nla->nla_len = attr_len + sizeof(struct nlattr); + nla->nla_type = attr_type; + if (attr_len > 0) { + if ((attr_len % 4) != 0) { + /* clear padding bytes */ + bzero((char *)nla + required_len - 4, 4); + } + memcpy((nla + 1), data, attr_len); + } + nb->datalen += required_len; + return (true); +} + +#include <netlink/ktest_netlink_message_writer.h> |