Linux CIFS filesystem development
 help / color / mirror / Atom feed
* [PATCH v2 05/11] smb: server: split interface management from TCP in preparation for QUIC
@ 2026-04-28 15:57 Henrique Carvalho
  2026-04-28 15:57 ` [PATCH v2 06/11] smb: server: add shared transport helpers " Henrique Carvalho
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Henrique Carvalho @ 2026-04-28 15:57 UTC (permalink / raw)
  To: linux-cifs
  Cc: linkinjeon, sfrench, metze, senozhatsky, tom, ematsumiya,
	Henrique Carvalho

Move active_num_conn out of transport_tcp.c and define it in
connection.c.

Move the transport-agnostic bits out into a new interface.c /
interface.h, network-interface bookkeeping, the iface_list, the
netdevice notifier, alloc/find/set helpers, and the per-iface forker
kthread plumbing.

Make the necessary changes to transport_tcp.c, without functional change
for the TCP path; this is purely a layering rearrangement to make room
for additional transports.

Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
---
 fs/smb/server/Makefile        |   2 +-
 fs/smb/server/connection.c    |   9 +-
 fs/smb/server/connection.h    |   1 +
 fs/smb/server/interface.c     | 196 ++++++++++++++++++++++++++++++
 fs/smb/server/interface.h     |  34 ++++++
 fs/smb/server/transport_ipc.c |   2 +-
 fs/smb/server/transport_tcp.c | 223 ++++------------------------------
 fs/smb/server/transport_tcp.h |   7 +-
 8 files changed, 265 insertions(+), 209 deletions(-)
 create mode 100644 fs/smb/server/interface.c
 create mode 100644 fs/smb/server/interface.h

diff --git a/fs/smb/server/Makefile b/fs/smb/server/Makefile
index 6407ba6b9340..7bd6501bfdc9 100644
--- a/fs/smb/server/Makefile
+++ b/fs/smb/server/Makefile
@@ -8,7 +8,7 @@ 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_tcp.o transport_ipc.o smbacl.o smb2pdu.o \
+		interface.o transport_tcp.o transport_ipc.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 31392d245c30..7465b364c35c 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -12,6 +12,7 @@
 #include "smb_common.h"
 #include "mgmt/ksmbd_ida.h"
 #include "connection.h"
+#include "interface.h"
 #include "transport_tcp.h"
 #include "transport_rdma.h"
 #include "misc.h"
@@ -79,6 +80,8 @@ static int create_proc_clients(void) { return 0; }
 static void delete_proc_clients(void) {}
 #endif
 
+atomic_t active_num_conn = ATOMIC_INIT(0);
+
 /**
  * ksmbd_conn_free() - free resources of the connection instance
  *
@@ -512,9 +515,9 @@ int ksmbd_conn_transport_init(void)
 	int ret;
 
 	mutex_lock(&init_lock);
-	ret = ksmbd_tcp_init();
+	ret = ksmbd_interface_init();
 	if (ret) {
-		pr_err("Failed to init TCP subsystem: %d\n", ret);
+		pr_err("Failed to init interface: %d\n", ret);
 		goto out;
 	}
 
@@ -558,7 +561,7 @@ void ksmbd_conn_transport_destroy(void)
 {
 	delete_proc_clients();
 	mutex_lock(&init_lock);
-	ksmbd_tcp_destroy();
+	ksmbd_interface_destroy();
 	ksmbd_rdma_stop_listening();
 	stop_sessions();
 	mutex_unlock(&init_lock);
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index ae21a1bd4c70..b060baf5f688 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -157,6 +157,7 @@ struct ksmbd_transport {
 #define CONN_HASH_BITS	12
 extern DECLARE_HASHTABLE(conn_list, CONN_HASH_BITS);
 extern struct rw_semaphore conn_list_lock;
+extern atomic_t active_num_conn;
 
 bool ksmbd_conn_alive(struct ksmbd_conn *conn);
 void ksmbd_conn_wait_idle(struct ksmbd_conn *conn);
diff --git a/fs/smb/server/interface.c b/fs/smb/server/interface.c
new file mode 100644
index 000000000000..a5e59a420639
--- /dev/null
+++ b/fs/smb/server/interface.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ *   Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/freezer.h>
+
+#include "smb_common.h"
+#include "server.h"
+#include "auth.h"
+#include "connection.h"
+#include "transport_tcp.h"
+#include "interface.h"
+
+static LIST_HEAD(iface_list);
+static int bind_additional_ifaces;
+
+static struct interface *alloc_iface(char *ifname)
+{
+	struct interface *iface;
+
+	if (!ifname)
+		return NULL;
+
+	iface = kzalloc_obj(struct interface, KSMBD_DEFAULT_GFP);
+	if (!iface) {
+		kfree(ifname);
+		return NULL;
+	}
+
+	iface->name = ifname;
+	iface->state = IFACE_STATE_DOWN;
+	list_add(&iface->entry, &iface_list);
+	return iface;
+}
+
+/**
+ * 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")
+ *
+ * Return:	the started task_struct, or ERR_PTR on failure.
+ */
+struct task_struct *ksmbd_interface_run_kthread(struct interface *iface,
+						int (*kthread_fn)(void *), const char *suf)
+{
+	struct task_struct *kthread;
+
+	kthread = kthread_run(kthread_fn, (void *)iface, "ksmbd-%s-%s", iface->name, suf);
+	return kthread;
+}
+
+void ksmbd_interface_stop_kthread(struct task_struct *kthread)
+{
+	int ret;
+
+	if (!kthread)
+		return;
+
+	ret = kthread_stop(kthread);
+	if (ret)
+		pr_err("failed to stop forker thread\n");
+}
+
+static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
+			      void *ptr)
+{
+	struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
+	struct interface *iface;
+	int ret;
+
+	switch (event) {
+	case NETDEV_UP:
+		if (netif_is_bridge_port(netdev))
+			return NOTIFY_OK;
+
+		iface = ksmbd_find_netdev_name_iface_list(netdev->name);
+		if (iface && iface->state == IFACE_STATE_DOWN) {
+			ksmbd_debug(CONN, "netdev-up event: netdev(%s) is going up\n",
+					iface->name);
+
+			if (!iface->net)
+				iface->net = get_net(dev_net(netdev));
+
+			ret = ksmbd_tcp_create_socket(iface);
+			if (ret)
+				break;
+		}
+		if (!iface && bind_additional_ifaces) {
+			iface = alloc_iface(kstrdup(netdev->name, KSMBD_DEFAULT_GFP));
+			if (!iface)
+				return NOTIFY_OK;
+
+			ksmbd_debug(CONN, "netdev-up event: netdev(%s) is going up\n",
+				    iface->name);
+
+			iface->net = get_net(dev_net(netdev));
+
+			ret = ksmbd_tcp_create_socket(iface);
+			if (ret)
+				break;
+		}
+		break;
+	case NETDEV_DOWN:
+		iface = ksmbd_find_netdev_name_iface_list(netdev->name);
+		if (iface && iface->state == IFACE_STATE_CONFIGURED) {
+			ksmbd_debug(CONN, "netdev-down event: netdev(%s) is going down\n",
+					iface->name);
+
+			if (iface->ksmbd_tcp_socket)
+				kernel_sock_shutdown(iface->ksmbd_tcp_socket, SHUT_RDWR);
+
+			if (iface->ksmbd_tcp_kthread)
+				ksmbd_interface_stop_kthread(iface->ksmbd_tcp_kthread);
+
+			if (iface->ksmbd_tcp_socket)
+				sock_release(iface->ksmbd_tcp_socket);
+
+			iface->ksmbd_tcp_kthread = NULL;
+			iface->ksmbd_tcp_socket = NULL;
+
+			put_net(iface->net);
+			iface->net = NULL;
+
+			iface->state = IFACE_STATE_DOWN;
+			break;
+		}
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block ksmbd_netdev_notifier = {
+	.notifier_call = ksmbd_netdev_event,
+};
+
+int ksmbd_interface_init(void)
+{
+	register_netdevice_notifier(&ksmbd_netdev_notifier);
+
+	return 0;
+}
+
+
+void ksmbd_interface_destroy(void)
+{
+	struct interface *iface, *tmp;
+
+	unregister_netdevice_notifier(&ksmbd_netdev_notifier);
+
+	list_for_each_entry_safe(iface, tmp, &iface_list, entry) {
+		list_del(&iface->entry);
+		kfree(iface->name);
+		kfree(iface);
+	}
+}
+
+struct interface *
+ksmbd_find_netdev_name_iface_list(char *netdev_name)
+{
+	struct interface *iface;
+
+	list_for_each_entry(iface, &iface_list, entry)
+		if (!strcmp(iface->name, netdev_name))
+			return iface;
+	return NULL;
+}
+
+int ksmbd_set_interfaces(char *ifc_list, int ifc_list_sz)
+{
+	int sz = 0;
+
+	if (!ifc_list_sz) {
+		bind_additional_ifaces = 1;
+		return 0;
+	}
+
+	while (ifc_list_sz > 0) {
+		if (!alloc_iface(kstrdup(ifc_list, KSMBD_DEFAULT_GFP)))
+			return -ENOMEM;
+
+		sz = strlen(ifc_list);
+		if (!sz)
+			break;
+
+		ifc_list += sz + 1;
+		ifc_list_sz -= (sz + 1);
+	}
+
+	bind_additional_ifaces = 0;
+
+	return 0;
+}
diff --git a/fs/smb/server/interface.h b/fs/smb/server/interface.h
new file mode 100644
index 000000000000..7e35645076a9
--- /dev/null
+++ b/fs/smb/server/interface.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *   Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ *   Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ *
+ */
+
+#ifndef __KSMBD_INTERFACE_H__
+#define __KSMBD_INTERFACE_H__
+
+#include <linux/freezer.h>
+
+#define IFACE_STATE_DOWN		BIT(0)
+#define IFACE_STATE_CONFIGURED		BIT(1)
+
+struct interface {
+	struct task_struct	*ksmbd_tcp_kthread;
+	struct socket		*ksmbd_tcp_socket;
+	struct list_head	entry;
+	char			*name;
+	int			state;
+	struct net		*net;
+};
+
+int ksmbd_interface_init(void);
+void ksmbd_interface_destroy(void);
+int ksmbd_set_interfaces(char *ifc_list, int ifc_list_sz);
+struct interface *ksmbd_find_netdev_name_iface_list(char *netdev_name);
+struct task_struct *ksmbd_interface_run_kthread(struct interface *iface,
+						int (*kthread_fn)(void *),
+						const char *suf);
+void ksmbd_interface_stop_kthread(struct task_struct *kthread);
+
+#endif /* _KSMBD_INTERFACE_H */
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 2dbabe2d8005..6ccfb1cf8ed7 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -348,7 +348,7 @@ static int ipc_server_config_on_startup(struct ksmbd_startup_request *req)
 	ret |= ksmbd_set_server_string(req->server_string);
 	ret |= ksmbd_set_work_group(req->work_group);
 	server_conf.bind_interfaces_only = req->bind_interfaces_only;
-	ret |= ksmbd_tcp_set_interfaces(KSMBD_STARTUP_CONFIG_INTERFACES(req),
+	ret |= ksmbd_set_interfaces(KSMBD_STARTUP_CONFIG_INTERFACES(req),
 					req->ifc_list_sz);
 out:
 	if (ret) {
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 7e29b06820e2..37f4238f72f5 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -11,23 +11,7 @@
 #include "auth.h"
 #include "connection.h"
 #include "transport_tcp.h"
-
-#define IFACE_STATE_DOWN		BIT(0)
-#define IFACE_STATE_CONFIGURED		BIT(1)
-
-static atomic_t active_num_conn;
-
-struct interface {
-	struct task_struct	*ksmbd_kthread;
-	struct socket		*ksmbd_socket;
-	struct list_head	entry;
-	char			*name;
-	int			state;
-};
-
-static LIST_HEAD(iface_list);
-
-static int bind_additional_ifaces;
+#include "interface.h"
 
 struct tcp_transport {
 	struct ksmbd_transport		transport;
@@ -38,8 +22,6 @@ struct tcp_transport {
 
 static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops;
 
-static void tcp_stop_kthread(struct task_struct *kthread);
-static struct interface *alloc_iface(char *ifname);
 static void ksmbd_tcp_disconnect(struct ksmbd_transport *t);
 
 #define KSMBD_TRANS(t)	(&(t)->transport)
@@ -214,7 +196,7 @@ static int ksmbd_tcp_new_connection(struct socket *client_sk)
  *
  * Return:	0 on success, error number otherwise
  */
-static int ksmbd_kthread_fn(void *p)
+static int tcp_kthread_fn(void *p)
 {
 	struct socket *client_sk = NULL;
 	struct interface *iface = (struct interface *)p;
@@ -223,10 +205,9 @@ static int ksmbd_kthread_fn(void *p)
 	unsigned int max_ip_conns;
 
 	while (!kthread_should_stop()) {
-		if (!iface->ksmbd_socket) {
+		if (!iface->ksmbd_tcp_socket)
 			break;
-		}
-		ret = kernel_accept(iface->ksmbd_socket, &client_sk, 0);
+		ret = kernel_accept(iface->ksmbd_tcp_socket, &client_sk, 0);
 		if (ret == -EINVAL)
 			break;
 		if (ret)
@@ -298,32 +279,6 @@ static int ksmbd_kthread_fn(void *p)
 	return 0;
 }
 
-/**
- * ksmbd_tcp_run_kthread() - start forker thread
- * @iface: pointer to struct interface
- *
- * 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
- */
-static int ksmbd_tcp_run_kthread(struct interface *iface)
-{
-	int rc;
-	struct task_struct *kthread;
-
-	kthread = kthread_run(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s",
-			      iface->name);
-	if (IS_ERR(kthread)) {
-		rc = PTR_ERR(kthread);
-		return rc;
-	}
-	iface->ksmbd_kthread = kthread;
-
-	return 0;
-}
-
 /**
  * ksmbd_tcp_readv() - read data from socket in given iovec
  * @t:			TCP transport instance
@@ -432,7 +387,7 @@ static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
 		atomic_dec(&active_num_conn);
 }
 
-static void tcp_destroy_socket(struct socket *ksmbd_socket)
+void ksmbd_tcp_destroy_socket(struct socket *ksmbd_socket)
 {
 	int ret;
 
@@ -451,21 +406,22 @@ static void tcp_destroy_socket(struct socket *ksmbd_socket)
  *
  * Return:	0 on success, error number otherwise
  */
-static int create_socket(struct interface *iface)
+int ksmbd_tcp_create_socket(struct interface *iface)
 {
 	int ret;
 	struct sockaddr_in6 sin6;
 	struct sockaddr_in sin;
 	struct socket *ksmbd_socket;
+	struct task_struct *kthread;
 	bool ipv4 = false;
 
-	ret = sock_create_kern(current->nsproxy->net_ns, PF_INET6, SOCK_STREAM,
-			IPPROTO_TCP, &ksmbd_socket);
+	ret = sock_create_kern(iface->net, PF_INET6, SOCK_STREAM,
+			       IPPROTO_TCP, &ksmbd_socket);
 	if (ret) {
 		if (ret != -EAFNOSUPPORT)
 			pr_err("Can't create socket for ipv6, fallback to ipv4: %d\n", ret);
-		ret = sock_create_kern(current->nsproxy->net_ns, PF_INET,
-				SOCK_STREAM, IPPROTO_TCP, &ksmbd_socket);
+		ret = sock_create_kern(iface->net, PF_INET, SOCK_STREAM,
+				       IPPROTO_TCP, &ksmbd_socket);
 		if (ret) {
 			pr_err("Can't create socket for ipv4: %d\n", ret);
 			goto out_clear;
@@ -515,165 +471,28 @@ static int create_socket(struct interface *iface)
 		goto out_error;
 	}
 
-	iface->ksmbd_socket = ksmbd_socket;
-	ret = ksmbd_tcp_run_kthread(iface);
-	if (ret) {
+	iface->ksmbd_tcp_socket = ksmbd_socket;
+
+	kthread = ksmbd_interface_run_kthread(iface, tcp_kthread_fn, "tcp");
+	if (IS_ERR(kthread)) {
 		pr_err("Can't start ksmbd main kthread: %d\n", ret);
+		ret = PTR_ERR(kthread);
 		goto out_error;
 	}
+
+	iface->ksmbd_tcp_kthread = kthread;
 	iface->state = IFACE_STATE_CONFIGURED;
 
 	return 0;
 
 out_error:
-	tcp_destroy_socket(ksmbd_socket);
+	ksmbd_tcp_destroy_socket(ksmbd_socket);
 out_clear:
-	iface->ksmbd_socket = NULL;
+	iface->ksmbd_tcp_socket = NULL;
+	iface->ksmbd_tcp_kthread = NULL;
 	return ret;
 }
 
-struct interface *ksmbd_find_netdev_name_iface_list(char *netdev_name)
-{
-	struct interface *iface;
-
-	list_for_each_entry(iface, &iface_list, entry)
-		if (!strcmp(iface->name, netdev_name))
-			return iface;
-	return NULL;
-}
-
-static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
-			      void *ptr)
-{
-	struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
-	struct interface *iface;
-	int ret;
-
-	switch (event) {
-	case NETDEV_UP:
-		if (netif_is_bridge_port(netdev))
-			return NOTIFY_OK;
-
-		iface = ksmbd_find_netdev_name_iface_list(netdev->name);
-		if (iface && iface->state == IFACE_STATE_DOWN) {
-			ksmbd_debug(CONN, "netdev-up event: netdev(%s) is going up\n",
-					iface->name);
-			ret = create_socket(iface);
-			if (ret)
-				return NOTIFY_OK;
-		}
-		if (!iface && bind_additional_ifaces) {
-			iface = alloc_iface(kstrdup(netdev->name, KSMBD_DEFAULT_GFP));
-			if (!iface)
-				return NOTIFY_OK;
-			ksmbd_debug(CONN, "netdev-up event: netdev(%s) is going up\n",
-				    iface->name);
-			ret = create_socket(iface);
-			if (ret)
-				break;
-		}
-		break;
-	case NETDEV_DOWN:
-		iface = ksmbd_find_netdev_name_iface_list(netdev->name);
-		if (iface && iface->state == IFACE_STATE_CONFIGURED) {
-			ksmbd_debug(CONN, "netdev-down event: netdev(%s) is going down\n",
-					iface->name);
-			kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR);
-			tcp_stop_kthread(iface->ksmbd_kthread);
-			iface->ksmbd_kthread = NULL;
-			sock_release(iface->ksmbd_socket);
-			iface->ksmbd_socket = NULL;
-
-			iface->state = IFACE_STATE_DOWN;
-			break;
-		}
-		break;
-	}
-
-	return NOTIFY_DONE;
-}
-
-static struct notifier_block ksmbd_netdev_notifier = {
-	.notifier_call = ksmbd_netdev_event,
-};
-
-int ksmbd_tcp_init(void)
-{
-	register_netdevice_notifier(&ksmbd_netdev_notifier);
-
-	return 0;
-}
-
-static void tcp_stop_kthread(struct task_struct *kthread)
-{
-	int ret;
-
-	if (!kthread)
-		return;
-
-	ret = kthread_stop(kthread);
-	if (ret)
-		pr_err("failed to stop forker thread\n");
-}
-
-void ksmbd_tcp_destroy(void)
-{
-	struct interface *iface, *tmp;
-
-	unregister_netdevice_notifier(&ksmbd_netdev_notifier);
-
-	list_for_each_entry_safe(iface, tmp, &iface_list, entry) {
-		list_del(&iface->entry);
-		kfree(iface->name);
-		kfree(iface);
-	}
-}
-
-static struct interface *alloc_iface(char *ifname)
-{
-	struct interface *iface;
-
-	if (!ifname)
-		return NULL;
-
-	iface = kzalloc_obj(struct interface, KSMBD_DEFAULT_GFP);
-	if (!iface) {
-		kfree(ifname);
-		return NULL;
-	}
-
-	iface->name = ifname;
-	iface->state = IFACE_STATE_DOWN;
-	list_add(&iface->entry, &iface_list);
-	return iface;
-}
-
-int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz)
-{
-	int sz = 0;
-
-	if (!ifc_list_sz) {
-		bind_additional_ifaces = 1;
-		return 0;
-	}
-
-	while (ifc_list_sz > 0) {
-		if (!alloc_iface(kstrdup(ifc_list, KSMBD_DEFAULT_GFP)))
-			return -ENOMEM;
-
-		sz = strlen(ifc_list);
-		if (!sz)
-			break;
-
-		ifc_list += sz + 1;
-		ifc_list_sz -= (sz + 1);
-	}
-
-	bind_additional_ifaces = 0;
-
-	return 0;
-}
-
 static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = {
 	.read		= ksmbd_tcp_read,
 	.writev		= ksmbd_tcp_writev,
diff --git a/fs/smb/server/transport_tcp.h b/fs/smb/server/transport_tcp.h
index 1e51675ee1b2..d122e4b69d65 100644
--- a/fs/smb/server/transport_tcp.h
+++ b/fs/smb/server/transport_tcp.h
@@ -6,10 +6,13 @@
 #ifndef __KSMBD_TRANSPORT_TCP_H__
 #define __KSMBD_TRANSPORT_TCP_H__
 
-int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz);
-struct interface *ksmbd_find_netdev_name_iface_list(char *netdev_name);
+#include "interface.h"
+
 void ksmbd_free_transport(struct ksmbd_transport *kt);
 int ksmbd_tcp_init(void);
 void ksmbd_tcp_destroy(void);
+int ksmbd_tcp_create_socket(struct interface *iface);
+void ksmbd_tcp_stop_kthread(struct task_struct *kthread);
+void ksmbd_tcp_destroy_socket(struct socket *ksmbd_socket);
 
 #endif /* __KSMBD_TRANSPORT_TCP_H__ */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 06/11] smb: server: add shared transport helpers in preparation for QUIC
  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 ` 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
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 7+ messages in thread
From: Henrique Carvalho @ 2026-04-28 15:57 UTC (permalink / raw)
  To: linux-cifs
  Cc: linkinjeon, sfrench, metze, senozhatsky, tom, ematsumiya,
	Henrique Carvalho

