mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	Introduce support for attribute type bitfield32. Note that since the generated code works with struct nla_bitfield32, the generator adds netlink.h to the list of includes for userspace headers in case any bitfield32 is present. Note that this is added only to genetlink-legacy scheme as requested by Jakub Kicinski. Signed-off-by: Jiri Pirko <jiri@nvidia.com> Reviewed-by: Jacob Keller <jacob.e.keller@intel.com> Link: https://lore.kernel.org/r/20231021112711.660606-3-jiri@resnulli.us Signed-off-by: Jakub Kicinski <kuba@kernel.org>
		
			
				
	
	
		
			913 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			913 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 | 
						|
#include <errno.h>
 | 
						|
#include <poll.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <linux/types.h>
 | 
						|
 | 
						|
#include <libmnl/libmnl.h>
 | 
						|
#include <linux/genetlink.h>
 | 
						|
 | 
						|
#include "ynl.h"
 | 
						|
 | 
						|
#define ARRAY_SIZE(arr)		(sizeof(arr) / sizeof(*arr))
 | 
						|
 | 
						|
#define __yerr_msg(yse, _msg...)					\
 | 
						|
	({								\
 | 
						|
		struct ynl_error *_yse = (yse);				\
 | 
						|
									\
 | 
						|
		if (_yse) {						\
 | 
						|
			snprintf(_yse->msg, sizeof(_yse->msg) - 1,  _msg); \
 | 
						|
			_yse->msg[sizeof(_yse->msg) - 1] = 0;		\
 | 
						|
		}							\
 | 
						|
	})
 | 
						|
 | 
						|
#define __yerr_code(yse, _code...)		\
 | 
						|
	({					\
 | 
						|
		struct ynl_error *_yse = (yse);	\
 | 
						|
						\
 | 
						|
		if (_yse) {			\
 | 
						|
			_yse->code = _code;	\
 | 
						|
		}				\
 | 
						|
	})
 | 
						|
 | 
						|
#define __yerr(yse, _code, _msg...)		\
 | 
						|
	({					\
 | 
						|
		__yerr_msg(yse, _msg);		\
 | 
						|
		__yerr_code(yse, _code);	\
 | 
						|
	})
 | 
						|
 | 
						|
#define __perr(yse, _msg)		__yerr(yse, errno, _msg)
 | 
						|
 | 
						|
#define yerr_msg(_ys, _msg...)		__yerr_msg(&(_ys)->err, _msg)
 | 
						|
#define yerr(_ys, _code, _msg...)	__yerr(&(_ys)->err, _code, _msg)
 | 
						|
#define perr(_ys, _msg)			__yerr(&(_ys)->err, errno, _msg)
 | 
						|
 | 
						|
/* -- Netlink boiler plate */
 | 
						|
static int
 | 
						|
