|
@@ -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);
|