ipv6: lockless IPV6_RECVERR implemetation

np->recverr is moved to inet->inet_flags to fix data-races.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Eric Dumazet 2023-09-12 16:02:08 +00:00 committed by David S. Miller
parent 1086ca7cce
commit 3fa29971c6
11 changed files with 25 additions and 32 deletions

View File

@ -243,8 +243,7 @@ struct ipv6_pinfo {
} rxopt; } rxopt;
/* sockopt flags */ /* sockopt flags */
__u16 recverr:1, __u16 sndflow:1,
sndflow:1,
repflow:1, repflow:1,
pmtudisc:3, pmtudisc:3,
padding:1, /* 1 bit hole */ padding:1, /* 1 bit hole */

View File

@ -274,6 +274,7 @@ enum {
INET_FLAGS_AUTOFLOWLABEL_SET = 23, INET_FLAGS_AUTOFLOWLABEL_SET = 23,
INET_FLAGS_AUTOFLOWLABEL = 24, INET_FLAGS_AUTOFLOWLABEL = 24,
INET_FLAGS_DONTFRAG = 25, INET_FLAGS_DONTFRAG = 25,
INET_FLAGS_RECVERR6 = 26,
}; };
/* cmsg flags for inet */ /* cmsg flags for inet */

View File

@ -1303,9 +1303,7 @@ static inline int ip6_sock_set_v6only(struct sock *sk)
static inline void ip6_sock_set_recverr(struct sock *sk) static inline void ip6_sock_set_recverr(struct sock *sk)
{ {
lock_sock(sk); inet6_set_bit(RECVERR6, sk);
inet6_sk(sk)->recverr = true;
release_sock(sk);
} }
static inline int __ip6_sock_set_addr_preferences(struct sock *sk, int val) static inline int __ip6_sock_set_addr_preferences(struct sock *sk, int val)

View File

