Path: blob/main/sys/netlink/netlink_message_writer.c
39475 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2022 Alexander V. Chernikov <[email protected]>4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <sys/param.h>28#include <sys/malloc.h>29#include <sys/lock.h>30#include <sys/rmlock.h>31#include <sys/mbuf.h>32#include <sys/socket.h>33#include <sys/socketvar.h>34#include <sys/syslog.h>3536#include <netlink/netlink.h>37#include <netlink/netlink_ctl.h>38#include <netlink/netlink_linux.h>39#include <netlink/netlink_var.h>4041#define DEBUG_MOD_NAME nl_writer42#define DEBUG_MAX_LEVEL LOG_DEBUG343#include <netlink/netlink_debug.h>44_DECLARE_DEBUG(LOG_INFO);4546static bool47nlmsg_get_buf(struct nl_writer *nw, size_t len, bool waitok)48{49const int mflag = waitok ? M_WAITOK : M_NOWAIT;5051MPASS(nw->buf == NULL);5253NL_LOG(LOG_DEBUG3, "Setting up nw %p len %zu %s", nw, len,54waitok ? "wait" : "nowait");5556nw->buf = nl_buf_alloc(len, mflag);57if (__predict_false(nw->buf == NULL))58return (false);59nw->hdr = NULL;60nw->malloc_flag = mflag;61nw->num_messages = 0;62nw->enomem = false;6364return (true);65}6667static bool68nl_send_one(struct nl_writer *nw)69{7071return (nl_send(nw, nw->nlp));72}7374bool75_nl_writer_unicast(struct nl_writer *nw, size_t size, struct nlpcb *nlp,76bool waitok)77{78*nw = (struct nl_writer){79.nlp = nlp,80.cb = nl_send_one,81};8283return (nlmsg_get_buf(nw, size, waitok));84}8586bool87_nl_writer_group(struct nl_writer *nw, size_t size, uint16_t protocol,88uint16_t group_id, int priv, bool waitok)89{90*nw = (struct nl_writer){91.group.proto = protocol,92.group.id = group_id,93.group.priv = priv,94.cb = nl_send_group,95};9697return (nlmsg_get_buf(nw, size, waitok));98}99100void101_nlmsg_ignore_limit(struct nl_writer *nw)102{103nw->ignore_limit = true;104}105106bool107_nlmsg_flush(struct nl_writer *nw)108{109bool result;110111if (__predict_false(nw->hdr != NULL)) {112/* Last message has not been completed, skip it. */113int completed_len = (char *)nw->hdr - nw->buf->data;114/* Send completed messages */115nw->buf->datalen -= nw->buf->datalen - completed_len;116nw->hdr = NULL;117}118119if (nw->buf->datalen == 0) {120MPASS(nw->num_messages == 0);121nl_buf_free(nw->buf);122nw->buf = NULL;123return (true);124}125126result = nw->cb(nw);127nw->num_messages = 0;128129if (!result) {130NL_LOG(LOG_DEBUG, "nw %p flush with %p() failed", nw, nw->cb);131}132133return (result);134}135136/*137* Flushes previous data and allocates new underlying storage138* sufficient for holding at least @required_len bytes.139* Return true on success.140*/141bool142_nlmsg_refill_buffer(struct nl_writer *nw, size_t required_len)143{144struct nl_buf *new;145size_t completed_len, new_len, last_len;146147MPASS(nw->buf != NULL);148149if (nw->enomem)150return (false);151152NL_LOG(LOG_DEBUG3, "no space at offset %u/%u (want %zu), trying to "153"reclaim", nw->buf->datalen, nw->buf->buflen, required_len);154155/* Calculate new buffer size and allocate it. */156completed_len = (nw->hdr != NULL) ?157(char *)nw->hdr - nw->buf->data : nw->buf->datalen;158if (completed_len > 0 && required_len < NLMBUFSIZE) {159/* We already ran out of space, use largest effective size. */160new_len = max(nw->buf->buflen, NLMBUFSIZE);161} else {162if (nw->buf->buflen < NLMBUFSIZE)163/* XXXGL: does this happen? */164new_len = NLMBUFSIZE;165else166new_len = nw->buf->buflen * 2;167while (new_len < required_len)168new_len *= 2;169}170171new = nl_buf_alloc(new_len, nw->malloc_flag | M_ZERO);172if (__predict_false(new == NULL)) {173nw->enomem = true;174NL_LOG(LOG_DEBUG, "getting new buf failed, setting ENOMEM");175return (false);176}177178/* Copy last (unfinished) header to the new storage. */179last_len = nw->buf->datalen - completed_len;180if (last_len > 0) {181memcpy(new->data, nw->hdr, last_len);182new->datalen = last_len;183}184185NL_LOG(LOG_DEBUG2, "completed: %zu bytes, copied: %zu bytes",186completed_len, last_len);187188if (completed_len > 0) {189nlmsg_flush(nw);190MPASS(nw->buf == NULL);191} else192nl_buf_free(nw->buf);193nw->buf = new;194nw->hdr = (last_len > 0) ? (struct nlmsghdr *)new->data : NULL;195NL_LOG(LOG_DEBUG2, "switched buffer: used %u/%u bytes",196new->datalen, new->buflen);197198return (true);199}200201bool202_nlmsg_add(struct nl_writer *nw, uint32_t portid, uint32_t seq, uint16_t type,203uint16_t flags, uint32_t len)204{205struct nl_buf *nb = nw->buf;206struct nlmsghdr *hdr;207size_t required_len;208209MPASS(nw->hdr == NULL);210211required_len = NETLINK_ALIGN(len + sizeof(struct nlmsghdr));212if (__predict_false(nb->datalen + required_len > nb->buflen)) {213if (!nlmsg_refill_buffer(nw, required_len))214return (false);215nb = nw->buf;216}217218hdr = (struct nlmsghdr *)(&nb->data[nb->datalen]);219220hdr->nlmsg_len = len;221hdr->nlmsg_type = type;222hdr->nlmsg_flags = flags;223hdr->nlmsg_seq = seq;224hdr->nlmsg_pid = portid;225226nw->hdr = hdr;227nb->datalen += sizeof(struct nlmsghdr);228229return (true);230}231232bool233_nlmsg_end(struct nl_writer *nw)234{235struct nl_buf *nb = nw->buf;236237MPASS(nw->hdr != NULL);238239if (nw->enomem) {240NL_LOG(LOG_DEBUG, "ENOMEM when dumping message");241nlmsg_abort(nw);242return (false);243}244245nw->hdr->nlmsg_len = nb->data + nb->datalen - (char *)nw->hdr;246NL_LOG(LOG_DEBUG2, "wrote msg len: %u type: %d: flags: 0x%X seq: %u pid: %u",247nw->hdr->nlmsg_len, nw->hdr->nlmsg_type, nw->hdr->nlmsg_flags,248nw->hdr->nlmsg_seq, nw->hdr->nlmsg_pid);249nw->hdr = NULL;250nw->num_messages++;251return (true);252}253254void255_nlmsg_abort(struct nl_writer *nw)256{257struct nl_buf *nb = nw->buf;258259if (nw->hdr != NULL) {260nb->datalen = (char *)nw->hdr - nb->data;261nw->hdr = NULL;262}263}264265void266nlmsg_ack(struct nlpcb *nlp, int error, struct nlmsghdr *hdr,267struct nl_pstate *npt)268{269struct nlmsgerr *errmsg;270int payload_len;271uint32_t flags = nlp->nl_flags;272struct nl_writer *nw = npt->nw;273bool cap_ack;274275payload_len = sizeof(struct nlmsgerr);276277/*278* The only case when we send the full message in the279* reply is when there is an error and NETLINK_CAP_ACK280* is not set.281*/282cap_ack = (error == 0) || (flags & NLF_CAP_ACK);283if (!cap_ack)284payload_len += hdr->nlmsg_len - sizeof(struct nlmsghdr);285payload_len = NETLINK_ALIGN(payload_len);286287uint16_t nl_flags = cap_ack ? NLM_F_CAPPED : 0;288if ((npt->err_msg || npt->err_off) && nlp->nl_flags & NLF_EXT_ACK)289nl_flags |= NLM_F_ACK_TLVS;290291NL_LOG(LOG_DEBUG3, "acknowledging message type %d seq %d",292hdr->nlmsg_type, hdr->nlmsg_seq);293294if (!nlmsg_add(nw, nlp->nl_port, hdr->nlmsg_seq, NLMSG_ERROR, nl_flags, payload_len))295goto enomem;296297errmsg = nlmsg_reserve_data(nw, payload_len, struct nlmsgerr);298errmsg->error = error;299/* In case of error copy the whole message, else just the header */300memcpy(&errmsg->msg, hdr, cap_ack ? sizeof(*hdr) : hdr->nlmsg_len);301302if (npt->err_msg != NULL && nlp->nl_flags & NLF_EXT_ACK)303nlattr_add_string(nw, NLMSGERR_ATTR_MSG, npt->err_msg);304if (npt->err_off != 0 && nlp->nl_flags & NLF_EXT_ACK)305nlattr_add_u32(nw, NLMSGERR_ATTR_OFFS, npt->err_off);306if (npt->cookie != NULL)307nlattr_add_raw(nw, npt->cookie);308309if (nlmsg_end(nw))310return;311enomem:312NLP_LOG(LOG_DEBUG, nlp, "error allocating ack data for message %d seq %u",313hdr->nlmsg_type, hdr->nlmsg_seq);314nlmsg_abort(nw);315}316317bool318_nlmsg_end_dump(struct nl_writer *nw, int error, struct nlmsghdr *hdr)319{320if (!nlmsg_add(nw, hdr->nlmsg_pid, hdr->nlmsg_seq, NLMSG_DONE, 0, sizeof(int))) {321NL_LOG(LOG_DEBUG, "Error finalizing table dump");322return (false);323}324/* Save operation result */325int *perror = nlmsg_reserve_object(nw, int);326NL_LOG(LOG_DEBUG2, "record error=%d at off %d (%p)", error,327nw->buf->datalen, perror);328*perror = error;329nlmsg_end(nw);330nw->suppress_ack = true;331332return (true);333}334335/*336* KPI functions.337*/338339u_int340nlattr_save_offset(const struct nl_writer *nw)341{342return (nw->buf->datalen - ((char *)nw->hdr - nw->buf->data));343}344345void *346nlmsg_reserve_data_raw(struct nl_writer *nw, size_t sz)347{348struct nl_buf *nb = nw->buf;349void *data;350351sz = NETLINK_ALIGN(sz);352if (__predict_false(nb->datalen + sz > nb->buflen)) {353if (!nlmsg_refill_buffer(nw, sz))354return (NULL);355nb = nw->buf;356}357358data = &nb->data[nb->datalen];359bzero(data, sz);360nb->datalen += sz;361362return (data);363}364365bool366nlattr_add(struct nl_writer *nw, uint16_t attr_type, uint16_t attr_len,367const void *data)368{369struct nl_buf *nb = nw->buf;370struct nlattr *nla;371size_t required_len;372373KASSERT(attr_len <= UINT16_MAX - sizeof(struct nlattr),374("%s: invalid attribute length %u", __func__, attr_len));375376required_len = NLA_ALIGN(attr_len + sizeof(struct nlattr));377if (__predict_false(nb->datalen + required_len > nb->buflen)) {378if (!nlmsg_refill_buffer(nw, required_len))379return (false);380nb = nw->buf;381}382383nla = (struct nlattr *)(&nb->data[nb->datalen]);384385nla->nla_len = attr_len + sizeof(struct nlattr);386nla->nla_type = attr_type;387if (attr_len > 0) {388if ((attr_len % 4) != 0) {389/* clear padding bytes */390bzero((char *)nla + required_len - 4, 4);391}392memcpy((nla + 1), data, attr_len);393}394nb->datalen += required_len;395return (true);396}397398#include <netlink/ktest_netlink_message_writer.h>399400401