lnet_sock_listen(int local_port, int backlog, struct net *ns)
{
struct socket *sock;
- mm_segment_t oldfs;
int val = 0;
int rc;
* 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)