diff --git a/sys/netlink/netlink_domain.c b/sys/netlink/netlink_domain.c
index 94d8377b9e15..cc7018ed2582 100644
--- a/sys/netlink/netlink_domain.c
+++ b/sys/netlink/netlink_domain.c
@@ -1,1015 +1,1019 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * Copyright (c) 2021 Ng Peng Nam Sean
  * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
  * Copyright (c) 2023 Gleb Smirnoff <glebius@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.
  */
 
 /*
  * This file contains socket and protocol bindings for netlink.
  */
 
 #include <sys/param.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
 #include <sys/lock.h>
 #include <sys/rmlock.h>
 #include <sys/domain.h>
 #include <sys/jail.h>
 #include <sys/mbuf.h>
 #include <sys/osd.h>
 #include <sys/protosw.h>
 #include <sys/proc.h>
 #include <sys/ck.h>
 #include <sys/socket.h>
 #include <sys/socketvar.h>
 #include <sys/sysent.h>
 #include <sys/syslog.h>
 #include <sys/priv.h> /* priv_check */
 #include <sys/uio.h>
 
 #include <netlink/netlink.h>
 #include <netlink/netlink_ctl.h>
 #include <netlink/netlink_var.h>
 
 #define	DEBUG_MOD_NAME	nl_domain
 #define	DEBUG_MAX_LEVEL	LOG_DEBUG3
 #include <netlink/netlink_debug.h>
 _DECLARE_DEBUG(LOG_INFO);
 
 _Static_assert((NLP_MAX_GROUPS % 64) == 0,
     "NLP_MAX_GROUPS has to be multiple of 64");
 _Static_assert(NLP_MAX_GROUPS >= 64,
     "NLP_MAX_GROUPS has to be at least 64");
 
 #define	NLCTL_TRACKER		struct rm_priotracker nl_tracker
 #define	NLCTL_RLOCK(_ctl)	rm_rlock(&((_ctl)->ctl_lock), &nl_tracker)
 #define	NLCTL_RUNLOCK(_ctl)	rm_runlock(&((_ctl)->ctl_lock), &nl_tracker)
 
 #define	NLCTL_WLOCK(_ctl)	rm_wlock(&((_ctl)->ctl_lock))
 #define	NLCTL_WUNLOCK(_ctl)	rm_wunlock(&((_ctl)->ctl_lock))
 
 static u_long nl_sendspace = NLSNDQ;
 SYSCTL_ULONG(_net_netlink, OID_AUTO, sendspace, CTLFLAG_RW, &nl_sendspace, 0,
     "Default netlink socket send space");
 
 static u_long nl_recvspace = NLSNDQ;
 SYSCTL_ULONG(_net_netlink, OID_AUTO, recvspace, CTLFLAG_RW, &nl_recvspace, 0,
     "Default netlink socket receive space");
 
 extern u_long sb_max_adj;
 static u_long nl_maxsockbuf = 512 * 1024 * 1024; /* 512M, XXX: init based on physmem */
 static int sysctl_handle_nl_maxsockbuf(SYSCTL_HANDLER_ARGS);
 SYSCTL_OID(_net_netlink, OID_AUTO, nl_maxsockbuf,
     CTLTYPE_ULONG | CTLFLAG_RW | CTLFLAG_MPSAFE, &nl_maxsockbuf, 0,
     sysctl_handle_nl_maxsockbuf, "LU",
     "Maximum Netlink socket buffer size");
 
 
 static unsigned int osd_slot_id = 0;
 
 void
 nl_osd_register(void)
 {
 	osd_slot_id = osd_register(OSD_THREAD, NULL, NULL);
 }
 
 void
 nl_osd_unregister(void)
 {
 	osd_deregister(OSD_THREAD, osd_slot_id);
 }
 
 struct nlpcb *
 _nl_get_thread_nlp(struct thread *td)
 {
 	return (osd_get(OSD_THREAD, &td->td_osd, osd_slot_id));
 }
 
 void
 nl_set_thread_nlp(struct thread *td, struct nlpcb *nlp)
 {
 	NLP_LOG(LOG_DEBUG2, nlp, "Set thread %p nlp to %p (slot %u)", td, nlp, osd_slot_id);
 	if (osd_set(OSD_THREAD, &td->td_osd, osd_slot_id, nlp) == 0)
 		return;
 	/* Failed, need to realloc */
 	void **rsv = osd_reserve(osd_slot_id);
 	osd_set_reserved(OSD_THREAD, &td->td_osd, osd_slot_id, rsv, nlp);
 }
 
 /*
  * Looks up a nlpcb struct based on the @portid. Need to claim nlsock_mtx.
  * Returns nlpcb pointer if present else NULL
  */
 static struct nlpcb *
 nl_port_lookup(uint32_t port_id)
 {
 	struct nlpcb *nlp;
 
 	CK_LIST_FOREACH(nlp, &V_nl_ctl->ctl_port_head, nl_port_next) {
 		if (nlp->nl_port == port_id)
 			return (nlp);
 	}
 	return (NULL);
 }
 
 static void
 nl_add_group_locked(struct nlpcb *nlp, unsigned int group_id)
 {
 	MPASS(group_id <= NLP_MAX_GROUPS);
 	--group_id;
 
 	/* TODO: add family handler callback */
 	if (!nlp_unconstrained_vnet(nlp))
 		return;
 
 	nlp->nl_groups[group_id / 64] |= (uint64_t)1 << (group_id % 64);
 }
 
 static void
 nl_del_group_locked(struct nlpcb *nlp, unsigned int group_id)
 {
 	MPASS(group_id <= NLP_MAX_GROUPS);
 	--group_id;
 
 	nlp->nl_groups[group_id / 64] &= ~((uint64_t)1 << (group_id % 64));
 }
 
 static bool
 nl_isset_group_locked(struct nlpcb *nlp, unsigned int group_id)
 {
 	MPASS(group_id <= NLP_MAX_GROUPS);
 	--group_id;
 
 	return (nlp->nl_groups[group_id / 64] & ((uint64_t)1 << (group_id % 64)));
 }
 
 static uint32_t
 nl_get_groups_compat(struct nlpcb *nlp)
 {
 	uint32_t groups_mask = 0;
 
 	for (int i = 0; i < 32; i++) {
 		if (nl_isset_group_locked(nlp, i + 1))
 			groups_mask |= (1 << i);
 	}
 
 	return (groups_mask);
 }
 
