Linux CIFS filesystem development
 help / color / mirror / Atom feed
From: Henrique Carvalho <henrique.carvalho@suse.com>
To: linux-cifs@vger.kernel.org
Cc: linkinjeon@kernel.org, sfrench@samba.org, metze@samba.org,
	senozhatsky@chromium.org, tom@talpey.com, ematsumiya@suse.de,
	Henrique Carvalho <henrique.carvalho@suse.com>
Subject: [PATCH v2 08/11] smb: server: add QUIC transport support
Date: Tue, 28 Apr 2026 12:57:57 -0300	[thread overview]
Message-ID: <20260428155759.226368-4-henrique.carvalho@suse.com> (raw)
In-Reply-To: <20260428155759.226368-1-henrique.carvalho@suse.com>

Add a QUIC listener and connection transport for ksmbd on top of the
shared server transport helpers. This wires in the QUIC transport file,
adds the per-interface QUIC socket/kthread state, and enables QUIC
listener startup and teardown alongside the existing TCP path.

Also add the server-side QUIC port configuration and pass the client
peer address through the existing tree connect management path for QUIC
connections.

Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
---
 fs/smb/server/Makefile         |   4 +-
 fs/smb/server/connection.c     |   1 +
 fs/smb/server/interface.c      |  30 ++-
 fs/smb/server/interface.h      |   2 +
 fs/smb/server/server.h         |   1 +
 fs/smb/server/smb2pdu.c        |   1 +
 fs/smb/server/transport_ipc.c  |   1 +
 fs/smb/server/transport_quic.c | 327 +++++++++++++++++++++++++++++++++
 fs/smb/server/transport_quic.h |  20 ++
 9 files changed, 380 insertions(+), 7 deletions(-)
 create mode 100644 fs/smb/server/transport_quic.c
 create mode 100644 fs/smb/server/transport_quic.h

diff --git a/fs/smb/server/Makefile b/fs/smb/server/Makefile
index 89a9955f607d..569be14b32d4 100644
--- a/fs/smb/server/Makefile
+++ b/fs/smb/server/Makefile
@@ -8,8 +8,8 @@ ksmbd-y :=	unicode.o auth.o vfs.o vfs_cache.o server.o ndr.o \
 		misc.o oplock.o connection.o ksmbd_work.o crypto_ctx.o \
 		mgmt/ksmbd_ida.o mgmt/user_config.o mgmt/share_config.o \
 		mgmt/tree_connect.o mgmt/user_session.o smb_common.o \
-		transport.o transport_tcp.o transport_ipc.o interface.o \
-		smbacl.o smb2pdu.o \
+		transport.o transport_tcp.o transport_ipc.o transport_quic.o \
+		interface.o smbacl.o smb2pdu.o \
 		smb2ops.o smb2misc.o ksmbd_spnego_negtokeninit.asn1.o \
 		ksmbd_spnego_negtokentarg.asn1.o asn1.o
 
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 07bbf4eb7b05..1db8e0a8b47d 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -15,6 +15,7 @@
 #include "interface.h"
 #include "transport.h"
 #include "transport_tcp.h"
+#include "transport_quic.h"
 #include "transport_rdma.h"
 #include "misc.h"
 
diff --git a/fs/smb/server/interface.c b/fs/smb/server/interface.c
index a5e59a420639..25b8e7f44059 100644
--- a/fs/smb/server/interface.c
+++ b/fs/smb/server/interface.c
@@ -11,6 +11,7 @@
 #include "auth.h"
 #include "connection.h"
 #include "transport_tcp.h"
+#include "transport_quic.h"
 #include "interface.h"
 
 static LIST_HEAD(iface_list);
@@ -36,12 +37,14 @@ static struct interface *alloc_iface(char *ifname)
 }
 
 /**
- * ksmbd_interface_run_kthread() - start a per-interface forker thread
- * @iface:	pointer to struct interface
- * @kthread_fn:	thread routine to run
- * @suf:	suffix to append to the thread name (e.g. "tcp")
+ * ksmbd_tcp_run_kthread() - start forker thread
+ * @iface: pointer to struct interface
  *
- * Return:	the started task_struct, or ERR_PTR on failure.
+ * start forker thread(ksmbd/0) at module init time to listen
+ * on port 445 for new SMB connection requests. It creates per connection
+ * server threads(ksmbd/x)
+ *
+ * Return:	0 on success or error number
  */
 struct task_struct *ksmbd_interface_run_kthread(struct interface *iface,
 						int (*kthread_fn)(void *), const char *suf)
