Commit 11bc150d authored by Paolo Abeni's avatar Paolo Abeni

Merge branch 'netlink-support-reporting-missing-attributes'

Jakub Kicinski says:

====================
netlink: support reporting missing attributes

This series adds support for reporting missing attributes
in a structured way. We communicate the type of the missing
attribute and if it was missing inside a nest the offset
of that nest.

Example of (YAML-based) user space reporting ethtool header
missing:

 Kernel error: missing attribute: .header

I was tempted to integrate the check with the policy
but it seems tricky without doing a full scan, and there
may be a ton of attrs in the policy. So leaving that
for later.
====================

Link: https://lore.kernel.org/r/20220826030935.2165661-1-kuba@kernel.orgSigned-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parents 47cf8899 4f5059e6
......@@ -359,8 +359,8 @@ compatibility this feature has to be explicitly enabled by setting
the ``NETLINK_EXT_ACK`` setsockopt() to ``1``.
Types of extended ack attributes are defined in enum nlmsgerr_attrs.
The two most commonly used attributes are ``NLMSGERR_ATTR_MSG``
and ``NLMSGERR_ATTR_OFFS``.
The most commonly used attributes are ``NLMSGERR_ATTR_MSG``,
``NLMSGERR_ATTR_OFFS`` and ``NLMSGERR_ATTR_MISS_*``.
``NLMSGERR_ATTR_MSG`` carries a message in English describing
the encountered problem. These messages are far more detailed
......@@ -368,6 +368,9 @@ than what can be expressed thru standard UNIX error codes.
``NLMSGERR_ATTR_OFFS`` points to the attribute which caused the problem.
``NLMSGERR_ATTR_MISS_TYPE`` and ``NLMSGERR_ATTR_MISS_NEST``
inform about a missing attribute.
Extended ACKs can be reported on errors as well as in case of success.
The latter should be treated as a warning.
......
......@@ -71,6 +71,8 @@ netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
* %NL_SET_ERR_MSG
* @bad_attr: attribute with error
* @policy: policy for a bad attribute
* @miss_type: attribute type which was missing
* @miss_nest: nest missing an attribute (%NULL if missing top level attr)
* @cookie: cookie data to return to userspace (for success)
* @cookie_len: actual cookie data length
*/
......@@ -78,6 +80,8 @@ struct netlink_ext_ack {
const char *_msg;
const struct nlattr *bad_attr;
const struct nla_policy *policy;
const struct nlattr *miss_nest;
u16 miss_type;
u8 cookie[NETLINK_MAX_COOKIE_LEN];
u8 cookie_len;
};
......@@ -126,6 +130,26 @@ struct netlink_ext_ack {
#define NL_SET_ERR_MSG_ATTR(extack, attr, msg) \
NL_SET_ERR_MSG_ATTR_POL(extack, attr, NULL, msg)
#define NL_SET_ERR_ATTR_MISS(extack, nest, type) do { \
struct netlink_ext_ack *__extack = (extack); \
\
if (__extack) { \
__extack->miss_nest = (nest); \
__extack->miss_type = (type); \
} \
} while (0)
#define NL_REQ_ATTR_CHECK(extack, nest, tb, type) ({ \
struct nlattr **__tb = (tb); \
u32 __attr = (type); \
int __retval; \
\
__retval = !__tb[__attr]; \
if (__retval) \
NL_SET_ERR_ATTR_MISS((extack), (nest), __attr); \
__retval; \
})
static inline void nl_set_extack_cookie_u64(struct netlink_ext_ack *extack,
u64 cookie)
{
......
......@@ -110,6 +110,13 @@ static inline void genl_info_net_set(struct genl_info *info, struct net *net)
#define GENL_SET_ERR_MSG(info, msg) NL_SET_ERR_MSG((info)->extack, msg)
/* Report that a root attribute is missing */
#define GENL_REQ_ATTR_CHECK(info, attr) ({ \
struct genl_info *__info = (info); \
\
NL_REQ_ATTR_CHECK(__info->extack, NULL, __info->attrs, (attr)); \
})
enum genl_validate_flags {
GENL_DONT_VALIDATE_STRICT = BIT(0),
GENL_DONT_VALIDATE_DUMP = BIT(1),
......
......@@ -140,6 +140,10 @@ struct nlmsgerr {
* be used - in the success case - to identify a created
* object or operation or similar (binary)
* @NLMSGERR_ATTR_POLICY: policy for a rejected attribute
* @NLMSGERR_ATTR_MISS_TYPE: type of a missing required attribute,
* %NLMSGERR_ATTR_MISS_NEST will not be present if the attribute was
* missing at the message level
* @NLMSGERR_ATTR_MISS_NEST: offset of the nest where attribute was missing
* @__NLMSGERR_ATTR_MAX: number of attributes
* @NLMSGERR_ATTR_MAX: highest attribute number
*/
......@@ -149,6 +153,8 @@ enum nlmsgerr_attrs {
NLMSGERR_ATTR_OFFS,
NLMSGERR_ATTR_COOKIE,
NLMSGERR_ATTR_POLICY,
NLMSGERR_ATTR_MISS_TYPE,
NLMSGERR_ATTR_MISS_NEST,
__NLMSGERR_ATTR_MAX,
NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
......
......@@ -1710,7 +1710,7 @@ static int devlink_nl_cmd_port_split_doit(struct sk_buff *skb,
struct devlink *devlink = info->user_ptr[0];
u32 count;
if (!info->attrs[DEVLINK_ATTR_PORT_SPLIT_COUNT])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_SPLIT_COUNT))
return -EINVAL;
if (!devlink->ops->port_split)
return -EOPNOTSUPP;
......@@ -1838,7 +1838,7 @@ static int devlink_nl_cmd_port_del_doit(struct sk_buff *skb,
if (!devlink->ops->port_del)
return -EOPNOTSUPP;
if (!info->attrs[DEVLINK_ATTR_PORT_INDEX]) {
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_INDEX)) {
NL_SET_ERR_MSG_MOD(extack, "Port index is not specified");
return -EINVAL;
}
......@@ -2690,7 +2690,7 @@ static int devlink_nl_cmd_sb_pool_set_doit(struct sk_buff *skb,
if (err)
return err;
if (!info->attrs[DEVLINK_ATTR_SB_POOL_SIZE])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_POOL_SIZE))
return -EINVAL;
size = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_POOL_SIZE]);
......@@ -2900,7 +2900,7 @@ static int devlink_nl_cmd_sb_port_pool_set_doit(struct sk_buff *skb,
if (err)
return err;
if (!info->attrs[DEVLINK_ATTR_SB_THRESHOLD])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_THRESHOLD))
return -EINVAL;
threshold = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_THRESHOLD]);
......@@ -3156,7 +3156,7 @@ static int devlink_nl_cmd_sb_tc_pool_bind_set_doit(struct sk_buff *skb,
if (err)
return err;
if (!info->attrs[DEVLINK_ATTR_SB_THRESHOLD])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_THRESHOLD))
return -EINVAL;
threshold = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_THRESHOLD]);
......@@ -3845,7 +3845,7 @@ static int devlink_nl_cmd_dpipe_entries_get(struct sk_buff *skb,
struct devlink_dpipe_table *table;
const char *table_name;
if (!info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_DPIPE_TABLE_NAME))
return -EINVAL;
table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]);
......@@ -4029,8 +4029,9 @@ static int devlink_nl_cmd_dpipe_table_counters_set(struct sk_buff *skb,
const char *table_name;
bool counters_enable;
if (!info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME] ||
!info->attrs[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_DPIPE_TABLE_NAME) ||
GENL_REQ_ATTR_CHECK(info,
DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED))
return -EINVAL;
table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]);
......@@ -4119,8 +4120,8 @@ static int devlink_nl_cmd_resource_set(struct sk_buff *skb,
u64 size;
int err;
if (!info->attrs[DEVLINK_ATTR_RESOURCE_ID] ||
!info->attrs[DEVLINK_ATTR_RESOURCE_SIZE])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_ID) ||
GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_SIZE))
return -EINVAL;
resource_id = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_ID]);
......@@ -4821,7 +4822,7 @@ static int devlink_nl_cmd_flash_update(struct sk_buff *skb,
if (!devlink->ops->flash_update)
return -EOPNOTSUPP;
if (!info->attrs[DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME))
return -EINVAL;
ret = devlink_flash_component_get(devlink,
......@@ -4998,10 +4999,8 @@ static int devlink_nl_cmd_selftests_run(struct sk_buff *skb,
if (!devlink->ops->selftest_run || !devlink->ops->selftest_check)
return -EOPNOTSUPP;
if (!info->attrs[DEVLINK_ATTR_SELFTESTS]) {
NL_SET_ERR_MSG_MOD(info->extack, "selftest required");
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SELFTESTS))
return -EINVAL;
}
attrs = info->attrs[DEVLINK_ATTR_SELFTESTS];
......@@ -5455,7 +5454,7 @@ static int
devlink_param_type_get_from_info(struct genl_info *info,
enum devlink_param_type *param_type)
{
if (!info->attrs[DEVLINK_ATTR_PARAM_TYPE])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_TYPE))
return -EINVAL;
switch (nla_get_u8(info->attrs[DEVLINK_ATTR_PARAM_TYPE])) {
......@@ -5532,7 +5531,7 @@ devlink_param_get_from_info(struct list_head *param_list,
{
char *param_name;
if (!info->attrs[DEVLINK_ATTR_PARAM_NAME])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_NAME))
return NULL;
param_name = nla_data(info->attrs[DEVLINK_ATTR_PARAM_NAME]);
......@@ -5598,7 +5597,7 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink,
return err;
}
if (!info->attrs[DEVLINK_ATTR_PARAM_VALUE_CMODE])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_VALUE_CMODE))
return -EINVAL;
cmode = nla_get_u8(info->attrs[DEVLINK_ATTR_PARAM_VALUE_CMODE]);
if (!devlink_param_cmode_is_supported(param, cmode))
......@@ -6118,7 +6117,7 @@ static int devlink_nl_cmd_region_get_doit(struct sk_buff *skb,
unsigned int index;
int err;
if (!info->attrs[DEVLINK_ATTR_REGION_NAME])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME))
return -EINVAL;
if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) {
......@@ -6251,8 +6250,8 @@ static int devlink_nl_cmd_region_del(struct sk_buff *skb,
unsigned int index;
u32 snapshot_id;
if (!info->attrs[DEVLINK_ATTR_REGION_NAME] ||
!info->attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID])
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME) ||
GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_SNAPSHOT_ID))
return -EINVAL;
region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]);
......@@ -6300,7 +6299,7 @@ devlink_nl_cmd_region_new(struct sk_buff *skb, struct genl_info *info)
u8 *data;
int err;
if (!info->attrs[DEVLINK_ATTR_REGION_NAME]) {
if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME)) {
NL_SET_ERR_MSG_MOD(info->extack, "No region name provided");
return -EINVAL;
}
......
......@@ -361,6 +361,9 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
ops = ethnl_default_requests[cmd];
if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", cmd))
return -EOPNOTSUPP;
if (GENL_REQ_ATTR_CHECK(info, ops->hdr_attr))
return -EINVAL;
req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
if (!req_info)
return -ENOMEM;
......
......@@ -167,7 +167,7 @@ static int strset_get_id(const struct nlattr *nest, u32 *val,
get_stringset_policy, extack);
if (ret < 0)
return ret;
if (!tb[ETHTOOL_A_STRINGSET_ID])
if (NL_REQ_ATTR_CHECK(extack, nest, tb, ETHTOOL_A_STRINGSET_ID))
return -EINVAL;
*val = nla_get_u32(tb[ETHTOOL_A_STRINGSET_ID]);
......
......@@ -2400,6 +2400,69 @@ int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb,
}
EXPORT_SYMBOL(__netlink_dump_start);
static size_t
netlink_ack_tlv_len(struct netlink_sock *nlk, int err,
const struct netlink_ext_ack *extack)
{
size_t tlvlen;
if (!extack || !(nlk->flags & NETLINK_F_EXT_ACK))
return 0;
tlvlen = 0;
if (extack->_msg)
tlvlen += nla_total_size(strlen(extack->_msg) + 1);
if (extack->cookie_len)
tlvlen += nla_total_size(extack->cookie_len);
/* Following attributes are only reported as error (not warning) */
if (!err)
return tlvlen;
if (extack->bad_attr)
tlvlen += nla_total_size(sizeof(u32));
if (extack->policy)
tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy);
if (extack->miss_type)
tlvlen += nla_total_size(sizeof(u32));
if (extack->miss_nest)
tlvlen += nla_total_size(sizeof(u32));
return tlvlen;
}
static void
netlink_ack_tlv_fill(struct sk_buff *in_skb, struct sk_buff *skb,
struct nlmsghdr *nlh, int err,
const struct netlink_ext_ack *extack)
{
if (extack->_msg)
WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg));
if (extack->cookie_len)
WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE,
extack->cookie_len, extack->cookie));
if (!err)
return;
if (extack->bad_attr &&
!WARN_ON((u8 *)extack->bad_attr < in_skb->data ||
(u8 *)extack->bad_attr >= in_skb->data + in_skb->len))
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS,
(u8 *)extack->bad_attr - (u8 *)nlh));
if (extack->policy)
netlink_policy_dump_write_attr(skb, extack->policy,
NLMSGERR_ATTR_POLICY);
if (extack->miss_type)
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_TYPE,
extack->miss_type));
if (extack->miss_nest &&
!WARN_ON((u8 *)extack->miss_nest < in_skb->data ||
(u8 *)extack->miss_nest > in_skb->data + in_skb->len))
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_MISS_NEST,
(u8 *)extack->miss_nest - (u8 *)nlh));
}
void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err,
const struct netlink_ext_ack *extack)
{
......@@ -2407,29 +2470,20 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err,
struct nlmsghdr *rep;
struct nlmsgerr *errmsg;
size_t payload = sizeof(*errmsg);
size_t tlvlen = 0;
struct netlink_sock *nlk = nlk_sk(NETLINK_CB(in_skb).sk);
unsigned int flags = 0;
bool nlk_has_extack = nlk->flags & NETLINK_F_EXT_ACK;
size_t tlvlen;
/* Error messages get the original request appened, unless the user
* requests to cap the error message, and get extra error data if
* requested.
*/
if (nlk_has_extack && extack && extack->_msg)
tlvlen += nla_total_size(strlen(extack->_msg) + 1);
if (err && !(nlk->flags & NETLINK_F_CAP_ACK))
payload += nlmsg_len(nlh);
else
flags |= NLM_F_CAPPED;
if (err && nlk_has_extack && extack && extack->bad_attr)
tlvlen += nla_total_size(sizeof(u32));
if (nlk_has_extack && extack && extack->cookie_len)
tlvlen += nla_total_size(extack->cookie_len);
if (err && nlk_has_extack && extack && extack->policy)
tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy);
tlvlen = netlink_ack_tlv_len(nlk, err, extack);
if (tlvlen)
flags |= NLM_F_ACK_TLVS;
......@@ -2446,25 +2500,8 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err,
errmsg->error = err;
memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh));
if (nlk_has_extack && extack) {
if (extack->_msg) {
WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG,
extack->_msg));
}
if (err && extack->bad_attr &&
!WARN_ON((u8 *)extack->bad_attr < in_skb->data ||
(u8 *)extack->bad_attr >= in_skb->data +
in_skb->len))
WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS,
(u8 *)extack->bad_attr -
(u8 *)nlh));
if (extack->cookie_len)
WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE,
extack->cookie_len, extack->cookie));
if (extack->policy)
netlink_policy_dump_write_attr(skb, extack->policy,
NLMSGERR_ATTR_POLICY);
}
if (tlvlen)
netlink_ack_tlv_fill(in_skb, skb, nlh, err, extack);
nlmsg_end(skb, rep);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment