Whamcloud - gitweb
LU-14195 lnet: improve compat code for IPV6_V6ONLY sock opt 59/43559/3
authorMr NeilBrown <neilb@suse.de>
Thu, 6 May 2021 01:27:28 +0000 (11:27 +1000)
committerOleg Drokin <green@whamcloud.com>
Wed, 19 May 2021 02:03:39 +0000 (02:03 +0000)
As get_fs() and set_fs() are deprecated, using them to call
sock->ops->setsockopt() is not a good solution.
Since linux 5.9 (v5.8-rc4-1952-ga7b75c5a8c41) it has been
possible to pass a "sockptr" to ->setsockopt() which can provide
a kernel address.

Prior to 5.8, kernet_setsockopt() is available and should still be
used.

For 5.8, when neither preferred option is available, we can pass
a NULL pointer which has the same effect as a pointer to zero.

Fixes: 10d99554631b ("LU-13783 lnet: remove kernel_setsockopt() from lnet_sock_listen()")
Signed-off-by: Mr NeilBrown <neilb@suse.de>
Change-Id: I78c1f735a73cc9c835371c139e946144c6df5108
Reviewed-on: https://review.whamcloud.com/43559
Tested-by: jenkins <devops@whamcloud.com>
Reviewed-by: James Simmons <jsimmons@infradead.org>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Arshad Hussain <arshad.hussain@aeoncomputing.com>
Reviewed-by: Chris Horn <chris.horn@hpe.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
lnet/lnet/lib-socket.c

index 4a86114..d443ae4 100644 (file)
@@ -343,7 +343,6 @@ struct socket *
 lnet_sock_listen(int local_port, int backlog, struct net *ns)
 {
        struct socket *sock;
-       mm_segment_t oldfs;
        int val = 0;
        int rc;
 
@@ -360,11 +359,34 @@ lnet_sock_listen(int local_port, int backlog, struct net *ns)
         * This is the default, but it can be overridden so
         * we force it back.
         */
-       oldfs = get_fs();
-       set_fs(KERNEL_DS);
-       sock->ops->setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
-                             (char __user __force *) &val, sizeof(val));
-       set_fs(oldfs);
+#ifdef HAVE_KERNEL_SETSOCKOPT
+       kernel_setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+                         (char *) &val, sizeof(val));
+#elif defined(_LINUX_SOCKPTR_H)
+       /* sockptr_t was introduced around v5.8-rc4-1952-ga7b75c5a8c41
+        * and allows a kernel address to be passed to ->setsockopt
+        */
+       if (ipv6_only_sock(sock->sk)) {
+               sockptr_t optval = KERNEL_SOCKPTR(&val);
+               sock->ops->setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+                                     optval, sizeof(val));
+       }
+#else
+       /* From v5.7-rc6-2614-g5a892ff2facb when kernel_setsockopt()
+        * was removed until sockptr_t (above) there is no clean
+        * way to pass kernel address to setsockopt.  We could use
+        * get_fs()/set_fs(), but in this particular situation there
+        * is an easier way.
+        * It depends on the fact that at least for these few kernels
+        * a NULL address to ipv6_setsockopt() is treated like the address
+        * of a zero.
+        */
+       if (ipv6_only_sock(sock->sk) && !val) {
+               void *optval = NULL;
+               sock->ops->setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+                               optval, sizeof(val));
+       }
+#endif
 
        rc = kernel_listen(sock, backlog);
        if (rc == 0)