ynl_err_walk_report_one(struct ynl_policy_nest *policy, unsigned int type,
 | 
						|
			char *str, int str_sz, int *n)
 | 
						|
{
 | 
						|
	if (!policy) {
 | 
						|
		if (*n < str_sz)
 | 
						|
			*n += snprintf(str, str_sz, "!policy");
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (type > policy->max_attr) {
 | 
						|
		if (*n < str_sz)
 | 
						|
			*n += snprintf(str, str_sz, "!oob");
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!policy->table[type].name) {
 | 
						|
		if (*n < str_sz)
 | 
						|
			*n += snprintf(str, str_sz, "!name");
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (*n < str_sz)
 | 
						|
		*n += snprintf(str, str_sz - *n,
 | 
						|
			       ".%s", policy->table[type].name);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off,
 | 
						|
	     struct ynl_policy_nest *policy, char *str, int str_sz,
 | 
						|
	     struct ynl_policy_nest **nest_pol)
 | 
						|
{
 | 
						|
	unsigned int astart_off, aend_off;
 | 
						|
	const struct nlattr *attr;
 | 
						|
	unsigned int data_len;
 | 
						|
	unsigned int type;
 | 
						|
	bool found = false;
 | 
						|
	int n = 0;
 | 
						|
 | 
						|
	if (!policy) {
 | 
						|
		if (n < str_sz)
 | 
						|
			n += snprintf(str, str_sz, "!policy");
 | 
						|
		return n;
 | 
						|
	}
 | 
						|
 | 
						|
	data_len = end - start;
 | 
						|
 | 
						|
	mnl_attr_for_each_payload(start, data_len) {
 | 
						|
		astart_off = (char *)attr - (char *)start;
 | 
						|
		aend_off = astart_off + mnl_attr_get_payload_len(attr);
 | 
						|
		if (aend_off <= off)
 | 
						|
			continue;
 | 
						|
 | 
						|
		found = true;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	if (!found)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	off -= astart_off;
 | 
						|
 | 
						|
	type = mnl_attr_get_type(attr);
 | 
						|
 | 
						|
	if (ynl_err_walk_report_one(policy, type, str, str_sz, &n))
 | 
						|
		return n;
 | 
						|
 | 
						|
	if (!off) {
 | 
						|
		if (nest_pol)
 | 
						|
			*nest_pol = policy->table[type].nest;
 | 
						|
		return n;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!policy->table[type].nest) {
 | 
						|
		if (n < str_sz)
 | 
						|
			n += snprintf(str, str_sz, "!nest");
 | 
						|
		return n;
 | 
						|
	}
 | 
						|
 | 
						|
	off -= sizeof(struct nlattr);
 | 
						|
	start =  mnl_attr_get_payload(attr);
 | 
						|
	end = start + mnl_attr_get_payload_len(attr);
 | 
						|
 | 
						|
	return n + ynl_err_walk(ys, start, end, off, policy->table[type].nest,
 | 
						|
				&str[n], str_sz - n, nest_pol);
 | 
						|
}
 | 
						|
 | 
						|
#define NLMSGERR_ATTR_MISS_TYPE (NLMSGERR_ATTR_POLICY + 1)
 | 
						|
#define NLMSGERR_ATTR_MISS_NEST (NLMSGERR_ATTR_POLICY + 2)
 | 
						|
#define NLMSGERR_ATTR_MAX (NLMSGERR_ATTR_MAX + 2)
 | 
						|
 | 
						|
static int
 | 
						|
ynl_ext_ack_check(struct ynl_sock *ys, const struct nlmsghdr *nlh,
 | 
						|
		  unsigned int hlen)
 | 
						|
{
 | 
						|
	const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
 | 
						|
	char miss_attr[sizeof(ys->err.msg)];
 | 
						|
	char bad_attr[sizeof(ys->err.msg)];
 | 
						|
	const struct nlattr *attr;
 | 
						|
	const char *str = NULL;
 | 
						|
 | 
						|
	if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
 | 
						|
		return MNL_CB_OK;
 | 
						|
 | 
						|
	mnl_attr_for_each(attr, nlh, hlen) {
 | 
						|
		unsigned int len, type;
 | 
						|
 | 
						|
		len = mnl_attr_get_payload_len(attr);
 | 
						|
		type = mnl_attr_get_type(attr);
 | 
						|
 | 
						|
		if (type > NLMSGERR_ATTR_MAX)
 | 
						|
			continue;
 | 
						|
 | 
						|
		tb[type] = attr;
 | 
						|
 | 
						|
		switch (type) {
 | 
						|
		case NLMSGERR_ATTR_OFFS:
 | 
						|
		case NLMSGERR_ATTR_MISS_TYPE:
 | 
						|
		case NLMSGERR_ATTR_MISS_NEST:
 | 
						|
			if (len != sizeof(__u32))
 | 
						|
				return MNL_CB_ERROR;
 | 
						|
			break;
 | 
						|
		case NLMSGERR_ATTR_MSG:
 | 
						|
			str = mnl_attr_get_payload(attr);
 | 
						|
			if (str[len - 1])
 | 
						|
				return MNL_CB_ERROR;
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	bad_attr[0] = '\0';
 | 
						|
	miss_attr[0] = '\0';
 | 
						|
 | 
						|
	if (tb[NLMSGERR_ATTR_OFFS]) {
 | 
						|
		unsigned int n, off;
 | 
						|
		void *start, *end;
 | 
						|
 | 
						|
		ys->err.attr_offs = mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]);
 | 
						|
 | 
						|
		n = snprintf(bad_attr, sizeof(bad_attr), "%sbad attribute: ",
 | 
						|
			     str ? " (" : "");
 | 
						|
 | 
						|
		start = mnl_nlmsg_get_payload_offset(ys->nlh,
 | 
						|
						     sizeof(struct genlmsghdr));
 | 
						|
		end = mnl_nlmsg_get_payload_tail(ys->nlh);
 | 
						|
 | 
						|
		off = ys->err.attr_offs;
 | 
						|
		off -= sizeof(struct nlmsghdr);
 | 
						|
		off -= sizeof(struct genlmsghdr);
 | 
						|
 | 
						|
		n += ynl_err_walk(ys, start, end, off, ys->req_policy,
 | 
						|
				  &bad_attr[n], sizeof(bad_attr) - n, NULL);
 | 
						|
 | 
						|
		if (n >= sizeof(bad_attr))
 | 
						|
			n = sizeof(bad_attr) - 1;
 | 
						|
		bad_attr[n] = '\0';
 | 
						|
	}
 | 
						|
	if (tb[NLMSGERR_ATTR_MISS_TYPE]) {
 | 
						|
		struct ynl_policy_nest *nest_pol = NULL;
 | 
						|
		unsigned int n, off, type;
 | 
						|
		void *start, *end;
 | 
						|
		int n2;
 | 
						|
 | 
						|
		type = mnl_attr_get_u32(tb[NLMSGERR_ATTR_MISS_TYPE]);
 | 
						|
 | 
						|
		n = snprintf(miss_attr, sizeof(miss_attr), "%smissing attribute: ",
 | 
						|
			     bad_attr[0] ? ", " : (str ? " (" : ""));
 | 
						|
 | 
						|
		start = mnl_nlmsg_get_payload_offset(ys->nlh,
 | 
						|
						     sizeof(struct genlmsghdr));
 | 
						|
		end = mnl_nlmsg_get_payload_tail(ys->nlh);
 | 
						|
 | 
						|
		nest_pol = ys->req_policy;
 | 
						|
		if (tb[NLMSGERR_ATTR_MISS_NEST]) {
 | 
						|
			off = mnl_attr_get_u32(tb[NLMSGERR_ATTR_MISS_NEST]);
 | 
						|
			off -= sizeof(struct nlmsghdr);
 | 
						|
			off -= sizeof(struct genlmsghdr);
 | 
						|
 | 
						|
			n += ynl_err_walk(ys, start, end, off, ys->req_policy,
 | 
						|
					  &miss_attr[n], sizeof(miss_attr) - n,
 | 
						|
					  &nest_pol);
 | 
						|
		}
 | 
						|
 | 
						|
		n2 = 0;
 | 
						|
		ynl_err_walk_report_one(nest_pol, type, &miss_attr[n],
 | 
						|
					sizeof(miss_attr) - n, &n2);
 | 
						|
		n += n2;
 | 
						|
 | 
						|
		if (n >= sizeof(miss_attr))
 | 
						|
			n = sizeof(miss_attr) - 1;
 | 
						|
		miss_attr[n] = '\0';
 | 
						|
	}
 | 
						|
 | 
						|
	/* Implicitly depend on ys->err.code already set */
 | 
						|
	if (str)
 | 
						|
		yerr_msg(ys, "Kernel %s: '%s'%s%s%s",
 | 
						|
			 ys->err.code ? "error" : "warning",
 | 
						|
			 str, bad_attr, miss_attr,
 | 
						|
			 bad_attr[0] || miss_attr[0] ? ")" : "");
 | 
						|
	else if (bad_attr[0] || miss_attr[0])
 | 
						|
		yerr_msg(ys, "Kernel %s: %s%s",
 | 
						|
			 ys->err.code ? "error" : "warning",
 | 
						|
			 bad_attr, miss_attr);
 | 
						|
 | 
						|
	return MNL_CB_OK;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_cb_error(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
 | 
						|
	struct ynl_parse_arg *yarg = data;
 | 
						|
	unsigned int hlen;
 | 
						|
	int code;
 | 
						|
 | 
						|
	code = err->error >= 0 ? err->error : -err->error;
 | 
						|
	yarg->ys->err.code = code;
 | 
						|
	errno = code;
 | 
						|
 | 
						|
	hlen = sizeof(*err);
 | 
						|
	if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
 | 
						|
		hlen += mnl_nlmsg_get_payload_len(&err->msg);
 | 
						|
 | 
						|
	ynl_ext_ack_check(yarg->ys, nlh, hlen);
 | 
						|
 | 
						|
	return code ? MNL_CB_ERROR : MNL_CB_STOP;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_cb_done(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	struct ynl_parse_arg *yarg = data;
 | 
						|
	int err;
 | 
						|
 | 
						|
	err = *(int *)NLMSG_DATA(nlh);
 | 
						|
	if (err < 0) {
 | 
						|
		yarg->ys->err.code = -err;
 | 
						|
		errno = -err;
 | 
						|
 | 
						|
		ynl_ext_ack_check(yarg->ys, nlh, sizeof(int));
 | 
						|
 | 
						|
		return MNL_CB_ERROR;
 | 
						|
	}
 | 
						|
	return MNL_CB_STOP;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_cb_noop(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	return MNL_CB_OK;
 | 
						|
}
 | 
						|
 | 
						|
mnl_cb_t ynl_cb_array[NLMSG_MIN_TYPE] = {
 | 
						|
	[NLMSG_NOOP]	= ynl_cb_noop,
 | 
						|
	[NLMSG_ERROR]	= ynl_cb_error,
 | 
						|
	[NLMSG_DONE]	= ynl_cb_done,
 | 
						|
	[NLMSG_OVERRUN]	= ynl_cb_noop,
 | 
						|
};
 | 
						|
 | 
						|
/* Attribute validation */
 | 
						|
 | 
						|
int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr)
 | 
						|
{
 | 
						|
	struct ynl_policy_attr *policy;
 | 
						|
	unsigned int type, len;
 | 
						|
	unsigned char *data;
 | 
						|
 | 
						|
	data = mnl_attr_get_payload(attr);
 | 
						|
	len = mnl_attr_get_payload_len(attr);
 | 
						|
	type = mnl_attr_get_type(attr);
 | 
						|
	if (type > yarg->rsp_policy->max_attr) {
 | 
						|
		yerr(yarg->ys, YNL_ERROR_INTERNAL,
 | 
						|
		     "Internal error, validating unknown attribute");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	policy = &yarg->rsp_policy->table[type];
 | 
						|
 | 
						|
	switch (policy->type) {
 | 
						|
	case YNL_PT_REJECT:
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Rejected attribute (%s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_IGNORE:
 | 
						|
		break;
 | 
						|
	case YNL_PT_U8:
 | 
						|
		if (len == sizeof(__u8))
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (u8 %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_U16:
 | 
						|
		if (len == sizeof(__u16))
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (u16 %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_U32:
 | 
						|
		if (len == sizeof(__u32))
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (u32 %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_U64:
 | 
						|
		if (len == sizeof(__u64))
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (u64 %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_UINT:
 | 
						|
		if (len == sizeof(__u32) || len == sizeof(__u64))
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (uint %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_FLAG:
 | 
						|
		/* Let flags grow into real attrs, why not.. */
 | 
						|
		break;
 | 
						|
	case YNL_PT_NEST:
 | 
						|
		if (!len || len >= sizeof(*attr))
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (nest %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_BINARY:
 | 
						|
		if (!policy->len || len == policy->len)
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (binary %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_NUL_STR:
 | 
						|
		if ((!policy->len || len <= policy->len) && !data[len - 1])
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (string %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	case YNL_PT_BITFIELD32:
 | 
						|
		if (len == sizeof(struct nla_bitfield32))
 | 
						|
			break;
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (bitfield32 %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	default:
 | 
						|
		yerr(yarg->ys, YNL_ERROR_ATTR_INVALID,
 | 
						|
		     "Invalid attribute (unknown %s)", policy->name);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Generic code */
 | 
						|
 | 
						|
static void ynl_err_reset(struct ynl_sock *ys)
 | 
						|
{
 | 
						|
	ys->err.code = 0;
 | 
						|
	ys->err.attr_offs = 0;
 | 
						|
	ys->err.msg[0] = 0;
 | 
						|
}
 | 
						|
 | 
						|
struct nlmsghdr *ynl_msg_start(struct ynl_sock *ys, __u32 id, __u16 flags)
 | 
						|
{
 | 
						|
	struct nlmsghdr *nlh;
 | 
						|
 | 
						|
	ynl_err_reset(ys);
 | 
						|
 | 
						|
	nlh = ys->nlh = mnl_nlmsg_put_header(ys->tx_buf);
 | 
						|
	nlh->nlmsg_type	= id;
 | 
						|
	nlh->nlmsg_flags = flags;
 | 
						|
	nlh->nlmsg_seq = ++ys->seq;
 | 
						|
 | 
						|
	return nlh;
 | 
						|
}
 | 
						|
 | 
						|
struct nlmsghdr *
 | 
						|
ynl_gemsg_start(struct ynl_sock *ys, __u32 id, __u16 flags,
 | 
						|
		__u8 cmd, __u8 version)
 | 
						|
{
 | 
						|
	struct genlmsghdr gehdr;
 | 
						|
	struct nlmsghdr *nlh;
 | 
						|
	void *data;
 | 
						|
 | 
						|
	nlh = ynl_msg_start(ys, id, flags);
 | 
						|
 | 
						|
	memset(&gehdr, 0, sizeof(gehdr));
 | 
						|
	gehdr.cmd = cmd;
 | 
						|
	gehdr.version = version;
 | 
						|
 | 
						|
	data = mnl_nlmsg_put_extra_header(nlh, sizeof(gehdr));
 | 
						|
	memcpy(data, &gehdr, sizeof(gehdr));
 | 
						|
 | 
						|
	return nlh;
 | 
						|
}
 | 
						|
 | 
						|
void ynl_msg_start_req(struct ynl_sock *ys, __u32 id)
 | 
						|
{
 | 
						|
	ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK);
 | 
						|
}
 | 
						|
 | 
						|
void ynl_msg_start_dump(struct ynl_sock *ys, __u32 id)
 | 
						|
{
 | 
						|
	ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
 | 
						|
}
 | 
						|
 | 
						|
struct nlmsghdr *
 | 
						|
ynl_gemsg_start_req(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version)
 | 
						|
{
 | 
						|
	return ynl_gemsg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK, cmd, version);
 | 
						|
}
 | 
						|
 | 
						|
struct nlmsghdr *
 | 
						|
ynl_gemsg_start_dump(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version)
 | 
						|
{
 | 
						|
	return ynl_gemsg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP,
 | 
						|
			       cmd, version);
 | 
						|
}
 | 
						|
 | 
						|
int ynl_recv_ack(struct ynl_sock *ys, int ret)
 | 
						|
{
 | 
						|
	if (!ret) {
 | 
						|
		yerr(ys, YNL_ERROR_EXPECT_ACK,
 | 
						|
		     "Expecting an ACK but nothing received");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE);
 | 
						|
	if (ret < 0) {
 | 
						|
		perr(ys, "Socket receive failed");
 | 
						|
		return ret;
 | 
						|
	}
 | 
						|
	return mnl_cb_run(ys->rx_buf, ret, ys->seq, ys->portid,
 | 
						|
			  ynl_cb_null, ys);
 | 
						|
}
 | 
						|
 | 
						|
int ynl_cb_null(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	struct ynl_parse_arg *yarg = data;
 | 
						|
 | 
						|
	yerr(yarg->ys, YNL_ERROR_UNEXPECT_MSG,
 | 
						|
	     "Received a message when none were expected");
 | 
						|
 | 
						|
	return MNL_CB_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
/* Init/fini and genetlink boiler plate */
 | 
						|
static int
 | 
						|
ynl_get_family_info_mcast(struct ynl_sock *ys, const struct nlattr *mcasts)
 | 
						|
{
 | 
						|
	const struct nlattr *entry, *attr;
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	mnl_attr_for_each_nested(attr, mcasts)
 | 
						|
		ys->n_mcast_groups++;
 | 
						|
 | 
						|
	if (!ys->n_mcast_groups)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	ys->mcast_groups = calloc(ys->n_mcast_groups,
 | 
						|
				  sizeof(*ys->mcast_groups));
 | 
						|
	if (!ys->mcast_groups)
 | 
						|
		return MNL_CB_ERROR;
 | 
						|
 | 
						|
	i = 0;
 | 
						|
	mnl_attr_for_each_nested(entry, mcasts) {
 | 
						|
		mnl_attr_for_each_nested(attr, entry) {
 | 
						|
			if (mnl_attr_get_type(attr) == CTRL_ATTR_MCAST_GRP_ID)
 | 
						|
				ys->mcast_groups[i].id = mnl_attr_get_u32(attr);
 | 
						|
			if (mnl_attr_get_type(attr) == CTRL_ATTR_MCAST_GRP_NAME) {
 | 
						|
				strncpy(ys->mcast_groups[i].name,
 | 
						|
					mnl_attr_get_str(attr),
 | 
						|
					GENL_NAMSIZ - 1);
 | 
						|
				ys->mcast_groups[i].name[GENL_NAMSIZ - 1] = 0;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_get_family_info_cb(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	struct ynl_parse_arg *yarg = data;
 | 
						|
	struct ynl_sock *ys = yarg->ys;
 | 
						|
	const struct nlattr *attr;
 | 
						|
	bool found_id = true;
 | 
						|
 | 
						|
	mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) {
 | 
						|
		if (mnl_attr_get_type(attr) == CTRL_ATTR_MCAST_GROUPS)
 | 
						|
			if (ynl_get_family_info_mcast(ys, attr))
 | 
						|
				return MNL_CB_ERROR;
 | 
						|
 | 
						|
		if (mnl_attr_get_type(attr) != CTRL_ATTR_FAMILY_ID)
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (mnl_attr_get_payload_len(attr) != sizeof(__u16)) {
 | 
						|
			yerr(ys, YNL_ERROR_ATTR_INVALID, "Invalid family ID");
 | 
						|
			return MNL_CB_ERROR;
 | 
						|
		}
 | 
						|
 | 
						|
		ys->family_id = mnl_attr_get_u16(attr);
 | 
						|
		found_id = true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!found_id) {
 | 
						|
		yerr(ys, YNL_ERROR_ATTR_MISSING, "Family ID missing");
 | 
						|
		return MNL_CB_ERROR;
 | 
						|
	}
 | 
						|
	return MNL_CB_OK;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_sock_read_family(struct ynl_sock *ys, const char *family_name)
 | 
						|
{
 | 
						|
	struct ynl_parse_arg yarg = { .ys = ys, };
 | 
						|
	struct nlmsghdr *nlh;
 | 
						|
	int err;
 | 
						|
 | 
						|
	nlh = ynl_gemsg_start_req(ys, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 1);
 | 
						|
	mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
 | 
						|
 | 
						|
	err = mnl_socket_sendto(ys->sock, nlh, nlh->nlmsg_len);
 | 
						|
	if (err < 0) {
 | 
						|
		perr(ys, "failed to request socket family info");
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
 | 
						|
	err = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE);
 | 
						|
	if (err <= 0) {
 | 
						|
		perr(ys, "failed to receive the socket family info");
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
	err = mnl_cb_run2(ys->rx_buf, err, ys->seq, ys->portid,
 | 
						|
			  ynl_get_family_info_cb, &yarg,
 | 
						|
			  ynl_cb_array, ARRAY_SIZE(ynl_cb_array));
 | 
						|
	if (err < 0) {
 | 
						|
		free(ys->mcast_groups);
 | 
						|
		perr(ys, "failed to receive the socket family info - no such family?");
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
 | 
						|
	return ynl_recv_ack(ys, err);
 | 
						|
}
 | 
						|
 | 
						|
struct ynl_sock *
 | 
						|
ynl_sock_create(const struct ynl_family *yf, struct ynl_error *yse)
 | 
						|
{
 | 
						|
	struct ynl_sock *ys;
 | 
						|
	int one = 1;
 | 
						|
 | 
						|
	ys = malloc(sizeof(*ys) + 2 * MNL_SOCKET_BUFFER_SIZE);
 | 
						|
	if (!ys)
 | 
						|
		return NULL;
 | 
						|
	memset(ys, 0, sizeof(*ys));
 | 
						|
 | 
						|
	ys->family = yf;
 | 
						|
	ys->tx_buf = &ys->raw_buf[0];
 | 
						|
	ys->rx_buf = &ys->raw_buf[MNL_SOCKET_BUFFER_SIZE];
 | 
						|
	ys->ntf_last_next = &ys->ntf_first;
 | 
						|
 | 
						|
	ys->sock = mnl_socket_open(NETLINK_GENERIC);
 | 
						|
	if (!ys->sock) {
 | 
						|
		__perr(yse, "failed to create a netlink socket");
 | 
						|
		goto err_free_sock;
 | 
						|
	}
 | 
						|
 | 
						|
	if (mnl_socket_setsockopt(ys->sock, NETLINK_CAP_ACK,
 | 
						|
				  &one, sizeof(one))) {
 | 
						|
		__perr(yse, "failed to enable netlink ACK");
 | 
						|
		goto err_close_sock;
 | 
						|
	}
 | 
						|
	if (mnl_socket_setsockopt(ys->sock, NETLINK_EXT_ACK,
 | 
						|
				  &one, sizeof(one))) {
 | 
						|
		__perr(yse, "failed to enable netlink ext ACK");
 | 
						|
		goto err_close_sock;
 | 
						|
	}
 | 
						|
 | 
						|
	ys->seq = random();
 | 
						|
	ys->portid = mnl_socket_get_portid(ys->sock);
 | 
						|
 | 
						|
	if (ynl_sock_read_family(ys, yf->name)) {
 | 
						|
		if (yse)
 | 
						|
			memcpy(yse, &ys->err, sizeof(*yse));
 | 
						|
		goto err_close_sock;
 | 
						|
	}
 | 
						|
 | 
						|
	return ys;
 | 
						|
 | 
						|
err_close_sock:
 | 
						|
	mnl_socket_close(ys->sock);
 | 
						|
err_free_sock:
 | 
						|
	free(ys);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
void ynl_sock_destroy(struct ynl_sock *ys)
 | 
						|
{
 | 
						|
	struct ynl_ntf_base_type *ntf;
 | 
						|
 | 
						|
	mnl_socket_close(ys->sock);
 | 
						|
	while ((ntf = ynl_ntf_dequeue(ys)))
 | 
						|
		ynl_ntf_free(ntf);
 | 
						|
	free(ys->mcast_groups);
 | 
						|
	free(ys);
 | 
						|
}
 | 
						|
 | 
						|
/* YNL multicast handling */
 | 
						|
 | 
						|
void ynl_ntf_free(struct ynl_ntf_base_type *ntf)
 | 
						|
{
 | 
						|
	ntf->free(ntf);
 | 
						|
}
 | 
						|
 | 
						|
int ynl_subscribe(struct ynl_sock *ys, const char *grp_name)
 | 
						|
{
 | 
						|
	unsigned int i;
 | 
						|
	int err;
 | 
						|
 | 
						|
	for (i = 0; i < ys->n_mcast_groups; i++)
 | 
						|
		if (!strcmp(ys->mcast_groups[i].name, grp_name))
 | 
						|
			break;
 | 
						|
	if (i == ys->n_mcast_groups) {
 | 
						|
		yerr(ys, ENOENT, "Multicast group '%s' not found", grp_name);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	err = mnl_socket_setsockopt(ys->sock, NETLINK_ADD_MEMBERSHIP,
 | 
						|
				    &ys->mcast_groups[i].id,
 | 
						|
				    sizeof(ys->mcast_groups[i].id));
 | 
						|
	if (err < 0) {
 | 
						|
		perr(ys, "Subscribing to multicast group failed");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int ynl_socket_get_fd(struct ynl_sock *ys)
 | 
						|
{
 | 
						|
	return mnl_socket_get_fd(ys->sock);
 | 
						|
}
 | 
						|
 | 
						|
struct ynl_ntf_base_type *ynl_ntf_dequeue(struct ynl_sock *ys)
 | 
						|
{
 | 
						|
	struct ynl_ntf_base_type *ntf;
 | 
						|
 | 
						|
	if (!ynl_has_ntf(ys))
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	ntf = ys->ntf_first;
 | 
						|
	ys->ntf_first = ntf->next;
 | 
						|
	if (ys->ntf_last_next == &ntf->next)
 | 
						|
		ys->ntf_last_next = &ys->ntf_first;
 | 
						|
 | 
						|
	return ntf;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_ntf_parse(struct ynl_sock *ys, const struct nlmsghdr *nlh)
 | 
						|
{
 | 
						|
	struct ynl_parse_arg yarg = { .ys = ys, };
 | 
						|
	const struct ynl_ntf_info *info;
 | 
						|
	struct ynl_ntf_base_type *rsp;
 | 
						|
	struct genlmsghdr *gehdr;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	gehdr = mnl_nlmsg_get_payload(nlh);
 | 
						|
	if (gehdr->cmd >= ys->family->ntf_info_size)
 | 
						|
		return MNL_CB_ERROR;
 | 
						|
	info = &ys->family->ntf_info[gehdr->cmd];
 | 
						|
	if (!info->cb)
 | 
						|
		return MNL_CB_ERROR;
 | 
						|
 | 
						|
	rsp = calloc(1, info->alloc_sz);
 | 
						|
	rsp->free = info->free;
 | 
						|
	yarg.data = rsp->data;
 | 
						|
	yarg.rsp_policy = info->policy;
 | 
						|
 | 
						|
	ret = info->cb(nlh, &yarg);
 | 
						|
	if (ret <= MNL_CB_STOP)
 | 
						|
		goto err_free;
 | 
						|
 | 
						|
	rsp->family = nlh->nlmsg_type;
 | 
						|
	rsp->cmd = gehdr->cmd;
 | 
						|
 | 
						|
	*ys->ntf_last_next = rsp;
 | 
						|
	ys->ntf_last_next = &rsp->next;
 | 
						|
 | 
						|
	return MNL_CB_OK;
 | 
						|
 | 
						|
err_free:
 | 
						|
	info->free(rsp);
 | 
						|
	return MNL_CB_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_ntf_trampoline(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	return ynl_ntf_parse((struct ynl_sock *)data, nlh);
 | 
						|
}
 | 
						|
 | 
						|
int ynl_ntf_check(struct ynl_sock *ys)
 | 
						|
{
 | 
						|
	ssize_t len;
 | 
						|
	int err;
 | 
						|
 | 
						|
	do {
 | 
						|
		/* libmnl doesn't let us pass flags to the recv to make
 | 
						|
		 * it non-blocking so we need to poll() or peek() :|
 | 
						|
		 */
 | 
						|
		struct pollfd pfd = { };
 | 
						|
 | 
						|
		pfd.fd = mnl_socket_get_fd(ys->sock);
 | 
						|
		pfd.events = POLLIN;
 | 
						|
		err = poll(&pfd, 1, 1);
 | 
						|
		if (err < 1)
 | 
						|
			return err;
 | 
						|
 | 
						|
		len = mnl_socket_recvfrom(ys->sock, ys->rx_buf,
 | 
						|
					  MNL_SOCKET_BUFFER_SIZE);
 | 
						|
		if (len < 0)
 | 
						|
			return len;
 | 
						|
 | 
						|
		err = mnl_cb_run2(ys->rx_buf, len, ys->seq, ys->portid,
 | 
						|
				  ynl_ntf_trampoline, ys,
 | 
						|
				  ynl_cb_array, NLMSG_MIN_TYPE);
 | 
						|
		if (err < 0)
 | 
						|
			return err;
 | 
						|
	} while (err > 0);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* YNL specific helpers used by the auto-generated code */
 | 
						|
 | 
						|
struct ynl_dump_list_type *YNL_LIST_END = (void *)(0xb4d123);
 | 
						|
 | 
						|
void ynl_error_unknown_notification(struct ynl_sock *ys, __u8 cmd)
 | 
						|
{
 | 
						|
	yerr(ys, YNL_ERROR_UNKNOWN_NTF,
 | 
						|
	     "Unknown notification message type '%d'", cmd);
 | 
						|
}
 | 
						|
 | 
						|
int ynl_error_parse(struct ynl_parse_arg *yarg, const char *msg)
 | 
						|
{
 | 
						|
	yerr(yarg->ys, YNL_ERROR_INV_RESP, "Error parsing response: %s", msg);
 | 
						|
	return MNL_CB_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
ynl_check_alien(struct ynl_sock *ys, const struct nlmsghdr *nlh, __u32 rsp_cmd)
 | 
						|
{
 | 
						|
	struct genlmsghdr *gehdr;
 | 
						|
 | 
						|
	if (mnl_nlmsg_get_payload_len(nlh) < sizeof(*gehdr)) {
 | 
						|
		yerr(ys, YNL_ERROR_INV_RESP,
 | 
						|
		     "Kernel responded with truncated message");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	gehdr = mnl_nlmsg_get_payload(nlh);
 | 
						|
	if (gehdr->cmd != rsp_cmd)
 | 
						|
		return ynl_ntf_parse(ys, nlh);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_req_trampoline(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	struct ynl_req_state *yrs = data;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	ret = ynl_check_alien(yrs->yarg.ys, nlh, yrs->rsp_cmd);
 | 
						|
	if (ret)
 | 
						|
		return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK;
 | 
						|
 | 
						|
	return yrs->cb(nlh, &yrs->yarg);
 | 
						|
}
 | 
						|
 | 
						|
int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
 | 
						|
	     struct ynl_req_state *yrs)
 | 
						|
{
 | 
						|
	ssize_t len;
 | 
						|
	int err;
 | 
						|
 | 
						|
	err = mnl_socket_sendto(ys->sock, req_nlh, req_nlh->nlmsg_len);
 | 
						|
	if (err < 0)
 | 
						|
		return err;
 | 
						|
 | 
						|
	do {
 | 
						|
		len = mnl_socket_recvfrom(ys->sock, ys->rx_buf,
 | 
						|
					  MNL_SOCKET_BUFFER_SIZE);
 | 
						|
		if (len < 0)
 | 
						|
			return len;
 | 
						|
 | 
						|
		err = mnl_cb_run2(ys->rx_buf, len, ys->seq, ys->portid,
 | 
						|
				  ynl_req_trampoline, yrs,
 | 
						|
				  ynl_cb_array, NLMSG_MIN_TYPE);
 | 
						|
		if (err < 0)
 | 
						|
			return err;
 | 
						|
	} while (err > 0);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int ynl_dump_trampoline(const struct nlmsghdr *nlh, void *data)
 | 
						|
{
 | 
						|
	struct ynl_dump_state *ds = data;
 | 
						|
	struct ynl_dump_list_type *obj;
 | 
						|
	struct ynl_parse_arg yarg = {};
 | 
						|
	int ret;
 | 
						|
 | 
						|
	ret = ynl_check_alien(ds->ys, nlh, ds->rsp_cmd);
 | 
						|
	if (ret)
 | 
						|
		return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK;
 | 
						|
 | 
						|
	obj = calloc(1, ds->alloc_sz);
 | 
						|
	if (!obj)
 | 
						|
		return MNL_CB_ERROR;
 | 
						|
 | 
						|
	if (!ds->first)
 | 
						|
		ds->first = obj;
 | 
						|
	if (ds->last)
 | 
						|
		ds->last->next = obj;
 | 
						|
	ds->last = obj;
 | 
						|
 | 
						|
	yarg.ys = ds->ys;
 | 
						|
	yarg.rsp_policy = ds->rsp_policy;
 | 
						|
	yarg.data = &obj->data;
 | 
						|
 | 
						|
	return ds->cb(nlh, &yarg);
 | 
						|
}
 | 
						|
 | 
						|
static void *ynl_dump_end(struct ynl_dump_state *ds)
 | 
						|
{
 | 
						|
	if (!ds->first)
 | 
						|
		return YNL_LIST_END;
 | 
						|
 | 
						|
	ds->last->next = YNL_LIST_END;
 | 
						|
	return ds->first;
 | 
						|
}
 | 
						|
 | 
						|
int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
 | 
						|
		  struct ynl_dump_state *yds)
 | 
						|
{
 | 
						|
	ssize_t len;
 | 
						|
	int err;
 | 
						|
 | 
						|
	err = mnl_socket_sendto(ys->sock, req_nlh, req_nlh->nlmsg_len);
 | 
						|
	if (err < 0)
 | 
						|
		return err;
 | 
						|
 | 
						|
	do {
 | 
						|
		len = mnl_socket_recvfrom(ys->sock, ys->rx_buf,
 | 
						|
					  MNL_SOCKET_BUFFER_SIZE);
 | 
						|
		if (len < 0)
 | 
						|
			goto err_close_list;
 | 
						|
 | 
						|
		err = mnl_cb_run2(ys->rx_buf, len, ys->seq, ys->portid,
 | 
						|
				  ynl_dump_trampoline, yds,
 | 
						|
				  ynl_cb_array, NLMSG_MIN_TYPE);
 | 
						|
		if (err < 0)
 | 
						|
			goto err_close_list;
 | 
						|
	} while (err > 0);
 | 
						|
 | 
						|
	yds->first = ynl_dump_end(yds);
 | 
						|
	return 0;
 | 
						|
 | 
						|
err_close_list:
 | 
						|
	yds->first = ynl_dump_end(yds);
 | 
						|
	return -1;
 | 
						|
}
 |