Move the generic transport functions out of the TCP transport code into
a shared transport layer. This keeps the common socket setup and checks,
accepted-connection initialization, and receive helpers in one place
instead of open-coding them in the individual transport implementation.

Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
---
 fs/smb/server/Makefile        |   3 +-
 fs/smb/server/connection.c    |   1 +
 fs/smb/server/connection.h    |   5 -
 fs/smb/server/transport.c     | 344 ++++++++++++++++++++++++++++++++++
 fs/smb/server/transport.h     |  40 ++++
 fs/smb/server/transport_tcp.c |   1 +
 6 files changed, 388 insertions(+), 6 deletions(-)
 create mode 100644 fs/smb/server/transport.c
 create mode 100644 fs/smb/server/transport.h

diff --git a/fs/smb/server/Makefile b/fs/smb/server/Makefile
index 7bd6501bfdc9..89a9955f607d 100644
--- a/fs/smb/server/Makefile
+++ b/fs/smb/server/Makefile
@@ -8,7 +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 \
-		interface.o transport_tcp.o transport_ipc.o smbacl.o smb2pdu.o \
+		transport.o transport_tcp.o transport_ipc.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 7465b364c35c..07bbf4eb7b05 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -13,6 +13,7 @@
 #include "mgmt/ksmbd_ida.h"
 #include "connection.h"
 #include "interface.h"