-static void
-nl_send_one_group(struct nl_writer *nw, struct nl_buf *nb, struct nlpcb *nlp)
-{
-	if (__predict_false(nlp->nl_flags & NLF_MSG_INFO))
-		nl_add_msg_info(nb);
-	nw->buf = nb;
-	(void)nl_send_one(nw);
-}
-
 static struct nl_buf *
 nl_buf_copy(struct nl_buf *nb)
 {
 	struct nl_buf *copy;
 
 	copy = nl_buf_alloc(nb->buflen, M_NOWAIT);
 	if (__predict_false(copy == NULL))
 		return (NULL);
 	memcpy(copy, nb, sizeof(*nb) + nb->buflen);
-	if (nb->control != NULL) {
-		copy->control = m_copym(nb->control, 0, M_COPYALL, M_NOWAIT);
-		if (__predict_false(copy->control == NULL)) {
-			nl_buf_free(copy);
-			return (NULL);
-		}
-	}
 
 	return (copy);
 }
 
 /*
  * Broadcasts in the writer's buffer.
  */
 bool
 nl_send_group(struct nl_writer *nw)
 {
 	struct nl_buf *nb = nw->buf;
 	struct nlpcb *nlp_last = NULL;
 	struct nlpcb *nlp;
 	NLCTL_TRACKER;
 
 	IF_DEBUG_LEVEL(LOG_DEBUG2) {
 		struct nlmsghdr *hdr = (struct nlmsghdr *)nb->data;
 		NL_LOG(LOG_DEBUG2, "MCAST len %u msg type %d len %u to group %d/%d",
 		    nb->datalen, hdr->nlmsg_type, hdr->nlmsg_len,
 		    nw->group.proto, nw->group.id);
 	}
 
 	nw->buf = NULL;
 
 	struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl);
 	if (__predict_false(ctl == NULL)) {
 		/*
 		 * Can be the case when notification is sent within VNET
 		 * which doesn't have any netlink sockets.
 		 */
 		nl_buf_free(nb);
 		return (false);
 	}
 
 	NLCTL_RLOCK(ctl);
 
 	CK_LIST_FOREACH(nlp, &ctl->ctl_pcb_head, nl_next) {
 		if (nl_isset_group_locked(nlp, nw->group.id) &&
 		    nlp->nl_proto == nw->group.proto) {
 			if (nlp_last != NULL) {
 				struct nl_buf *copy;
 
 				copy = nl_buf_copy(nb);
 				if (copy != NULL) {
-					nl_send_one_group(nw, copy, nlp_last);
+					nw->buf = copy;
+					(void)nl_send_one(nw);
 				} else {
 					NLP_LOCK(nlp_last);
 					if (nlp_last->nl_socket != NULL)
 						sorwakeup(nlp_last->nl_socket);
 					NLP_UNLOCK(nlp_last);
 				}
 			}
 			nlp_last = nlp;
 		}
 	}
