Changeset View
Changeset View
Standalone View
Standalone View
sys/netlink/netlink_message.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2022 Alexander V. Chernikov | |||||
* | |||||
* 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/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/rmlock.h> | |||||
#include <sys/mbuf.h> | |||||
#include <sys/ck.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_message | |||||
#define DEBUG_MAX_LEVEL LOG_DEBUG3 | |||||
#include <netlink/netlink_debug.h> | |||||
_DECLARE_DEBUG(LOG_DEBUG); | |||||
/* | |||||
* The goal of this file is to provide convenient message writing KPI on top of | |||||
* different storage methods (mbufs, uio, temporary memory chunjs). | |||||
* | |||||
* The main KPI guarantee is the the (last) message always resides in the contiguous | |||||
* memory buffer, so one is able to update the header after writing the entire message. | |||||
* | |||||
* This guarantee comes with a side effect of potentially reallocating underlying buffer, | |||||
* so one needs to update the desired pointers before using them if something was added | |||||
* to the header. | |||||
*/ | |||||
typedef bool nlwriter_op_init(struct nlmsg_state *ns, int size, bool waitok); | |||||
typedef bool nlwriter_op_write(struct nlmsg_state *ns, void *buf, int buflen, int cnt); | |||||
struct nlwriter_ops { | |||||
nlwriter_op_init *init; | |||||
nlwriter_op_write *write_socket; | |||||
nlwriter_op_write *write_group; | |||||
nlwriter_op_write *write_chain; | |||||
}; | |||||
/* | |||||
* NS_WRITER_TYPE_BUF | |||||
* Writes message to a temporary memory buffer, | |||||
* flushing to the socket/group when buffer size limit is reached | |||||
*/ | |||||
static bool | |||||
nlmsg_get_ns_buf(struct nlmsg_state *ns, int size, bool waitok) | |||||
{ | |||||
int mflag = waitok ? M_WAITOK : M_NOWAIT; | |||||
ns->_storage = malloc(size, M_NETLINK, mflag | M_ZERO); | |||||
if (__predict_false(ns->_storage == NULL)) | |||||
return (false); | |||||
ns->alloc_len = size; | |||||
ns->offset = 0; | |||||
ns->hdr = NULL; | |||||
ns->data = ns->_storage; | |||||
ns->writer_type = NS_WRITER_TYPE_BUF; | |||||
ns->malloc_flag = mflag; | |||||
ns->num_messages = 0; | |||||
return (true); | |||||
} | |||||
static bool | |||||
nlmsg_write_socket_buf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
RT_LOG(LOG_DEBUG2, "IN: ptr: %p len: %d arg: %p", buf, datalen, ns); | |||||
if (__predict_false(datalen == 0)) { | |||||
free(buf, M_NETLINK); | |||||
return (true); | |||||
} | |||||
struct mbuf *m = m_getm2(NULL, datalen, ns->malloc_flag, MT_DATA, M_PKTHDR); | |||||
if (__predict_false(m == NULL)) { | |||||
/* XXX: should we set sorcverr? */ | |||||
free(buf, M_NETLINK); | |||||
return (false); | |||||
} | |||||
m_append(m, datalen, buf); | |||||
free(buf, M_NETLINK); | |||||
int io_flags = (ns->ignore_limit) ? NL_IOF_IGNORE_LIMIT : 0; | |||||
return (nl_send_one(m, (struct nlpcb *)(ns->arg), cnt, io_flags)); | |||||
} | |||||
static bool | |||||
nlmsg_write_group_buf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
RT_LOG(LOG_DEBUG2, "IN: ptr: %p len: %d arg: %p", buf, datalen, ns->arg); | |||||
if (__predict_false(datalen == 0)) { | |||||
free(buf, M_NETLINK); | |||||
return (true); | |||||
} | |||||
struct mbuf *m = m_getm2(NULL, datalen, ns->malloc_flag, MT_DATA, M_PKTHDR); | |||||
if (__predict_false(m == NULL)) { | |||||
free(buf, M_NETLINK); | |||||
return (false); | |||||
} | |||||
bool success = m_append(m, datalen, buf) != 0; | |||||
free(buf, M_NETLINK); | |||||
if (!success) | |||||
return (false); | |||||
nl_send_group(m, cnt, (uint32_t)(uintptr_t)(ns->arg)); | |||||
return (true); | |||||
} | |||||
static bool | |||||
nlmsg_write_chain_buf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
struct mbuf **m0 = (struct mbuf **)(ns->arg); | |||||
RT_LOG(LOG_DEBUG2, "IN: ptr: %p len: %d arg: %p", buf, datalen, ns->arg); | |||||
if (__predict_false(datalen == 0)) { | |||||
free(buf, M_NETLINK); | |||||
return (true); | |||||
} | |||||
if (*m0 == NULL) { | |||||
struct mbuf *m; | |||||
m = m_getm2(NULL, datalen, ns->malloc_flag, MT_DATA, M_PKTHDR); | |||||
if (__predict_false(m == NULL)) { | |||||
free(buf, M_NETLINK); | |||||
return (false); | |||||
} | |||||
*m0 = m; | |||||
} | |||||
if (__predict_false(m_append(*m0, datalen, buf) == 0)) { | |||||
free(buf, M_NETLINK); | |||||
return (false); | |||||
} | |||||
return (true); | |||||
} | |||||
/* | |||||
* NS_WRITER_TYPE_MBUF | |||||
* Writes message to the allocated mbuf, | |||||
* flushing to socket/group when mbuf size limit is reached. | |||||
* This is the most efficient mechanism as it avoids double-copying. | |||||
* | |||||
* Allocates a single mbuf suitable to store up to @size bytes of data. | |||||
* If size < MHLEN (around 160 bytes), allocates mbuf with pkghdr | |||||
* If size <= MCLBYTES (2k), allocate a single mbuf cluster | |||||
* Otherwise, return NULL. | |||||
*/ | |||||
static bool | |||||
nlmsg_get_ns_mbuf(struct nlmsg_state *ns, int size, bool waitok) | |||||
{ | |||||
struct mbuf *m; | |||||
int mflag = waitok ? M_WAITOK : M_NOWAIT; | |||||
m = m_get2(size, mflag, MT_DATA, M_PKTHDR); | |||||
if (__predict_false(m == NULL)) | |||||
return (false); | |||||
ns->alloc_len = M_TRAILINGSPACE(m); | |||||
ns->offset = 0; | |||||
ns->hdr = NULL; | |||||
ns->_storage = (void *)m; | |||||
ns->data = mtod(m, void *); | |||||
ns->writer_type = NS_WRITER_TYPE_MBUF; | |||||
ns->malloc_flag = mflag; | |||||
ns->num_messages = 0; | |||||
RT_LOG(LOG_DEBUG2, "alloc mbuf %p req_len %d alloc_len %d data_ptr %p", | |||||
m, size, ns->alloc_len, ns->data); | |||||
return (true); | |||||
} | |||||
static bool | |||||
nlmsg_write_socket_mbuf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
struct mbuf *m = (struct mbuf *)buf; | |||||
RT_LOG(LOG_DEBUG2, "IN: ptr: %p len: %d arg: %p", buf, datalen, ns->arg); | |||||
if (__predict_false(datalen == 0)) { | |||||
m_freem(m); | |||||
return (true); | |||||
} | |||||
m->m_pkthdr.len = datalen; | |||||
m->m_len = datalen; | |||||
int io_flags = (ns->ignore_limit) ? NL_IOF_IGNORE_LIMIT : 0; | |||||
return (nl_send_one(m, (struct nlpcb *)(ns->arg), cnt, io_flags)); | |||||
} | |||||
static bool | |||||
nlmsg_write_group_mbuf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
struct mbuf *m = (struct mbuf *)buf; | |||||
RT_LOG(LOG_DEBUG2, "IN: ptr: %p len: %d arg: %p", buf, datalen, ns->arg); | |||||
if (__predict_false(datalen == 0)) { | |||||
m_freem(m); | |||||
return (true); | |||||
} | |||||
m->m_pkthdr.len = datalen; | |||||
m->m_len = datalen; | |||||
nl_send_group(m, cnt, (uint32_t)(uintptr_t)(ns->arg)); | |||||
return (true); | |||||
} | |||||
static bool | |||||
nlmsg_write_chain_mbuf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
struct mbuf *m_new = (struct mbuf *)buf; | |||||
struct mbuf **m0 = (struct mbuf **)(ns->arg); | |||||
RT_LOG(LOG_DEBUG2, "IN: ptr: %p len: %d arg: %p", buf, datalen, ns->arg); | |||||
if (__predict_false(datalen == 0)) { | |||||
m_freem(m_new); | |||||
return (true); | |||||
} | |||||
m_new->m_pkthdr.len = datalen; | |||||
m_new->m_len = datalen; | |||||
if (*m0 == NULL) { | |||||
*m0 = m_new; | |||||
} else { | |||||
struct mbuf *m_last; | |||||
for (m_last = *m0; m_last->m_next != NULL; m_last = m_last->m_next) | |||||
; | |||||
m_last->m_next = m_new; | |||||
(*m0)->m_pkthdr.len += datalen; | |||||
} | |||||
return (true); | |||||
} | |||||
/* | |||||
* NS_WRITER_TYPE_LBUF | |||||
* Writes message to the allocated memory buffer, | |||||
* flushing to socket/group when mbuf size limit is reached. | |||||
* Calls linux handler to rewrite messages before sending to the socket. | |||||
*/ | |||||
static bool | |||||
nlmsg_get_ns_lbuf(struct nlmsg_state *ns, int size, bool waitok) | |||||
{ | |||||
int mflag = waitok ? M_WAITOK : M_NOWAIT; | |||||
size = roundup2(size, sizeof(void *)); | |||||
int add_size = sizeof(struct linear_buffer) + SCRATCH_BUFFER_SIZE; | |||||
char *buf = malloc(add_size + size * 2, M_NETLINK, mflag | M_ZERO); | |||||
if (__predict_false(buf == NULL)) | |||||
return (false); | |||||
/* Fill buffer header first */ | |||||
struct linear_buffer *lb = (struct linear_buffer *)buf; | |||||
lb->base = &buf[sizeof(struct linear_buffer) + size]; | |||||
lb->size = size + SCRATCH_BUFFER_SIZE; | |||||
ns->alloc_len = size; | |||||
ns->offset = 0; | |||||
ns->hdr = NULL; | |||||
ns->_storage = buf; | |||||
ns->data = (char *)(lb + 1); | |||||
ns->malloc_flag = mflag; | |||||
ns->writer_type = NS_WRITER_TYPE_LBUF; | |||||
ns->num_messages = 0; | |||||
return (true); | |||||
} | |||||
static bool | |||||
nlmsg_write_socket_lbuf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
struct linear_buffer *lb = (struct linear_buffer *)buf; | |||||
char *data = (char *)(lb + 1); | |||||
struct nlpcb *nlp = (struct nlpcb *)(ns->arg); | |||||
if (__predict_false(datalen == 0)) { | |||||
free(buf, M_NETLINK); | |||||
return (true); | |||||
} | |||||
struct mbuf *m = NULL; | |||||
if (linux_netlink_p != NULL) | |||||
m = linux_netlink_p->msgs_to_linux(nlp->nl_proto, data, datalen, nlp); | |||||
free(buf, M_NETLINK); | |||||
if (__predict_false(m == NULL)) { | |||||
/* XXX: should we set sorcverr? */ | |||||
return (false); | |||||
} | |||||
int io_flags = (ns->ignore_limit) ? NL_IOF_IGNORE_LIMIT : 0; | |||||
return (nl_send_one(m, nlp, cnt, io_flags)); | |||||
} | |||||
/* Shouldn't be called (maybe except Linux code originating message) */ | |||||
static bool | |||||
nlmsg_write_group_lbuf(struct nlmsg_state *ns, void *buf, int datalen, int cnt) | |||||
{ | |||||
struct linear_buffer *lb = (struct linear_buffer *)buf; | |||||
char *data = (char *)(lb + 1); | |||||
if (__predict_false(datalen == 0)) { | |||||
free(buf, M_NETLINK); | |||||
return (true); | |||||
} | |||||
struct mbuf *m = m_getm2(NULL, datalen, ns->malloc_flag, MT_DATA, M_PKTHDR); | |||||
if (__predict_false(m == NULL)) { | |||||
free(buf, M_NETLINK); | |||||
return (false); | |||||
} | |||||
m_append(m, datalen, data); | |||||
free(buf, M_NETLINK); | |||||
nl_send_group(m, cnt, (uint32_t)(uintptr_t)(ns->arg)); | |||||
return (true); | |||||
} | |||||
struct nlwriter_ops nlmsg_writers[] = { | |||||
/* NS_WRITER_TYPE_MBUF */ | |||||
{ | |||||
.init = nlmsg_get_ns_mbuf, | |||||
.write_socket = nlmsg_write_socket_mbuf, | |||||
.write_group = nlmsg_write_group_mbuf, | |||||
.write_chain = nlmsg_write_chain_mbuf, | |||||
}, | |||||
/* NS_WRITER_TYPE_BUF */ | |||||
{ | |||||
.init = nlmsg_get_ns_buf, | |||||
.write_socket = nlmsg_write_socket_buf, | |||||
.write_group = nlmsg_write_group_buf, | |||||
.write_chain = nlmsg_write_chain_buf, | |||||
}, | |||||
/* NS_WRITER_TYPE_LBUF */ | |||||
{ | |||||
.init = nlmsg_get_ns_lbuf, | |||||
.write_socket = nlmsg_write_socket_lbuf, | |||||
.write_group = nlmsg_write_group_lbuf, | |||||
}, | |||||
}; | |||||
static void | |||||
nlmsg_set_callback(struct nlmsg_state *ns) | |||||
{ | |||||
struct nlwriter_ops *pops = &nlmsg_writers[ns->writer_type]; | |||||
switch (ns->writer_target) { | |||||
case NS_WRITER_TARGET_SOCKET: | |||||
ns->cb = pops->write_socket; | |||||
break; | |||||
case NS_WRITER_TARGET_GROUP: | |||||
ns->cb = pops->write_group; | |||||
break; | |||||
case NS_WRITER_TARGET_CHAIN: | |||||
ns->cb = pops->write_chain; | |||||
break; | |||||
default: | |||||
panic("not implemented"); | |||||
} | |||||
} | |||||
static bool | |||||
nlmsg_get_buf_type(struct nlmsg_state *ns, int size, int type, bool waitok) | |||||
{ | |||||
MPASS(type + 1 <= sizeof(nlmsg_writers) / sizeof(nlmsg_writers[0])); | |||||
RT_LOG(LOG_DEBUG3, "Setting up ns %p size %d type %d", ns, size, type); | |||||
return (nlmsg_writers[type].init(ns, size, waitok)); | |||||
} | |||||
static bool | |||||
nlmsg_get_buf(struct nlmsg_state *ns, int size, bool waitok, bool is_linux) | |||||
{ | |||||
int type; | |||||
if (!is_linux) { | |||||
if (__predict_true(size <= MCLBYTES)) | |||||
type = NS_WRITER_TYPE_MBUF; | |||||
else | |||||
type = NS_WRITER_TYPE_BUF; | |||||
} else | |||||
type = NS_WRITER_TYPE_LBUF; | |||||
return (nlmsg_get_buf_type(ns, size, type, waitok)); | |||||
} | |||||
bool | |||||
nlmsg_get_socket_writer(int size, struct nlpcb *nlp, struct nlmsg_state *ns) | |||||
{ | |||||
if (!nlmsg_get_buf(ns, size, false, nlp->nl_linux)) | |||||
return (false); | |||||
ns->arg = (void *)nlp; | |||||
ns->writer_target = NS_WRITER_TARGET_SOCKET; | |||||
nlmsg_set_callback(ns); | |||||
return (true); | |||||
} | |||||
bool | |||||
nlmsg_get_group_writer(int size, uint32_t group_mask, struct nlmsg_state *ns) | |||||
{ | |||||
if (!nlmsg_get_buf(ns, size, false, false)) | |||||
return (false); | |||||
ns->arg = (void *)(uintptr_t)group_mask; | |||||
ns->writer_target = NS_WRITER_TARGET_GROUP; | |||||
nlmsg_set_callback(ns); | |||||
return (true); | |||||
} | |||||
bool | |||||
nlmsg_get_chain_writer(int size, struct mbuf **pm, struct nlmsg_state *ns) | |||||
{ | |||||
if (!nlmsg_get_buf(ns, size, false, false)) | |||||
return (false); | |||||
*pm = NULL; | |||||
ns->arg = (void *)pm; | |||||
ns->writer_target = NS_WRITER_TARGET_CHAIN; | |||||
nlmsg_set_callback(ns); | |||||
RT_LOG(LOG_DEBUG3, "setup cb %p (need %p)", ns->cb, &nlmsg_write_chain_mbuf); | |||||
return (true); | |||||
} | |||||
void | |||||
nlmsg_ignore_limit(struct nlmsg_state *ns) | |||||
{ | |||||
ns->ignore_limit = true; | |||||
} | |||||
bool | |||||
nlmsg_flush(struct nlmsg_state *ns) | |||||
{ | |||||
if (__predict_false(ns->hdr != NULL)) { | |||||
/* Last message has not been completed, skip it. */ | |||||
int completed_len = (char *)ns->hdr - ns->data; | |||||
/* Send completed messages */ | |||||
ns->offset -= ns->offset - completed_len; | |||||
ns->hdr = NULL; | |||||
} | |||||
bool result = ns->cb(ns, ns->_storage, ns->offset, ns->num_messages); | |||||
ns->_storage = NULL; | |||||
if (!result) { | |||||
RT_LOG(LOG_DEBUG, "ns %p offset %d: flush with %p() failed", ns, ns->offset, ns->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 nlmsg_state *ns, int required_len) | |||||
{ | |||||
struct nlmsg_state ns_new = {}; | |||||
int completed_len, new_len; | |||||
RT_LOG(LOG_DEBUG3, "no space at offset %d/%d (want %d), trying to reclaim", | |||||
ns->offset, ns->alloc_len, required_len); | |||||
/* Calculated new buffer size and allocate it s*/ | |||||
completed_len = (ns->hdr != NULL) ? (char *)ns->hdr - ns->data : ns->offset; | |||||
if (completed_len > 0 && required_len < MCLBYTES) { | |||||
/* We already ran out of space, use the largest effective size */ | |||||
new_len = max(ns->alloc_len, MCLBYTES); | |||||
} else { | |||||
if (ns->alloc_len < MCLBYTES) | |||||
new_len = MCLBYTES; | |||||
else | |||||
new_len = ns->alloc_len * 2; | |||||
while (new_len < required_len) | |||||
new_len *= 2; | |||||
} | |||||
bool waitok = (ns->malloc_flag == M_WAITOK); | |||||
bool is_linux = (ns->writer_type == NS_WRITER_TYPE_LBUF); | |||||
if (!nlmsg_get_buf(&ns_new, new_len, waitok, is_linux)) | |||||
return (false); | |||||
if (ns->ignore_limit) | |||||
nlmsg_ignore_limit(&ns_new); | |||||
/* Update callback data */ | |||||
ns_new.writer_target = ns->writer_target; | |||||
nlmsg_set_callback(&ns_new); | |||||
ns_new.arg = ns->arg; | |||||
/* Copy last (unfinished) header to the new storage */ | |||||
int last_len = ns->offset - completed_len; | |||||
if (last_len > 0) { | |||||
memcpy(ns_new.data, ns->hdr, last_len); | |||||
ns_new.hdr = (struct nlmsghdr *)ns_new.data; | |||||
ns_new.offset = last_len; | |||||
} | |||||
RT_LOG(LOG_DEBUG2, "completed: %d bytes, copied: %d bytes", completed_len, last_len); | |||||
/* Flush completed headers */ | |||||
if (completed_len > 0) { | |||||
RT_LOG(LOG_DEBUG2, "Flushing %u completed message(s) (%d bytes)", | |||||
ns->num_messages, completed_len); | |||||
ns->offset -= last_len; | |||||
ns->hdr = NULL; | |||||
nlmsg_flush(ns); | |||||
} | |||||
/* Update state */ | |||||
memcpy(ns, &ns_new, sizeof(struct nlmsg_state)); | |||||
RT_LOG(LOG_DEBUG2, "switched mbuf: used %d/%d bytes", ns->offset, ns->alloc_len); | |||||
return (true); | |||||
} | |||||
bool | |||||
nlmsg_add(struct nlmsg_state *ns, uint32_t portid, uint32_t seq, uint16_t type, | |||||
uint16_t flags, uint32_t len) | |||||
{ | |||||
struct nlmsghdr *hdr; | |||||
MPASS(ns->hdr == NULL); | |||||
int required_len = NETLINK_ALIGN(len + sizeof(struct nlmsghdr)); | |||||
if (__predict_false(ns->offset + required_len > ns->alloc_len)) { | |||||
if (!nlmsg_refill_buffer(ns, required_len)) | |||||
return (false); | |||||
} | |||||
hdr = (struct nlmsghdr *)(&ns->data[ns->offset]); | |||||
hdr->nlmsg_len = len; | |||||
hdr->nlmsg_type = type; | |||||
hdr->nlmsg_flags = flags; | |||||
hdr->nlmsg_seq = seq; | |||||
hdr->nlmsg_pid = portid; | |||||
ns->hdr = hdr; | |||||
ns->offset += sizeof(struct nlmsghdr); | |||||
return (true); | |||||
} | |||||
void | |||||
nlmsg_end(struct nlmsg_state *ns) | |||||
{ | |||||
MPASS(ns->hdr != NULL); | |||||
ns->hdr->nlmsg_len = (uint32_t)(ns->data + ns->offset - (char *)ns->hdr); | |||||
ns->hdr = NULL; | |||||
ns->num_messages++; | |||||
} | |||||
void | |||||
nlmsg_abort(struct nlmsg_state *ns) | |||||
{ | |||||
if (ns->hdr != NULL) { | |||||
ns->offset = (uint32_t)((char *)ns->hdr - ns->data); | |||||
ns->hdr = NULL; | |||||
} | |||||
} | |||||