+#include "transport.h"
 #include "transport_tcp.h"
 #include "transport_rdma.h"
 #include "misc.h"
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index b060baf5f688..0e87283a9ddb 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -145,11 +145,6 @@ struct ksmbd_transport_ops {
 	void (*free_transport)(struct ksmbd_transport *kt);
 };
 
-struct ksmbd_transport {
-	struct ksmbd_conn			*conn;
-	const struct ksmbd_transport_ops	*ops;
-};
-
 #define KSMBD_TCP_RECV_TIMEOUT	(7 * HZ)
 #define KSMBD_TCP_SEND_TIMEOUT	(5 * HZ)
 #define KSMBD_TCP_PEER_SOCKADDR(c)	((struct sockaddr *)&((c)->peer_addr))
diff --git a/fs/smb/server/transport.c b/fs/smb/server/transport.c
new file mode 100644
index 000000000000..0a99fb821e02
--- /dev/null
+++ b/fs/smb/server/transport.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/freezer.h>
+
+#include "server.h"
+#include "glob.h"
+#include "connection.h"
+#include "interface.h"
+#include "transport.h"
+
+/*
+ * ksmbd_transport_iovec - get iovec for reading from socket
+ * @t:		ksmbd transport struct
+ * @nr_segs:	number of iov segments needed for reading
+ *
+ * Return:	return existing or newly allocate iovec
+ */
+struct kvec *ksmbd_transport_iovec(struct ksmbd_transport *t,
+				   unsigned int nr_segs)
+{
+	struct kvec *new_iov;
+
+	if (t->io.iov && nr_segs <= t->io.nr_iov)
+		return t->io.iov;
+
+	/* not big enough -- allocate a new one and release the old */
+	new_iov = kmalloc_array(nr_segs, sizeof(*new_iov), KSMBD_DEFAULT_GFP);
+	if (new_iov) {
+		kfree(t->io.iov);
+		t->io.iov = new_iov;
+		t->io.nr_iov = nr_segs;
+	}
+	return new_iov;
+}
+
+int ksmbd_transport_alloc_conn(struct ksmbd_transport *t,
+			       struct socket *client_sk)
+{
+	struct task_struct *handler;
+	struct ksmbd_conn *conn;
+
+	conn = ksmbd_conn_alloc();
+	if (!conn)
+		return -ENOMEM;
+
+	conn->transport = t;
+	t->conn = conn;
+
+#if IS_ENABLED(CONFIG_IPV6)
+	if (client_sk->sk->sk_family == AF_INET6) {
+		memcpy(&conn->inet6_addr, &client_sk->sk->sk_v6_daddr, 16);
+		conn->inet_hash = ipv6_addr_hash(&client_sk->sk->sk_v6_daddr);
+	} else {
+		conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr;
+		conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
+	}
+#else
+	conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr;
+	conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
+#endif
+	down_write(&conn_list_lock);
+	hash_add(conn_list, &conn->hlist, conn->inet_hash);
+	up_write(&conn_list_lock);
+
+#if IS_ENABLED(CONFIG_IPV6)
+	if (client_sk->sk->sk_family == AF_INET6)
+		handler = kthread_run(ksmbd_conn_handler_loop,
+				      conn, "ksmbd:%pI6c",
+				      &conn->inet6_addr);
+	else
+		handler = kthread_run(ksmbd_conn_handler_loop,
+				      conn, "ksmbd:%pI4",
+				      &conn->inet_addr);
+#else
+	handler = kthread_run(ksmbd_conn_handler_loop,
+			      conn, "ksmbd:%pI4",
+			      &conn->inet_addr);
+#endif
+	if (IS_ERR(handler)) {
+		pr_err("cannot start conn thread\n");
+		return PTR_ERR(handler);
+	}
+
+	return 0;
+}
+
+/**
+ * kvec_array_init() - initialize a IO vector segment
+ * @new:	IO vector to be initialized
+ * @iov:	base IO vector
+ * @nr_segs:	number of segments in base iov
+ * @bytes:	total iovec length so far for read
+ *
+ * Return:	Number of IO segments
+ */
+static unsigned int kvec_array_init(struct kvec *new, struct kvec *iov,
+				    unsigned int nr_segs, size_t bytes)
+{
+	size_t base = 0;
+
+	while (bytes || !iov->iov_len) {
+		int copy = min(bytes, iov->iov_len);
+
+		bytes -= copy;
+		base += copy;
+		if (iov->iov_len == base) {
+			iov++;
+			nr_segs--;
+			base = 0;
+		}
+	}
+
+	memcpy(new, iov, sizeof(*iov) * nr_segs);
+	new->iov_base += base;
+	new->iov_len -= base;
+	return nr_segs;
+}
+
+/**
+ * ksmbd_readv() - read data from socket in given iovec
+ * @t:			ksmbd transport instance
+ * @sock:		socket to read from
+ * @iov_orig:		base IO vector
+ * @nr_segs:		number of segments in base iov
+ * @to_read:		number of bytes to read from socket
+ * @max_retries:	maximum retry count
+ *
+ * Return:	on success return number of bytes read from socket,
+ *		otherwise return error number
+ */
+int ksmbd_readv(struct ksmbd_transport *t, struct socket *sock,
+		struct kvec *iov_orig, unsigned int nr_segs,
+		unsigned int to_read, int max_retries)
+{
+	int length = 0;
+	int total_read;
+	unsigned int segs;
+	struct msghdr ksmbd_msg;
+	struct kvec *iov;
+	struct ksmbd_conn *conn = t->conn;
+
+	iov = ksmbd_transport_iovec(t, nr_segs);
+	if (!iov)
+		return -ENOMEM;
+
+	ksmbd_msg.msg_control = NULL;
+	ksmbd_msg.msg_controllen = 0;
+
+	for (total_read = 0; to_read; total_read += length, to_read -= length) {
+		try_to_freeze();
+
+		if (!ksmbd_conn_alive(conn)) {
+			total_read = -ESHUTDOWN;
+			break;
+		}
+		segs = kvec_array_init(iov, iov_orig, nr_segs, total_read);
+
+		length = kernel_recvmsg(sock, &ksmbd_msg,
+					iov, segs, to_read, 0);
+
+		if (length == -EINTR) {
+			total_read = -ESHUTDOWN;
+			break;
+		} else if (ksmbd_conn_need_reconnect(conn)) {
+			total_read = -EAGAIN;
+			break;
+		} else if (length == -ERESTARTSYS || length == -EAGAIN) {
+			/*
+			 * If max_retries is negative, Allow unlimited
+			 * retries to keep connection with inactive sessions.
+			 */
+			if (max_retries == 0) {
+				total_read = length;
+				break;
+			} else if (max_retries > 0) {
+				max_retries--;
+			}
+
+			usleep_range(1000, 2000);
+			length = 0;
+			continue;
+		} else if (length <= 0) {
+			total_read = length;
+			break;
+		}
+	}
+	return total_read;
+}
+
+struct socket *ksmbd_create_socket(struct interface *iface, int sock_type,
+				   int ipproto, int port)
+{
+	int ret;
+	struct sockaddr_in6 sin6;
+	struct sockaddr_in sin;
+	struct socket *ksmbd_socket;
+	bool ipv4 = false;
+
+	ret = sock_create_kern(iface->net, PF_INET6, sock_type,
+			       ipproto, &ksmbd_socket);
+	if (ret) {
+		if (ret != -EAFNOSUPPORT)
+			pr_err("Can't create socket for ipv6, fallback to ipv4: %d\n", ret);
+		ret = sock_create_kern(iface->net, PF_INET, sock_type,
+				       ipproto, &ksmbd_socket);
+		if (ret) {
+			pr_err("Can't create socket for ipv4: %d\n", ret);
+			return ERR_PTR(ret);
+		}
+
+		sin.sin_family = PF_INET;
+		sin.sin_addr.s_addr = htonl(INADDR_ANY);
+		sin.sin_port = htons(port);
+		ipv4 = true;
+	} else {
+		sin6.sin6_family = PF_INET6;
+		sin6.sin6_addr = in6addr_any;
+		sin6.sin6_port = htons(port);
+
+		lock_sock(ksmbd_socket->sk);
+		ksmbd_socket->sk->sk_ipv6only = false;
+		release_sock(ksmbd_socket->sk);
+	}
+
+	ret = sock_setsockopt(ksmbd_socket,
+			      SOL_SOCKET,
+			      SO_BINDTODEVICE,
+			      KERNEL_SOCKPTR(iface->name),
+			      strlen(iface->name));
+	if (ret != -ENODEV && ret < 0) {
+		pr_err("Failed to set SO_BINDTODEVICE: %d\n", ret);
+		goto out_error;
+	}
+
+	if (ipv4)
+		ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&sin,
+				  sizeof(sin));
+	else
+		ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&sin6,
+				  sizeof(sin6));
+	if (ret) {
+		pr_err("Failed to bind socket: %d\n", ret);
+		goto out_error;
+	}
+
+	ret = kernel_listen(ksmbd_socket, KSMBD_SOCKET_BACKLOG);
+	if (ret) {
+		pr_err("Port listen() error: %d\n", ret);
+		goto out_error;
+	}
+
+	return ksmbd_socket;
+
+out_error:
+	ksmbd_destroy_socket(ksmbd_socket);
+	return ERR_PTR(ret);
+}
+
+void ksmbd_destroy_socket(struct socket *ksmbd_socket)
+{
+	int ret;
+
+	if (!ksmbd_socket)
+		return;
+
+	ret = kernel_sock_shutdown(ksmbd_socket, SHUT_RDWR);
+	if (ret)
+		pr_err("Failed to shutdown socket: %d\n", ret);
+	sock_release(ksmbd_socket);
+}
+
+/**
+ * ksmbd_check_max_ip_conns() - reject when per-IP connection cap reached
+ * @client_sk:	freshly accepted client socket
+ *
+ * Return:	0 if a new connection from this peer is allowed, -EAGAIN
+ *		when server_conf.max_ip_connections has been reached.
+ */
+int ksmbd_check_max_ip_conns(struct socket *client_sk)
+{
+	struct ksmbd_conn *conn;
+	unsigned int max_ip_conns = 0;
+	int inet_hash;
+	int ret = 0;
+
+	if (!server_conf.max_ip_connections)
+		return 0;
+
+#if IS_ENABLED(CONFIG_IPV6)
+	if (client_sk->sk->sk_family == AF_INET6)
+		inet_hash = ipv6_addr_hash(&client_sk->sk->sk_v6_daddr);
+	else
+		inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
+#else
+	inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
+#endif
+
+	down_read(&conn_list_lock);
+	hash_for_each_possible(conn_list, conn, hlist, inet_hash) {
+#if IS_ENABLED(CONFIG_IPV6)
+		if (client_sk->sk->sk_family == AF_INET6) {
+			if (memcmp(&client_sk->sk->sk_v6_daddr,
+				   &conn->inet6_addr, 16) == 0)
+				max_ip_conns++;
+		} else if (inet_sk(client_sk->sk)->inet_daddr ==
+			   conn->inet_addr)
+			max_ip_conns++;
+#else
+		if (inet_sk(client_sk->sk)->inet_daddr == conn->inet_addr)
+			max_ip_conns++;
+#endif
+		if (server_conf.max_ip_connections <= max_ip_conns) {
+			pr_info_ratelimited("Maximum IP connections exceeded (%u/%u)\n",
+					    max_ip_conns,
+					    server_conf.max_ip_connections);
+			ret = -EAGAIN;
+			break;
+		}
+	}
+	up_read(&conn_list_lock);
+	return ret;
+}
+
+/**
+ * ksmbd_check_max_conns() - reserve a slot under the global connection cap
+ *
+ * On success the global counter is incremented; the caller is responsible for
+ * decrementing it on disconnect.
+ *
+ * Return:	0 if allowed, -EAGAIN if server_conf.max_connections reached.
+ */
+int ksmbd_check_max_conns(void)
+{
+	if (!server_conf.max_connections)
+		return 0;
+
+	if (atomic_inc_return(&active_num_conn) >= server_conf.max_connections) {
+		pr_info_ratelimited("Limit the maximum number of connections(%u)\n",
+				    atomic_read(&active_num_conn));
+		atomic_dec(&active_num_conn);
+		return -EAGAIN;
+	}
+	return 0;
+}
diff --git a/fs/smb/server/transport.h b/fs/smb/server/transport.h
new file mode 100644
index 000000000000..6b2fb4f9005c
--- /dev/null
+++ b/fs/smb/server/transport.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *   Copyright (C) 2026, SUSE LLC
+ *
+ *   Author: Henrique Carvalho <henrique.carvalho@suse.com>
+ */
+
+#ifndef __KSMBD_TRANSPORT_H__
+#define __KSMBD_TRANSPORT_H__
+
+#include <net/sock.h>
+
+#include "connection.h"
+#include "interface.h"
+
+struct ksmbd_transport {
+	struct ksmbd_conn			*conn;
+	const struct ksmbd_transport_ops	*ops;
+	struct {
+		struct kvec			*iov;
+		unsigned int			nr_iov;
+	} io;
+};
+
+#define KSMBD_TRANS(t)	(&(t)->transport)
+
+struct kvec *ksmbd_transport_iovec(struct ksmbd_transport *t,
+				   unsigned int nr_segs);
+int ksmbd_transport_alloc_conn(struct ksmbd_transport *t,
+			       struct socket *client_sk);
+int ksmbd_readv(struct ksmbd_transport *t, struct socket *sock,
+		struct kvec *iov_orig, unsigned int nr_segs,
+		unsigned int to_read, int max_retries);
+struct socket *ksmbd_create_socket(struct interface *iface, int sock_type,
+				   int ipproto, int port);
+void ksmbd_destroy_socket(struct socket *ksmbd_socket);
+int ksmbd_check_max_ip_conns(struct socket *client_sk);
+int ksmbd_check_max_conns(void);
+
+#endif /* __KSMBD_TRANSPORT_H__ */
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 37f4238f72f5..07123ebcf8fd 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -10,6 +10,7 @@
 #include "server.h"
 #include "auth.h"
 #include "connection.h"
