netlink: use custom uma zone for the mbuf storage.

Netlink communicates with userland via sockets, utilising
 MCLBYTES-sized mbufs to append data to the socket buffers.
These mbufs are never transmitted via logical or physical network.

It may be possible that the 2k mbuf zone is temporary exhausted
 due to the DDoS-style traffic, leading to Netlink failure to
 respond to the requests.

To address it, this change introduces a custom Netlink-specific
 zone for the mbuf storage. It has the following benefits:
* no precious memory from UMA_ZONE_CONTIG zones is utilized for Netlink
* Netlink becomes (more) independent from the traffic spikes and
 other related network "corner" conditions.
* Netlink allocations are now isolated within a specific zone, making it
 easier to track Netlink mbuf usage and attribute mbufs.

Reviewed by:	gallatin, adrian
Differential Revision: https://reviews.freebsd.org/D40356
MFC after:	2 weeks
This commit is contained in:
Alexander V. Chernikov 2023-05-31 18:02:49 +00:00
parent 4f2cc73f34
commit d187154750
3 changed files with 81 additions and 9 deletions

View file

@ -53,7 +53,7 @@ _DECLARE_DEBUG(LOG_INFO);
* The goal of this file is to provide convenient message writing KPI on top of
* different storage methods (mbufs, uio, temporary memory chunks).
*
* The main KPI guarantee is the the (last) message always resides in the contiguous
* The main KPI guarantee is that 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
@ -79,6 +79,71 @@ _DECLARE_DEBUG(LOG_INFO);
* change. It happens transparently to the caller.
*/
/*
* Uma zone for the mbuf-based Netlink storage
*/
static uma_zone_t nlmsg_zone;
static void
nl_free_mbuf_storage(struct mbuf *m)
{
uma_zfree(nlmsg_zone, m->m_ext.ext_buf);
}
static int
nl_setup_mbuf_storage(void *mem, int size, void *arg, int how __unused)
{
struct mbuf *m = (struct mbuf *)arg;
if (m != NULL)
m_extadd(m, mem, size, nl_free_mbuf_storage, NULL, NULL, 0, EXT_MOD_TYPE);
return (0);
}
static struct mbuf *
nl_get_mbuf_flags(int size, int malloc_flags, int mbuf_flags)
{
struct mbuf *m, *m_storage;
if (size <= MHLEN)
return (m_get2(size, malloc_flags, MT_DATA, mbuf_flags));
if (__predict_false(size > NLMBUFSIZE))
return (NULL);
m = m_gethdr(malloc_flags, MT_DATA);
if (m == NULL)
return (NULL);
m_storage = uma_zalloc_arg(nlmsg_zone, m, malloc_flags);
if (m_storage == NULL) {
m_free_raw(m);
return (NULL);
}
return (m);
}
static struct mbuf *
nl_get_mbuf(int size, int malloc_flags)
{
return (nl_get_mbuf_flags(size, malloc_flags, M_PKTHDR));
}
void
nl_init_msg_zone(void)
{
nlmsg_zone = uma_zcreate("netlink", NLMBUFSIZE, nl_setup_mbuf_storage,
NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
}
void
nl_destroy_msg_zone(void)
{
uma_zdestroy(nlmsg_zone);
}
typedef bool nlwriter_op_init(struct nl_writer *nw, int size, bool waitok);
typedef bool nlwriter_op_write(struct nl_writer *nw, void *buf, int buflen, int cnt);
@ -196,17 +261,16 @@ nlmsg_write_chain_buf(struct nl_writer *nw, void *buf, int datalen, int cnt)
* 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.
* If size < MHLEN (around 160 bytes), allocates mbuf with pkghdr.
* If the size <= NLMBUFSIZE (2k), allocate mbuf+storage out of nlmsg_zone.
* Returns NULL on greater size or the allocation failure.
*/
static bool
nlmsg_get_ns_mbuf(struct nl_writer *nw, int size, bool waitok)
{
struct mbuf *m;
int mflag = waitok ? M_WAITOK : M_NOWAIT;
m = m_get2(size, mflag, MT_DATA, M_PKTHDR);
struct mbuf *m = nl_get_mbuf(size, mflag);
if (__predict_false(m == NULL))
return (false);
nw->alloc_len = M_TRAILINGSPACE(m);

View file

@ -223,6 +223,7 @@ netlink_modevent(module_t mod __unused, int what, void *priv __unused)
switch (what) {
case MOD_LOAD:
NL_LOG(LOG_DEBUG2, "Loading");
nl_init_msg_zone();
nl_osd_register();
#if !defined(NETLINK) && defined(NETLINK_MODULE)
nl_set_functions(&nl_module);
@ -238,6 +239,7 @@ netlink_modevent(module_t mod __unused, int what, void *priv __unused)
nl_set_functions(NULL);
#endif
nl_osd_unregister();
nl_destroy_msg_zone();
} else
ret = EBUSY;
break;

View file

@ -36,8 +36,10 @@
#include <sys/taskqueue.h>
#include <net/vnet.h>
#define NLSNDQ 65536 /* Default socket sendspace */
#define NLRCVQ 65536 /* Default socket recvspace */
#define NLSNDQ 65536 /* Default socket sendspace */
#define NLRCVQ 65536 /* Default socket recvspace */
#define NLMBUFSIZE 2048 /* External storage size for Netlink mbufs */
struct ucred;
@ -152,6 +154,10 @@ 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 mbuf *m);
/* netlink_message_writer.c */
void nl_init_msg_zone(void);
void nl_destroy_msg_zone(void);
/* netlink_generic.c */
struct genl_family {
const char *family_name;