netlink: add NETLINK_CAP_ACK socket option
authorChristophe Ricard <christophe.ricard@gmail.com>
Fri, 28 Aug 2015 05:07:48 +0000 (07:07 +0200)
committerDavid S. Miller <davem@davemloft.net>
Sat, 29 Aug 2015 05:25:42 +0000 (22:25 -0700)
Since commit c05cdb1b864f ("netlink: allow large data transfers from
user-space"), the kernel may fail to allocate the necessary room for the
acknowledgment message back to userspace. This patch introduces a new
socket option that trims off the payload of the original netlink message.

The netlink message header is still included, so the user can guess from
the sequence number what is the message that has triggered the
acknowledgment.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Christophe Ricard <christophe-h.ricard@st.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/netlink.h
net/netlink/af_netlink.c

index cf6a65cccbdf565a10af8f9a094334d070165ebb..6f3fe16cd22a24149b144133d2a181d9d4448f96 100644 (file)
@@ -110,6 +110,7 @@ struct nlmsgerr {
 #define NETLINK_TX_RING                        7
 #define NETLINK_LISTEN_ALL_NSID                8
 #define NETLINK_LIST_MEMBERSHIPS       9
+#define NETLINK_CAP_ACK                        10
 
 struct nl_pktinfo {
        __u32   group;
index a774985489e21e3bafffa8c8b6947290d0f58f75..3eea0b2a3239b11a8a64b26c12f345fde96b0986 100644 (file)
@@ -84,6 +84,7 @@ struct listeners {
 #define NETLINK_F_BROADCAST_SEND_ERROR 0x4
 #define NETLINK_F_RECV_NO_ENOBUFS      0x8
 #define NETLINK_F_LISTEN_ALL_NSID      0x10
+#define NETLINK_F_CAP_ACK              0x20
 
 static inline int netlink_is_kernel(struct sock *sk)
 {
@@ -2258,6 +2259,13 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname,
                        nlk->flags &= ~NETLINK_F_LISTEN_ALL_NSID;
                err = 0;
                break;
+       case NETLINK_CAP_ACK:
+               if (val)
+                       nlk->flags |= NETLINK_F_CAP_ACK;
+               else
+                       nlk->flags &= ~NETLINK_F_CAP_ACK;
+               err = 0;
+               break;
        default:
                err = -ENOPROTOOPT;
        }
@@ -2332,6 +2340,16 @@ static int netlink_getsockopt(struct socket *sock, int level, int optname,
                netlink_table_ungrab();
                break;
        }
+       case NETLINK_CAP_ACK:
+               if (len < sizeof(int))
+                       return -EINVAL;
+               len = sizeof(int);
+               val = nlk->flags & NETLINK_F_CAP_ACK ? 1 : 0;
+               if (put_user(len, optlen) ||
+                   put_user(val, optval))
+                       return -EFAULT;
+               err = 0;
+               break;
        default:
                err = -ENOPROTOOPT;
        }
@@ -2873,9 +2891,12 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err)
        struct nlmsghdr *rep;
        struct nlmsgerr *errmsg;
        size_t payload = sizeof(*errmsg);
+       struct netlink_sock *nlk = nlk_sk(NETLINK_CB(in_skb).sk);
 
-       /* error messages get the original request appened */
-       if (err)
+       /* Error messages get the original request appened, unless the user
+        * requests to cap the error message.
+        */
+       if (!(nlk->flags & NETLINK_F_CAP_ACK) && err)
                payload += nlmsg_len(nlh);
 
        skb = netlink_alloc_skb(in_skb->sk, nlmsg_total_size(payload),
@@ -2898,7 +2919,7 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err)
                          NLMSG_ERROR, payload, 0);
        errmsg = nlmsg_data(rep);
        errmsg->error = err;
-       memcpy(&errmsg->msg, nlh, err ? nlh->nlmsg_len : sizeof(*nlh));
+       memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh));
        netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).portid, MSG_DONTWAIT);
 }
 EXPORT_SYMBOL(netlink_ack);