ipv6: lockless IPV6_MULTICAST_HOPS implementation
authorEric Dumazet <edumazet@google.com>
Tue, 12 Sep 2023 16:02:01 +0000 (16:02 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 15 Sep 2023 09:33:46 +0000 (10:33 +0100)
This fixes data-races around np->mcast_hops,
and make IPV6_MULTICAST_HOPS lockless.

Note that np->mcast_hops is never negative,
thus can fit an u8 field instead of s16.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/ipv6.h
include/net/ipv6.h
net/dccp/ipv6.c
net/ipv6/ipv6_sockglue.c
net/ipv6/tcp_ipv6.c
net/netfilter/ipvs/ip_vs_sync.c

index 68cf1ca..9cc278b 100644 (file)
@@ -214,15 +214,8 @@ struct ipv6_pinfo {
        __u32                   frag_size;
 
        s16                     hop_limit;
+       u8                      mcast_hops;
 
-#if defined(__BIG_ENDIAN_BITFIELD)
-       /* Packed in 16bits. */
-       __s16                   mcast_hops:9;
-       __u16                   __unused_2:7,
-#else
-       __u16                   __unused_2:7;
-       __s16                   mcast_hops:9;
-#endif
        int                     ucast_oif;
        int                     mcast_oif;
 
index 2e8e7e3..8a04a89 100644 (file)
@@ -914,7 +914,7 @@ static inline int ip6_sk_dst_hoplimit(struct ipv6_pinfo *np, struct flowi6 *fl6,
        int hlimit;
 
        if (ipv6_addr_is_multicast(&fl6->daddr))
-               hlimit = np->mcast_hops;
+               hlimit = READ_ONCE(np->mcast_hops);
        else
                hlimit = READ_ONCE(np->hop_limit);
        if (hlimit < 0)
index 33f6ccf..83617a1 100644 (file)
@@ -676,7 +676,7 @@ ipv6_pktoptions:
                if (np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo)
                        np->mcast_oif = inet6_iif(opt_skb);
                if (np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim)
-                       np->mcast_hops = ipv6_hdr(opt_skb)->hop_limit;
+                       WRITE_ONCE(np->mcast_hops, ipv6_hdr(opt_skb)->hop_limit);
                if (np->rxopt.bits.rxflow || np->rxopt.bits.rxtclass)
                        np->rcv_flowinfo = ip6_flowinfo(ipv6_hdr(opt_skb));
                if (np->repflow)
index 755fac8..5fff19a 100644 (file)
@@ -431,6 +431,16 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
                        return -EINVAL;
                inet6_assign_bit(MC6_LOOP, sk, valbool);
                return 0;
+       case IPV6_MULTICAST_HOPS:
+               if (sk->sk_type == SOCK_STREAM)
+                       return retv;
+               if (optlen < sizeof(int))
+                       return -EINVAL;
+               if (val > 255 || val < -1)
+                       return -EINVAL;
+               WRITE_ONCE(np->mcast_hops,
+                          val == -1 ? IPV6_DEFAULT_MCASTHOPS : val);
+               return 0;
        }
        if (needs_rtnl)
                rtnl_lock();
@@ -751,16 +761,6 @@ done:
                break;
        }
 
-       case IPV6_MULTICAST_HOPS:
-               if (sk->sk_type == SOCK_STREAM)
-                       break;
-               if (optlen < sizeof(int))
-                       goto e_inval;
-               if (val > 255 || val < -1)
-                       goto e_inval;
-               np->mcast_hops = (val == -1 ? IPV6_DEFAULT_MCASTHOPS : val);
-               retv = 0;
-               break;
 
        case IPV6_UNICAST_IF:
        {
@@ -1180,7 +1180,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
                                put_cmsg(&msg, SOL_IPV6, IPV6_PKTINFO, sizeof(src_info), &src_info);
                        }
                        if (np->rxopt.bits.rxhlim) {
-                               int hlim = np->mcast_hops;
+                               int hlim = READ_ONCE(np->mcast_hops);
+
                                put_cmsg(&msg, SOL_IPV6, IPV6_HOPLIMIT, sizeof(hlim), &hlim);
                        }
                        if (np->rxopt.bits.rxtclass) {
@@ -1197,7 +1198,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
                                put_cmsg(&msg, SOL_IPV6, IPV6_2292PKTINFO, sizeof(src_info), &src_info);
                        }
                        if (np->rxopt.bits.rxohlim) {
-                               int hlim = np->mcast_hops;
+                               int hlim = READ_ONCE(np->mcast_hops);
+
                                put_cmsg(&msg, SOL_IPV6, IPV6_2292HOPLIMIT, sizeof(hlim), &hlim);
                        }
                        if (np->rxopt.bits.rxflow) {
@@ -1349,7 +1351,7 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
                if (optname == IPV6_UNICAST_HOPS)
                        val = READ_ONCE(np->hop_limit);
                else
-                       val = np->mcast_hops;
+                       val = READ_ONCE(np->mcast_hops);
 
                if (val < 0) {
                        rcu_read_lock();
index 3a88545..54db5fa 100644 (file)
@@ -1542,7 +1542,8 @@ ipv6_pktoptions:
                if (np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo)
                        np->mcast_oif = tcp_v6_iif(opt_skb);
                if (np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim)
-                       np->mcast_hops = ipv6_hdr(opt_skb)->hop_limit;
+                       WRITE_ONCE(np->mcast_hops,
+                                  ipv6_hdr(opt_skb)->hop_limit);
                if (np->rxopt.bits.rxflow || np->rxopt.bits.rxtclass)
                        np->rcv_flowinfo = ip6_flowinfo(ipv6_hdr(opt_skb));
                if (np->repflow)
index 3c2251c..df1b33b 100644 (file)
@@ -1322,7 +1322,7 @@ static void set_mcast_ttl(struct sock *sk, u_char ttl)
                struct ipv6_pinfo *np = inet6_sk(sk);
 
                /* IPV6_MULTICAST_HOPS */
-               np->mcast_hops = ttl;
+               WRITE_ONCE(np->mcast_hops, ttl);
        }
 #endif
        release_sock(sk);