netlink: extended ACK reporting
authorJohannes Berg <johannes.berg@intel.com>
Wed, 12 Apr 2017 12:34:04 +0000 (14:34 +0200)
committerDavid S. Miller <davem@davemloft.net>
Thu, 13 Apr 2017 17:58:20 +0000 (13:58 -0400)
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 ideas.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Reviewed-by: David Ahern <dsa@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
17 files changed:
crypto/crypto_user.c
drivers/infiniband/core/netlink.c
drivers/scsi/scsi_netlink.c
include/linux/netlink.h
include/net/netlink.h
include/uapi/linux/netlink.h
kernel/audit.c
net/core/rtnetlink.c
net/core/sock_diag.c
net/decnet/netfilter/dn_rtmsg.c
net/hsr/hsr_netlink.c
net/netfilter/ipset/ip_set_core.c
net/netfilter/nfnetlink.c
net/netlink/af_netlink.c
net/netlink/af_netlink.h
net/netlink/genetlink.c
net/xfrm/xfrm_user.c

index a90404a0c5ff1dd0cd68c857575705aaa3190bae..4a44830741c1162a939ceaf60c9ca5379c2d4f18 100644 (file)
@@ -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;
index 10469b0088b500fbe2012bc4bd43d249dad3b705..b784055423c8346b80e00f98d58f6499e7adc8ef 100644 (file)
@@ -146,7 +146,8 @@ nla_put_failure:
 }
 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)
index 109802f776ed71cea6857eda9ae6ccc3e0b41f80..50e624fb8307922cf1cb35f244aad13f4da035df 100644 (file)
@@ -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);
        }
index da14ab61f3634cae5c9ec1636c2774a6f388083d..60e7137f840dd833e7802e507a05cdd93bccea9c 100644 (file)
@@ -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);
index b239fcd33d8091268fd793fd45cc755264fa592f..a064ec3e2ee19c759c0c58f7ff837ede9bd4a531 100644 (file)
@@ -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);
 
index b2c9c26ea30ffde432ee2a2c0516cfc9fcd1d372..7df88770e0290af6c865f5c8c87787cfa30b9a43 100644 (file)
@@ -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)
+ * @__NLMSGERR_ATTR_MAX: number of attributes
+ * @NLMSGERR_ATTR_MAX: highest attribute number
+ */
+enum nlmsgerr_attrs {
+       NLMSGERR_ATTR_UNUSED,
+       NLMSGERR_ATTR_MSG,
+       NLMSGERR_ATTR_OFFS,
+
+       __NLMSGERR_ATTR_MAX,
+       NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 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;
index 2f4964cfde0b4f142778c199e606dc3d519ebbf0..d54bf59323745d54d4c5190945efc6624c2696b6 100644 (file)
@@ -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);
        }
index c138b6b75e594228b1d6a5c8255c922ad3f36423..3cc4a627a537e68660b2989a33bdd3f762e48ae3 100644 (file)
@@ -4046,7 +4046,8 @@ out:
 
 /* 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;
index fb9d0e2fd148aa78fa9c33e27e341af5a47c530f..217f4e3b82f6edca841b66089ab0772bd8d391e7 100644 (file)
@@ -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;
 
index 85f2fdc360c27b21cb5b9c486e2d05ffb1d557d7..c8bf5136a72bcb19db1b0523c743e4a4dce6514a 100644 (file)
@@ -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)
 {
index 1ab30e7d3f99e19c5e54fd9747cfce7a5c1f559b..81dac16933fc05a19224a7c9ef450d34dea1be91 100644 (file)
@@ -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:
index c296f9b606d495d59c50ee46a04e7e17b21c1530..26356bf8cebf0597fb943e32701972960189f620 100644 (file)
@@ -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;
                }
        }
index 68eda920160e12f3cb904dad08b7f6cc9422571e..181d3bb800e66bb32e677008d7fbee51959ecabd 100644 (file)
@@ -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 @@ replay:
 #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 @@ ack:
                                 * 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;
        }
 
index fc232441cf230faacdebf83fd1735462a6453d03..c1564768000ed0a3ab0e392b9928733e987f7f62 100644 (file)
@@ -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 @@ error_free:
 }
 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);
index f792f8d7f982d3e7d5b67b7cf1c5f01475728e1f..3490f2430532cf753118e14883b52c4af151b18c 100644 (file)
@@ -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))
index 92e0981f74040d7029b65863167b459322612024..57b2e3648bc02b6c39b2e56705c52d6c96885ee2 100644 (file)
@@ -605,7 +605,8 @@ out:
        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;
index 4f7e62ddc17e431902a3a31a4eea7f1563d758bd..e93d5c0471b22a7c1ab53dc798a3171d7e0b0c4a 100644 (file)
@@ -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];