@ -185,7 +185,7 @@ static int dccp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
goto out; goto out;
} }
if (!sock_owned_by_user(sk) && np->recverr) { if (!sock_owned_by_user(sk) && inet6_test_bit(RECVERR6, sk)) {
sk->sk_err = err; sk->sk_err = err;
sk_error_report(sk); sk_error_report(sk);
} else { } else {

View File

@ -581,7 +581,7 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
* 4.1.3.3. * 4.1.3.3.
*/ */
if ((family == AF_INET && !inet_test_bit(RECVERR, sk)) || if ((family == AF_INET && !inet_test_bit(RECVERR, sk)) ||
(family == AF_INET6 && !inet6_sk(sk)->recverr)) { (family == AF_INET6 && !inet6_test_bit(RECVERR6, sk))) {
if (!harderr || sk->sk_state != TCP_ESTABLISHED) if (!harderr || sk->sk_state != TCP_ESTABLISHED)
goto out; goto out;
} else { } else {

View File

@ -305,11 +305,10 @@ static void ipv6_icmp_error_rfc4884(const struct sk_buff *skb,
void ipv6_icmp_error(struct sock *sk, struct sk_buff *skb, int err, void ipv6_icmp_error(struct sock *sk, struct sk_buff *skb, int err,
__be16 port, u32 info, u8 *payload) __be16 port, u32 info, u8 *payload)
{ {
struct ipv6_pinfo *np = inet6_sk(sk);
struct icmp6hdr *icmph = icmp6_hdr(skb); struct icmp6hdr *icmph = icmp6_hdr(skb);
struct sock_exterr_skb *serr; struct sock_exterr_skb *serr;
if (!np->recverr) if (!inet6_test_bit(RECVERR6, sk))
return; return;
skb = skb_clone(skb, GFP_ATOMIC); skb = skb_clone(skb, GFP_ATOMIC);
@ -344,12 +343,11 @@ EXPORT_SYMBOL_GPL(ipv6_icmp_error);
void ipv6_local_error(struct sock *sk, int err, struct flowi6 *fl6, u32 info) void ipv6_local_error(struct sock *sk, int err, struct flowi6 *fl6, u32 info)
{ {
const struct ipv6_pinfo *np = inet6_sk(sk);
struct sock_exterr_skb *serr; struct sock_exterr_skb *serr;
struct ipv6hdr *iph; struct ipv6hdr *iph;
struct sk_buff *skb; struct sk_buff *skb;
if (!np->recverr) if (!inet6_test_bit(RECVERR6, sk))
return; return;
skb = alloc_skb(sizeof(struct ipv6hdr), GFP_ATOMIC); skb = alloc_skb(sizeof(struct ipv6hdr), GFP_ATOMIC);

View File

@ -481,6 +481,13 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
case IPV6_DONTFRAG: case IPV6_DONTFRAG:
inet6_assign_bit(DONTFRAG, sk, valbool); inet6_assign_bit(DONTFRAG, sk, valbool);
return 0; return 0;
case IPV6_RECVERR:
if (optlen < sizeof(int))
return -EINVAL;
inet6_assign_bit(RECVERR6, sk, valbool);
if (!val)
skb_errqueue_purge(&sk->sk_error_queue);
return 0;
} }
if (needs_rtnl) if (needs_rtnl)
rtnl_lock(); rtnl_lock();
@ -943,14 +950,6 @@ done:
np->pmtudisc = val; np->pmtudisc = val;
retv = 0; retv = 0;
break; break;
case IPV6_RECVERR:
if (optlen < sizeof(int))
goto e_inval;
np->recverr = valbool;
if (!val)
skb_errqueue_purge(&sk->sk_error_queue);
retv = 0;
break;
case IPV6_FLOWINFO_SEND: case IPV6_FLOWINFO_SEND:
if (optlen < sizeof(int)) if (optlen < sizeof(int))
goto e_inval; goto e_inval;
@ -1380,7 +1379,7 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
break; break;
case IPV6_RECVERR: case IPV6_RECVERR:
val = np->recverr; val = inet6_test_bit(RECVERR6, sk);
break; break;
case IPV6_FLOWINFO_SEND: case IPV6_FLOWINFO_SEND:

View File

@ -291,6 +291,7 @@ static void rawv6_err(struct sock *sk, struct sk_buff *skb,
struct inet6_skb_parm *opt, struct inet6_skb_parm *opt,
u8 type, u8 code, int offset, __be32 info) u8 type, u8 code, int offset, __be32 info)
{ {
bool recverr = inet6_test_bit(RECVERR6, sk);
struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_pinfo *np = inet6_sk(sk);
int err; int err;
int harderr; int harderr;
@ -300,7 +301,7 @@ static void rawv6_err(struct sock *sk, struct sk_buff *skb,
2. Socket is connected (otherwise the error indication 2. Socket is connected (otherwise the error indication
is useless without recverr and error is hard. is useless without recverr and error is hard.
*/ */
if (!np->recverr && sk->sk_state != TCP_ESTABLISHED) if (!recverr && sk->sk_state != TCP_ESTABLISHED)
return; return;
harderr = icmpv6_err_convert(type, code, &err); harderr = icmpv6_err_convert(type, code, &err);
@ -312,14 +313,14 @@ static void rawv6_err(struct sock *sk, struct sk_buff *skb,
ip6_sk_redirect(skb, sk); ip6_sk_redirect(skb, sk);
return; return;
} }
if (np->recverr) { if (recverr) {
u8 *payload = skb->data; u8 *payload = skb->data;
if (!inet_test_bit(HDRINCL, sk)) if (!inet_test_bit(HDRINCL, sk))
payload += offset; payload += offset;
ipv6_icmp_error(sk, skb, err, 0, ntohl(info), payload); ipv6_icmp_error(sk, skb, err, 0, ntohl(info), payload);
} }
if (np->recverr || harderr) { if (recverr || harderr) {
sk->sk_err = err; sk->sk_err = err;
sk_error_report(sk); sk_error_report(sk);
} }
@ -587,7 +588,6 @@ static int rawv6_send_hdrinc(struct sock *sk, struct msghdr *msg, int length,
struct flowi6 *fl6, struct dst_entry **dstp, struct flowi6 *fl6, struct dst_entry **dstp,
unsigned int flags, const struct sockcm_cookie *sockc) unsigned int flags, const struct sockcm_cookie *sockc)
{ {
struct ipv6_pinfo *np = inet6_sk(sk);
struct net *net = sock_net(sk); struct net *net = sock_net(sk);
struct ipv6hdr *iph; struct ipv6hdr *iph;
struct sk_buff *skb; struct sk_buff *skb;
@ -668,7 +668,7 @@ out:
error: error:
IP6_INC_STATS(net, rt->rt6i_idev, IPSTATS_MIB_OUTDISCARDS); IP6_INC_STATS(net, rt->rt6i_idev, IPSTATS_MIB_OUTDISCARDS);
error_check: error_check:
if (err == -ENOBUFS && !np->recverr) if (err == -ENOBUFS && !inet6_test_bit(RECVERR6, sk))
err = 0; err = 0;
return err; return err;
} }

View File

@ -508,7 +508,7 @@ static int tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
tcp_ld_RTO_revert(sk, seq); tcp_ld_RTO_revert(sk, seq);
} }
if (!sock_owned_by_user(sk) && np->recverr) { if (!sock_owned_by_user(sk) && inet6_test_bit(RECVERR6, sk)) {
WRITE_ONCE(sk->sk_err, err); WRITE_ONCE(sk->sk_err, err);
sk_error_report(sk); sk_error_report(sk);
} else { } else {

View File

@ -619,7 +619,7 @@ int __udp6_lib_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
goto out; goto out;
} }
if (!np->recverr) { if (!inet6_test_bit(RECVERR6, sk)) {
if (!harderr || sk->sk_state != TCP_ESTABLISHED) if (!harderr || sk->sk_state != TCP_ESTABLISHED)
goto out; goto out;
} else { } else {
@ -1283,7 +1283,7 @@ csum_partial:
send: send:
err = ip6_send_skb(skb); err = ip6_send_skb(skb);
if (err) { if (err) {
if (err == -ENOBUFS && !inet6_sk(sk)->recverr) { if (err == -ENOBUFS && !inet6_test_bit(RECVERR6, sk)) {
UDP6_INC_STATS(sock_net(sk), UDP6_INC_STATS(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite); UDP_MIB_SNDBUFERRORS, is_udplite);
err = 0; err = 0;
@ -1608,7 +1608,7 @@ do_append_data:
up->pending = 0; up->pending = 0;
if (err > 0) if (err > 0)
err = np->recverr ? net_xmit_errno(err) : 0; err = inet6_test_bit(RECVERR6, sk) ? net_xmit_errno(err) : 0;
release_sock(sk); release_sock(sk);
out: out:

View File

@ -128,7 +128,6 @@ static void sctp_v6_err_handle(struct sctp_transport *t, struct sk_buff *skb,
{ {
struct sctp_association *asoc = t->asoc; struct sctp_association *asoc = t->asoc;
struct sock *sk = asoc->base.sk; struct sock *sk = asoc->base.sk;
struct ipv6_pinfo *np;
int err = 0; int err = 0;
switch (type) { switch (type) {
@ -149,9 +148,8 @@ static void sctp_v6_err_handle(struct sctp_transport *t, struct sk_buff *skb,
break; break;
} }
np = inet6_sk(sk);
icmpv6_err_convert(type, code, &err); icmpv6_err_convert(type, code, &err);
if (!sock_owned_by_user(sk) && np->recverr) { if (!sock_owned_by_user(sk) && inet6_test_bit(RECVERR6, sk)) {
sk->sk_err = err; sk->sk_err = err;
sk_error_report(sk); sk_error_report(sk);
} else { } else {