-	if (nlp_last != NULL)
-		nl_send_one_group(nw, nb, nlp_last);
-	else
+	if (nlp_last != NULL) {
+		nw->buf = nb;
+		(void)nl_send_one(nw);
+	} else
 		nl_buf_free(nb);
 
 	NLCTL_RUNLOCK(ctl);
 
 	return (true);
 }
 
 bool
 nl_has_listeners(int netlink_family, uint32_t groups_mask)
 {
 	return (V_nl_ctl != NULL);
 }
 
 static uint32_t
 nl_find_port(void)
 {
 	/*
 	 * app can open multiple netlink sockets.
 	 * Start with current pid, if already taken,
 	 * try random numbers in 65k..256k+65k space,
 	 * avoiding clash with pids.
 	 */
 	if (nl_port_lookup(curproc->p_pid) == NULL)
 		return (curproc->p_pid);
 	for (int i = 0; i < 16; i++) {
 		uint32_t nl_port = (arc4random() % 65536) + 65536 * 4;
 		if (nl_port_lookup(nl_port) == 0)
 			return (nl_port);
 		NL_LOG(LOG_DEBUG3, "tried %u\n", nl_port);
 	}
 	return (curproc->p_pid);
 }
 
 static int
 nl_bind_locked(struct nlpcb *nlp, struct sockaddr_nl *snl)
 {
 	if (nlp->nl_bound) {
 		if (nlp->nl_port != snl->nl_pid) {
 			NL_LOG(LOG_DEBUG,
 			    "bind() failed: program pid %d "
 			    "is different from provided pid %d",
 			    nlp->nl_port, snl->nl_pid);
 			return (EINVAL); // XXX: better error
 		}
 	} else {
 		if (snl->nl_pid == 0)
 			snl->nl_pid = nl_find_port();
 		if (nl_port_lookup(snl->nl_pid) != NULL)
 			return (EADDRINUSE);
 		nlp->nl_port = snl->nl_pid;
 		nlp->nl_bound = true;
 		CK_LIST_INSERT_HEAD(&V_nl_ctl->ctl_port_head, nlp, nl_port_next);
 	}
 	for (int i = 0; i < 32; i++) {
 		if (snl->nl_groups & ((uint32_t)1 << i))
 			nl_add_group_locked(nlp, i + 1);
 		else
 			nl_del_group_locked(nlp, i + 1);
 	}
 
 	return (0);
 }
 
 static int
 nl_pru_attach(struct socket *so, int proto, struct thread *td)
 {
 	struct nlpcb *nlp;
 	int error;
 
 	if (__predict_false(netlink_unloading != 0))
 		return (EAFNOSUPPORT);
 
 	error = nl_verify_proto(proto);
 	if (error != 0)
 		return (error);
 
 	bool is_linux = SV_PROC_ABI(td->td_proc) == SV_ABI_LINUX;
 	NL_LOG(LOG_DEBUG2, "socket %p, %sPID %d: attaching socket to %s",
 	    so, is_linux ? "(linux) " : "", curproc->p_pid,
 	    nl_get_proto_name(proto));
 
 	/* Create per-VNET state on first socket init */
 	struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl);
 	if (ctl == NULL)
 		ctl = vnet_nl_ctl_init();
 	KASSERT(V_nl_ctl != NULL, ("nl_attach: vnet_sock_init() failed"));
 
 	MPASS(sotonlpcb(so) == NULL);
 
 	nlp = malloc(sizeof(struct nlpcb), M_PCB, M_WAITOK | M_ZERO);
 	error = soreserve(so, nl_sendspace, nl_recvspace);
 	if (error != 0) {
 		free(nlp, M_PCB);
 		return (error);
 	}
 	TAILQ_INIT(&so->so_rcv.nl_queue);
 	TAILQ_INIT(&so->so_snd.nl_queue);
 	so->so_pcb = nlp;
 	nlp->nl_socket = so;
 	/* Copy so_cred to avoid having socket_var.h in every header */
 	nlp->nl_cred = so->so_cred;
 	nlp->nl_proto = proto;
 	nlp->nl_process_id = curproc->p_pid;
 	nlp->nl_linux = is_linux;
 	nlp->nl_unconstrained_vnet = !jailed_without_vnet(so->so_cred);
 	nlp->nl_need_thread_setup = true;
 	NLP_LOCK_INIT(nlp);
 	refcount_init(&nlp->nl_refcount, 1);
 
 	nlp->nl_taskqueue = taskqueue_create("netlink_socket", M_WAITOK,
 	    taskqueue_thread_enqueue, &nlp->nl_taskqueue);
 	TASK_INIT(&nlp->nl_task, 0, nl_taskqueue_handler, nlp);
 	taskqueue_start_threads(&nlp->nl_taskqueue, 1, PWAIT,
 	    "netlink_socket (PID %u)", nlp->nl_process_id);
 
 	NLCTL_WLOCK(ctl);
 	/* XXX: check ctl is still alive */
 	CK_LIST_INSERT_HEAD(&ctl->ctl_pcb_head, nlp, nl_next);
 	NLCTL_WUNLOCK(ctl);
 
 	soisconnected(so);
 
 	return (0);
 }
 
 static int
 nl_pru_bind(struct socket *so, struct sockaddr *sa, struct thread *td)
 {
 	struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl);
 	struct nlpcb *nlp = sotonlpcb(so);
 	struct sockaddr_nl *snl = (struct sockaddr_nl *)sa;
 	int error;
 
 	NL_LOG(LOG_DEBUG3, "socket %p, PID %d", so, curproc->p_pid);
 	if (snl->nl_len != sizeof(*snl)) {
 		NL_LOG(LOG_DEBUG, "socket %p, wrong sizeof(), ignoring bind()", so);
 		return (EINVAL);
 	}
 
 
 	NLCTL_WLOCK(ctl);
 	NLP_LOCK(nlp);
 	error = nl_bind_locked(nlp, snl);
 	NLP_UNLOCK(nlp);
 	NLCTL_WUNLOCK(ctl);
 	NL_LOG(LOG_DEBUG2, "socket %p, bind() to %u, groups %u, error %d", so,
 	    snl->nl_pid, snl->nl_groups, error);
 
 	return (error);
 }
 
 
 static int
 nl_assign_port(struct nlpcb *nlp, uint32_t port_id)
 {
 	struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl);
 	struct sockaddr_nl snl = {
 		.nl_pid = port_id,
 	};
 	int error;
 
 	NLCTL_WLOCK(ctl);
 	NLP_LOCK(nlp);
 	snl.nl_groups = nl_get_groups_compat(nlp);
 	error = nl_bind_locked(nlp, &snl);
 	NLP_UNLOCK(nlp);
 	NLCTL_WUNLOCK(ctl);
 
 	NL_LOG(LOG_DEBUG3, "socket %p, port assign: %d, error: %d", nlp->nl_socket, port_id, error);
 	return (error);
 }
 
 /*
  * nl_autobind_port binds a unused portid to @nlp
  * @nlp: pcb data for the netlink socket
  * @candidate_id: first id to consider
  */
 static int
 nl_autobind_port(struct nlpcb *nlp, uint32_t candidate_id)
 {
 	struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl);
 	uint32_t port_id = candidate_id;
 	NLCTL_TRACKER;
 	bool exist;
 	int error = EADDRINUSE;
 
 	for (int i = 0; i < 10; i++) {
 		NL_LOG(LOG_DEBUG3, "socket %p, trying to assign port %d", nlp->nl_socket, port_id);
 		NLCTL_RLOCK(ctl);
 		exist = nl_port_lookup(port_id) != 0;
 		NLCTL_RUNLOCK(ctl);
 		if (!exist) {
 			error = nl_assign_port(nlp, port_id);
 			if (error != EADDRINUSE)
 				break;
 		}
 		port_id++;
 	}
 	NL_LOG(LOG_DEBUG3, "socket %p, autobind to %d, error: %d", nlp->nl_socket, port_id, error);
 	return (error);
 }
 
 static int
 nl_pru_connect(struct socket *so, struct sockaddr *sa, struct thread *td)
 {
 	struct sockaddr_nl *snl = (struct sockaddr_nl *)sa;
 	struct nlpcb *nlp;
 
 	NL_LOG(LOG_DEBUG3, "socket %p, PID %d", so, curproc->p_pid);
 	if (snl->nl_len != sizeof(*snl)) {
 		NL_LOG(LOG_DEBUG, "socket %p, wrong sizeof(), ignoring bind()", so);
 		return (EINVAL);
 	}
 
 	nlp = sotonlpcb(so);
 	if (!nlp->nl_bound) {
 		int error = nl_autobind_port(nlp, td->td_proc->p_pid);
 		if (error != 0) {
 			NL_LOG(LOG_DEBUG, "socket %p, nl_autobind() failed: %d", so, error);
 			return (error);
 		}
 	}
 	/* XXX: Handle socket flags & multicast */
 	soisconnected(so);
 
 	NL_LOG(LOG_DEBUG2, "socket %p, connect to %u", so, snl->nl_pid);
 
 	return (0);
 }
 
 static void
 destroy_nlpcb_epoch(epoch_context_t ctx)
 {
 	struct nlpcb *nlp;
 
 	nlp = __containerof(ctx, struct nlpcb, nl_epoch_ctx);
 
 	NLP_LOCK_DESTROY(nlp);
 	free(nlp, M_PCB);
 }
 
 static void
 nl_close(struct socket *so)
 {
 	struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl);
 	MPASS(sotonlpcb(so) != NULL);
 	struct nlpcb *nlp;
 	struct nl_buf *nb;
 
 	NL_LOG(LOG_DEBUG2, "detaching socket %p, PID %d", so, curproc->p_pid);
 	nlp = sotonlpcb(so);
 
 	/* Mark as inactive so no new work can be enqueued */
 	NLP_LOCK(nlp);
 	bool was_bound = nlp->nl_bound;
 	NLP_UNLOCK(nlp);
 
 	/* Wait till all scheduled work has been completed  */
 	taskqueue_drain_all(nlp->nl_taskqueue);
 	taskqueue_free(nlp->nl_taskqueue);
 
 	NLCTL_WLOCK(ctl);
 	NLP_LOCK(nlp);
 	if (was_bound) {
 		CK_LIST_REMOVE(nlp, nl_port_next);
 		NL_LOG(LOG_DEBUG3, "socket %p, unlinking bound pid %u", so, nlp->nl_port);
 	}
 	CK_LIST_REMOVE(nlp, nl_next);
 	nlp->nl_socket = NULL;
 	NLP_UNLOCK(nlp);
 	NLCTL_WUNLOCK(ctl);
 
 	so->so_pcb = NULL;
 
 	while ((nb = TAILQ_FIRST(&so->so_snd.nl_queue)) != NULL) {
 		TAILQ_REMOVE(&so->so_snd.nl_queue, nb, tailq);
 		nl_buf_free(nb);
 	}
 	while ((nb = TAILQ_FIRST(&so->so_rcv.nl_queue)) != NULL) {
 		TAILQ_REMOVE(&so->so_rcv.nl_queue, nb, tailq);
 		nl_buf_free(nb);
 	}
 
 	NL_LOG(LOG_DEBUG3, "socket %p, detached", so);
 
 	/* XXX: is delayed free needed? */
 	NET_EPOCH_CALL(destroy_nlpcb_epoch, &nlp->nl_epoch_ctx);
 }
 
 static int
 nl_pru_disconnect(struct socket *so)
 {
 	NL_LOG(LOG_DEBUG3, "socket %p, PID %d", so, curproc->p_pid);
 	MPASS(sotonlpcb(so) != NULL);
 	return (ENOTCONN);
 }
 
 static int
 nl_pru_shutdown(struct socket *so)
 {
 	NL_LOG(LOG_DEBUG3, "socket %p, PID %d", so, curproc->p_pid);
 	MPASS(sotonlpcb(so) != NULL);
 	socantsendmore(so);
 	return (0);
 }
 
 static int
 nl_sockaddr(struct socket *so, struct sockaddr *sa)
 {
 
 	*(struct sockaddr_nl *)sa = (struct sockaddr_nl ){
 		/* TODO: set other fields */
 		.nl_len = sizeof(struct sockaddr_nl),
 		.nl_family = AF_NETLINK,
 		.nl_pid = sotonlpcb(so)->nl_port,
 	};
 
 	return (0);
 }
 
 static int
 nl_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio,
     struct mbuf *m, struct mbuf *control, int flags, struct thread *td)
 {
 	struct nlpcb *nlp = sotonlpcb(so);
 	struct sockbuf *sb = &so->so_snd;
 	struct nl_buf *nb;
 	u_int len;
 	int error;
 
 	MPASS(m == NULL && uio != NULL);
 
         NL_LOG(LOG_DEBUG2, "sending message to kernel");
 
 	if (__predict_false(control != NULL)) {
 		m_freem(control);
 		return (EINVAL);
 	}
 
 	if (__predict_false(flags & MSG_OOB))	/* XXXGL: or just ignore? */
 		return (EOPNOTSUPP);
 
 	if (__predict_false(uio->uio_resid < sizeof(struct nlmsghdr)))
 		return (ENOBUFS);		/* XXXGL: any better error? */
 
 	NL_LOG(LOG_DEBUG3, "sending message to kernel async processing");
 
 	error = SOCK_IO_SEND_LOCK(so, SBLOCKWAIT(flags));
 	if (error)
 		return (error);
 
 	len = roundup2(uio->uio_resid, 8) + SCRATCH_BUFFER_SIZE;
 	if (nlp->nl_linux)
 		len += roundup2(uio->uio_resid, 8);
 	nb = nl_buf_alloc(len, M_WAITOK);
 	nb->datalen = uio->uio_resid;
 	error = uiomove(&nb->data[0], uio->uio_resid, uio);
 	if (__predict_false(error))
 		goto out;
 
 	SOCK_SENDBUF_LOCK(so);
 restart:
 	if (sb->sb_hiwat - sb->sb_ccc >= nb->datalen) {
 		TAILQ_INSERT_TAIL(&sb->nl_queue, nb, tailq);
 		sb->sb_acc += nb->datalen;
 		sb->sb_ccc += nb->datalen;
 		nb = NULL;
 	} else if ((so->so_state & SS_NBIO) ||
 	    (flags & (MSG_NBIO | MSG_DONTWAIT)) != 0) {
 		SOCK_SENDBUF_UNLOCK(so);
 		error = EWOULDBLOCK;
 		goto out;
 	} else {
 		if ((error = sbwait(so, SO_SND)) != 0) {
 			SOCK_SENDBUF_UNLOCK(so);
 			goto out;
 		} else
 			goto restart;
 	}
 	SOCK_SENDBUF_UNLOCK(so);
 
 	if (nb == NULL) {
 		NL_LOG(LOG_DEBUG3, "enqueue %u bytes", nb->datalen);
 		NLP_LOCK(nlp);
 		nl_schedule_taskqueue(nlp);
 		NLP_UNLOCK(nlp);
 	}
 
 out:
 	SOCK_IO_SEND_UNLOCK(so);
 	if (nb != NULL)
 		nl_buf_free(nb);
 	return (error);
 }
 
