git: 76f6d391504b - main - netlink: add basic message writing support to snl(3).

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Thu, 09 Mar 2023 14:35:32 UTC
The branch main has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=76f6d391504b040528882a908147e47ea90b7b90

commit 76f6d391504b040528882a908147e47ea90b7b90
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-03-09 14:33:26 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-03-09 14:33:26 +0000

    netlink: add basic message writing support to snl(3).
    
    Differential Revision:  https://reviews.freebsd.org/D38947
    MFC after:      2 weeks
---
 sys/netlink/netlink_snl.h       | 324 ++++++++++++++++++++++++++++++++++++++++
 sys/netlink/netlink_snl_route.h |  36 +++++
 2 files changed, 360 insertions(+)

diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h
index 3c63e3f95419..19b3e2f985f3 100644
--- a/sys/netlink/netlink_snl.h
+++ b/sys/netlink/netlink_snl.h
@@ -124,6 +124,7 @@ struct snl_state {
 	struct linear_buffer *lb;
 };
 #define	SCRATCH_BUFFER_SIZE	1024
+#define	SNL_WRITER_BUFFER_SIZE	256
 
 typedef void snl_parse_field_f(struct snl_state *ss, void *hdr, void *target);
 struct snl_field_parser {
@@ -294,6 +295,30 @@ snl_read_message(struct snl_state *ss)
 	return (hdr);
 }
 
+static inline struct nlmsghdr *
+snl_read_reply(struct snl_state *ss, uint32_t nlmsg_seq)
+{
+	while (true) {
+		struct nlmsghdr *hdr = snl_read_message(ss);
+		if (hdr == NULL)
+			break;
+		if (hdr->nlmsg_seq == nlmsg_seq)
+			return (hdr);
+	}
+
+	return (NULL);
+}
+
+static inline struct nlmsghdr *
+snl_get_reply(struct snl_state *ss, struct nlmsghdr *hdr)
+{
+	uint32_t nlmsg_seq = hdr->nlmsg_seq;
+
+	if (snl_send(ss, hdr, hdr->nlmsg_len))
+		return (snl_read_reply(ss, nlmsg_seq));
+	return (NULL);
+}
+
 /*
  * Checks that attributes are sorted by attribute type.
  */
@@ -511,4 +536,303 @@ snl_field_get_uint32(struct snl_state *ss __unused, void *src, void *target)
 	*((uint32_t *)target) = *((uint32_t *)src);
 }
 
