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 06/11] smb: server: add shared transport helpers in preparation for QUIC
Date: Tue, 28 Apr 2026 12:57:55 -0300	[thread overview]
Message-ID: <20260428155759.226368-2-henrique.carvalho@suse.com> (raw)
In-Reply-To: <20260428155759.226368-1-henrique.carvalho@suse.com>

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


  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 ` Henrique Carvalho [this message]
2026-04-29 22:24   ` [PATCH v2 06/11] smb: server: add shared transport helpers " 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

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-2-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