+/* Create control data for recvmsg(2) on Netlink socket. */
+static struct mbuf *
+nl_createcontrol(struct nlpcb *nlp)
+{
+	struct {
+		struct nlattr nla;
+		uint32_t val;
+	} data[] = {
+		{
+			.nla.nla_len = sizeof(struct nlattr) + sizeof(uint32_t),
+			.nla.nla_type = NLMSGINFO_ATTR_PROCESS_ID,
+			.val = nlp->nl_process_id,
+		},
+		{
+			.nla.nla_len = sizeof(struct nlattr) + sizeof(uint32_t),
+			.nla.nla_type = NLMSGINFO_ATTR_PORT_ID,
+			.val = nlp->nl_port,
+		},
+	};
+
+	return (sbcreatecontrol(data, sizeof(data), NETLINK_MSG_INFO,
+	    SOL_NETLINK, M_WAITOK));
+}
+
 static int
 nl_soreceive(struct socket *so, struct sockaddr **psa, struct uio *uio,
     struct mbuf **mp, struct mbuf **controlp, int *flagsp)
 {
 	static const struct sockaddr_nl nl_empty_src = {
 		.nl_len = sizeof(struct sockaddr_nl),
 		.nl_family = PF_NETLINK,
 		.nl_pid = 0 /* comes from the kernel */
 	};
 	struct sockbuf *sb = &so->so_rcv;
+	struct nlpcb *nlp = sotonlpcb(so);
 	struct nl_buf *first, *last, *nb, *next;
 	struct nlmsghdr *hdr;
 	int flags, error;
 	u_int len, overflow, partoff, partlen, msgrcv, datalen;
 	bool nonblock, trunc, peek;
 
 	MPASS(mp == NULL && uio != NULL);
 
 	NL_LOG(LOG_DEBUG3, "socket %p, PID %d", so, curproc->p_pid);
 
 	if (psa != NULL)
 		*psa = sodupsockaddr((const struct sockaddr *)&nl_empty_src,
 		    M_WAITOK);
 
+	if (controlp != NULL && (nlp->nl_flags & NLF_MSG_INFO))
+		*controlp = nl_createcontrol(nlp);
+
 	flags = flagsp != NULL ? *flagsp & ~MSG_TRUNC : 0;
 	trunc = flagsp != NULL ? *flagsp & MSG_TRUNC : false;
 	nonblock = (so->so_state & SS_NBIO) ||
 	    (flags & (MSG_DONTWAIT | MSG_NBIO));
 	peek = flags & MSG_PEEK;
 
 	error = SOCK_IO_RECV_LOCK(so, SBLOCKWAIT(flags));
 	if (__predict_false(error))
 		return (error);
 
-	if (controlp != NULL)
-		*controlp = NULL;
-
 	len = 0;
 	overflow = 0;
 	msgrcv = 0;
 	datalen = 0;
 
 	SOCK_RECVBUF_LOCK(so);
 	while ((first = TAILQ_FIRST(&sb->nl_queue)) == NULL) {
 		if (nonblock) {
 			SOCK_RECVBUF_UNLOCK(so);
 			SOCK_IO_RECV_UNLOCK(so);
 			return (EWOULDBLOCK);
 		}
 		error = sbwait(so, SO_RCV);
 		if (error) {
 			SOCK_RECVBUF_UNLOCK(so);
 			SOCK_IO_RECV_UNLOCK(so);
 			return (error);
 		}
 	}
 
 	/*
 	 * Netlink socket buffer consists of a queue of nl_bufs, but for the
 	 * userland there should be no boundaries.  However, there are Netlink
 	 * messages, that shouldn't be split.  Internal invariant is that a
 	 * message never spans two nl_bufs.
 	 * If a large userland buffer is provided, we would traverse the queue
 	 * until either queue end is reached or the buffer is fulfilled.  If
 	 * an application provides a buffer that isn't able to fit a single
 	 * message, we would truncate it and lose its tail.  This is the only
 	 * condition where we would lose data.  If buffer is able to fit at
 	 * least one message, we would return it and won't truncate the next.
 	 *
 	 * We use same code for normal and MSG_PEEK case.  At first queue pass
 	 * we scan nl_bufs and count lenght.  In case we can read entire buffer
 	 * at one write everything is trivial.  In case we can not, we save
 	 * pointer to the last (or partial) nl_buf and in the !peek case we
 	 * split the queue into two pieces.  We can safely drop the queue lock,
 	 * as kernel would only append nl_bufs to the end of the queue, and
 	 * we are the exclusive owner of queue beginning due to sleepable lock.
 	 * At the second pass we copy data out and in !peek case free nl_bufs.
 	 *
 	 * XXX: current implementation of control data implies one chunk of
 	 * data per one nl_buf.  This doesn't map well with idea of no
 	 * boundaries between nl_bufs.
 	 */
 	TAILQ_FOREACH(nb, &sb->nl_queue, tailq) {
 		u_int offset;
 
 		/*
 		 * XXXGL: zero length buffer may be at the tail of a queue
 		 * when a writer overflows socket buffer.  When this is
 		 * improved, use MPASS(nb->offset < nb->datalen).
 		 */
 		MPASS(nb->offset <= nb->datalen);
 		offset = nb->offset;
 		while (offset < nb->datalen) {
 			hdr = (struct nlmsghdr *)&nb->data[offset];
 			if (uio->uio_resid < len + hdr->nlmsg_len) {
 				overflow = len + hdr->nlmsg_len -
 				    uio->uio_resid;
 				partoff = nb->offset;
 				if (offset > partoff) {
 					partlen = offset - partoff;
 					if (!peek) {
 						nb->offset = offset;
 						datalen += partlen;
 					}
 				} else if (len == 0 && uio->uio_resid > 0) {
 					flags |= MSG_TRUNC;
 					partlen = uio->uio_resid;
 					if (!peek) {
 						/* XXX: may leave empty nb */
 						nb->offset += hdr->nlmsg_len;
 						datalen += hdr->nlmsg_len;
 					}
 				} else
 					partlen = 0;
 				goto nospace;
 			}
 			len += hdr->nlmsg_len;
 			offset += hdr->nlmsg_len;
 			MPASS(offset <= nb->buflen);
 			msgrcv++;
 		}
 		MPASS(offset == nb->datalen);
 		datalen += nb->datalen;
 	}
 nospace:
 	last = nb;
 	if (!peek) {
 		if (last == NULL)
 			TAILQ_INIT(&sb->nl_queue);
 		else {
 			/* XXXGL: create TAILQ_SPLIT */
 			TAILQ_FIRST(&sb->nl_queue) = last;
 			last->tailq.tqe_prev = &TAILQ_FIRST(&sb->nl_queue);
 		}
 		sb->sb_acc -= datalen;
 		sb->sb_ccc -= datalen;
 	}
 	SOCK_RECVBUF_UNLOCK(so);
 
 	for (nb = first; nb != last; nb = next) {
 		next = TAILQ_NEXT(nb, tailq);
-
-		/* XXXGL multiple controls??? */
-		if (controlp != NULL && *controlp == NULL &&
-		    nb->control != NULL && !peek) {
-			*controlp = nb->control;
-			nb->control = NULL;
-		}
 		if (__predict_true(error == 0))
 			error = uiomove(&nb->data[nb->offset],
 			    (int)(nb->datalen - nb->offset), uio);
 		if (!peek)
 			nl_buf_free(nb);
 	}
 	if (last != NULL && partlen > 0 && __predict_true(error == 0))
 		error = uiomove(&nb->data[partoff], (int)partlen, uio);
 
 	if (trunc && overflow > 0) {
 		uio->uio_resid -= overflow;
 		MPASS(uio->uio_resid < 0);
 	} else
 		MPASS(uio->uio_resid >= 0);
 
 	if (uio->uio_td)
 		uio->uio_td->td_ru.ru_msgrcv += msgrcv;
 
 	if (flagsp != NULL)
 		*flagsp |= flags;
 
 	SOCK_IO_RECV_UNLOCK(so);
 
 	nl_on_transmit(sotonlpcb(so));
 
 	return (error);
 }
 
 static int
 nl_getoptflag(int sopt_name)
 {
 	switch (sopt_name) {
 	case NETLINK_CAP_ACK:
 		return (NLF_CAP_ACK);
 	case NETLINK_EXT_ACK:
 		return (NLF_EXT_ACK);
 	case NETLINK_GET_STRICT_CHK:
 		return (NLF_STRICT);
 	case NETLINK_MSG_INFO:
 		return (NLF_MSG_INFO);
 	}
 
 	return (0);
 }
 
 static int
 nl_ctloutput(struct socket *so, struct sockopt *sopt)
 {
 	struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl);
 	struct nlpcb *nlp = sotonlpcb(so);
 	uint32_t flag;
 	int optval, error = 0;
 	NLCTL_TRACKER;
 
 	NL_LOG(LOG_DEBUG2, "%ssockopt(%p, %d)", (sopt->sopt_dir) ? "set" : "get",
 	    so, sopt->sopt_name);
 
 	switch (sopt->sopt_dir) {
 	case SOPT_SET:
 		switch (sopt->sopt_name) {
 		case NETLINK_ADD_MEMBERSHIP:
 		case NETLINK_DROP_MEMBERSHIP:
 			error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval));
 			if (error != 0)
 				break;
 			if (optval <= 0 || optval >= NLP_MAX_GROUPS) {
 				error = ERANGE;
 				break;
 			}
 			NL_LOG(LOG_DEBUG2, "ADD/DEL group %d", (uint32_t)optval);
 
 			NLCTL_WLOCK(ctl);
 			if (sopt->sopt_name == NETLINK_ADD_MEMBERSHIP)
 				nl_add_group_locked(nlp, optval);
 			else
 				nl_del_group_locked(nlp, optval);
 			NLCTL_WUNLOCK(ctl);
 			break;
 		case NETLINK_CAP_ACK:
 		case NETLINK_EXT_ACK:
 		case NETLINK_GET_STRICT_CHK:
 		case NETLINK_MSG_INFO:
 			error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval));
 			if (error != 0)
 				break;
 
 			flag = nl_getoptflag(sopt->sopt_name);
 
 			if ((flag == NLF_MSG_INFO) && nlp->nl_linux) {
 				error = EINVAL;
 				break;
 			}
 
 			NLCTL_WLOCK(ctl);
 			if (optval != 0)
 				nlp->nl_flags |= flag;
 			else
 				nlp->nl_flags &= ~flag;
 			NLCTL_WUNLOCK(ctl);
 			break;
 		default:
 			error = ENOPROTOOPT;
 		}
 		break;
 	case SOPT_GET:
 		switch (sopt->sopt_name) {
 		case NETLINK_LIST_MEMBERSHIPS:
 			NLCTL_RLOCK(ctl);
 			optval = nl_get_groups_compat(nlp);
 			NLCTL_RUNLOCK(ctl);
 			error = sooptcopyout(sopt, &optval, sizeof(optval));
 			break;
 		case NETLINK_CAP_ACK:
 		case NETLINK_EXT_ACK:
 		case NETLINK_GET_STRICT_CHK:
 		case NETLINK_MSG_INFO:
 			NLCTL_RLOCK(ctl);
 			optval = (nlp->nl_flags & nl_getoptflag(sopt->sopt_name)) != 0;
 			NLCTL_RUNLOCK(ctl);
 			error = sooptcopyout(sopt, &optval, sizeof(optval));
 			break;
 		default:
 			error = ENOPROTOOPT;
 		}
 		break;
 	default:
 		error = ENOPROTOOPT;
 	}
 
 	return (error);
 }
 
 static int
 sysctl_handle_nl_maxsockbuf(SYSCTL_HANDLER_ARGS)
 {
 	int error = 0;
 	u_long tmp_maxsockbuf = nl_maxsockbuf;
 
 	error = sysctl_handle_long(oidp, &tmp_maxsockbuf, arg2, req);
 	if (error || !req->newptr)
 		return (error);
 	if (tmp_maxsockbuf < MSIZE + MCLBYTES)
 		return (EINVAL);
 	nl_maxsockbuf = tmp_maxsockbuf;
 
 	return (0);
 }
 
 static int
 nl_setsbopt(struct socket *so, struct sockopt *sopt)
 {
 	int error, optval;
 	bool result;
 
 	if (sopt->sopt_name != SO_RCVBUF)
 		return (sbsetopt(so, sopt));
 
 	/* Allow to override max buffer size in certain conditions */
 
 	error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval);
 	if (error != 0)
 		return (error);
 	NL_LOG(LOG_DEBUG2, "socket %p, PID %d, SO_RCVBUF=%d", so, curproc->p_pid, optval);
 	if (optval > sb_max_adj) {
 		if (priv_check(curthread, PRIV_NET_ROUTE) != 0)
 			return (EPERM);
 	}
 
 	SOCK_RECVBUF_LOCK(so);
 	result = sbreserve_locked_limit(so, SO_RCV, optval, nl_maxsockbuf, curthread);
 	SOCK_RECVBUF_UNLOCK(so);
 
 	return (result ? 0 : ENOBUFS);
 }
 
 #define	NETLINK_PROTOSW						\
 	.pr_flags = PR_ATOMIC | PR_ADDR | PR_SOCKBUF,		\
 	.pr_ctloutput = nl_ctloutput,				\
 	.pr_setsbopt = nl_setsbopt,				\
 	.pr_attach = nl_pru_attach,				\
 	.pr_bind = nl_pru_bind,					\
 	.pr_connect = nl_pru_connect,				\
 	.pr_disconnect = nl_pru_disconnect,			\
 	.pr_sosend = nl_sosend,					\
 	.pr_soreceive = nl_soreceive,				\
 	.pr_shutdown = nl_pru_shutdown,				\
 	.pr_sockaddr = nl_sockaddr,				\
 	.pr_close = nl_close
 
 static struct protosw netlink_raw_sw = {
 	.pr_type = SOCK_RAW,
 	NETLINK_PROTOSW
 };
 
 static struct protosw netlink_dgram_sw = {
 	.pr_type = SOCK_DGRAM,
 	NETLINK_PROTOSW
 };
 
 static struct domain netlinkdomain = {
 	.dom_family = PF_NETLINK,
 	.dom_name = "netlink",
 	.dom_flags = DOMF_UNLOADABLE,
 	.dom_nprotosw =		2,
 	.dom_protosw =		{ &netlink_raw_sw, &netlink_dgram_sw },
 };
 
 DOMAIN_SET(netlink);
