]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s3:smbd: add support for SMB_TRANSPORT_TYPE_QUIC
authorStefan Metzmacher <metze@samba.org>
Thu, 3 Apr 2025 15:32:58 +0000 (17:32 +0200)
committerStefan Metzmacher <metze@samba.org>
Thu, 17 Jul 2025 08:59:37 +0000 (08:59 +0000)
This requires https://github.com/lxin/quic, which provides a kernel
module quic.ko for Linux (tested with Linux 6.8 and 6.14).

The userspace libquic is mirrored under third_party/quic for now.

This can be activated by adding 'quic' to 'server smb transports'.

The following smb.conf options are also relevant:
'tls enabled'
'tls cafile'
'tls certfile'
'tls keyfile'

If the files pointed to by 'tls cafile', 'tls certfile' and
'tls keyfile' all don't exist, self-signed tls certificates are
generated automatically at startup.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
docs-xml/smbdotconf/protocol/serversmbtransports.xml
source3/smbd/server.c
source3/smbd/smb2_process.c
source3/wscript_build

index 83a4c62ddc0b692c31328923b5507f4b7d250410..f506e4e862a0afa75184bdc01c6e4ce28af9ad9f 100644 (file)
        after ':', e.g. 'nbt:1139'.
        </para>
 
+       <para>The transport 'quic' uses the quic protocol on top of udp.
+       The default port for 'quic' is 443. Other ports can be specified by adding it
+       after ':', e.g. 'quic:1443'.
+       The following options are also relevant:
+       <smbconfoption name="tls enabled"/>,
+       <smbconfoption name="tls cafile"/>,
+       <smbconfoption name="tls certfile"/> and
+       <smbconfoption name="tls keyfile"/>.
+       If the files pointed to by
+       <smbconfoption name="tls cafile"/>,
+       <smbconfoption name="tls certfile"/> and
+       <smbconfoption name="tls keyfile"/> all do not exist,
+       a self-signed tls certificate is generated automatically at startup.
+       </para>
+
+       <para>
+       Note: 'quic' requires the quic.ko kernel module for Linux from
+       https://github.com/lxin/quic (tested with Linux 6.14). Future
+       Linux versions may support it natively.
+       </para>
+
        <para>Numerical ports are handled as 'tcp' except port '139' is handled as 'nbt'.
        </para>
 
@@ -29,6 +50,8 @@
 <value type="example">445</value>
 <value type="example">tcp, tcp:1445</value>
 <value type="example">8000, nbt:1139</value>
+<value type="example">tcp, quic, nbt</value>
+<value type="example">+quic</value>
 
 <value type="default">tcp, nbt</value>
 </samba:parameter>
index 97d0fbbbc60f74be880e402ea5a10e20bb01ad8e..f7f55420c006377a58fb7985fd9f3ee0dad9752f 100644 (file)
 #include "lib/global_contexts.h"
 #include "source3/lib/substitute.h"
 #include "lib/addrchange.h"
+#include "../source4/lib/tls/tls.h"
+
+#ifdef HAVE_LIBQUIC
+#include <netinet/quic.h>
+#endif
 
 #ifdef CLUSTER_SUPPORT
 #include "ctdb_protocol.h"