+#include "transport.h"
 #include "transport_tcp.h"
 #include "interface.h"
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 07/11] smb: server: reuse common transport helpers in TCP transport
  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-28 15:57 ` Henrique Carvalho
  2026-04-28 15:57 ` [PATCH v2 08/11] smb: server: add QUIC transport support Henrique Carvalho
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Henrique Carvalho @ 2026-04-28 15:57 UTC (permalink / raw)
  To: linux-cifs
  Cc: linkinjeon, sfrench, metze, senozhatsky, tom, ematsumiya,
	Henrique Carvalho

The new transport core already provides shared helpers for listener
creation, accepted-connection setup, receive-side iovec handling, and
connection-limit checks. Keep the TCP transport focused on TCP-specific
socket options and send/disconnect behavior by switching it over to the
common helpers.

Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
---
 fs/smb/server/transport_tcp.c | 312 +++-------------------------------
 1 file changed, 19 insertions(+), 293 deletions(-)

diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 07123ebcf8fd..1496f0336af6 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -17,8 +17,6 @@
 struct tcp_transport {
 	struct ksmbd_transport		transport;
 	struct socket			*sock;
-	struct kvec			*iov;
-	unsigned int			nr_iov;
 };
 
 static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops;
@@ -39,50 +37,11 @@ static inline void ksmbd_tcp_reuseaddr(struct socket *sock)
 	sock_set_reuseaddr(sock->sk);
 }
 