diff --git a/sys/netlink/netlink_io.c b/sys/netlink/netlink_io.c
index 56e430cdcfa8..fb8e0a46e8dd 100644
--- a/sys/netlink/netlink_io.c
+++ b/sys/netlink/netlink_io.c
@@ -1,410 +1,367 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * Copyright (c) 2021 Ng Peng Nam Sean
  * 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/ck.h>
 #include <sys/lock.h>
 #include <sys/malloc.h>
 #include <sys/mbuf.h>
 #include <sys/mutex.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_io
 #define	DEBUG_MAX_LEVEL	LOG_DEBUG3
 #include <netlink/netlink_debug.h>
 _DECLARE_DEBUG(LOG_INFO);
 
 /*
  * The logic below provide a p2p interface for receiving and
  * sending netlink data between the kernel and userland.
  */
 
 static bool nl_process_nbuf(struct nl_buf *nb, struct nlpcb *nlp);
 
 struct nl_buf *
 nl_buf_alloc(size_t len, int mflag)
 {
 	struct nl_buf *nb;
 
 	nb = malloc(sizeof(struct nl_buf) + len, M_NETLINK, mflag);
 	if (__predict_true(nb != NULL)) {
 		nb->buflen = len;
 		nb->datalen = nb->offset = 0;
-		nb->control = NULL;
 	}
 
 	return (nb);
 }
 
 void
 nl_buf_free(struct nl_buf *nb)
 {
 
-	if (nb->control)
-		m_freem(nb->control);
 	free(nb, M_NETLINK);
 }
 