@@ -87,6 +92,8 @@ struct smbd_parent_context {
        struct server_id notifyd;
 
        struct tevent_timer *cleanup_te;
+
+       struct tstream_tls_params *quic_tlsp;
 };
 
 struct smbd_open_socket {
@@ -244,6 +251,76 @@ static void smb_parent_send_to_children(struct messaging_context *ctx,
        messaging_send_to_children(ctx, msg_type, msg_data);
 }
 
+static NTSTATUS smb_parent_load_tls_certificates(struct smbd_parent_context *parent,
+                                                struct loadparm_context *lp_ctx)
+{
+       struct tstream_tls_params *quic_tlsp = NULL;
+       const char *dns_hostname = NULL;
+       NTSTATUS status;
+
+       if (parent == NULL) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       dns_hostname = lpcfg_dns_hostname(lp_ctx);
+       if (dns_hostname == NULL) {
+               DBG_ERR("ERROR: lpcfg_dns_hostname() failed\n");
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       status = tstream_tls_params_server_lpcfg(parent,
+                                                dns_hostname,
+                                                lp_ctx,
+                                                &quic_tlsp);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("tstream_tls_params_server_lpcfg(): %s\n",
+                       nt_errstr(status));
+               return status;
+       }
+
+       status = tstream_tls_params_quic_prepare(quic_tlsp);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("tstream_tls_params_quic_prepare(): %s\n",
+                       nt_errstr(status));
+               return status;
+       }
+
+       TALLOC_FREE(parent->quic_tlsp);
+       parent->quic_tlsp = quic_tlsp;
+       return NT_STATUS_OK;
+}
+
+static void smb_parent_reload_tls_certificates(struct messaging_context *ctx,
+                                              void *private_data,
+                                              uint32_t msg_type,
+                                              struct server_id srv_id,
+                                              DATA_BLOB* msg_data)
+{
+       struct smbd_parent_context *parent = am_parent;
+       struct loadparm_context *lp_ctx = NULL;
+       NTSTATUS status;
+
+       if (parent == NULL) {
+               return;
+       }
+
+       lp_ctx = loadparm_init_s3(talloc_tos(), loadparm_s3_helpers());
+       if (lp_ctx == NULL) {
+               DBG_ERR("loadparm_init_s3() failed\n");
+               return;
+       }
+
+       status = smb_parent_load_tls_certificates(parent, lp_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("smb_parent_load_tls_certificates(): %s\n",
+                       nt_errstr(status));
+               return;
+       }
+
+       DBG_DEBUG("smb_parent_load_tls_certificates(): %s\n",
+                 nt_errstr(status));
+}
+
 /*
  * Parent smbd process sets its own debug level first and then
  * sends a message to all the smbd children to adjust their debug
@@ -988,6 +1065,26 @@ static void smbd_accept_connection(struct tevent_context *ev,
                        exit_server("reinit_after_fork() failed");
                        return;
                }
+               if (transport_type == SMB_TRANSPORT_TYPE_QUIC) {
+                       struct tstream_tls_params *quic_tlsp =
+                               s->parent->quic_tlsp;
+
+                       /*
+                        * In interactive mode it's ok to do a
+                        * sync handshake, there's no point in
+                        * doing it async.
+                        */
+                       status = tstream_tls_quic_handshake(quic_tlsp,
+                                                           true, /* is_server */
+                                                           5000, /* 5 secs */
+                                                           "smb",
+                                                           fd);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               DBG_WARNING("tstream_tls_quic_handshake(%d): %s\n",
+                                           fd, nt_errstr(status));
+                               exit_server_cleanly("tstream_tls_quic_handshake");
+                       }
+               }
                smbd_process(ev, msg_ctx, fd, true, transport_type);
                exit_server_cleanly("end of interactive mode");
                return;
@@ -1001,9 +1098,15 @@ static void smbd_accept_connection(struct tevent_context *ev,
        pid = fork();
        if (pid == 0) {
                enum smb_transport_type transport_type = s->transport.type;
+               struct tstream_tls_params *quic_tlsp = NULL;
                char addrstr[INET6_ADDRSTRLEN];
                NTSTATUS status = NT_STATUS_OK;
 
+               if (transport_type == SMB_TRANSPORT_TYPE_QUIC) {
+                       quic_tlsp = talloc_move(talloc_tos(),
+                                               &s->parent->quic_tlsp);
+               }
+
                /*
                 * Can't use TALLOC_FREE here. Nulling out the argument to it
                 * would overwrite memory we've just freed.
@@ -1042,6 +1145,25 @@ static void smbd_accept_connection(struct tevent_context *ev,
                print_sockaddr(addrstr, sizeof(addrstr), &caddr.u.ss);
                process_set_title("smbd[%s]", "client [%s]", addrstr);
 
+               if (transport_type == SMB_TRANSPORT_TYPE_QUIC) {
+                       /*
+                        * We just forked and this process only
+                        * handles a single connection, so it's ok
+                        * to do a sync handshake, there's no point in
+                        * doing it async.
+                        */
+                       status = tstream_tls_quic_handshake(quic_tlsp,
+                                                           true, /* is_server */
+                                                           5000, /* 5 secs */
+                                                           "smb",
+                                                           fd);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               DBG_WARNING("tstream_tls_quic_handshake(%d): %s\n",
+                                           fd, nt_errstr(status));
+                               exit_server_cleanly("tstream_tls_quic_handshake");
+                       }
+               }
+               TALLOC_FREE(quic_tlsp);
                smbd_process(ev, msg_ctx, fd, false, transport_type);
         exit:
                exit_server_cleanly("end of child");
