Message ID | 20170411065700.2623-2-johannes@sipsolutions.net |
---|---|
State | Superseded, archived |
Headers | show |
Tue, Apr 11, 2017 at 08:56:56AM CEST, johannes@sipsolutions.net wrote: >From: Johannes Berg <johannes.berg@intel.com> > >Add the base infrastructure and UAPI for netlink >extended ACK reporting. All "manual" calls to >netlink_ack() pass NULL for now and thus don't >get extended ACK reporting. Johannes, I asked already why you are not using 80 cols. This narrow descriptions look odd. > >Big thanks goes to Pablo Neira Ayuso for not only >bringing up the whole topic at netconf (again) but >also coming up with the nlattr passing trick and >various other ideads. > >Signed-off-by: Johannes Berg <johannes.berg@intel.com> >--- > crypto/crypto_user.c | 3 +- > drivers/infiniband/core/netlink.c | 5 +-- > drivers/scsi/scsi_netlink.c | 2 +- > include/linux/netlink.h | 26 +++++++++++++- > include/net/netlink.h | 3 +- > include/uapi/linux/netlink.h | 32 ++++++++++++++++++ > kernel/audit.c | 2 +- > net/core/rtnetlink.c | 3 +- > net/core/sock_diag.c | 3 +- > net/decnet/netfilter/dn_rtmsg.c | 2 +- > net/hsr/hsr_netlink.c | 4 +-- > net/netfilter/ipset/ip_set_core.c | 2 +- > net/netfilter/nfnetlink.c | 22 ++++++------ > net/netlink/af_netlink.c | 71 ++++++++++++++++++++++++++++++++++----- > net/netlink/af_netlink.h | 1 + > net/netlink/genetlink.c | 3 +- > net/xfrm/xfrm_user.c | 3 +- > 17 files changed, 153 insertions(+), 34 deletions(-) > >diff --git a/crypto/crypto_user.c b/crypto/crypto_user.c >index a90404a0c5ff..4a44830741c1 100644 >--- a/crypto/crypto_user.c >+++ b/crypto/crypto_user.c >@@ -483,7 +483,8 @@ static const struct crypto_link { > [CRYPTO_MSG_DELRNG - CRYPTO_MSG_BASE] = { .doit = crypto_del_rng }, > }; > >-static int crypto_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) >+static int crypto_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, >+ struct netlink_ext_ack *extack) > { > struct nlattr *attrs[CRYPTOCFGA_MAX+1]; > const struct crypto_link *link; >diff --git a/drivers/infiniband/core/netlink.c b/drivers/infiniband/core/netlink.c >index 10469b0088b5..b784055423c8 100644 >--- a/drivers/infiniband/core/netlink.c >+++ b/drivers/infiniband/core/netlink.c >@@ -146,7 +146,8 @@ int ibnl_put_attr(struct sk_buff *skb, struct nlmsghdr *nlh, > } > EXPORT_SYMBOL(ibnl_put_attr); > >-static int ibnl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) >+static int ibnl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, >+ struct netlink_ext_ack *extack) > { > struct ibnl_client *client; > int type = nlh->nlmsg_type; >@@ -209,7 +210,7 @@ static void ibnl_rcv_reply_skb(struct sk_buff *skb) > if (nlh->nlmsg_flags & NLM_F_REQUEST) > return; > >- ibnl_rcv_msg(skb, nlh); >+ ibnl_rcv_msg(skb, nlh, NULL); > > msglen = NLMSG_ALIGN(nlh->nlmsg_len); > if (msglen > skb->len) >diff --git a/drivers/scsi/scsi_netlink.c b/drivers/scsi/scsi_netlink.c >index 109802f776ed..50e624fb8307 100644 >--- a/drivers/scsi/scsi_netlink.c >+++ b/drivers/scsi/scsi_netlink.c >@@ -111,7 +111,7 @@ scsi_nl_rcv_msg(struct sk_buff *skb) > > next_msg: > if ((err) || (nlh->nlmsg_flags & NLM_F_ACK)) >- netlink_ack(skb, nlh, err); >+ netlink_ack(skb, nlh, err, NULL); > > skb_pull(skb, rlen); > } >diff --git a/include/linux/netlink.h b/include/linux/netlink.h >index da14ab61f363..60e7137f840d 100644 >--- a/include/linux/netlink.h >+++ b/include/linux/netlink.h >@@ -62,11 +62,35 @@ netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) > return __netlink_kernel_create(net, unit, THIS_MODULE, cfg); > } > >+/** >+ * struct netlink_ext_ack - netlink extended ACK report struct >+ * @_msg: message string to report - don't access directly, use >+ * %NL_SET_ERR_MSG >+ * @bad_attr: attribute with error >+ */ >+struct netlink_ext_ack { >+ const char *_msg; >+ const struct nlattr *bad_attr; >+}; >+ >+/* Always use this macro, this allows later putting the >+ * message into a separate section or such for things >+ * like translation or listing all possible messages. >+ * Currently string formatting is not supported (due >+ * to the lack of an output buffer.) >+ */ >+#define NL_SET_ERR_MSG(extack, msg) do { \ >+ static const char _msg[] = (msg); \ >+ \ >+ (extack)->_msg = _msg; \ >+} while (0) >+ > extern void netlink_kernel_release(struct sock *sk); > extern int __netlink_change_ngroups(struct sock *sk, unsigned int groups); > extern int netlink_change_ngroups(struct sock *sk, unsigned int groups); > extern void __netlink_clear_multicast_users(struct sock *sk, unsigned int group); >-extern void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err); >+extern void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, >+ const struct netlink_ext_ack *extack); > extern int netlink_has_listeners(struct sock *sk, unsigned int group); > > extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock); >diff --git a/include/net/netlink.h b/include/net/netlink.h >index b239fcd33d80..a064ec3e2ee1 100644 >--- a/include/net/netlink.h >+++ b/include/net/netlink.h >@@ -233,7 +233,8 @@ struct nl_info { > }; > > int netlink_rcv_skb(struct sk_buff *skb, >- int (*cb)(struct sk_buff *, struct nlmsghdr *)); >+ int (*cb)(struct sk_buff *, struct nlmsghdr *, >+ struct netlink_ext_ack *)); > int nlmsg_notify(struct sock *sk, struct sk_buff *skb, u32 portid, > unsigned int group, int report, gfp_t flags); > >diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h >index b2c9c26ea30f..96e613db68cb 100644 >--- a/include/uapi/linux/netlink.h >+++ b/include/uapi/linux/netlink.h >@@ -69,6 +69,10 @@ struct nlmsghdr { > #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ > #define NLM_F_APPEND 0x800 /* Add to end of list */ > >+/* Flags for ACK message */ >+#define NLM_F_CAPPED 0x100 /* request was capped */ >+#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ >+ > /* > 4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL > 4.4BSD CHANGE NLM_F_REPLACE >@@ -101,6 +105,33 @@ struct nlmsghdr { > struct nlmsgerr { > int error; > struct nlmsghdr msg; >+ /* >+ * followed by the message contents unless NETLINK_CAP_ACK was set >+ * or the ACK indicates success (error == 0) >+ * message length is aligned with NLMSG_ALIGN() >+ */ >+ /* >+ * followed by TLVs defined in enum nlmsgerr_attrs >+ * if NETLINK_EXT_ACK was set >+ */ >+}; >+ >+/** >+ * enum nlmsgerr_attrs - nlmsgerr attributes >+ * @NLMSGERR_ATTR_UNUSED: unused >+ * @NLMSGERR_ATTR_MSG: error message string (string) >+ * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original >+ * message, counting from the beginning of the header (u32) >+ * @NUM_NLMSGERR_ATTRS: number of attributes >+ * @NLMSGERR_ATTR_MAX: highest attribute number >+ */ >+enum nlmsgerr_attrs { >+ NLMSGERR_ATTR_UNUSED, >+ NLMSGERR_ATTR_MSG, >+ NLMSGERR_ATTR_OFFS, >+ >+ NUM_NLMSGERR_ATTRS, >+ NLMSGERR_ATTR_MAX = NUM_NLMSGERR_ATTRS - 1 > }; > > #define NETLINK_ADD_MEMBERSHIP 1 >@@ -115,6 +146,7 @@ struct nlmsgerr { > #define NETLINK_LISTEN_ALL_NSID 8 > #define NETLINK_LIST_MEMBERSHIPS 9 > #define NETLINK_CAP_ACK 10 >+#define NETLINK_EXT_ACK 11 > > struct nl_pktinfo { > __u32 group; >diff --git a/kernel/audit.c b/kernel/audit.c >index 2f4964cfde0b..d54bf5932374 100644 >--- a/kernel/audit.c >+++ b/kernel/audit.c >@@ -1402,7 +1402,7 @@ static void audit_receive_skb(struct sk_buff *skb) > err = audit_receive_msg(skb, nlh); > /* if err or if this message says it wants a response */ > if (err || (nlh->nlmsg_flags & NLM_F_ACK)) >- netlink_ack(skb, nlh, err); >+ netlink_ack(skb, nlh, err, NULL); > > nlh = nlmsg_next(nlh, &len); > } >diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c >index 58419da7961b..09a2e86f7b92 100644 >--- a/net/core/rtnetlink.c >+++ b/net/core/rtnetlink.c >@@ -4046,7 +4046,8 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb) > > /* Process one rtnetlink message. */ > >-static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) >+static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, >+ struct netlink_ext_ack *extack) > { > struct net *net = sock_net(skb->sk); > rtnl_doit_func doit; >diff --git a/net/core/sock_diag.c b/net/core/sock_diag.c >index fb9d0e2fd148..217f4e3b82f6 100644 >--- a/net/core/sock_diag.c >+++ b/net/core/sock_diag.c >@@ -238,7 +238,8 @@ static int __sock_diag_cmd(struct sk_buff *skb, struct nlmsghdr *nlh) > return err; > } > >-static int sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) >+static int sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, >+ struct netlink_ext_ack *extack) > { > int ret; > >diff --git a/net/decnet/netfilter/dn_rtmsg.c b/net/decnet/netfilter/dn_rtmsg.c >index 85f2fdc360c2..c8bf5136a72b 100644 >--- a/net/decnet/netfilter/dn_rtmsg.c >+++ b/net/decnet/netfilter/dn_rtmsg.c >@@ -96,7 +96,7 @@ static unsigned int dnrmg_hook(void *priv, > } > > >-#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0) >+#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err), NULL); return; } while (0) > > static inline void dnrmg_receive_user_skb(struct sk_buff *skb) > { >diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c >index 1ab30e7d3f99..81dac16933fc 100644 >--- a/net/hsr/hsr_netlink.c >+++ b/net/hsr/hsr_netlink.c >@@ -350,7 +350,7 @@ static int hsr_get_node_status(struct sk_buff *skb_in, struct genl_info *info) > return 0; > > invalid: >- netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL); >+ netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL, NULL); > return 0; > > nla_put_failure: >@@ -432,7 +432,7 @@ static int hsr_get_node_list(struct sk_buff *skb_in, struct genl_info *info) > return 0; > > invalid: >- netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL); >+ netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL, NULL); > return 0; > > nla_put_failure: >diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c >index c296f9b606d4..26356bf8cebf 100644 >--- a/net/netfilter/ipset/ip_set_core.c >+++ b/net/netfilter/ipset/ip_set_core.c >@@ -1305,7 +1305,7 @@ ip_set_dump_start(struct sk_buff *skb, struct netlink_callback *cb) > * manually :-( > */ > if (nlh->nlmsg_flags & NLM_F_ACK) >- netlink_ack(cb->skb, nlh, ret); >+ netlink_ack(cb->skb, nlh, ret, NULL); > return ret; > } > } >diff --git a/net/netfilter/nfnetlink.c b/net/netfilter/nfnetlink.c >index 68eda920160e..181d3bb800e6 100644 >--- a/net/netfilter/nfnetlink.c >+++ b/net/netfilter/nfnetlink.c >@@ -148,7 +148,8 @@ int nfnetlink_unicast(struct sk_buff *skb, struct net *net, u32 portid, > EXPORT_SYMBOL_GPL(nfnetlink_unicast); > > /* Process one complete nfnetlink message. */ >-static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) >+static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, >+ struct netlink_ext_ack *extack) > { > struct net *net = sock_net(skb->sk); > const struct nfnl_callback *nc; >@@ -261,7 +262,7 @@ static void nfnl_err_deliver(struct list_head *err_list, struct sk_buff *skb) > struct nfnl_err *nfnl_err, *next; > > list_for_each_entry_safe(nfnl_err, next, err_list, head) { >- netlink_ack(skb, nfnl_err->nlh, nfnl_err->err); >+ netlink_ack(skb, nfnl_err->nlh, nfnl_err->err, NULL); > nfnl_err_del(nfnl_err); > } > } >@@ -284,13 +285,13 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, > int err; > > if (subsys_id >= NFNL_SUBSYS_COUNT) >- return netlink_ack(skb, nlh, -EINVAL); >+ return netlink_ack(skb, nlh, -EINVAL, NULL); > replay: > status = 0; > > skb = netlink_skb_clone(oskb, GFP_KERNEL); > if (!skb) >- return netlink_ack(oskb, nlh, -ENOMEM); >+ return netlink_ack(oskb, nlh, -ENOMEM, NULL); > > nfnl_lock(subsys_id); > ss = nfnl_dereference_protected(subsys_id); >@@ -304,20 +305,20 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, > #endif > { > nfnl_unlock(subsys_id); >- netlink_ack(oskb, nlh, -EOPNOTSUPP); >+ netlink_ack(oskb, nlh, -EOPNOTSUPP, NULL); > return kfree_skb(skb); > } > } > > if (!ss->commit || !ss->abort) { > nfnl_unlock(subsys_id); >- netlink_ack(oskb, nlh, -EOPNOTSUPP); >+ netlink_ack(oskb, nlh, -EOPNOTSUPP, NULL); > return kfree_skb(skb); > } > > if (genid && ss->valid_genid && !ss->valid_genid(net, genid)) { > nfnl_unlock(subsys_id); >- netlink_ack(oskb, nlh, -ERESTART); >+ netlink_ack(oskb, nlh, -ERESTART, NULL); > return kfree_skb(skb); > } > >@@ -407,7 +408,8 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, > * pointing to the batch header. > */ > nfnl_err_reset(&err_list); >- netlink_ack(oskb, nlmsg_hdr(oskb), -ENOMEM); >+ netlink_ack(oskb, nlmsg_hdr(oskb), -ENOMEM, >+ NULL); > status |= NFNL_BATCH_FAILURE; > goto done; > } >@@ -467,7 +469,7 @@ static void nfnetlink_rcv_skb_batch(struct sk_buff *skb, struct nlmsghdr *nlh) > > err = nla_parse(cda, NFNL_BATCH_MAX, attr, attrlen, nfnl_batch_policy); > if (err < 0) { >- netlink_ack(skb, nlh, err); >+ netlink_ack(skb, nlh, err, NULL); > return; > } > if (cda[NFNL_BATCH_GENID]) >@@ -493,7 +495,7 @@ static void nfnetlink_rcv(struct sk_buff *skb) > return; > > if (!netlink_net_capable(skb, CAP_NET_ADMIN)) { >- netlink_ack(skb, nlh, -EPERM); >+ netlink_ack(skb, nlh, -EPERM, NULL); > return; > } > >diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c >index fc232441cf23..c1564768000e 100644 >--- a/net/netlink/af_netlink.c >+++ b/net/netlink/af_netlink.c >@@ -1652,6 +1652,13 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname, > nlk->flags &= ~NETLINK_F_CAP_ACK; > err = 0; > break; >+ case NETLINK_EXT_ACK: >+ if (val) >+ nlk->flags |= NETLINK_F_EXT_ACK; >+ else >+ nlk->flags &= ~NETLINK_F_EXT_ACK; >+ err = 0; >+ break; > default: > err = -ENOPROTOOPT; > } >@@ -1736,6 +1743,15 @@ static int netlink_getsockopt(struct socket *sock, int level, int optname, > return -EFAULT; > err = 0; > break; >+ case NETLINK_EXT_ACK: >+ if (len < sizeof(int)) >+ return -EINVAL; >+ len = sizeof(int); >+ val = nlk->flags & NETLINK_F_EXT_ACK ? 1 : 0; >+ if (put_user(len, optlen) || put_user(val, optval)) >+ return -EFAULT; >+ err = 0; >+ break; > default: > err = -ENOPROTOOPT; > } >@@ -2267,21 +2283,40 @@ int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb, > } > EXPORT_SYMBOL(__netlink_dump_start); > >-void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err) >+void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, >+ const struct netlink_ext_ack *extack) > { > struct sk_buff *skb; > 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; > > /* Error messages get the original request appened, unless the user >- * requests to cap the error message. >+ * requests to cap the error message, and get extra error data if >+ * requested. > */ >- if (!(nlk->flags & NETLINK_F_CAP_ACK) && err) >- payload += nlmsg_len(nlh); >+ if (err) { >+ if (!(nlk->flags & NETLINK_F_CAP_ACK)) >+ payload += nlmsg_len(nlh); >+ else >+ flags |= NLM_F_CAPPED; >+ if (nlk->flags & NETLINK_F_EXT_ACK && extack) { >+ if (extack->_msg) >+ tlvlen += nla_total_size(strlen(extack->_msg) + 1); >+ if (extack->bad_attr) >+ tlvlen += nla_total_size(sizeof(u32)); >+ } >+ } else { >+ flags |= NLM_F_CAPPED; >+ } > >- skb = nlmsg_new(payload, GFP_KERNEL); >+ if (tlvlen) >+ flags |= NLM_F_ACK_TLVS; >+ >+ skb = nlmsg_new(payload + tlvlen, GFP_KERNEL); > if (!skb) { > struct sock *sk; > >@@ -2297,17 +2332,35 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err) > } > > rep = __nlmsg_put(skb, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, >- NLMSG_ERROR, payload, 0); >+ NLMSG_ERROR, payload, flags); > errmsg = nlmsg_data(rep); > errmsg->error = err; > memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh)); >+ >+ if (err && nlk->flags & NETLINK_F_EXT_ACK && extack) { >+ if (extack->_msg) >+ WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, >+ extack->_msg)); >+ 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 - >+ in_skb->data)); >+ } >+ >+ nlmsg_end(skb, rep); >+ > netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).portid, MSG_DONTWAIT); > } > EXPORT_SYMBOL(netlink_ack); > > int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, >- struct nlmsghdr *)) >+ struct nlmsghdr *, >+ struct netlink_ext_ack *)) > { >+ struct netlink_ext_ack extack = {}; > struct nlmsghdr *nlh; > int err; > >@@ -2328,13 +2381,13 @@ int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, > if (nlh->nlmsg_type < NLMSG_MIN_TYPE) > goto ack; > >- err = cb(skb, nlh); >+ err = cb(skb, nlh, &extack); > if (err == -EINTR) > goto skip; > > ack: > if (nlh->nlmsg_flags & NLM_F_ACK || err) >- netlink_ack(skb, nlh, err); >+ netlink_ack(skb, nlh, err, &extack); > > skip: > msglen = NLMSG_ALIGN(nlh->nlmsg_len); >diff --git a/net/netlink/af_netlink.h b/net/netlink/af_netlink.h >index f792f8d7f982..3490f2430532 100644 >--- a/net/netlink/af_netlink.h >+++ b/net/netlink/af_netlink.h >@@ -13,6 +13,7 @@ > #define NETLINK_F_RECV_NO_ENOBUFS 0x8 > #define NETLINK_F_LISTEN_ALL_NSID 0x10 > #define NETLINK_F_CAP_ACK 0x20 >+#define NETLINK_F_EXT_ACK 0x40 > > #define NLGRPSZ(x) (ALIGN(x, sizeof(unsigned long) * 8) / 8) > #define NLGRPLONGS(x) (NLGRPSZ(x)/sizeof(unsigned long)) >diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c >index 92e0981f7404..57b2e3648bc0 100644 >--- a/net/netlink/genetlink.c >+++ b/net/netlink/genetlink.c >@@ -605,7 +605,8 @@ static int genl_family_rcv_msg(const struct genl_family *family, > return err; > } > >-static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) >+static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, >+ struct netlink_ext_ack *extack) > { > const struct genl_family *family; > int err; >diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c >index 40a8aa39220d..1ba8c115a993 100644 >--- a/net/xfrm/xfrm_user.c >+++ b/net/xfrm/xfrm_user.c >@@ -2448,7 +2448,8 @@ static const struct xfrm_link { > [XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo }, > }; > >-static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) >+static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, >+ struct netlink_ext_ack *extack) > { > struct net *net = sock_net(skb->sk); > struct nlattr *attrs[XFRMA_MAX+1]; >-- >2.11.0 >
Tue, Apr 11, 2017 at 08:56:56AM CEST, johannes@sipsolutions.net wrote: >From: Johannes Berg <johannes.berg@intel.com> > >Add the base infrastructure and UAPI for netlink >extended ACK reporting. All "manual" calls to >netlink_ack() pass NULL for now and thus don't >get extended ACK reporting. > >Big thanks goes to Pablo Neira Ayuso for not only >bringing up the whole topic at netconf (again) but >also coming up with the nlattr passing trick and >various other ideads. > >Signed-off-by: Johannes Berg <johannes.berg@intel.com> >--- [...] >+/** >+ * enum nlmsgerr_attrs - nlmsgerr attributes >+ * @NLMSGERR_ATTR_UNUSED: unused >+ * @NLMSGERR_ATTR_MSG: error message string (string) >+ * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original >+ * message, counting from the beginning of the header (u32) >+ * @NUM_NLMSGERR_ATTRS: number of attributes >+ * @NLMSGERR_ATTR_MAX: highest attribute number >+ */ >+enum nlmsgerr_attrs { >+ NLMSGERR_ATTR_UNUSED, >+ NLMSGERR_ATTR_MSG, >+ NLMSGERR_ATTR_OFFS, >+ >+ NUM_NLMSGERR_ATTRS, According to the rest of the uapi, this should be rather named: __NLMSGERR_ATTR_MAX >+ NLMSGERR_ATTR_MAX = NUM_NLMSGERR_ATTRS - 1 > }; > > #define NETLINK_ADD_MEMBERSHIP 1 >@@ -115,6 +146,7 @@ struct nlmsgerr { > #define NETLINK_LISTEN_ALL_NSID 8 > #define NETLINK_LIST_MEMBERSHIPS 9 > #define NETLINK_CAP_ACK 10 >+#define NETLINK_EXT_ACK 11 > > struct nl_pktinfo { > __u32 group; >diff --git a/kernel/audit.c b/kernel/audit.c >index 2f4964cfde0b..d54bf5932374 100644 >--- a/kernel/audit.c >+++ b/kernel/audit.c >@@ -1402,7 +1402,7 @@ static void audit_receive_skb(struct sk_buff *skb) > err = audit_receive_msg(skb, nlh); > /* if err or if this message says it wants a response */ > if (err || (nlh->nlmsg_flags & NLM_F_ACK)) >- netlink_ack(skb, nlh, err); >+ netlink_ack(skb, nlh, err, NULL); Wouldn't it make sense to leave netlink_ack as is and add netlink_ack_ext for those who need to pass non-null? > > nlh = nlmsg_next(nlh, &len); > }
On Tue, 2017-04-11 at 09:19 +0200, Jiri Pirko wrote: > > > + NUM_NLMSGERR_ATTRS, > > According to the rest of the uapi, this should be rather named: > __NLMSGERR_ATTR_MAX nl80211 uses NUM_ so I guess that's a matter of convention, but I can change that I guess. > > if (err || (nlh->nlmsg_flags & NLM_F_ACK)) > > - netlink_ack(skb, nlh, err); > > + netlink_ack(skb, nlh, err, NULL); > > Wouldn't it make sense to leave netlink_ack as is and add > netlink_ack_ext for those who need to pass non-null? I thought about it, but didn't really see much point. The churn isn't super big (a dozen callers or so), and I thought it makes sense to point out to the users that there's something here. johannes
Tue, Apr 11, 2017 at 09:29:18AM CEST, johannes@sipsolutions.net wrote: >On Tue, 2017-04-11 at 09:19 +0200, Jiri Pirko wrote: >> >> > + NUM_NLMSGERR_ATTRS, >> >> According to the rest of the uapi, this should be rather named: >> __NLMSGERR_ATTR_MAX > >nl80211 uses NUM_ so I guess that's a matter of convention, but I can >change that I guess. Please do. > >> > if (err || (nlh->nlmsg_flags & NLM_F_ACK)) >> > - netlink_ack(skb, nlh, err); >> > + netlink_ack(skb, nlh, err, NULL); >> >> Wouldn't it make sense to leave netlink_ack as is and add >> netlink_ack_ext for those who need to pass non-null? > >I thought about it, but didn't really see much point. The churn isn't >super big (a dozen callers or so), and I thought it makes sense to >point out to the users that there's something here. Makes sense.
On Tue, 2017-04-11 at 10:13 +0200, Jiri Pirko wrote: > Tue, Apr 11, 2017 at 09:29:18AM CEST, johannes@sipsolutions.net > wrote: > > On Tue, 2017-04-11 at 09:19 +0200, Jiri Pirko wrote: > > > > > > > + NUM_NLMSGERR_ATTRS, > > > > > > According to the rest of the uapi, this should be rather named: > > > __NLMSGERR_ATTR_MAX > > > > nl80211 uses NUM_ so I guess that's a matter of convention, but I > > can > > change that I guess. > > Please do. No further comments on any other parts of the patch(es)? :) I don't want to be resending all the time, so I guess I'll wait. For now, all code is in the nl-err branch in mac80211-next. johannes
Tue, Apr 11, 2017 at 10:29:52AM CEST, johannes@sipsolutions.net wrote: >On Tue, 2017-04-11 at 10:13 +0200, Jiri Pirko wrote: >> Tue, Apr 11, 2017 at 09:29:18AM CEST, johannes@sipsolutions.net >> wrote: >> > On Tue, 2017-04-11 at 09:19 +0200, Jiri Pirko wrote: >> > > >> > > > + NUM_NLMSGERR_ATTRS, >> > > >> > > According to the rest of the uapi, this should be rather named: >> > > __NLMSGERR_ATTR_MAX >> > >> > nl80211 uses NUM_ so I guess that's a matter of convention, but I >> > can >> > change that I guess. >> >> Please do. > >No further comments on any other parts of the patch(es)? :) I didn't found anything aside the narrow descriptions- you have it like that in 4 out of 5 patches :) > >I don't want to be resending all the time, so I guess I'll wait. For >now, all code is in the nl-err branch in mac80211-next. > >johannes
diff --git a/crypto/crypto_user.c b/crypto/crypto_user.c index a90404a0c5ff..4a44830741c1 100644 --- a/crypto/crypto_user.c +++ b/crypto/crypto_user.c @@ -483,7 +483,8 @@ static const struct crypto_link { [CRYPTO_MSG_DELRNG - CRYPTO_MSG_BASE] = { .doit = crypto_del_rng }, }; -static int crypto_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +static int crypto_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) { struct nlattr *attrs[CRYPTOCFGA_MAX+1]; const struct crypto_link *link; diff --git a/drivers/infiniband/core/netlink.c b/drivers/infiniband/core/netlink.c index 10469b0088b5..b784055423c8 100644 --- a/drivers/infiniband/core/netlink.c +++ b/drivers/infiniband/core/netlink.c @@ -146,7 +146,8 @@ int ibnl_put_attr(struct sk_buff *skb, struct nlmsghdr *nlh, } EXPORT_SYMBOL(ibnl_put_attr); -static int ibnl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +static int ibnl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) { struct ibnl_client *client; int type = nlh->nlmsg_type; @@ -209,7 +210,7 @@ static void ibnl_rcv_reply_skb(struct sk_buff *skb) if (nlh->nlmsg_flags & NLM_F_REQUEST) return; - ibnl_rcv_msg(skb, nlh); + ibnl_rcv_msg(skb, nlh, NULL); msglen = NLMSG_ALIGN(nlh->nlmsg_len); if (msglen > skb->len) diff --git a/drivers/scsi/scsi_netlink.c b/drivers/scsi/scsi_netlink.c index 109802f776ed..50e624fb8307 100644 --- a/drivers/scsi/scsi_netlink.c +++ b/drivers/scsi/scsi_netlink.c @@ -111,7 +111,7 @@ scsi_nl_rcv_msg(struct sk_buff *skb) next_msg: if ((err) || (nlh->nlmsg_flags & NLM_F_ACK)) - netlink_ack(skb, nlh, err); + netlink_ack(skb, nlh, err, NULL); skb_pull(skb, rlen); } diff --git a/include/linux/netlink.h b/include/linux/netlink.h index da14ab61f363..60e7137f840d 100644 --- a/include/linux/netlink.h +++ b/include/linux/netlink.h @@ -62,11 +62,35 @@ netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) return __netlink_kernel_create(net, unit, THIS_MODULE, cfg); } +/** + * struct netlink_ext_ack - netlink extended ACK report struct + * @_msg: message string to report - don't access directly, use + * %NL_SET_ERR_MSG + * @bad_attr: attribute with error + */ +struct netlink_ext_ack { + const char *_msg; + const struct nlattr *bad_attr; +}; + +/* Always use this macro, this allows later putting the + * message into a separate section or such for things + * like translation or listing all possible messages. + * Currently string formatting is not supported (due + * to the lack of an output buffer.) + */ +#define NL_SET_ERR_MSG(extack, msg) do { \ + static const char _msg[] = (msg); \ + \ + (extack)->_msg = _msg; \ +} while (0) + extern void netlink_kernel_release(struct sock *sk); extern int __netlink_change_ngroups(struct sock *sk, unsigned int groups); extern int netlink_change_ngroups(struct sock *sk, unsigned int groups); extern void __netlink_clear_multicast_users(struct sock *sk, unsigned int group); -extern void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err); +extern void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, + const struct netlink_ext_ack *extack); extern int netlink_has_listeners(struct sock *sk, unsigned int group); extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock); diff --git a/include/net/netlink.h b/include/net/netlink.h index b239fcd33d80..a064ec3e2ee1 100644 --- a/include/net/netlink.h +++ b/include/net/netlink.h @@ -233,7 +233,8 @@ struct nl_info { }; int netlink_rcv_skb(struct sk_buff *skb, - int (*cb)(struct sk_buff *, struct nlmsghdr *)); + int (*cb)(struct sk_buff *, struct nlmsghdr *, + struct netlink_ext_ack *)); int nlmsg_notify(struct sock *sk, struct sk_buff *skb, u32 portid, unsigned int group, int report, gfp_t flags); diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h index b2c9c26ea30f..96e613db68cb 100644 --- a/include/uapi/linux/netlink.h +++ b/include/uapi/linux/netlink.h @@ -69,6 +69,10 @@ struct nlmsghdr { #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */ +/* Flags for ACK message */ +#define NLM_F_CAPPED 0x100 /* request was capped */ +#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ + /* 4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL 4.4BSD CHANGE NLM_F_REPLACE @@ -101,6 +105,33 @@ struct nlmsghdr { struct nlmsgerr { int error; struct nlmsghdr msg; + /* + * followed by the message contents unless NETLINK_CAP_ACK was set + * or the ACK indicates success (error == 0) + * message length is aligned with NLMSG_ALIGN() + */ + /* + * followed by TLVs defined in enum nlmsgerr_attrs + * if NETLINK_EXT_ACK was set + */ +}; + +/** + * enum nlmsgerr_attrs - nlmsgerr attributes + * @NLMSGERR_ATTR_UNUSED: unused + * @NLMSGERR_ATTR_MSG: error message string (string) + * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original + * message, counting from the beginning of the header (u32) + * @NUM_NLMSGERR_ATTRS: number of attributes + * @NLMSGERR_ATTR_MAX: highest attribute number + */ +enum nlmsgerr_attrs { + NLMSGERR_ATTR_UNUSED, + NLMSGERR_ATTR_MSG, + NLMSGERR_ATTR_OFFS, + + NUM_NLMSGERR_ATTRS, + NLMSGERR_ATTR_MAX = NUM_NLMSGERR_ATTRS - 1 }; #define NETLINK_ADD_MEMBERSHIP 1 @@ -115,6 +146,7 @@ struct nlmsgerr { #define NETLINK_LISTEN_ALL_NSID 8 #define NETLINK_LIST_MEMBERSHIPS 9 #define NETLINK_CAP_ACK 10 +#define NETLINK_EXT_ACK 11 struct nl_pktinfo { __u32 group; diff --git a/kernel/audit.c b/kernel/audit.c index 2f4964cfde0b..d54bf5932374 100644 --- a/kernel/audit.c +++ b/kernel/audit.c @@ -1402,7 +1402,7 @@ static void audit_receive_skb(struct sk_buff *skb) err = audit_receive_msg(skb, nlh); /* if err or if this message says it wants a response */ if (err || (nlh->nlmsg_flags & NLM_F_ACK)) - netlink_ack(skb, nlh, err); + netlink_ack(skb, nlh, err, NULL); nlh = nlmsg_next(nlh, &len); } diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 58419da7961b..09a2e86f7b92 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -4046,7 +4046,8 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb) /* Process one rtnetlink message. */ -static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) { struct net *net = sock_net(skb->sk); rtnl_doit_func doit; diff --git a/net/core/sock_diag.c b/net/core/sock_diag.c index fb9d0e2fd148..217f4e3b82f6 100644 --- a/net/core/sock_diag.c +++ b/net/core/sock_diag.c @@ -238,7 +238,8 @@ static int __sock_diag_cmd(struct sk_buff *skb, struct nlmsghdr *nlh) return err; } -static int sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +static int sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) { int ret; diff --git a/net/decnet/netfilter/dn_rtmsg.c b/net/decnet/netfilter/dn_rtmsg.c index 85f2fdc360c2..c8bf5136a72b 100644 --- a/net/decnet/netfilter/dn_rtmsg.c +++ b/net/decnet/netfilter/dn_rtmsg.c @@ -96,7 +96,7 @@ static unsigned int dnrmg_hook(void *priv, } -#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0) +#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err), NULL); return; } while (0) static inline void dnrmg_receive_user_skb(struct sk_buff *skb) { diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c index 1ab30e7d3f99..81dac16933fc 100644 --- a/net/hsr/hsr_netlink.c +++ b/net/hsr/hsr_netlink.c @@ -350,7 +350,7 @@ static int hsr_get_node_status(struct sk_buff *skb_in, struct genl_info *info) return 0; invalid: - netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL); + netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL, NULL); return 0; nla_put_failure: @@ -432,7 +432,7 @@ static int hsr_get_node_list(struct sk_buff *skb_in, struct genl_info *info) return 0; invalid: - netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL); + netlink_ack(skb_in, nlmsg_hdr(skb_in), -EINVAL, NULL); return 0; nla_put_failure: diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index c296f9b606d4..26356bf8cebf 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -1305,7 +1305,7 @@ ip_set_dump_start(struct sk_buff *skb, struct netlink_callback *cb) * manually :-( */ if (nlh->nlmsg_flags & NLM_F_ACK) - netlink_ack(cb->skb, nlh, ret); + netlink_ack(cb->skb, nlh, ret, NULL); return ret; } } diff --git a/net/netfilter/nfnetlink.c b/net/netfilter/nfnetlink.c index 68eda920160e..181d3bb800e6 100644 --- a/net/netfilter/nfnetlink.c +++ b/net/netfilter/nfnetlink.c @@ -148,7 +148,8 @@ int nfnetlink_unicast(struct sk_buff *skb, struct net *net, u32 portid, EXPORT_SYMBOL_GPL(nfnetlink_unicast); /* Process one complete nfnetlink message. */ -static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) { struct net *net = sock_net(skb->sk); const struct nfnl_callback *nc; @@ -261,7 +262,7 @@ static void nfnl_err_deliver(struct list_head *err_list, struct sk_buff *skb) struct nfnl_err *nfnl_err, *next; list_for_each_entry_safe(nfnl_err, next, err_list, head) { - netlink_ack(skb, nfnl_err->nlh, nfnl_err->err); + netlink_ack(skb, nfnl_err->nlh, nfnl_err->err, NULL); nfnl_err_del(nfnl_err); } } @@ -284,13 +285,13 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, int err; if (subsys_id >= NFNL_SUBSYS_COUNT) - return netlink_ack(skb, nlh, -EINVAL); + return netlink_ack(skb, nlh, -EINVAL, NULL); replay: status = 0; skb = netlink_skb_clone(oskb, GFP_KERNEL); if (!skb) - return netlink_ack(oskb, nlh, -ENOMEM); + return netlink_ack(oskb, nlh, -ENOMEM, NULL); nfnl_lock(subsys_id); ss = nfnl_dereference_protected(subsys_id); @@ -304,20 +305,20 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, #endif { nfnl_unlock(subsys_id); - netlink_ack(oskb, nlh, -EOPNOTSUPP); + netlink_ack(oskb, nlh, -EOPNOTSUPP, NULL); return kfree_skb(skb); } } if (!ss->commit || !ss->abort) { nfnl_unlock(subsys_id); - netlink_ack(oskb, nlh, -EOPNOTSUPP); + netlink_ack(oskb, nlh, -EOPNOTSUPP, NULL); return kfree_skb(skb); } if (genid && ss->valid_genid && !ss->valid_genid(net, genid)) { nfnl_unlock(subsys_id); - netlink_ack(oskb, nlh, -ERESTART); + netlink_ack(oskb, nlh, -ERESTART, NULL); return kfree_skb(skb); } @@ -407,7 +408,8 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, * pointing to the batch header. */ nfnl_err_reset(&err_list); - netlink_ack(oskb, nlmsg_hdr(oskb), -ENOMEM); + netlink_ack(oskb, nlmsg_hdr(oskb), -ENOMEM, + NULL); status |= NFNL_BATCH_FAILURE; goto done; } @@ -467,7 +469,7 @@ static void nfnetlink_rcv_skb_batch(struct sk_buff *skb, struct nlmsghdr *nlh) err = nla_parse(cda, NFNL_BATCH_MAX, attr, attrlen, nfnl_batch_policy); if (err < 0) { - netlink_ack(skb, nlh, err); + netlink_ack(skb, nlh, err, NULL); return; } if (cda[NFNL_BATCH_GENID]) @@ -493,7 +495,7 @@ static void nfnetlink_rcv(struct sk_buff *skb) return; if (!netlink_net_capable(skb, CAP_NET_ADMIN)) { - netlink_ack(skb, nlh, -EPERM); + netlink_ack(skb, nlh, -EPERM, NULL); return; } diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index fc232441cf23..c1564768000e 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -1652,6 +1652,13 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname, nlk->flags &= ~NETLINK_F_CAP_ACK; err = 0; break; + case NETLINK_EXT_ACK: + if (val) + nlk->flags |= NETLINK_F_EXT_ACK; + else + nlk->flags &= ~NETLINK_F_EXT_ACK; + err = 0; + break; default: err = -ENOPROTOOPT; } @@ -1736,6 +1743,15 @@ static int netlink_getsockopt(struct socket *sock, int level, int optname, return -EFAULT; err = 0; break; + case NETLINK_EXT_ACK: + if (len < sizeof(int)) + return -EINVAL; + len = sizeof(int); + val = nlk->flags & NETLINK_F_EXT_ACK ? 1 : 0; + if (put_user(len, optlen) || put_user(val, optval)) + return -EFAULT; + err = 0; + break; default: err = -ENOPROTOOPT; } @@ -2267,21 +2283,40 @@ int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb, } EXPORT_SYMBOL(__netlink_dump_start); -void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err) +void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, + const struct netlink_ext_ack *extack) { struct sk_buff *skb; 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; /* Error messages get the original request appened, unless the user - * requests to cap the error message. + * requests to cap the error message, and get extra error data if + * requested. */ - if (!(nlk->flags & NETLINK_F_CAP_ACK) && err) - payload += nlmsg_len(nlh); + if (err) { + if (!(nlk->flags & NETLINK_F_CAP_ACK)) + payload += nlmsg_len(nlh); + else + flags |= NLM_F_CAPPED; + if (nlk->flags & NETLINK_F_EXT_ACK && extack) { + if (extack->_msg) + tlvlen += nla_total_size(strlen(extack->_msg) + 1); + if (extack->bad_attr) + tlvlen += nla_total_size(sizeof(u32)); + } + } else { + flags |= NLM_F_CAPPED; + } - skb = nlmsg_new(payload, GFP_KERNEL); + if (tlvlen) + flags |= NLM_F_ACK_TLVS; + + skb = nlmsg_new(payload + tlvlen, GFP_KERNEL); if (!skb) { struct sock *sk; @@ -2297,17 +2332,35 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err) } rep = __nlmsg_put(skb, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, - NLMSG_ERROR, payload, 0); + NLMSG_ERROR, payload, flags); errmsg = nlmsg_data(rep); errmsg->error = err; memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh)); + + if (err && nlk->flags & NETLINK_F_EXT_ACK && extack) { + if (extack->_msg) + WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG, + extack->_msg)); + 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 - + in_skb->data)); + } + + nlmsg_end(skb, rep); + netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).portid, MSG_DONTWAIT); } EXPORT_SYMBOL(netlink_ack); int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, - struct nlmsghdr *)) + struct nlmsghdr *, + struct netlink_ext_ack *)) { + struct netlink_ext_ack extack = {}; struct nlmsghdr *nlh; int err; @@ -2328,13 +2381,13 @@ int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, if (nlh->nlmsg_type < NLMSG_MIN_TYPE) goto ack; - err = cb(skb, nlh); + err = cb(skb, nlh, &extack); if (err == -EINTR) goto skip; ack: if (nlh->nlmsg_flags & NLM_F_ACK || err) - netlink_ack(skb, nlh, err); + netlink_ack(skb, nlh, err, &extack); skip: msglen = NLMSG_ALIGN(nlh->nlmsg_len); diff --git a/net/netlink/af_netlink.h b/net/netlink/af_netlink.h index f792f8d7f982..3490f2430532 100644 --- a/net/netlink/af_netlink.h +++ b/net/netlink/af_netlink.h @@ -13,6 +13,7 @@ #define NETLINK_F_RECV_NO_ENOBUFS 0x8 #define NETLINK_F_LISTEN_ALL_NSID 0x10 #define NETLINK_F_CAP_ACK 0x20 +#define NETLINK_F_EXT_ACK 0x40 #define NLGRPSZ(x) (ALIGN(x, sizeof(unsigned long) * 8) / 8) #define NLGRPLONGS(x) (NLGRPSZ(x)/sizeof(unsigned long)) diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 92e0981f7404..57b2e3648bc0 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -605,7 +605,8 @@ static int genl_family_rcv_msg(const struct genl_family *family, return err; } -static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) { const struct genl_family *family; int err; diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 40a8aa39220d..1ba8c115a993 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -2448,7 +2448,8 @@ static const struct xfrm_link { [XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo }, }; -static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) { struct net *net = sock_net(skb->sk); struct nlattr *attrs[XFRMA_MAX+1];