-void
-nl_add_msg_info(struct nl_buf *nb)
-{
-	/* XXXGL pass nlp as arg? */
-	struct nlpcb *nlp = nl_get_thread_nlp(curthread);
-	NL_LOG(LOG_DEBUG2, "Trying to recover nlp from thread %p: %p",
-	    curthread, nlp);
-
-	if (nlp == NULL)
-		return;
-
-	/* Prepare what we want to encode - PID, socket PID & msg seq */
-	struct {
-		struct nlattr nla;
-		uint32_t val;
-	} data[] = {
-		{
-			.nla.nla_len = sizeof(struct nlattr) + sizeof(uint32_t),
-			.nla.nla_type = NLMSGINFO_ATTR_PROCESS_ID,
-			.val = nlp->nl_process_id,
-		},
-		{
-			.nla.nla_len = sizeof(struct nlattr) + sizeof(uint32_t),
-			.nla.nla_type = NLMSGINFO_ATTR_PORT_ID,
-			.val = nlp->nl_port,
-		},
-	};
-
-
-	nb->control = sbcreatecontrol(data, sizeof(data),
-	    NETLINK_MSG_INFO, SOL_NETLINK, M_NOWAIT);
-
-	if (__predict_true(nb->control != NULL))
-		NL_LOG(LOG_DEBUG2, "Storing %u bytes of control data, ctl: %p",
-		    (unsigned)sizeof(data), nb->control);
-	else
-		NL_LOG(LOG_DEBUG2, "Failed to allocate %u bytes of control",
-		    (unsigned)sizeof(data));
-}
-
 void
 nl_schedule_taskqueue(struct nlpcb *nlp)
 {
 	if (!nlp->nl_task_pending) {
 		nlp->nl_task_pending = true;
 		taskqueue_enqueue(nlp->nl_taskqueue, &nlp->nl_task);
 		NL_LOG(LOG_DEBUG3, "taskqueue scheduled");
 	} else {
 		NL_LOG(LOG_DEBUG3, "taskqueue schedule skipped");
 	}
 }
 
 static bool
 nl_process_received_one(struct nlpcb *nlp)
 {
 	struct socket *so = nlp->nl_socket;
 	struct sockbuf *sb;
 	struct nl_buf *nb;
 	bool reschedule = false;
 
 	NLP_LOCK(nlp);
 	nlp->nl_task_pending = false;
 	NLP_UNLOCK(nlp);
 
 	/*
 	 * Do not process queued up requests if there is no space to queue
 	 * replies.
 	 */
 	sb = &so->so_rcv;
 	SOCK_RECVBUF_LOCK(so);
 	if (sb->sb_hiwat <= sb->sb_ccc) {
 		SOCK_RECVBUF_UNLOCK(so);
 		return (false);
 	}
 	SOCK_RECVBUF_UNLOCK(so);
 
 	sb = &so->so_snd;
 	SOCK_SENDBUF_LOCK(so);
 	while ((nb = TAILQ_FIRST(&sb->nl_queue)) != NULL) {
 		TAILQ_REMOVE(&sb->nl_queue, nb, tailq);
 		SOCK_SENDBUF_UNLOCK(so);
 		reschedule = nl_process_nbuf(nb, nlp);
 		SOCK_SENDBUF_LOCK(so);
 		if (reschedule) {
 			sb->sb_acc -= nb->datalen;
 			sb->sb_ccc -= nb->datalen;
 			/* XXXGL: potentially can reduce lock&unlock count. */
 			sowwakeup_locked(so);
 			nl_buf_free(nb);
 			SOCK_SENDBUF_LOCK(so);
 		} else {
 			TAILQ_INSERT_HEAD(&sb->nl_queue, nb, tailq);
 			break;
 		}
 	}
 	SOCK_SENDBUF_UNLOCK(so);
 
 	return (reschedule);
 }
 
 static void
 nl_process_received(struct nlpcb *nlp)
 {
 	NL_LOG(LOG_DEBUG3, "taskqueue called");
 
 	if (__predict_false(nlp->nl_need_thread_setup)) {
 		nl_set_thread_nlp(curthread, nlp);
 		NLP_LOCK(nlp);
 		nlp->nl_need_thread_setup = false;
 		NLP_UNLOCK(nlp);
 	}
 
 	while (nl_process_received_one(nlp))
 		;
 }
 
 /*
  * Called after some data have been read from the socket.
  */
 void
 nl_on_transmit(struct nlpcb *nlp)
 {
 	NLP_LOCK(nlp);
 
 	struct socket *so = nlp->nl_socket;
 	if (__predict_false(nlp->nl_dropped_bytes > 0 && so != NULL)) {
 		unsigned long dropped_bytes = nlp->nl_dropped_bytes;
 		unsigned long dropped_messages = nlp->nl_dropped_messages;
 		nlp->nl_dropped_bytes = 0;
 		nlp->nl_dropped_messages = 0;
 
 		struct sockbuf *sb = &so->so_rcv;
 		NLP_LOG(LOG_DEBUG, nlp,
 		    "socket RX overflowed, %lu messages (%lu bytes) dropped. "
 		    "bytes: [%u/%u]", dropped_messages, dropped_bytes,
 		    sb->sb_ccc, sb->sb_hiwat);
 		/* TODO: send netlink message */
 	}
 
 	nl_schedule_taskqueue(nlp);
 	NLP_UNLOCK(nlp);
 }
 
 void
 nl_taskqueue_handler(void *_arg, int pending)
 {
 	struct nlpcb *nlp = (struct nlpcb *)_arg;
 
 	CURVNET_SET(nlp->nl_socket->so_vnet);
 	nl_process_received(nlp);
 	CURVNET_RESTORE();
 }
 
 /*
  * Tries to send current data buffer from writer.
  *
  * Returns true on success.
  * If no queue overrunes happened, wakes up socket owner.
  */
 bool
 nl_send_one(struct nl_writer *nw)
 {
 	struct nlpcb *nlp = nw->nlp;
 	struct socket *so = nlp->nl_socket;
 	struct sockbuf *sb = &so->so_rcv;
 	struct nl_buf *nb;
 
 	MPASS(nw->hdr == NULL);
 
 	IF_DEBUG_LEVEL(LOG_DEBUG2) {
 		struct nlmsghdr *hdr = (struct nlmsghdr *)nw->buf->data;
 		NLP_LOG(LOG_DEBUG2, nlp,
 		    "TX len %u msgs %u msg type %d first hdrlen %u",
 		    nw->buf->datalen, nw->num_messages, hdr->nlmsg_type,
 		    hdr->nlmsg_len);
 	}
 
 	if (nlp->nl_linux && linux_netlink_p != NULL &&
 	    __predict_false(!linux_netlink_p->msgs_to_linux(nw, nlp))) {
 		nl_buf_free(nw->buf);
 		nw->buf = NULL;
 		return (false);
 	}
 
 	nb = nw->buf;
 	nw->buf = NULL;
 
 	SOCK_RECVBUF_LOCK(so);
 	if (!nw->ignore_limit && __predict_false(sb->sb_hiwat <= sb->sb_ccc)) {
 		SOCK_RECVBUF_UNLOCK(so);
 		NLP_LOCK(nlp);
 		nlp->nl_dropped_bytes += nb->datalen;
 		nlp->nl_dropped_messages += nw->num_messages;
 		NLP_LOG(LOG_DEBUG2, nlp, "RX oveflow: %lu m (+%d), %lu b (+%d)",
 		    (unsigned long)nlp->nl_dropped_messages, nw->num_messages,
 		    (unsigned long)nlp->nl_dropped_bytes, nb->datalen);
 		NLP_UNLOCK(nlp);
 		nl_buf_free(nb);
 		return (false);
 	} else {
 		bool full;
 
 		TAILQ_INSERT_TAIL(&sb->nl_queue, nb, tailq);
 		sb->sb_acc += nb->datalen;
 		sb->sb_ccc += nb->datalen;
 		full = sb->sb_hiwat <= sb->sb_ccc;
 		sorwakeup_locked(so);
 		if (full) {
 			NLP_LOCK(nlp);
 			nlp->nl_tx_blocked = true;
 			NLP_UNLOCK(nlp);
 		}
 		return (true);
 	}
 }
 
 static int
 nl_receive_message(struct nlmsghdr *hdr, int remaining_length,
     struct nlpcb *nlp, struct nl_pstate *npt)
 {
 	nl_handler_f handler = nl_handlers[nlp->nl_proto].cb;
 	int error = 0;
 
 	NLP_LOG(LOG_DEBUG2, nlp, "msg len: %u type: %d: flags: 0x%X seq: %u pid: %u",
 	    hdr->nlmsg_len, hdr->nlmsg_type, hdr->nlmsg_flags, hdr->nlmsg_seq,
 	    hdr->nlmsg_pid);
 
 	if (__predict_false(hdr->nlmsg_len > remaining_length)) {
 		NLP_LOG(LOG_DEBUG, nlp, "message is not entirely present: want %d got %d",
 		    hdr->nlmsg_len, remaining_length);
 		return (EINVAL);
 	} else if (__predict_false(hdr->nlmsg_len < sizeof(*hdr))) {
 		NL_LOG(LOG_DEBUG, "message too short: %d", hdr->nlmsg_len);
 		return (EINVAL);
 	}
 	/* Stamp each message with sender pid */
 	hdr->nlmsg_pid = nlp->nl_port;
 
 	npt->hdr = hdr;
 
 	if (hdr->nlmsg_flags & NLM_F_REQUEST && hdr->nlmsg_type >= NLMSG_MIN_TYPE) {
 		NL_LOG(LOG_DEBUG2, "handling message with msg type: %d",
 		   hdr->nlmsg_type);
 
 		if (nlp->nl_linux && linux_netlink_p != NULL) {
 			struct nlmsghdr *hdr_orig = hdr;
 			hdr = linux_netlink_p->msg_from_linux(nlp->nl_proto, hdr, npt);
 			if (hdr == NULL) {
 				 /* Failed to translate to kernel format. Report an error back */
 				hdr = hdr_orig;
 				npt->hdr = hdr;
 				if (hdr->nlmsg_flags & NLM_F_ACK)
 					nlmsg_ack(nlp, EOPNOTSUPP, hdr, npt);
 				return (0);
 			}
 		}
 		error = handler(hdr, npt);
 		NL_LOG(LOG_DEBUG2, "retcode: %d", error);
 	}
 	if ((hdr->nlmsg_flags & NLM_F_ACK) || (error != 0 && error != EINTR)) {
 		if (!npt->nw->suppress_ack) {
 			NL_LOG(LOG_DEBUG3, "ack");
 			nlmsg_ack(nlp, error, hdr, npt);
 		}
 	}
 
 	return (0);
 }
 
 static void
 npt_clear(struct nl_pstate *npt)
 {
 	lb_clear(&npt->lb);
 	npt->error = 0;
 	npt->err_msg = NULL;
 	npt->err_off = 0;
 	npt->hdr = NULL;
 	npt->nw->suppress_ack = false;
 }
 
 /*
  * Processes an incoming packet, which can contain multiple netlink messages
  */
 static bool
 nl_process_nbuf(struct nl_buf *nb, struct nlpcb *nlp)
 {
 	struct nlmsghdr *hdr;
 	int error;
 
 	NL_LOG(LOG_DEBUG3, "RX netlink buf %p on %p", nb, nlp->nl_socket);
 
 	struct nl_writer nw = {};
 	if (!nlmsg_get_unicast_writer(&nw, NLMSG_SMALL, nlp)) {
 		NL_LOG(LOG_DEBUG, "error allocating socket writer");
 		return (true);
 	}
 
 	nlmsg_ignore_limit(&nw);
 
 	struct nl_pstate npt = {
 		.nlp = nlp,
 		.lb.base = &nb->data[roundup2(nb->datalen, 8)],
 		.lb.size = nb->buflen - roundup2(nb->datalen, 8),
 		.nw = &nw,
 		.strict = nlp->nl_flags & NLF_STRICT,
 	};
 
 	for (; nb->offset + sizeof(struct nlmsghdr) <= nb->datalen;) {
 		hdr = (struct nlmsghdr *)&nb->data[nb->offset];
 		/* Save length prior to calling handler */
 		int msglen = NLMSG_ALIGN(hdr->nlmsg_len);
 		NL_LOG(LOG_DEBUG3, "parsing offset %d/%d",
 		    nb->offset, nb->datalen);
 		npt_clear(&npt);
 		error = nl_receive_message(hdr, nb->datalen - nb->offset, nlp,
 		    &npt);
 		nb->offset += msglen;
 		if (__predict_false(error != 0 || nlp->nl_tx_blocked))
 			break;
 	}
 	NL_LOG(LOG_DEBUG3, "packet parsing done");
 	nlmsg_flush(&nw);
 
 	if (nlp->nl_tx_blocked) {
 		NLP_LOCK(nlp);
 		nlp->nl_tx_blocked = false;
 		NLP_UNLOCK(nlp);
 		return (false);
 	} else
 		return (true);
 }
diff --git a/sys/netlink/netlink_var.h b/sys/netlink/netlink_var.h
index 97532c31e54b..c8f0d02a0dab 100644
--- a/sys/netlink/netlink_var.h
+++ b/sys/netlink/netlink_var.h
@@ -1,204 +1,202 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * Copyright (c) 2021 Ng Peng Nam Sean
  * 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.
  */
 #ifndef _NETLINK_NETLINK_VAR_H_
 #define _NETLINK_NETLINK_VAR_H_
 
 #ifdef _KERNEL
 
 #include <sys/ck.h>
 #include <sys/epoch.h>
 #include <sys/sysctl.h>
 #include <sys/taskqueue.h>
 #include <net/vnet.h>
 
 #define	NLSNDQ  	65536 /* Default socket sendspace */
 #define	NLRCVQ		65536 /* Default socket recvspace */
 
 #define	NLMBUFSIZE	2048	/* External storage size for Netlink mbufs */
 
 struct ucred;
 
 struct nl_buf {
 	TAILQ_ENTRY(nl_buf)	tailq;
-	struct mbuf		*control;
 	u_int			buflen;
 	u_int			datalen;
 	u_int			offset;
 	char			data[];
 };
 
 #define	NLP_MAX_GROUPS		128
 
 struct nlpcb {
         struct socket           *nl_socket;
 	uint64_t	        nl_groups[NLP_MAX_GROUPS / 64];
 	uint32_t                nl_port;
 	uint32_t	        nl_flags;
 	uint32_t	        nl_process_id;
         int                     nl_proto;
 	bool			nl_bound;
         bool			nl_task_pending;
 	bool			nl_tx_blocked; /* No new requests accepted */
 	bool			nl_linux; /* true if running under compat */
 	bool			nl_unconstrained_vnet; /* true if running under VNET jail (or without jail) */
 	bool			nl_need_thread_setup;
 	struct taskqueue	*nl_taskqueue;
 	struct task		nl_task;
 	struct ucred		*nl_cred; /* Copy of nl_socket->so_cred */
 	uint64_t		nl_dropped_bytes;
 	uint64_t		nl_dropped_messages;
         CK_LIST_ENTRY(nlpcb)    nl_next;
         CK_LIST_ENTRY(nlpcb)    nl_port_next;
 	volatile u_int		nl_refcount;
 	struct mtx		nl_lock;
 	struct epoch_context	nl_epoch_ctx;
 };
 #define sotonlpcb(so)       ((struct nlpcb *)(so)->so_pcb)
 
 #define	NLP_LOCK_INIT(_nlp)	mtx_init(&((_nlp)->nl_lock), "nlp mtx", NULL, MTX_DEF)
 #define	NLP_LOCK_DESTROY(_nlp)	mtx_destroy(&((_nlp)->nl_lock))
 #define	NLP_LOCK(_nlp)		mtx_lock(&((_nlp)->nl_lock))
 #define	NLP_UNLOCK(_nlp)	mtx_unlock(&((_nlp)->nl_lock))
 
 #define	ALIGNED_NL_SZ(_data)	roundup2((((struct nlmsghdr *)(_data))->nlmsg_len), 16)
 
 /* nl_flags */
 #define NLF_CAP_ACK             0x01 /* Do not send message body with errmsg */
 #define NLF_EXT_ACK             0x02 /* Allow including extended TLVs in ack */
 #define	NLF_STRICT		0x04 /* Perform strict header checks */
 #define	NLF_MSG_INFO		0x08 /* Send caller info along with the notifications */
 
 SYSCTL_DECL(_net_netlink);
 SYSCTL_DECL(_net_netlink_debug);
 
 struct nl_control {
 	CK_LIST_HEAD(nl_pid_head, nlpcb)	ctl_port_head;
 	CK_LIST_HEAD(nlpcb_head, nlpcb)		ctl_pcb_head;
 	CK_LIST_ENTRY(nl_control)		ctl_next;
 	struct rmlock				ctl_lock;
 };
 VNET_DECLARE(struct nl_control *, nl_ctl);
 #define	V_nl_ctl	VNET(nl_ctl)
 
 
 struct sockaddr_nl;
 struct sockaddr;
 struct nlmsghdr;
 
 /* netlink_module.c */
 struct nl_control *vnet_nl_ctl_init(void);
 
 int nl_verify_proto(int proto);
 const char *nl_get_proto_name(int proto);
 
 extern int netlink_unloading;
 
 struct nl_proto_handler {
 	nl_handler_f	cb;
 	const char	*proto_name;
 };
 extern struct nl_proto_handler *nl_handlers;
 
 /* netlink_domain.c */
 bool nl_send_group(struct nl_writer *);
 void nl_osd_register(void);
 void nl_osd_unregister(void);
 void nl_set_thread_nlp(struct thread *td, struct nlpcb *nlp);
 
 /* netlink_io.c */
 #define	NL_IOF_UNTRANSLATED	0x01
 #define	NL_IOF_IGNORE_LIMIT	0x02
 bool nl_send_one(struct nl_writer *);
 void nlmsg_ack(struct nlpcb *nlp, int error, struct nlmsghdr *nlmsg,
     struct nl_pstate *npt);
 void nl_on_transmit(struct nlpcb *nlp);
 
 void nl_taskqueue_handler(void *_arg, int pending);
 void nl_schedule_taskqueue(struct nlpcb *nlp);
 void nl_process_receive_locked(struct nlpcb *nlp);
 void nl_set_source_metadata(struct mbuf *m, int num_messages);