+struct snl_errmsg_data {
+	uint32_t	nlmsg_seq;
+	int		error;
+	char		*error_str;
+	uint32_t	error_offs;
+	struct nlattr	*cookie;
+};
+#define	_IN(_field)	offsetof(struct nlmsgerr, _field)
+#define	_OUT(_field)	offsetof(struct snl_errmsg_data, _field)
+static const struct snl_attr_parser nla_p_errmsg[] = {
+	{ .type = NLMSGERR_ATTR_MSG, .off = _OUT(error_str), .cb = snl_attr_get_string },
+	{ .type = NLMSGERR_ATTR_OFFS, .off = _OUT(error_offs), .cb = snl_attr_get_uint32 },
+	{ .type = NLMSGERR_ATTR_COOKIE, .off = _OUT(cookie), .cb = snl_attr_get_nla },
+};
+
+static const struct snl_field_parser nlf_p_errmsg[] = {
+	{ .off_in = _IN(error), .off_out = _OUT(error), .cb = snl_field_get_uint32 },
+	{ .off_in = _IN(msg.nlmsg_seq), .off_out = _OUT(nlmsg_seq), .cb = snl_field_get_uint32 },
+};
+#undef _IN
+#undef _OUT
+SNL_DECLARE_PARSER(snl_errmsg_parser, struct nlmsgerr, nlf_p_errmsg, nla_p_errmsg);
+
+static inline bool
+snl_check_return(struct snl_state *ss, struct nlmsghdr *hdr, struct snl_errmsg_data *e)
+{
+	if (hdr != NULL && hdr->nlmsg_type == NLMSG_ERROR)
+		return (snl_parse_nlmsg(ss, hdr, &snl_errmsg_parser, e));
+	return (false);
+}
+
+/* writer logic */
+struct snl_writer {
+	char			*base;
+	uint32_t		offset;
+	uint32_t		size;
+	struct nlmsghdr		*hdr;
+	struct snl_state	*ss;
+	bool			error;
+};
+
+static inline void
+snl_init_writer(struct snl_state *ss, struct snl_writer *nw)
+{
+	nw->size = SNL_WRITER_BUFFER_SIZE;
+	nw->base = snl_allocz(ss, nw->size);
+	if (nw->base == NULL) {
+		nw->error = true;
+		nw->size = 0;
+	}
+
+	nw->offset = 0;
+	nw->hdr = NULL;
+	nw->error = false;
+	nw->ss = ss;
+}
+
+static inline bool
+snl_realloc_msg_buffer(struct snl_writer *nw, size_t sz)
+{
+	uint32_t new_size = nw->size * 2;
+
+	while (new_size < nw->size + sz)
+		new_size *= 2;
+
+	if (nw->error)
+		return (false);
+
+	void *new_base = snl_allocz(nw->ss, new_size);
+	if (new_base == NULL) {
+		nw->error = true;
+		return (false);
+	}
+
+	memcpy(new_base, nw->base, nw->offset);
+	if (nw->hdr != NULL) {
+		int hdr_off = (char *)(nw->hdr) - nw->base;
+		nw->hdr = (struct nlmsghdr *)(void *)((char *)new_base + hdr_off);
+	}
+	nw->base = new_base;
+
+	return (true);
+}
+
+static inline void *
+snl_reserve_msg_data_raw(struct snl_writer *nw, size_t sz)
+{
+	sz = NETLINK_ALIGN(sz);
+
+        if (__predict_false(nw->offset + sz > nw->size)) {
+		if (!snl_realloc_msg_buffer(nw, sz))
+			return (NULL);
+        }
+
+        void *data_ptr = &nw->base[nw->offset];
+        nw->offset += sz;
+
+        return (data_ptr);
+}
+#define snl_reserve_msg_object(_ns, _t)	((_t *)snl_reserve_msg_data_raw(_ns, sizeof(_t)))
+#define snl_reserve_msg_data(_ns, _sz, _t)	((_t *)snl_reserve_msg_data_raw(_ns, _sz))
+
+static inline void *
+_snl_reserve_msg_attr(struct snl_writer *nw, uint16_t nla_type, uint16_t sz)
+{
+	sz += sizeof(struct nlattr);
+
+	struct nlattr *nla = snl_reserve_msg_data(nw, sz, struct nlattr);
+	if (__predict_false(nla == NULL))
+		return (NULL);
+	nla->nla_type = nla_type;
+	nla->nla_len = sz;
+
+	return ((void *)(nla + 1));
+}
+#define	snl_reserve_msg_attr(_ns, _at, _t)	((_t *)_snl_reserve_msg_attr(_ns, _at, sizeof(_t)))
+
+static inline bool
+snl_add_msg_attr(struct snl_writer *nw, int attr_type, int attr_len, const void *data)
+{
+	int required_len = NLA_ALIGN(attr_len + sizeof(struct nlattr));
+
+        if (__predict_false(nw->offset + required_len > nw->size)) {
+		if (!snl_realloc_msg_buffer(nw, required_len))
+			return (false);
+	}
+
+        struct nlattr *nla = (struct nlattr *)(&nw->base[nw->offset]);
+
+        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);
+	}
+        nw->offset += required_len;
+        return (true);
+}
+
+static inline bool
+snl_add_msg_attr_raw(struct snl_writer *nw, const struct nlattr *nla_src)
+{
+	int attr_len = nla_src->nla_len - sizeof(struct nlattr);
+
+	assert(attr_len >= 0);
+
+	return (snl_add_msg_attr(nw, nla_src->nla_type, attr_len, (const void *)(nla_src + 1)));
+}
+
+static inline bool
+snl_add_msg_attr_u8(struct snl_writer *nw, int attrtype, uint8_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(uint8_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_u16(struct snl_writer *nw, int attrtype, uint16_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(uint16_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_u32(struct snl_writer *nw, int attrtype, uint32_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(uint32_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_u64(struct snl_writer *nw, int attrtype, uint64_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(uint64_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_s8(struct snl_writer *nw, int attrtype, int8_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(int8_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_s16(struct snl_writer *nw, int attrtype, int16_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(int16_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_s32(struct snl_writer *nw, int attrtype, int32_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(int32_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_s64(struct snl_writer *nw, int attrtype, int64_t value)
+{
+	return (snl_add_msg_attr(nw, attrtype, sizeof(int64_t), &value));
+}
+
+static inline bool
+snl_add_msg_attr_flag(struct snl_writer *nw, int attrtype)
+{
+	return (snl_add_msg_attr(nw, attrtype, 0, NULL));
+}
+
+static inline bool
+snl_add_msg_attr_string(struct snl_writer *nw, int attrtype, const char *str)
+{
+	return (snl_add_msg_attr(nw, attrtype, strlen(str) + 1, str));
+}
+
+
+static inline int
+snl_get_msg_offset(const struct snl_writer *nw)
+{
+        return (nw->offset - ((char *)nw->hdr - nw->base));
+}
+
+static inline void *
+_snl_restore_msg_offset(const struct snl_writer *nw, int off)
+{
+	return ((void *)((char *)nw->hdr + off));
+}
+#define	snl_restore_msg_offset(_ns, _off, _t)	((_t *)_snl_restore_msg_offset(_ns, _off))
+
+static inline int
+snl_add_msg_attr_nested(struct snl_writer *nw, int attrtype)
+{
+	int off = snl_get_msg_offset(nw);
+	struct nlattr *nla = snl_reserve_msg_data(nw, sizeof(struct nlattr), struct nlattr);
+	if (__predict_false(nla == NULL))
+		return (0);
+	nla->nla_type = attrtype;
+	return (off);
+}
+
+static inline void
+snl_end_attr_nested(const struct snl_writer *nw, int off)
+{
+	if (!nw->error) {
+		struct nlattr *nla = snl_restore_msg_offset(nw, off, struct nlattr);
+		nla->nla_len = NETLINK_ALIGN(snl_get_msg_offset(nw) - off);
+	}
+}
+
+static inline struct nlmsghdr *
+snl_create_msg_request(struct snl_writer *nw, int nlmsg_type)
+{
+	assert(nw->hdr == NULL);
+
+	struct nlmsghdr *hdr = snl_reserve_msg_object(nw, struct nlmsghdr);
+	hdr->nlmsg_type = nlmsg_type;
+	hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	nw->hdr = hdr;
+
+	return (hdr);
+}
+
+static void
+snl_abort_msg(struct snl_writer *nw)
+{
+	if (nw->hdr != NULL) {
+		int offset = (char *)(&nw->base[nw->offset]) - (char *)(nw->hdr);
+
+		nw->offset -= offset;
+		nw->hdr = NULL;
+	}
+}
+
+static inline struct nlmsghdr *
+snl_finalize_msg(struct snl_writer *nw)
+{
+	if (nw->error)
+		snl_abort_msg(nw);
+	if (nw->hdr != NULL) {
+		struct nlmsghdr *hdr = nw->hdr;
+
+		int offset = (char *)(&nw->base[nw->offset]) - (char *)(nw->hdr);
+		hdr->nlmsg_len = offset;
+		hdr->nlmsg_seq = snl_get_seq(nw->ss);
+		nw->hdr = NULL;
+
+		return (hdr);
+	}
+	return (NULL);
+}
+
+static bool
+snl_send_msgs(struct snl_writer *nw)
+{
+	int offset = nw->offset;
+
+	assert(nw->hdr == NULL);
+	nw->offset = 0;
+
+	return (snl_send(nw->ss, nw->base, offset));
+}
+
 #endif
diff --git a/sys/netlink/netlink_snl_route.h b/sys/netlink/netlink_snl_route.h
index 4adb3d697ecd..281794a9d6f7 100644
--- a/sys/netlink/netlink_snl_route.h
+++ b/sys/netlink/netlink_snl_route.h
@@ -127,4 +127,40 @@ snl_attr_get_ipvia(struct snl_state *ss, struct nlattr *nla,
 	return (false);
 }
 
+static inline bool
+snl_add_msg_attr_ip(struct snl_writer *nw, int attrtype, const struct sockaddr *sa)
+{
+	const void *addr;
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		addr = &((const struct sockaddr_in *)sa)->sin_addr;
+		return (snl_add_msg_attr(nw, attrtype, 4, addr));
+	case AF_INET6:
+		addr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
+		return (snl_add_msg_attr(nw, attrtype, 16, addr));
+	}
+
+	return (false);
+}
+
+static inline bool
+snl_add_msg_attr_ipvia(struct snl_writer *nw, int attrtype, const struct sockaddr *sa)
+{
+	char buf[17];
+
+	buf[0] = sa->sa_family;
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		memcpy(&buf[1], &((const struct sockaddr_in *)sa)->sin_addr, 4);
+		return (snl_add_msg_attr(nw, attrtype, 5, buf));
+	case AF_INET6:
+		memcpy(&buf[1], &((const struct sockaddr_in6 *)sa)->sin6_addr, 16);
+		return (snl_add_msg_attr(nw, attrtype, 17, buf));
+	}
+
+	return (false);
+}
+
 #endif