@@ -1103,9 +1225,11 @@ static bool smbd_open_one_socket(struct smbd_parent_context *parent,
                rebind = true;
                break;
        case SMB_TRANSPORT_TYPE_QUIC:
-               /*
-                * Not supported yet
-                */
+#ifdef HAVE_LIBQUIC
+               port = transport->port;
+               protocol = IPPROTO_QUIC;
+               rebind = false;
+#endif
                break;
        case SMB_TRANSPORT_TYPE_UNKNOWN:
                /*
@@ -1122,7 +1246,7 @@ static bool smbd_open_one_socket(struct smbd_parent_context *parent,
                return false;
        }
 
-       s = talloc(parent, struct smbd_open_socket);
+       s = talloc_zero(parent, struct smbd_open_socket);
        if (!s) {
                return false;
        }
@@ -1142,8 +1266,14 @@ static bool smbd_open_one_socket(struct smbd_parent_context *parent,
        }
 
        /* ready to listen */
-       set_socket_options(s->fd, "SO_KEEPALIVE");
-       set_socket_options(s->fd, lp_socket_options());
+       if (transport->type == SMB_TRANSPORT_TYPE_QUIC) {
+#ifdef HAVE_LIBQUIC
+               setsockopt(s->fd, SOL_QUIC, QUIC_SOCKOPT_ALPN, "smb", strlen("smb"));
+#endif /* HAVE_LIBQUIC */
+       } else {
+               set_socket_options(s->fd, "SO_KEEPALIVE");
+               set_socket_options(s->fd, lp_socket_options());
+       }
 
        /* Set server socket to
         * non-blocking for the accept. */
@@ -1328,6 +1458,13 @@ static bool open_sockets_smbd(struct smbd_parent_context *parent,
        messaging_register(msg_ctx, NULL, MSG_SMB_NOTIFY_STARTED,
                           smb_parent_send_to_children);
 
+       if (parent->quic_tlsp != NULL) {
+               messaging_register(msg_ctx,
+                                  NULL,
+                                  MSG_RELOAD_TLS_CERTIFICATES,
+                                  smb_parent_reload_tls_certificates);
+       }
+
        if (lp_interfaces() && lp_bind_interfaces_only()) {
                messaging_register(msg_ctx,
                                   NULL,
@@ -1858,6 +1995,8 @@ extern void build_options(bool screen);
                .exit_server = smbd_exit_server,
                .exit_server_cleanly = smbd_exit_server_cleanly,
        };
+       uint8_t ti;
+       bool quic_requested = false;
        bool ok;
 
        setproctitle_init(argc, discard_const(argv), environ);
@@ -2165,6 +2304,57 @@ extern void build_options(bool screen);
                }
        }
 
+       for (ti = 0; ti < parent->transports.num_transports; ti++) {
+               const struct smb_transport *t =
+                       &parent->transports.transports[ti];
+
+               if (t->type == SMB_TRANSPORT_TYPE_QUIC) {
+                       quic_requested = true;
+                       break;
+               }
+       }
+
+       if (quic_requested) {
+               status = smb_parent_load_tls_certificates(parent, lp_ctx);
+               if (NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
+                       ok = false;
+                       goto quic_disabled;
+               }
+               if (!NT_STATUS_IS_OK(status)) {
+                       exit_server("ERROR: smb_parent_load_tls_certificates");
+               }
+
+               ok = tstream_tls_params_quic_enabled(parent->quic_tlsp);
+quic_disabled:
+               if (!ok) {
+                       struct smb_transports tt = parent->transports;
+                       struct smb_transports *ts = &parent->transports;
+
+                       DBG_ERR("WARNING: ignore listening on transport 'quic'\n");
+
+                       /*
+                        * Filter out SMB_TRANSPORT_TYPE_QUIC
+                        */
+
+                       ts->num_transports = 0;
+                       for (ti = 0; ti < tt.num_transports; ti++) {
+                               const struct smb_transport *t =
+                                       &tt.transports[ti];
+
+                               if (t->type == SMB_TRANSPORT_TYPE_QUIC) {
+                                       continue;
+                               }
+
+                               ts->transports[ts->num_transports] = *t;
+                               ts->num_transports += 1;
+                       }
+               }
+       }
+
+       if (parent->transports.num_transports == 0) {
+               exit_server("No transports configured for listening");
+       }
+
        se = tevent_add_signal(parent->ev_ctx,
                               parent,
                               SIGTERM, 0,
index 590c7f360d25f1748390e85d0199eb3485c9a398..153f9a430b6ceb1235460f442c75ae5a725e2188 100644 (file)
@@ -2100,6 +2100,8 @@ void smbd_process(struct tevent_context *ev_ctx,
 
        messaging_deregister(sconn->msg_ctx, MSG_SMB_TELL_NUM_CHILDREN, NULL);
 
+       messaging_deregister(sconn->msg_ctx, MSG_RELOAD_TLS_CERTIFICATES, NULL);
+
        /*
         * Use the default MSG_DEBUG handler to avoid rebroadcasting
         * MSGs to all child processes
index 2870f1a704bc04260c7c7be0be46e7c0ee7a509c..472668a595b1dccea042493024fd2683b4e9f252 100644 (file)
@@ -764,6 +764,7 @@ bld.SAMBA3_LIBRARY('smbd_base',
                         fd_handle
                         cli_spoolss
                         samba3-namearray
+                        LIBTLS
                    ''' +
                    bld.env['dmapi_lib'] +
                    bld.env['legacy_quota_libs'] +