-void nl_add_msg_info(struct nl_buf *nb);
 struct nl_buf *nl_buf_alloc(size_t len, int mflag);
 void nl_buf_free(struct nl_buf *nb);
 
 /* netlink_generic.c */
 struct genl_family {
 	const char	*family_name;
 	uint16_t	family_hdrsize;
 	uint16_t	family_id;
 	uint16_t	family_version;
 	uint16_t	family_attr_max;
 	uint16_t	family_cmd_size;
 	uint16_t	family_num_groups;
 	struct genl_cmd	*family_cmds;
 };
 
 struct genl_group {
 	struct genl_family	*group_family;
 	const char		*group_name;
 };
 
 struct genl_family *genl_get_family(uint32_t family_id);
 struct genl_group *genl_get_group(uint32_t group_id);
 
 #define	MAX_FAMILIES	20
 #define	MAX_GROUPS	64
 
 #define	MIN_GROUP_NUM	48
 
 #define	CTRL_FAMILY_NAME	"nlctrl"
 
 struct ifnet;
 struct nl_parsed_link;
 struct nlattr_bmask;
 struct nl_pstate;
 
 /* Function map */
 struct nl_function_wrapper {
 	bool (*nlmsg_add)(struct nl_writer *nw, uint32_t portid, uint32_t seq, uint16_t type,
 	    uint16_t flags, uint32_t len);
 	bool (*nlmsg_refill_buffer)(struct nl_writer *nw, int required_len);
 	bool (*nlmsg_flush)(struct nl_writer *nw);
 	bool (*nlmsg_end)(struct nl_writer *nw);
 	void (*nlmsg_abort)(struct nl_writer *nw);
 	void (*nlmsg_ignore_limit)(struct nl_writer *nw);
 	bool (*nlmsg_get_unicast_writer)(struct nl_writer *nw, int size, struct nlpcb *nlp);
 	bool (*nlmsg_get_group_writer)(struct nl_writer *nw, int size, int protocol, int group_id);
 	bool (*nlmsg_get_chain_writer)(struct nl_writer *nw, int size, struct mbuf **pm);
 	bool (*nlmsg_end_dump)(struct nl_writer *nw, int error, struct nlmsghdr *hdr);
 	int (*nl_modify_ifp_generic)(struct ifnet *ifp, struct nl_parsed_link *lattrs,
 	    const struct nlattr_bmask *bm, struct nl_pstate *npt);
 	void (*nl_store_ifp_cookie)(struct nl_pstate *npt, struct ifnet *ifp);
 	struct nlpcb * (*nl_get_thread_nlp)(struct  thread *td);
 };
 void nl_set_functions(const struct nl_function_wrapper *nl);
 
 
 
 #endif
 #endif