-static struct tcp_transport *alloc_transport(struct socket *client_sk)
-{
-	struct tcp_transport *t;
-	struct ksmbd_conn *conn;
-
-	t = kzalloc_obj(*t, KSMBD_DEFAULT_GFP);
-	if (!t)
-		return NULL;
-	t->sock = client_sk;
-
-	conn = ksmbd_conn_alloc();
-	if (!conn) {
-		kfree(t);
-		return NULL;
-	}
-
-#if IS_ENABLED(CONFIG_IPV6)
-	if (client_sk->sk->sk_family == AF_INET6) {
-		memcpy(&conn->inet6_addr, &client_sk->sk->sk_v6_daddr, 16);
-		conn->inet_hash = ipv6_addr_hash(&client_sk->sk->sk_v6_daddr);
-	} else {
-		conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr;
-		conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
-	}
-#else
-	conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr;
-	conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
-#endif
-	down_write(&conn_list_lock);
-	hash_add(conn_list, &conn->hlist, conn->inet_hash);
-	up_write(&conn_list_lock);
-
-	conn->transport = KSMBD_TRANS(t);
-	KSMBD_TRANS(t)->conn = conn;
-	KSMBD_TRANS(t)->ops = &ksmbd_tcp_transport_ops;
-	return t;
-}
-
 static void ksmbd_tcp_free_transport(struct ksmbd_transport *kt)
 {
 	struct tcp_transport *t = TCP_TRANS(kt);
 
 	sock_release(t->sock);
-	kfree(t->iov);
 	kfree(t);
 }
 
@@ -92,62 +51,6 @@ static void free_transport(struct tcp_transport *t)
 	ksmbd_conn_free(KSMBD_TRANS(t)->conn);
 }
 
-/**
- * kvec_array_init() - initialize a IO vector segment
- * @new:	IO vector to be initialized
- * @iov:	base IO vector
- * @nr_segs:	number of segments in base iov
- * @bytes:	total iovec length so far for read
- *
- * Return:	Number of IO segments
- */
-static unsigned int kvec_array_init(struct kvec *new, struct kvec *iov,
-				    unsigned int nr_segs, size_t bytes)
-{
-	size_t base = 0;
-
-	while (bytes || !iov->iov_len) {
-		int copy = min(bytes, iov->iov_len);
-
-		bytes -= copy;
-		base += copy;
-		if (iov->iov_len == base) {
-			iov++;
-			nr_segs--;
-			base = 0;
-		}
-	}
-
-	memcpy(new, iov, sizeof(*iov) * nr_segs);
-	new->iov_base += base;
-	new->iov_len -= base;
-	return nr_segs;
-}
-
-/**
- * get_conn_iovec() - get connection iovec for reading from socket
- * @t:		TCP transport instance
- * @nr_segs:	number of segments in iov
- *
- * Return:	return existing or newly allocate iovec
- */
-static struct kvec *get_conn_iovec(struct tcp_transport *t, unsigned int nr_segs)
-{
-	struct kvec *new_iov;
-
-	if (t->iov && nr_segs <= t->nr_iov)
-		return t->iov;
-
-	/* not big enough -- allocate a new one and release the old */
-	new_iov = kmalloc_objs(*new_iov, nr_segs, KSMBD_DEFAULT_GFP);
-	if (new_iov) {
-		kfree(t->iov);
-		t->iov = new_iov;
-		t->nr_iov = nr_segs;
-	}
-	return new_iov;
-}
-
 /**
  * ksmbd_tcp_new_connection() - create a new tcp session on mount
  * @client_sk:	socket associated with new connection
@@ -161,33 +64,19 @@ static int ksmbd_tcp_new_connection(struct socket *client_sk)
 {
 	int rc = 0;
 	struct tcp_transport *t;
-	struct task_struct *handler;
 
-	t = alloc_transport(client_sk);
+	t = kzalloc_obj(*t, KSMBD_DEFAULT_GFP);
 	if (!t) {
 		sock_release(client_sk);
 		return -ENOMEM;
 	}
+	t->sock = client_sk;
+	KSMBD_TRANS(t)->ops = &ksmbd_tcp_transport_ops;
 
-#if IS_ENABLED(CONFIG_IPV6)
-	if (client_sk->sk->sk_family == AF_INET6)
-		handler = kthread_run(ksmbd_conn_handler_loop,
-				KSMBD_TRANS(t)->conn, "ksmbd:%pI6c",
-				&KSMBD_TRANS(t)->conn->inet6_addr);
-	else
-		handler = kthread_run(ksmbd_conn_handler_loop,
-				KSMBD_TRANS(t)->conn, "ksmbd:%pI4",
-				&KSMBD_TRANS(t)->conn->inet_addr);
-#else
-	handler = kthread_run(ksmbd_conn_handler_loop,
-			KSMBD_TRANS(t)->conn, "ksmbd:%pI4",
-			&KSMBD_TRANS(t)->conn->inet_addr);
-#endif
-	if (IS_ERR(handler)) {
-		pr_err("cannot start conn thread\n");
-		rc = PTR_ERR(handler);
+	rc = ksmbd_transport_alloc_conn(KSMBD_TRANS(t), client_sk);
+	if (rc)
 		ksmbd_tcp_disconnect(KSMBD_TRANS(t));
-	}
+
 	return rc;
 }
 
@@ -201,9 +90,7 @@ static int tcp_kthread_fn(void *p)
 {
 	struct socket *client_sk = NULL;
 	struct interface *iface = (struct interface *)p;
-	struct ksmbd_conn *conn;
-	int ret, inet_hash;
-	unsigned int max_ip_conns;
+	int ret;
 
 	while (!kthread_should_stop()) {
 		if (!iface->ksmbd_tcp_socket)
@@ -214,57 +101,14 @@ static int tcp_kthread_fn(void *p)
 		if (ret)
 			continue;
 
-		if (!server_conf.max_ip_connections)
-			goto skip_max_ip_conns_limit;
-
-		/*
-		 * Limits repeated connections from clients with the same IP.
-		 */
-#if IS_ENABLED(CONFIG_IPV6)
-		if (client_sk->sk->sk_family == AF_INET6)
-			inet_hash = ipv6_addr_hash(&client_sk->sk->sk_v6_daddr);
-		else
-			inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
-#else
-		inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
-#endif
-
-		max_ip_conns = 0;
-		down_read(&conn_list_lock);
-		hash_for_each_possible(conn_list, conn, hlist, inet_hash) {
-#if IS_ENABLED(CONFIG_IPV6)
-			if (client_sk->sk->sk_family == AF_INET6) {
-				if (memcmp(&client_sk->sk->sk_v6_daddr,
-					   &conn->inet6_addr, 16) == 0)
-					max_ip_conns++;
-			} else if (inet_sk(client_sk->sk)->inet_daddr ==
-				 conn->inet_addr)
-				max_ip_conns++;
-#else
-			if (inet_sk(client_sk->sk)->inet_daddr ==
-			    conn->inet_addr)
-				max_ip_conns++;
-#endif
-			if (server_conf.max_ip_connections <= max_ip_conns) {
-				pr_info_ratelimited("Maximum IP connections exceeded (%u/%u)\n",
-						    max_ip_conns, server_conf.max_ip_connections);
-				ret = -EAGAIN;
-				break;
-			}
-		}
-		up_read(&conn_list_lock);
+		ret = ksmbd_check_max_ip_conns(client_sk);
 		if (ret == -EAGAIN) {
-			/* Per-IP limit hit: release the just-accepted socket. */
 			sock_release(client_sk);
 			continue;
 		}
 
-skip_max_ip_conns_limit:
-		if (server_conf.max_connections &&
-		    atomic_inc_return(&active_num_conn) >= server_conf.max_connections) {
-			pr_info_ratelimited("Limit the maximum number of connections(%u)\n",
-					    atomic_read(&active_num_conn));
-			atomic_dec(&active_num_conn);
+		ret = ksmbd_check_max_conns();
+		if (ret == -EAGAIN) {
 			sock_release(client_sk);
 			continue;
 		}
@@ -280,76 +124,6 @@ static int tcp_kthread_fn(void *p)
 	return 0;
 }
 
-/**
- * ksmbd_tcp_readv() - read data from socket in given iovec
- * @t:			TCP transport instance
- * @iov_orig:		base IO vector
- * @nr_segs:		number of segments in base iov
- * @to_read:		number of bytes to read from socket
- * @max_retries:	maximum retry count
- *
- * Return:	on success return number of bytes read from socket,
- *		otherwise return error number
- */
-static int ksmbd_tcp_readv(struct tcp_transport *t, struct kvec *iov_orig,
-			   unsigned int nr_segs, unsigned int to_read,
-			   int max_retries)
-{
-	int length = 0;
-	int total_read;
-	unsigned int segs;
-	struct msghdr ksmbd_msg;
-	struct kvec *iov;
-	struct ksmbd_conn *conn = KSMBD_TRANS(t)->conn;
-
-	iov = get_conn_iovec(t, nr_segs);
-	if (!iov)
-		return -ENOMEM;
-
-	ksmbd_msg.msg_control = NULL;
-	ksmbd_msg.msg_controllen = 0;
-
-	for (total_read = 0; to_read; total_read += length, to_read -= length) {
-		try_to_freeze();
-
-		if (!ksmbd_conn_alive(conn)) {
-			total_read = -ESHUTDOWN;
-			break;
-		}
-		segs = kvec_array_init(iov, iov_orig, nr_segs, total_read);
-
-		length = kernel_recvmsg(t->sock, &ksmbd_msg,
-					iov, segs, to_read, 0);
-
-		if (length == -EINTR) {
-			total_read = -ESHUTDOWN;
-			break;
-		} else if (ksmbd_conn_need_reconnect(conn)) {
-			total_read = -EAGAIN;
-			break;
-		} else if (length == -ERESTARTSYS || length == -EAGAIN) {
-			/*
-			 * If max_retries is negative, Allow unlimited
-			 * retries to keep connection with inactive sessions.
-			 */
-			if (max_retries == 0) {
-				total_read = length;
-				break;
-			} else if (max_retries > 0) {
-				max_retries--;
-			}
-
-			usleep_range(1000, 2000);
-			length = 0;
-			continue;
-		} else if (length <= 0) {
-			total_read = length;
-			break;
-		}
-	}
-	return total_read;
-}
-
 /**
  * ksmbd_tcp_read() - read data from socket in given buffer
  * @t:		TCP transport instance
@@ -368,7 +142,8 @@ static int ksmbd_tcp_read(struct ksmbd_transport *t, char *buf,
 	iov.iov_base = buf;
 	iov.iov_len = to_read;
 
-	return ksmbd_tcp_readv(TCP_TRANS(t), &iov, 1, to_read, max_retries);
+	return ksmbd_readv(t, TCP_TRANS(t)->sock, &iov, 1, to_read,
+			   max_retries);
 }
 
 static int ksmbd_tcp_writev(struct ksmbd_transport *t, struct kvec *iov,
@@ -409,69 +184,20 @@ void ksmbd_tcp_destroy_socket(struct socket *ksmbd_socket)
  */
 int ksmbd_tcp_create_socket(struct interface *iface)
 {
-	int ret;
-	struct sockaddr_in6 sin6;
-	struct sockaddr_in sin;
 	struct socket *ksmbd_socket;
 	struct task_struct *kthread;
-	bool ipv4 = false;
-
-	ret = sock_create_kern(iface->net, PF_INET6, SOCK_STREAM,
-			       IPPROTO_TCP, &ksmbd_socket);
-	if (ret) {
-		if (ret != -EAFNOSUPPORT)
-			pr_err("Can't create socket for ipv6, fallback to ipv4: %d\n", ret);
-		ret = sock_create_kern(iface->net, PF_INET, SOCK_STREAM,
-				       IPPROTO_TCP, &ksmbd_socket);
-		if (ret) {
-			pr_err("Can't create socket for ipv4: %d\n", ret);
-			goto out_clear;
-		}
+	int ret;
 
-		sin.sin_family = PF_INET;
-		sin.sin_addr.s_addr = htonl(INADDR_ANY);
-		sin.sin_port = htons(server_conf.tcp_port);
-		ipv4 = true;
-	} else {
-		sin6.sin6_family = PF_INET6;
-		sin6.sin6_addr = in6addr_any;
-		sin6.sin6_port = htons(server_conf.tcp_port);
-
-		lock_sock(ksmbd_socket->sk);
-		ksmbd_socket->sk->sk_ipv6only = false;
-		release_sock(ksmbd_socket->sk);
+	ksmbd_socket = ksmbd_create_socket(iface, SOCK_STREAM, IPPROTO_TCP,
+					   server_conf.tcp_port);
+	if (IS_ERR(ksmbd_socket)) {
+		ret = PTR_ERR(ksmbd_socket);
+		goto out_clear;
 	}
 
 	ksmbd_tcp_nodelay(ksmbd_socket);
 	ksmbd_tcp_reuseaddr(ksmbd_socket);
 
-	ret = sock_setsockopt(ksmbd_socket,
-			      SOL_SOCKET,
-			      SO_BINDTODEVICE,
-			      KERNEL_SOCKPTR(iface->name),
-			      strlen(iface->name));
-	if (ret != -ENODEV && ret < 0) {
-		pr_err("Failed to set SO_BINDTODEVICE: %d\n", ret);
-		goto out_error;
-	}
-
-	if (ipv4)
-		ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&sin,
-				  sizeof(sin));
-	else
-		ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&sin6,
-				  sizeof(sin6));
-	if (ret) {
-		pr_err("Failed to bind socket: %d\n", ret);
-		goto out_error;
-	}
-
-	ret = kernel_listen(ksmbd_socket, KSMBD_SOCKET_BACKLOG);
-	if (ret) {
-		pr_err("Port listen() error: %d\n", ret);
-		goto out_error;
-	}
-
 	iface->ksmbd_tcp_socket = ksmbd_socket;
 
 	kthread = ksmbd_interface_run_kthread(iface, tcp_kthread_fn, "tcp");
@@ -487,7 +213,7 @@ int ksmbd_tcp_create_socket(struct interface *iface)
 	return 0;
 
 out_error:
-	ksmbd_tcp_destroy_socket(ksmbd_socket);
+	ksmbd_destroy_socket(ksmbd_socket);
 out_clear:
 	iface->ksmbd_tcp_socket = NULL;
 	iface->ksmbd_tcp_kthread = NULL;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 08/11] smb: server: add QUIC transport support
  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-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
  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
  4 siblings, 0 replies; 7+ messages in thread
From: Henrique Carvalho @ 2026-04-28 15:57 UTC (permalink / raw)
  To: linux-cifs
  Cc: linkinjeon, sfrench, metze, senozhatsky, tom, ematsumiya,
	Henrique Carvalho

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


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 09/11] smb: server: refactor TCP transport definitions
  2026-04-28 15:57 [PATCH v2 05/11] smb: server: split interface management from TCP in preparation for QUIC Henrique Carvalho
                   ` (2 preceding siblings ...)
  2026-04-28 15:57 ` [PATCH v2 08/11] smb: server: add QUIC transport support Henrique Carvalho
@ 2026-04-28 15:57 ` Henrique Carvalho
  2026-04-28 15:57 ` [PATCH v2 10/11] smb: server: track TCP and QUIC listener state independently Henrique Carvalho
  4 siblings, 0 replies; 7+ messages in thread
From: Henrique Carvalho @ 2026-04-28 15:57 UTC (permalink / raw)
  To: linux-cifs
  Cc: linkinjeon, sfrench, metze, senozhatsky, tom, ematsumiya,
	Henrique Carvalho

Move the TCP timeout definitions into transport_tcp.h and rename the
peer address accessor in connection.h to a transport-neutral name.

Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
---
 fs/smb/server/connection.h        | 4 +---
 fs/smb/server/mgmt/tree_connect.c | 2 +-
 fs/smb/server/transport_tcp.h     | 3 +++
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 0e87283a9ddb..f874bbdd4b34 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -145,9 +145,7 @@ struct ksmbd_transport_ops {
 	void (*free_transport)(struct ksmbd_transport *kt);
 };
 
-#define KSMBD_TCP_RECV_TIMEOUT	(7 * HZ)
-#define KSMBD_TCP_SEND_TIMEOUT	(5 * HZ)
-#define KSMBD_TCP_PEER_SOCKADDR(c)	((struct sockaddr *)&((c)->peer_addr))
+#define KSMBD_PEER_SOCKADDR(c)	((struct sockaddr *)&((c)->peer_addr))
 
 #define CONN_HASH_BITS	12
 extern DECLARE_HASHTABLE(conn_list, CONN_HASH_BITS);
diff --git a/fs/smb/server/mgmt/tree_connect.c b/fs/smb/server/mgmt/tree_connect.c
index a72d7e42a6c2..7704d8554591 100644
--- a/fs/smb/server/mgmt/tree_connect.c
+++ b/fs/smb/server/mgmt/tree_connect.c
@@ -44,7 +44,7 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
 		goto out_error;
 	}
 
-	peer_addr = KSMBD_TCP_PEER_SOCKADDR(conn);
+	peer_addr = KSMBD_PEER_SOCKADDR(conn);
 	resp = ksmbd_ipc_tree_connect_request(sess,
 					      sc,
 					      tree_conn,
diff --git a/fs/smb/server/transport_tcp.h b/fs/smb/server/transport_tcp.h
index d122e4b69d65..ef5ba9affc3f 100644
--- a/fs/smb/server/transport_tcp.h
+++ b/fs/smb/server/transport_tcp.h
@@ -8,6 +8,9 @@
 
 #include "interface.h"
 
+#define KSMBD_TCP_RECV_TIMEOUT	(7 * HZ)
+#define KSMBD_TCP_SEND_TIMEOUT	(5 * HZ)
+
 void ksmbd_free_transport(struct ksmbd_transport *kt);
 int ksmbd_tcp_init(void);
 void ksmbd_tcp_destroy(void);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 10/11] smb: server: track TCP and QUIC listener state independently
  2026-04-28 15:57 [PATCH v2 05/11] smb: server: split interface management from TCP in preparation for QUIC Henrique Carvalho
                   ` (3 preceding siblings ...)
  2026-04-28 15:57 ` [PATCH v2 09/11] smb: server: refactor TCP transport definitions Henrique Carvalho
@ 2026-04-28 15:57 ` Henrique Carvalho
  4 siblings, 0 replies; 7+ messages in thread
From: Henrique Carvalho @ 2026-04-28 15:57 UTC (permalink / raw)
  To: linux-cifs
  Cc: linkinjeon, sfrench, metze, senozhatsky, tom, ematsumiya,
	Henrique Carvalho

An interface can host both TCP and QUIC listeners, so a single
configured/down state is no longer precise enough once QUIC support is
added (e.g. a TCP listener may already be up when QUIC setup fails).

Turn iface->state into a transport bitmask and track TCP and QUIC
listener bring-up independently. This lets NETDEV_UP retry only the
missing transport and lets NETDEV_DOWN tear down only the listeners that
were actually started.

Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
---
 fs/smb/server/interface.c      | 61 ++++++++++++++++++++++------------
 fs/smb/server/interface.h      |  6 ++--
 fs/smb/server/transport_quic.c |  2 +-
 fs/smb/server/transport_tcp.c  |  2 +-
 4 files changed, 45 insertions(+), 26 deletions(-)

diff --git a/fs/smb/server/interface.c b/fs/smb/server/interface.c
index 25b8e7f44059..272c4aaad347 100644
--- a/fs/smb/server/interface.c
+++ b/fs/smb/server/interface.c
@@ -17,6 +17,11 @@
 static LIST_HEAD(iface_list);
 static int bind_additional_ifaces;
 
+static inline bool ksmbd_iface_transport_up(const struct interface *iface)
+{
+	return iface->state != 0;
+}
+
 static struct interface *alloc_iface(char *ifname)
 {
 	struct interface *iface;
@@ -31,7 +36,7 @@ static struct interface *alloc_iface(char *ifname)
 	}
 
 	iface->name = ifname;
-	iface->state = IFACE_STATE_DOWN;
+	iface->state = 0;
 	list_add(&iface->entry, &iface_list);
 	return iface;
 }
@@ -80,20 +85,30 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
 			return NOTIFY_OK;
 
 		iface = ksmbd_find_netdev_name_iface_list(netdev->name);
-		if (iface && iface->state == IFACE_STATE_DOWN) {
+		if (iface) {
 			ksmbd_debug(CONN, "netdev-up event: netdev(%s) is going up\n",
 					iface->name);
 
 			if (!iface->net)
 				iface->net = get_net(dev_net(netdev));
 
-			ret = ksmbd_tcp_create_socket(iface);
-			if (ret)
-				break;
-
-			ret = ksmbd_quic_create_socket(iface);
-			if (ret)
-				break;
+			if (!(iface->state & IFACE_STATE_TCP_UP)) {
+				ret = ksmbd_tcp_create_socket(iface);
+				if (ret && !ksmbd_iface_transport_up(iface)) {
+					put_net(iface->net);
+					iface->net = NULL;
+					break;
+				}
+			}
+
+			if (!(iface->state & IFACE_STATE_QUIC_UP)) {
+				ret = ksmbd_quic_create_socket(iface);
+				if (ret && !ksmbd_iface_transport_up(iface)) {
+					put_net(iface->net);
+					iface->net = NULL;
+					break;
+				}
+			}
 		}
 		if (!iface && bind_additional_ifaces) {
 			iface = alloc_iface(kstrdup(netdev->name, KSMBD_DEFAULT_GFP));
@@ -106,44 +121,49 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
 			iface->net = get_net(dev_net(netdev));
 
 			ret = ksmbd_tcp_create_socket(iface);
-			if (ret)
+			if (ret && !ksmbd_iface_transport_up(iface)) {
+				put_net(iface->net);
+				iface->net = NULL;
 				break;
+			}
 
 			ret = ksmbd_quic_create_socket(iface);
-			if (ret)
+			if (ret && !ksmbd_iface_transport_up(iface)) {
+				put_net(iface->net);
+				iface->net = NULL;
 				break;
+			}
 		}
 		break;
 	case NETDEV_DOWN:
 		iface = ksmbd_find_netdev_name_iface_list(netdev->name);
-		if (iface && iface->state == IFACE_STATE_CONFIGURED) {
+		if (iface && ksmbd_iface_transport_up(iface)) {
 			ksmbd_debug(CONN, "netdev-down event: netdev(%s) is going down\n",
 					iface->name);
 
-			if (iface->ksmbd_tcp_socket)
+			if (iface->state & IFACE_STATE_TCP_UP)
 				kernel_sock_shutdown(iface->ksmbd_tcp_socket, SHUT_RDWR);
-			if (iface->ksmbd_quic_socket)
+			if (iface->state & IFACE_STATE_QUIC_UP)
 				kernel_sock_shutdown(iface->ksmbd_quic_socket, SHUT_RDWR);
 
-			if (iface->ksmbd_tcp_kthread)
+			if (iface->state & IFACE_STATE_TCP_UP)
 				ksmbd_interface_stop_kthread(iface->ksmbd_tcp_kthread);
-			if (iface->ksmbd_quic_kthread)
+			if (iface->state & IFACE_STATE_QUIC_UP)
 				ksmbd_interface_stop_kthread(iface->ksmbd_quic_kthread);
 
-			if (iface->ksmbd_tcp_socket)
+			if (iface->state & IFACE_STATE_TCP_UP)
 				sock_release(iface->ksmbd_tcp_socket);
-			if (iface->ksmbd_quic_socket)
+			if (iface->state & IFACE_STATE_QUIC_UP)
 				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;
+			iface->state = 0;
 
 			put_net(iface->net);
 			iface->net = NULL;
-
-			iface->state = IFACE_STATE_DOWN;
 			break;
 		}
 		break;
@@ -213,4 +233,3 @@ 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 94ffb73fd635..d99b1f3c8bc0 100644
--- a/fs/smb/server/interface.h
+++ b/fs/smb/server/interface.h
@@ -10,8 +10,8 @@
 
 #include <linux/freezer.h>
 
-#define IFACE_STATE_DOWN		BIT(0)
-#define IFACE_STATE_CONFIGURED		BIT(1)
+#define IFACE_STATE_TCP_UP		BIT(0)
+#define IFACE_STATE_QUIC_UP		BIT(1)
 
 struct interface {
 	struct task_struct	*ksmbd_tcp_kthread;
@@ -20,7 +20,7 @@ struct interface {
 	struct socket		*ksmbd_quic_socket;
 	struct list_head	entry;
 	char			*name;
-	int			state;
+	unsigned int		state;
 	struct net		*net;
 };
 
diff --git a/fs/smb/server/transport_quic.c b/fs/smb/server/transport_quic.c
index 84067225c1d3..da089b655e6c 100644
--- a/fs/smb/server/transport_quic.c
+++ b/fs/smb/server/transport_quic.c
@@ -314,7 +314,7 @@ int ksmbd_quic_create_socket(struct interface *iface)
 
 	iface->ksmbd_quic_socket  = ksmbd_socket;
 	iface->ksmbd_quic_kthread = kthread;
-	iface->state = IFACE_STATE_CONFIGURED;
+	iface->state |= IFACE_STATE_QUIC_UP;
 #endif /* CONFIG_IP_QUIC */
 	return 0;
 }
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 1496f0336af6..81bf6996a6f7 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -208,7 +208,7 @@ int ksmbd_tcp_create_socket(struct interface *iface)
 	}
 
 	iface->ksmbd_tcp_kthread = kthread;
-	iface->state = IFACE_STATE_CONFIGURED;
+	iface->state |= IFACE_STATE_TCP_UP;
 
 	return 0;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v2 06/11] smb: server: add shared transport helpers in preparation for QUIC
  2026-04-28 15:57 ` [PATCH v2 06/11] smb: server: add shared transport helpers " Henrique Carvalho
@ 2026-04-29 22:24   ` Namjae Jeon
  0 siblings, 0 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-04-29 22:24 UTC (permalink / raw)
  To: Henrique Carvalho
  Cc: linux-cifs, sfrench, metze, senozhatsky, tom, ematsumiya

> diff --git a/fs/smb/server/transport.c b/fs/smb/server/transport.c
> new file mode 100644
> index 000000000000..0a99fb821e02
> --- /dev/null
> +++ b/fs/smb/server/transport.c
> @@ -0,0 +1,344 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
You have to also move the copyright here.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-04-29 22:24 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH v2 08/11] smb: server: add QUIC transport support Henrique Carvalho
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox