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
next prev 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 ` 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