@@ -87,6 +90,10 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
 			ret = ksmbd_tcp_create_socket(iface);
 			if (ret)
 				break;
+
+			ret = ksmbd_quic_create_socket(iface);
+			if (ret)
+				break;
 		}
 		if (!iface && bind_additional_ifaces) {
 			iface = alloc_iface(kstrdup(netdev->name, KSMBD_DEFAULT_GFP));
@@ -101,6 +108,10 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
 			ret = ksmbd_tcp_create_socket(iface);
 			if (ret)
 				break;
+
+			ret = ksmbd_quic_create_socket(iface);
+			if (ret)
+				break;
 		}
 		break;
 	case NETDEV_DOWN:
@@ -111,15 +122,23 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
 
 			if (iface->ksmbd_tcp_socket)
 				kernel_sock_shutdown(iface->ksmbd_tcp_socket, SHUT_RDWR);
+			if (iface->ksmbd_quic_socket)
+				kernel_sock_shutdown(iface->ksmbd_quic_socket, SHUT_RDWR);
 
 			if (iface->ksmbd_tcp_kthread)
 				ksmbd_interface_stop_kthread(iface->ksmbd_tcp_kthread);
+			if (iface->ksmbd_quic_kthread)
+				ksmbd_interface_stop_kthread(iface->ksmbd_quic_kthread);
 
 			if (iface->ksmbd_tcp_socket)
 				sock_release(iface->ksmbd_tcp_socket);
+			if (iface->ksmbd_quic_socket)
+				sock_release(iface->ksmbd_quic_socket);
 
 			iface->ksmbd_tcp_kthread = NULL;
+			iface->ksmbd_quic_kthread = NULL;
 			iface->ksmbd_tcp_socket = NULL;
+			iface->ksmbd_quic_socket = NULL;
 
 			put_net(iface->net);
 			iface->net = NULL;
@@ -194,3 +213,4 @@ int ksmbd_set_interfaces(char *ifc_list, int ifc_list_sz)
 
 	return 0;
 }
+
diff --git a/fs/smb/server/interface.h b/fs/smb/server/interface.h
index 7e35645076a9..94ffb73fd635 100644
--- a/fs/smb/server/interface.h
+++ b/fs/smb/server/interface.h
@@ -16,6 +16,8 @@
 struct interface {
 	struct task_struct	*ksmbd_tcp_kthread;
 	struct socket		*ksmbd_tcp_socket;
+	struct task_struct	*ksmbd_quic_kthread;
+	struct socket		*ksmbd_quic_socket;
 	struct list_head	entry;
 	char			*name;
 	int			state;
diff --git a/fs/smb/server/server.h b/fs/smb/server/server.h
index b8a7317be86b..197d99264071 100644
--- a/fs/smb/server/server.h
+++ b/fs/smb/server/server.h
@@ -35,6 +35,7 @@ struct ksmbd_server_config {
 	short			min_protocol;
 	short			max_protocol;
 	unsigned short		tcp_port;
+	unsigned short		quic_port;
 	unsigned short		ipc_timeout;
 	unsigned long		ipc_last_active;
 	unsigned long		deadtime;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 1f579ef0e9d6..6f82f6bd378a 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -31,6 +31,7 @@
 
 #include "server.h"
 #include "smb_common.h"
+#include "interface.h"
 #include "../common/smb2status.h"
 #include "ksmbd_work.h"
 #include "mgmt/user_config.h"
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 6ccfb1cf8ed7..d14f282f3337 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -315,6 +315,7 @@ static int ipc_server_config_on_startup(struct ksmbd_startup_request *req)
 	server_conf.flags = req->flags;
 	server_conf.signing = req->signing;
 	server_conf.tcp_port = req->tcp_port;
+	server_conf.quic_port = 443; /* MS-SMB2 1.9 */
 	server_conf.ipc_timeout = req->ipc_timeout * HZ;
 	if (check_mul_overflow(req->deadtime, SMB_ECHO_INTERVAL,
 					&server_conf.deadtime)) {
diff --git a/fs/smb/server/transport_quic.c b/fs/smb/server/transport_quic.c
new file mode 100644
index 000000000000..84067225c1d3
--- /dev/null
+++ b/fs/smb/server/transport_quic.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2026, SUSE LLC
+ *
+ *   Author: Henrique Carvalho <henrique.carvalho@suse.com>
+ */
+
+#include <linux/freezer.h>
+#include <linux/socket.h>
+#include <linux/quic.h>
+#include <linux/key.h>
+#include <net/handshake.h>
+#include <keys/asymmetric-type.h>
+
+#include "smb_common.h"
+#include "server.h"
+#include "auth.h"
+#include "connection.h"
+#include "transport.h"
+#include "glob.h"
+#include "transport_quic.h"
+#include "interface.h"
+
+struct quic_transport {
+	struct ksmbd_transport		transport;
+	struct socket			*sock;
+};
+
+static const struct ksmbd_transport_ops ksmbd_quic_transport_ops;
+
+#define QUIC_TRANS(t)	((struct quic_transport *)container_of(t, struct quic_transport, transport))
+
+static inline void ksmbd_quic_reuseaddr(struct socket *sock)
+{
+	sock_set_reuseaddr(sock->sk);
+}
+
+static void ksmbd_quic_disconnect(struct ksmbd_transport *kt)
+{
+	struct quic_transport *t = QUIC_TRANS(kt);
+
+	kernel_sock_shutdown(t->sock, SHUT_RDWR);
+	ksmbd_conn_free(KSMBD_TRANS(t)->conn);
+	if (server_conf.max_connections)
+		atomic_dec(&active_num_conn);
+}
+
+static void ksmbd_quic_close_client_sk(struct socket *client_sk)
+{
+	if (client_sk->file)
+		fput(client_sk->file);
+	else
+		sock_release(client_sk);
+}
+
+static struct quic_transport *ksmbd_quic_alloc(void)
+{
+	struct quic_transport *t;
+
+	t = kzalloc_obj(*t, KSMBD_DEFAULT_GFP);
+	if (!t)
+		return NULL;
+
+	KSMBD_TRANS(t)->ops = &ksmbd_quic_transport_ops;
+	return t;
+}
+
+static void ksmbd_quic_free(struct ksmbd_transport *kt)
+{
+	struct quic_transport *t = QUIC_TRANS(kt);
+
+	ksmbd_quic_close_client_sk(t->sock);
+	kfree(kt->io.iov); /* TODO (transport.c): move to ksmbd_conn_free */
+	kfree(t);
+}
+
+/**
+ * ksmbd_quic_new_connection() - create a new quic session on mount
+ * @client_sk:	socket associated with new connection
+ *
+ * whenever a new connection is requested, create a conn thread
+ * (session thread) to handle new incoming smb requests from the connection
+ *
+ * Return:	0 on success, otherwise error
+ */
+static int ksmbd_quic_new_connection(struct socket *client_sk)
+{
+	int rc;
+	struct quic_transport *t;
+
+	t = ksmbd_quic_alloc();
+	if (!t) {
+		ksmbd_quic_close_client_sk(client_sk);
+		return -ENOMEM;
+	}
+
+	t->sock = client_sk;
+
+	rc = ksmbd_transport_alloc_conn(KSMBD_TRANS(t), client_sk);
+	if (rc) {
+		if (KSMBD_TRANS(t)->conn) {
+			/*
+			 * conn was allocated and hashed but the handler
+			 * thread failed; tear down via the conn refcount
+			 * path which also calls ->free_transport().
+			 */
+			ksmbd_quic_disconnect(KSMBD_TRANS(t));
+		} else {
+			ksmbd_quic_close_client_sk(client_sk);
+			kfree(t);
+		}
+	}
+
+	return rc;
+}
+
+static void ksmbd_tls_done(void *data, int status, key_serial_t peerid)
+{
+	struct smb_tls *kstls = (struct smb_tls *)data;
+
+	kstls->status = status;
+	kstls->peerid = peerid;
+
+	complete(&kstls->handshake_done);
+}
+
+static struct smb_tls *ksmbd_tls_init(void)
+{
+	struct smb_tls *kstls;
+	const unsigned int timeout_ms = 5000;
+
+	kstls = kzalloc_obj(*kstls, KSMBD_DEFAULT_GFP);
+	if (!kstls)
+		return ERR_PTR(-ENOMEM);
+
+	kstls->status = 0;
+
+	kstls->args.ta_done = ksmbd_tls_done;
+	kstls->args.ta_data = kstls;
+	kstls->args.ta_timeout_ms = timeout_ms;
+
+	init_completion(&kstls->handshake_done);
+
+	return kstls;
+}
+
+static void ksmbd_tls_reinit(struct smb_tls *kstls, struct socket *client_sk)
+{
+	kstls->status = 0;
+	kstls->peerid = TLS_NO_PEERID;
+	kstls->args.ta_sock = client_sk;
+	reinit_completion(&kstls->handshake_done);
+}
+
+static void ksmbd_tls_free(struct smb_tls **kstls)
+{
+	kfree_sensitive(*kstls);
+	*kstls = NULL;
+}
+
+/**
+ * ksmbd_quic_kthread_fn() - listen to new SMB QUIC connections and callback server
+ * @p:		arguments to forker thread
+ *
+ * Return:	0 on success, error number otherwise
+ */
+static int ksmbd_quic_kthread_fn(void *p)
+{
+	struct socket *client_sk = NULL;
+	struct interface *iface = (struct interface *)p;
+	struct smb_tls *ksmbd_tls;
+	int ret;
+
+	ksmbd_tls = ksmbd_tls_init();
+	if (IS_ERR(ksmbd_tls))
+		return PTR_ERR(ksmbd_tls);
+
+	while (!kthread_should_stop()) {
+		struct file *filp;
+
+		if (!iface->ksmbd_quic_socket)
+			break;
+
+		ret = kernel_accept(iface->ksmbd_quic_socket, &client_sk, 0);
+		if (ret == -EINVAL)
+			break;
+		if (ret)
+			continue;
+
+		ret = quic_do_setsockopt(client_sk->sk,
+					 QUIC_SOCKOPT_ALPN,
+					 KERNEL_SOCKPTR("smb"),
+					 3);
+		if (ret < 0) {
+			ksmbd_debug(CONN, "Failed to set QUIC_SOCKOPT_ALPN: %d\n", ret);
+			ksmbd_quic_close_client_sk(client_sk);
+			continue;
+		}
+
+		filp = sock_alloc_file(client_sk, 0, NULL);
+		if (IS_ERR(filp)) {
+			ksmbd_debug(CONN, "Socket file allocation failed: %ld", PTR_ERR(filp));
+			ksmbd_quic_close_client_sk(client_sk);
+			continue;
+		}
+
+		ksmbd_tls_reinit(ksmbd_tls, client_sk);
+
+		ret = tls_server_hello_x509(&ksmbd_tls->args, KSMBD_DEFAULT_GFP);
+		if (ret < 0) {
+			ksmbd_debug(CONN, "Handshake failed: %d", ret);
+			ksmbd_quic_close_client_sk(client_sk);
+			continue;
+		}
+
+		ret = wait_for_completion_interruptible(&ksmbd_tls->handshake_done);
+		if (ret < 0) {
+			tls_handshake_cancel(client_sk->sk);
+			ksmbd_quic_close_client_sk(client_sk);
+			ksmbd_debug(CONN, "Handshake failed: %d", ret);
+			continue;
+		}
+		if (ksmbd_tls->status) {
+			ksmbd_quic_close_client_sk(client_sk);
+			ksmbd_debug(CONN, "Handshake failed: %d", ksmbd_tls->status);
+			continue;
+		}
+		ksmbd_debug(CONN, "Handshake successful");
+
+		if (ksmbd_check_max_ip_conns(client_sk)) {
+			ksmbd_quic_close_client_sk(client_sk);
+			continue;
+		}
+
+		if (ksmbd_check_max_conns()) {
+			ksmbd_quic_close_client_sk(client_sk);
+			continue;
+		}
+
+		ksmbd_debug(CONN, "connect success: accepted new connection\n");
+		client_sk->sk->sk_rcvtimeo = KSMBD_QUIC_RECV_TIMEOUT;
+		client_sk->sk->sk_sndtimeo = KSMBD_QUIC_SEND_TIMEOUT;
+
+		ksmbd_quic_new_connection(client_sk);
+	}
+
+	ksmbd_tls_free(&ksmbd_tls);
+
+	ksmbd_debug(CONN, "releasing socket\n");
+	return 0;
+}
+
+/**
+ * ksmbd_quic_read() - read data from socket in given buffer
+ * @t:		transport instance
+ * @buf:	buffer to store read data from socket
+ * @to_read:	number of bytes to read from socket
+ * @max_retries: number of retries if reading from socket fails
+ *
+ * Return:	on success return number of bytes read from socket,
+ *		otherwise return error number
+ */
+static int ksmbd_quic_readv(struct ksmbd_transport *t, char *buf,
+			   unsigned int to_read, int max_retries)
+{
+	struct kvec iov;
+
+	iov.iov_base = buf;
+	iov.iov_len = to_read;
+
+	return ksmbd_readv(t, QUIC_TRANS(t)->sock, &iov, 1, to_read, max_retries);
+}
+
+static int ksmbd_quic_writev(struct ksmbd_transport *t, struct kvec *iov,
+			    int nvecs, int size, bool need_invalidate,
+			    unsigned int remote_key)
+
+{
+	int err = -EOPNOTSUPP;
+#if IS_ENABLED(CONFIG_IP_QUIC)
+	struct msghdr ksmbd_msg = {.msg_flags = MSG_NOSIGNAL};
+
+	err = kernel_sendmsg(QUIC_TRANS(t)->sock, &ksmbd_msg, iov, nvecs, size);
+#endif
+	return err;
+}
+
+void ksmbd_quic_destroy_socket(struct socket *ksmbd_socket)
+{
+	ksmbd_destroy_socket(ksmbd_socket);
+}
+
+int ksmbd_quic_create_socket(struct interface *iface)
+{
+#if IS_ENABLED(CONFIG_IP_QUIC)
+	struct task_struct *kthread;
+	struct socket *ksmbd_socket;
+	int ret;
+
+	ksmbd_socket = ksmbd_create_socket(iface, SOCK_DGRAM, IPPROTO_QUIC,
+					   server_conf.quic_port);
+	if (IS_ERR(ksmbd_socket))
+		return PTR_ERR(ksmbd_socket);
+
+	ksmbd_quic_reuseaddr(ksmbd_socket);
+
+	kthread = ksmbd_interface_run_kthread(iface, ksmbd_quic_kthread_fn, "quic");
+	if (IS_ERR(kthread)) {
+		ret = PTR_ERR(kthread);
+		pr_err("Can't start ksmbd main kthread for quic: %d\n", ret);
+		ksmbd_destroy_socket(ksmbd_socket);
+		return ret;
+	}
+
+	iface->ksmbd_quic_socket  = ksmbd_socket;
+	iface->ksmbd_quic_kthread = kthread;
+	iface->state = IFACE_STATE_CONFIGURED;
+#endif /* CONFIG_IP_QUIC */
+	return 0;
+}
+
+static const struct ksmbd_transport_ops ksmbd_quic_transport_ops = {
+	.read		= ksmbd_quic_readv,
+	.writev		= ksmbd_quic_writev,
+	.disconnect	= ksmbd_quic_disconnect,
+	.free_transport = ksmbd_quic_free,
+};
diff --git a/fs/smb/server/transport_quic.h b/fs/smb/server/transport_quic.h
new file mode 100644
index 000000000000..5c62c45affc4
--- /dev/null
+++ b/fs/smb/server/transport_quic.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *   Copyright (C) 2026, SUSE LLC.
+ */
+
+#ifndef __KSMBD_TRANSPORT_QUIC_H__
+#define __KSMBD_TRANSPORT_QUIC_H__
+
+#include "interface.h"
+
+#define KSMBD_QUIC_RECV_TIMEOUT	(7 * HZ)
+#define KSMBD_QUIC_SEND_TIMEOUT	(5 * HZ)
+
+int ksmbd_quic_init(void);
+void ksmbd_quic_destroy(void);
+int ksmbd_quic_create_socket(struct interface *iface);
+void ksmbd_quic_destroy_socket(struct socket *ksmbd_socket);
+void ksmbd_quic_stop_kthread(struct task_struct *kthread);
+
+#endif /* __KSMBD_TRANSPORT_QUIC_H__ */
-- 
2.53.0


  parent reply	other threads:[~2026-04-28 15:58 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-28 15:57 [PATCH v2 05/11] smb: server: split interface management from TCP in preparation for QUIC Henrique Carvalho
2026-04-28 15:57 ` [PATCH v2 06/11] smb: server: add shared transport helpers " Henrique Carvalho
2026-04-29 22:24   ` Namjae Jeon
2026-04-28 15:57 ` [PATCH v2 07/11] smb: server: reuse common transport helpers in TCP transport Henrique Carvalho
2026-04-28 15:57 ` Henrique Carvalho [this message]
2026-04-28 15:57 ` [PATCH v2 09/11] smb: server: refactor TCP transport definitions Henrique Carvalho
2026-04-28 15:57 ` [PATCH v2 10/11] smb: server: track TCP and QUIC listener state independently Henrique Carvalho

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260428155759.226368-4-henrique.carvalho@suse.com \
    --to=henrique.carvalho@suse.com \
    --cc=ematsumiya@suse.de \
    --cc=linkinjeon@kernel.org \
    --cc=linux-cifs@vger.kernel.org \
    --cc=metze@samba.org \
    --cc=senozhatsky@chromium.org \
    --cc=sfrench@samba.org \
    --cc=tom@talpey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox