]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
third_party: import quic from https://github.com/lxin/quic.git
authorStefan Metzmacher <metze@samba.org>
Tue, 15 Apr 2025 09:00:17 +0000 (11:00 +0200)
committerStefan Metzmacher <metze@samba.org>
Thu, 17 Jul 2025 08:59:37 +0000 (08:59 +0000)
For now the VERSION argument to third_party/quic/update.sh
is ignored as there are no versions yet. For now we require
version 1.1 (not releases) for a system library, in order to make sure
it is recent enough.

This import is based on commit 846dddb24f007c8356ce3c19c74445160a8d94f7.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
13 files changed:
buildtools/wafsamba/samba_third_party.py
third_party/quic/COPYING [new file with mode: 0644]
third_party/quic/libquic/Makefile.am [new file with mode: 0644]
third_party/quic/libquic/client.c [new file with mode: 0644]
third_party/quic/libquic/handshake.c [new file with mode: 0644]
third_party/quic/libquic/libquic.pc.in [new file with mode: 0644]
third_party/quic/libquic/netinet/quic.h [new file with mode: 0644]
third_party/quic/libquic/quic.man [new file with mode: 0644]
third_party/quic/libquic/server.c [new file with mode: 0644]
third_party/quic/modules/include/uapi/linux/quic.h [new file with mode: 0644]
third_party/quic/update.sh [new file with mode: 0755]
third_party/quic/wscript [new file with mode: 0644]
third_party/wscript

index a3c45775a4b9c8dde1cd719fa2a527ad2cc31b4e..ed1dd2f06c06c9b17335f8e4e1ecbcf38a862aa9 100644 (file)
@@ -46,3 +46,8 @@ Build.BuildContext.CHECK_UID_WRAPPER = CHECK_UID_WRAPPER
 def CHECK_PAM_WRAPPER(conf):
     return conf.CHECK_BUNDLED_SYSTEM_PKG('pam_wrapper', minversion='1.1.8')
 Build.BuildContext.CHECK_PAM_WRAPPER = CHECK_PAM_WRAPPER
+
+@conf
+def CHECK_LIBQUIC(conf):
+    return conf.CHECK_BUNDLED_SYSTEM_PKG('libquic', minversion='1.1')
+Build.BuildContext.CHECK_LIBQUIC = CHECK_LIBQUIC
diff --git a/third_party/quic/COPYING b/third_party/quic/COPYING
new file mode 100644 (file)
index 0000000..ace100b
--- /dev/null
@@ -0,0 +1,37 @@
+This project contains components licensed under different open-source
+licenses.
+
+- modules/net/quic/, modules/include/linux/
+
+  These directories are licensed under the GNU General Public License,
+  version 2 or later (GPL-2.0+).
+
+  License text:
+
+    https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+
+- modules/include/uapi/linux/
+
+  This directory is licensed under the GNU General Public License,
+  version 2 or later, with the Linux-syscall-note exception (GPL-2.0+
+  WITH Linux-syscall-note).
+
+  The Linux-syscall-note clarifies that user-space programs including
+  these headers are not considered derivative works.
+
+  GPL-2.0 license text:
+
+    https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+
+  Linux-syscall-note documentation:
+
+    https://www.kernel.org/doc/html/latest/process/license-rules.html#kernel-headers
+
+- libquic/
+
+  This directory is licensed under the GNU Lesser General Public License,
+  version 2.1 or later (LGPL-2.1+).
+
+  License text:
+
+    https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
diff --git a/third_party/quic/libquic/Makefile.am b/third_party/quic/libquic/Makefile.am
new file mode 100644 (file)
index 0000000..c1799f4
--- /dev/null
@@ -0,0 +1,24 @@
+man7_MANS              = quic.man
+EXTRA_DIST             = $(man7_MANS)
+
+lib_LTLIBRARIES                = libquic.la
+libquic_la_SOURCES     = client.c handshake.c server.c
+libquic_la_CPPFLAGS    = -I$(top_builddir)/libquic/
+libquic_la_CFLAGS      = -Werror -Wall $(LIBGNUTLS_CFLAGS)
+libquic_la_LIBADD      = $(LIBGNUTLS_LIBS)
+libquic_la_LDFLAGS     = -version-info 1:0:0
+
+libcnetinetdir         = $(includedir)/netinet
+libcnetinet_HEADERS    = netinet/quic.h
+
+if !BUILTIN_MODULES
+libquic_la_CPPFLAGS    += -I$(top_builddir)/modules/include/uapi/
+
+libclinuxdir           = $(includedir)/linux
+libclinux_HEADERS      = $(top_builddir)/modules/include/uapi/linux/quic.h
+endif
+
+pkgconfigdir           = $(libdir)/pkgconfig
+pkgconfig_DATA         = libquic.pc
+
+MAINTAINERCLEANFILES   = Makefile.in
diff --git a/third_party/quic/libquic/client.c b/third_party/quic/libquic/client.c
new file mode 100644 (file)
index 0000000..940301a
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Perform a QUIC client-side handshake.
+ *
+ * Copyright (c) 2024 Red Hat, Inc.
+ *
+ * libquic is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ */
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "netinet/quic.h"
+
+static int quic_client_psk_handshake(int sockfd, const char *identity,
+                                    const gnutls_datum_t *key, const char *alpns)
+{
+       gnutls_psk_client_credentials_t cred;
+       gnutls_session_t session;
+       size_t alpn_len;
+       char alpn[64];
+       int ret;
+
+       ret = gnutls_psk_allocate_client_credentials(&cred);
+       if (ret)
+               goto err;
+       ret = gnutls_psk_set_client_credentials(cred, identity, key, GNUTLS_PSK_KEY_RAW);
+       if (ret)
+               goto err_cred;
+
+       ret = gnutls_init(&session, GNUTLS_CLIENT);
+       if (ret)
+               goto err_cred;
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_PSK, cred);
+       if (ret)
+               goto err_session;
+
+       ret = gnutls_priority_set_direct(session, QUIC_PRIORITY, NULL);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               ret = quic_session_set_alpn(session, alpns, strlen(alpns));
+               if (ret)
+                       goto err_session;
+       }
+
+       gnutls_transport_set_int(session, sockfd);
+
+       ret = quic_handshake(session);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               alpn_len = sizeof(alpn);
+               ret = quic_session_get_alpn(session, alpn, &alpn_len);
+       }
+
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_psk_free_client_credentials(cred);
+err:
+       return ret;
+}
+
+static int quic_client_x509_handshake(int sockfd, const char *alpns, const char *host)
+{
+       gnutls_certificate_credentials_t cred;
+       gnutls_session_t session;
+       size_t alpn_len;
+       char alpn[64];
+       int ret;
+
+       ret = gnutls_certificate_allocate_credentials(&cred);
+       if (ret)
+               goto err;
+       ret = gnutls_certificate_set_x509_system_trust(cred);
+       if (ret < 0)
+               goto err_cred;
+
+       ret = gnutls_init(&session, GNUTLS_CLIENT);
+       if (ret)
+               goto err_cred;
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
+       if (ret)
+               goto err_session;
+
+       ret = gnutls_priority_set_direct(session, QUIC_PRIORITY, NULL);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               ret = quic_session_set_alpn(session, alpns, strlen(alpns));
+               if (ret)
+                       goto err_session;
+       }
+
+       if (host) {
+               ret = gnutls_server_name_set(session, GNUTLS_NAME_DNS, host, strlen(host));
+               if (ret)
+                       goto err_session;
+       }
+
+       gnutls_transport_set_int(session, sockfd);
+
+       ret = quic_handshake(session);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               alpn_len = sizeof(alpn);
+               ret = quic_session_get_alpn(session, alpn, &alpn_len);
+       }
+
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_certificate_free_credentials(cred);
+err:
+       return ret;
+}
+
+static int quic_file_read_psk(const char *file, char *identity[], gnutls_datum_t *pkey)
+{
+       unsigned char *end, *head, *key, *buf;
+       int fd, err = -1, i = 0;
+       struct stat statbuf;
+       gnutls_datum_t gkey;
+       unsigned int size;
+
+       fd = open(file, O_RDONLY);
+       if (fd == -1)
+               return -1;
+       if (fstat(fd, &statbuf))
+               goto out;
+
+       size = (unsigned int)statbuf.st_size;
+       head = malloc(size);
+       if (!head)
+               goto out;
+       if (read(fd, head, size) == -1) {
+               free(head);
+               goto out;
+       }
+
+       buf = head;
+       end = buf + size - 1;
+       do {
+               key = (unsigned char *)strchr((char *)buf, ':');
+               if (!key) {
+                       free(head);
+                       goto out;
+               }
+               *key = '\0';
+               identity[i] = (char *)buf;
+
+               key++;
+               gkey.data = key;
+
+               buf = (unsigned char *)strchr((char *)key, '\n');
+               if (!buf) {
+                       gkey.size = end - gkey.data;
+                       buf = end;
+                       goto decode;
+               }
+               *buf = '\0';
+               buf++;
+               gkey.size = strlen((char *)gkey.data);
+decode:
+               if (gnutls_hex_decode2(&gkey, &pkey[i])) {
+                       free(head);
+                       goto out;
+               }
+               i++;
+       } while (buf < end && i < 5);
+
+       err = i;
+out:
+       close(fd);
+       return err;
+}
+
+/**
+ * quic_client_handshake - start a QUIC handshake with Certificate or PSK mode on client side
+ * @sockfd: IPPROTO_QUIC type socket
+ * @psk_file: pre-shared key file for PSK mode
+ * @hostname: server name for Certificate mode
+ * @alpns: ALPNs supported and split by ','
+ *
+ * Return values:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_client_handshake(int sockfd, const char *pkey_file,
+                         const char *hostname, const char *alpns)
+{
+       gnutls_datum_t keys[5];
+       char *identities[5];
+       int ret;
+
+       if (!pkey_file)
+               return quic_client_x509_handshake(sockfd, alpns, hostname);
+
+       ret = quic_file_read_psk(pkey_file, identities, keys);
+       if (ret <= 0)
+               return -EINVAL;
+
+       ret = quic_client_psk_handshake(sockfd, identities[0], &keys[0], alpns);
+
+       free(identities[0]);
+       return ret;
+}
diff --git a/third_party/quic/libquic/handshake.c b/third_party/quic/libquic/handshake.c
new file mode 100644 (file)
index 0000000..46c9932
--- /dev/null
@@ -0,0 +1,1085 @@
+/*
+ * Provide APIs for QUIC handshake.
+ *
+ * Copyright (c) 2024 Red Hat, Inc.
+ *
+ * libquic is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ */
+
+#include <sys/syslog.h>
+#include <linux/tls.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <poll.h>
+
+#include "netinet/quic.h"
+
+#define QUIC_TLSEXT_TP_PARAM   0x39u
+
+#define QUIC_MSG_STREAM_FLAGS \
+       (MSG_STREAM_NEW | MSG_STREAM_FIN | MSG_STREAM_UNI | MSG_STREAM_DONTWAIT)
+
+struct quic_rmsg {
+       char cmsg[CMSG_SPACE(sizeof(struct quic_handshake_info))];
+       struct iovec iov;
+       struct msghdr msg;
+       unsigned flags;
+       uint8_t level;
+       uint8_t data[65536];
+};
+
+struct quic_smsg {
+       struct quic_smsg *next;
+       char cmsg[CMSG_SPACE(sizeof(struct quic_handshake_info))];
+       struct iovec iov;
+       struct msghdr msg;
+       unsigned flags;
+       uint8_t level;
+       uint8_t data[];
+};
+
+struct quic_handshake_ctx;
+
+typedef int (*quic_handshake_step_process_fn_t)(struct quic_handshake_ctx *ctx);
+
+struct quic_handshake_step_internal {
+       struct quic_handshake_step step;
+       quic_handshake_step_process_fn_t process_fn;
+};
+
+struct quic_handshake_ctx {
+       gnutls_session_t session;
+       struct quic_smsg *send_list;
+       struct quic_smsg *send_last;
+       struct quic_rmsg rmsg;
+       uint8_t completed:1;
+       uint8_t is_serv:1;
+       struct quic_handshake_step_internal next_step;
+};
+
+static struct quic_handshake_ctx *quic_handshake_ctx_get(gnutls_session_t session)
+{
+       gnutls_ext_priv_data_t data = NULL;
+
+       if (gnutls_ext_get_data(session, QUIC_TLSEXT_TP_PARAM, &data) != 0)
+               return NULL;
+
+       return data;
+}
+
+/*
+ * The caller needs to opt-in in order
+ * to get log messages
+ */
+static int quic_log_level = -1;
+static quic_set_log_func_t quic_log_func;
+
+static void quic_log_error(char const *fmt, ...);
+
+/**
+ * quic_log_debug - log msg with debug level
+ *
+ */
+static void quic_log_debug(char const *fmt, ...)
+{
+       char msg[128];
+       va_list arg;
+       int rc;
+
+       if (quic_log_level < LOG_DEBUG)
+               return;
+
+       va_start(arg, fmt);
+       rc = vsnprintf(msg, sizeof(msg), fmt, arg);
+       va_end(arg);
+       if (rc < 0) {
+               quic_log_error("%s: msg size is greater than 128 bytes!",
+                              __func__);
+               return;
+       }
+
+       if (quic_log_func) {
+               quic_log_func(LOG_DEBUG, msg);
+               return;
+       }
+       printf("[DEBUG] %s\n", msg);
+}
+
+/**
+ * quic_log_notice - log msg with notice level
+ *
+ */
+static void quic_log_notice(char const *fmt, ...)
+{
+       char msg[128];
+       va_list arg;
+       int rc;
+
+       if (quic_log_level < LOG_NOTICE)
+               return;
+
+       va_start(arg, fmt);
+       rc = vsnprintf(msg, sizeof(msg), fmt, arg);
+       va_end(arg);
+       if (rc < 0) {
+               quic_log_error("%s: msg size is greater than 128 bytes!",
+                              __func__);
+               return;
+       }
+
+       if (quic_log_func) {
+               quic_log_func(LOG_NOTICE, msg);
+               return;
+       }
+       printf("[NOTICE] %s\n", msg);
+}
+
+/**
+ * quic_log_error - log msg with error level
+ *
+ */
+static void quic_log_error(char const *fmt, ...)
+{
+       char msg[128];
+       va_list arg;
+       int rc;
+
+       if (quic_log_level < LOG_ERR)
+               return;
+
+       va_start(arg, fmt);
+       rc = vsnprintf(msg, sizeof(msg), fmt, arg);
+       va_end(arg);
+       if (rc < 0) {
+               snprintf(msg, sizeof(msg),
+                        "%s: msg size is greater than 128 bytes!",
+                        __func__);
+       }
+
+       if (quic_log_func) {
+               quic_log_func(LOG_ERR, msg);
+               return;
+       }
+       printf("[ERROR] %s\n", msg);
+}
+
+/**
+ * quic_log_gnutls_error - log msg with error level and gnutls strerror converted
+ * @error: the error code returned from gnutls APIs
+ *
+ */
+static void quic_log_gnutls_error(int error)
+{
+       quic_log_error("gnutls: %s (%d)", gnutls_strerror(error), error);
+}
+
+/**
+ * quic_set_log_level - change the log_level
+ * @level: the level it changes to (LOG_XXX from sys/syslog.h)
+ *
+ * Return values:
+ * - The old @level
+ */
+int quic_set_log_level(int level)
+{
+       int old = quic_log_level;
+       quic_log_level = level;
+       return old;
+}
+
+/**
+ * quic_set_log_func - change the log func
+ * @func: the log func it changes to
+ *
+ * Return values:
+ * - The old @func
+ */
+quic_set_log_func_t quic_set_log_func(quic_set_log_func_t func)
+{
+       quic_set_log_func_t old = quic_log_func;
+       quic_log_func = func;
+       return old;
+}
+
+static void quic_prepare_sendmsg_step(struct quic_handshake_ctx *ctx,
+                                     quic_handshake_step_process_fn_t process_fn,
+                                     const struct msghdr *msg,
+                                     int flags)
+{
+       struct quic_handshake_step_sendmsg *s = &ctx->next_step.step.s_sendmsg;
+
+       ctx->next_step.step.op = QUIC_HANDSHAKE_STEP_OP_SENDMSG;
+       *s = (struct quic_handshake_step_sendmsg) {
+               .msg = msg,
+               .flags = flags,
+               .retval = -EUCLEAN,
+       };
+
+       ctx->next_step.process_fn = process_fn;
+}
+
+static void quic_prepare_recvmsg_step(struct quic_handshake_ctx *ctx,
+                                     quic_handshake_step_process_fn_t process_fn,
+                                     struct msghdr *msg,
+                                     int flags)
+{
+       struct quic_handshake_step_recvmsg *s = &ctx->next_step.step.s_recvmsg;
+
+       ctx->next_step.step.op = QUIC_HANDSHAKE_STEP_OP_RECVMSG;
+       *s = (struct quic_handshake_step_recvmsg) {
+               .msg = msg,
+               .flags = flags,
+               .retval = -EUCLEAN,
+       };
+
+       ctx->next_step.process_fn = process_fn;
+}
+
+/**
+ * quic_recvmsg - receive msg and also get stream ID and flag
+ * @sockfd: IPPROTO_QUIC type socket
+ * @msg: msg buffer
+ * @len: msg buffer length
+ * @sid: stream ID got from kernel
+ * @flag: stream flag got from kernel
+ *
+ * Return values:
+ * - On success, the number of bytes received is returned.
+ * - On error, -1 is returned, and errno is set to indicate the error.
+ */
+ssize_t quic_recvmsg(int sockfd, void *msg, size_t len, int64_t *sid, uint32_t *flags)
+{
+       char incmsg[CMSG_SPACE(sizeof(struct quic_stream_info))];
+       struct quic_stream_info *info;
+       struct cmsghdr *cmsg;
+       struct msghdr inmsg;
+       struct iovec iov;
+       ssize_t ret;
+
+       iov.iov_base = msg;
+       iov.iov_len = len;
+
+       memset(&inmsg, 0, sizeof(inmsg));
+       inmsg.msg_iov = &iov;
+       inmsg.msg_iovlen = 1;
+       inmsg.msg_control = incmsg;
+       inmsg.msg_controllen = sizeof(incmsg);
+
+       ret = recvmsg(sockfd, &inmsg, flags ? (int)*flags : 0);
+       if (ret < 0)
+               return ret;
+
+       if (flags)
+               *flags = inmsg.msg_flags;
+
+       cmsg = CMSG_FIRSTHDR(&inmsg);
+       if (!cmsg)
+               return ret;
+
+       if (SOL_QUIC == cmsg->cmsg_level &&  QUIC_STREAM_INFO == cmsg->cmsg_type) {
+               info = (struct quic_stream_info *)CMSG_DATA(cmsg);
+               if (sid)
+                       *sid = info->stream_id;
+               if (flags)
+                       *flags |= info->stream_flags;
+       }
+       return ret;
+}
+
+/**
+ * quic_sendmsg - send msg with stream ID and flag
+ * @sockfd: IPPROTO_QUIC type socket
+ * @msg: msg to send
+ * @len: the length of the msg to send
+ * @sid: stream ID
+ * @flag: stream flag
+ *
+ * Return values:
+ * - On success, the number of bytes sent is returned.
+ * - On error, -1 is returned, and errno is set to indicate the error.
+ */
+ssize_t quic_sendmsg(int sockfd, const void *msg, size_t len, int64_t sid, uint32_t flags)
+{
+       char outcmsg[CMSG_SPACE(sizeof(struct quic_stream_info))];
+       struct quic_stream_info *info;
+       struct msghdr outmsg;
+       struct cmsghdr *cmsg;
+       struct iovec iov;
+
+       iov.iov_base = (void *)msg;
+       iov.iov_len = len;
+
+       memset(&outmsg, 0, sizeof(outmsg));
+       outmsg.msg_iov = &iov;
+       outmsg.msg_iovlen = 1;
+       outmsg.msg_control = outcmsg;
+       outmsg.msg_controllen = sizeof(outcmsg);
+
+       cmsg = CMSG_FIRSTHDR(&outmsg);
+       cmsg->cmsg_level = SOL_QUIC;
+       cmsg->cmsg_type = QUIC_STREAM_INFO;
+       cmsg->cmsg_len = CMSG_LEN(sizeof(*info));
+
+       outmsg.msg_controllen = cmsg->cmsg_len;
+       info = (struct quic_stream_info *)CMSG_DATA(cmsg);
+       info->stream_id = sid;
+       info->stream_flags = (flags & QUIC_MSG_STREAM_FLAGS);
+
+       return sendmsg(sockfd, &outmsg, (int)(flags & ~QUIC_MSG_STREAM_FLAGS));
+}
+
+static uint32_t quic_tls_cipher_type(gnutls_cipher_algorithm_t cipher)
+{
+       switch (cipher) {
+       case GNUTLS_CIPHER_AES_128_GCM:
+               return TLS_CIPHER_AES_GCM_128;
+       case GNUTLS_CIPHER_AES_128_CCM:
+               return TLS_CIPHER_AES_CCM_128;
+       case GNUTLS_CIPHER_AES_256_GCM:
+               return TLS_CIPHER_AES_GCM_256;
+       case GNUTLS_CIPHER_CHACHA20_POLY1305:
+               return TLS_CIPHER_CHACHA20_POLY1305;
+       default:
+               quic_log_notice("%s: %d", __func__, cipher);
+               return 0;
+       }
+}
+
+static uint8_t quic_crypto_level(gnutls_record_encryption_level_t level)
+{
+       switch (level) {
+       case GNUTLS_ENCRYPTION_LEVEL_INITIAL:
+               return QUIC_CRYPTO_INITIAL;
+       case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE:
+               return QUIC_CRYPTO_HANDSHAKE;
+       case GNUTLS_ENCRYPTION_LEVEL_APPLICATION:
+               return QUIC_CRYPTO_APP;
+       case GNUTLS_ENCRYPTION_LEVEL_EARLY:
+               return QUIC_CRYPTO_EARLY;
+       default:
+               quic_log_notice("%s: %d", __func__, level);
+               return QUIC_CRYPTO_MAX;
+       }
+}
+
+static gnutls_record_encryption_level_t quic_tls_crypto_level(uint8_t level)
+{
+       switch (level) {
+       case QUIC_CRYPTO_INITIAL:
+               return GNUTLS_ENCRYPTION_LEVEL_INITIAL;
+       case QUIC_CRYPTO_HANDSHAKE:
+               return GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE;
+       case QUIC_CRYPTO_APP:
+               return GNUTLS_ENCRYPTION_LEVEL_APPLICATION;
+       case QUIC_CRYPTO_EARLY:
+               return GNUTLS_ENCRYPTION_LEVEL_EARLY;
+       default:
+               quic_log_notice("%s: %d", __func__, level);
+               return GNUTLS_ENCRYPTION_LEVEL_APPLICATION + 1;
+       }
+}
+
+static int quic_set_secret(gnutls_session_t session, gnutls_record_encryption_level_t level,
+                          const void *rx_secret, const void *tx_secret, size_t secretlen)
+{
+       gnutls_cipher_algorithm_t type  = gnutls_cipher_get(session);
+       struct quic_handshake_ctx *ctx = quic_handshake_ctx_get(session);
+       struct quic_crypto_secret secret = {};
+       int sockfd, ret, len = sizeof(secret);
+
+       if (!ctx || ctx->completed)
+               return 0;
+
+       if (secretlen > QUIC_CRYPTO_SECRET_BUFFER_SIZE) {
+               quic_log_error("secretlen[%zu] > %u",
+                              secretlen, QUIC_CRYPTO_SECRET_BUFFER_SIZE);
+               return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
+       }
+
+       if (level == GNUTLS_ENCRYPTION_LEVEL_EARLY)
+               type = gnutls_early_cipher_get(session);
+
+       sockfd = gnutls_transport_get_int(session);
+       secret.level = quic_crypto_level(level);
+       secret.type = quic_tls_cipher_type(type);
+       if (tx_secret) {
+               secret.send = 1;
+               memcpy(secret.secret, tx_secret, secretlen);
+               ret = setsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_CRYPTO_SECRET, &secret, len);
+               gnutls_memset(secret.secret, 0, secretlen);
+               if (ret) {
+                       quic_log_error("socket setsockopt tx secret error %d %u", errno, level);
+                       return -1;
+               }
+       }
+       if (rx_secret) {
+               secret.send = 0;
+               memcpy(secret.secret, rx_secret, secretlen);
+               ret = setsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_CRYPTO_SECRET, &secret, len);
+               gnutls_memset(secret.secret, 0, secretlen);
+               if (ret) {
+                       quic_log_error("socket setsockopt rx secret error %d %u", errno, level);
+                       return -1;
+               }
+               if (secret.level == QUIC_CRYPTO_APP) {
+                       if (ctx->is_serv) {
+                               ret = gnutls_session_ticket_send(session, 1, 0);
+                               if (ret) {
+                                       quic_log_gnutls_error(ret);
+                                       return ret;
+                               }
+                       }
+                       ctx->completed = 1;
+               }
+       }
+       quic_log_debug("  Secret func: %u %u %u", secret.level, !!tx_secret, !!rx_secret);
+       return 0;
+}
+
+static int quic_alert_read(gnutls_session_t session,
+                          gnutls_record_encryption_level_t gtls_level,
+                          gnutls_alert_level_t alert_level,
+                          gnutls_alert_description_t alert_desc)
+{
+       quic_log_notice("%s: %u %u %u %u", __func__,
+                       !!session, gtls_level, alert_level, alert_desc);
+       return 0;
+}
+
+static int quic_tp_recv(gnutls_session_t session, const uint8_t *buf, size_t len)
+{
+       int sockfd = gnutls_transport_get_int(session);
+
+       if (setsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_TRANSPORT_PARAM_EXT, buf, len)) {
+               quic_log_error("socket setsockopt transport_param_ext error %d", errno);
+               return -1;
+       }
+       return 0;
+}
+
+static int quic_tp_send(gnutls_session_t session, gnutls_buffer_t extdata)
+{
+       int ret, sockfd = gnutls_transport_get_int(session);
+       uint8_t buf[256];
+       unsigned int len;
+
+       len = sizeof(buf);
+       if (getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_TRANSPORT_PARAM_EXT, buf, &len)) {
+               quic_log_error("socket getsockopt transport_param_ext error %d", errno);
+               return -1;
+       }
+
+       ret = gnutls_buffer_append_data(extdata, buf, len);
+       if (ret) {
+               quic_log_gnutls_error(ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static struct quic_smsg *quic_smsg_create(uint8_t level,
+                                         const void *data,
+                                         size_t datalen)
+{
+       struct quic_handshake_info *info;
+       struct quic_smsg *smsg;
+       struct cmsghdr *cmsg;
+
+       smsg = malloc(sizeof(*smsg) + datalen);
+       if (!smsg)
+               return NULL;
+
+       memset(smsg, 0, sizeof(*smsg));
+       memcpy(smsg->data, data, datalen);
+
+       smsg->iov.iov_base = smsg->data;
+       smsg->iov.iov_len = datalen;
+
+       smsg->msg.msg_iov = &smsg->iov;
+       smsg->msg.msg_iovlen = 1;
+       smsg->msg.msg_control = smsg->cmsg;
+       smsg->msg.msg_controllen = sizeof(smsg->cmsg);
+
+       cmsg = CMSG_FIRSTHDR(&smsg->msg);
+       cmsg->cmsg_level = SOL_QUIC;
+       cmsg->cmsg_type = QUIC_HANDSHAKE_INFO;
+       cmsg->cmsg_len = CMSG_LEN(sizeof(*info));
+
+       info = (struct quic_handshake_info *)CMSG_DATA(cmsg);
+       info->crypto_level = level;
+
+       smsg->flags = MSG_NOSIGNAL;
+       smsg->level = level;
+
+       return smsg;
+}
+
+static void quic_smsg_append_list(struct quic_handshake_ctx *ctx,
+                                 struct quic_smsg *smsg)
+{
+       if (!ctx->send_list)
+               ctx->send_list = smsg;
+       else {
+               ctx->send_last->flags |= MSG_MORE;
+               ctx->send_last->next = smsg;
+       }
+       ctx->send_last = smsg;
+}
+
+static void quic_smsg_destroy(struct quic_smsg *smsg)
+{
+       gnutls_memset(smsg, 0, sizeof(*smsg) + smsg->iov.iov_len);
+       free(smsg);
+}
+
+static int quic_msg_read(gnutls_session_t session, gnutls_record_encryption_level_t level,
+                        gnutls_handshake_description_t htype, const void *data, size_t datalen)
+{
+       struct quic_handshake_ctx *ctx = quic_handshake_ctx_get(session);
+       uint8_t qlevel = quic_crypto_level(level);
+       struct quic_smsg *smsg;
+
+       if (!ctx || htype == GNUTLS_HANDSHAKE_KEY_UPDATE)
+               return 0;
+
+       smsg = quic_smsg_create(qlevel, data, datalen);
+       if (!smsg) {
+               quic_log_error("msg create error %d", ENOMEM);
+               return -1;
+       }
+
+       quic_smsg_append_list(ctx, smsg);
+
+       quic_log_debug("  Read func: %u %u %u", level, htype, datalen);
+       return 0;
+}
+
+static int quic_handshake_process(gnutls_session_t session, uint8_t level,
+                                 const uint8_t *data, size_t datalen)
+{
+       gnutls_record_encryption_level_t l;
+       int ret;
+
+       l = quic_tls_crypto_level(level);
+       if (datalen > 0) {
+               ret = gnutls_handshake_write(session, l, data, datalen);
+               if (ret != 0) {
+                       if (!gnutls_error_is_fatal(ret))
+                               return 0;
+                       goto err;
+               }
+       }
+
+       ret = gnutls_handshake(session);
+       if (ret < 0) {
+               if (!gnutls_error_is_fatal(ret))
+                       return 0;
+               goto err;
+       }
+       return 0;
+err:
+       gnutls_alert_send_appropriate(session, ret);
+       quic_log_gnutls_error(ret);
+       return ret;
+}
+
+static void quic_prepare_rmsg(struct quic_rmsg *rmsg)
+{
+       gnutls_memset(rmsg, 0, sizeof(*rmsg));
+
+       rmsg->iov.iov_base = rmsg->data;
+       rmsg->iov.iov_len = sizeof(rmsg->data);
+
+       rmsg->msg.msg_iov = &rmsg->iov;
+       rmsg->msg.msg_iovlen = 1;
+       rmsg->msg.msg_control = rmsg->cmsg;
+       rmsg->msg.msg_controllen = sizeof(rmsg->cmsg);
+
+       rmsg->flags = MSG_DONTWAIT;
+}
+
+static int quic_check_rmsg_level(struct quic_rmsg *rmsg)
+{
+       struct quic_handshake_info *info;
+       struct cmsghdr *cmsg;
+
+       if (rmsg->msg.msg_flags & MSG_CTRUNC) {
+               quic_log_error("rmsg: got MSG_CTRUNC");
+               return -1;
+       }
+
+       cmsg = CMSG_FIRSTHDR(&rmsg->msg);
+       if (!cmsg) {
+               quic_log_error("rmsg: got no CMSG_FIRSTHDR");
+               return -1;
+       }
+
+       if (SOL_QUIC != cmsg->cmsg_level) {
+               quic_log_error("rmsg: got no %d instead of SOL_QUIC[%d]",
+                              cmsg->cmsg_level, SOL_QUIC);
+               return -1;
+       }
+
+       if (QUIC_HANDSHAKE_INFO != cmsg->cmsg_type) {
+               quic_log_error("rmsg: got no %d instead of QUIC_HANDSHAKE_INFO[%d]",
+                              cmsg->cmsg_type, QUIC_HANDSHAKE_INFO);
+               return -1;
+       }
+
+       info = (struct quic_handshake_info *)CMSG_DATA(cmsg);
+       rmsg->level = info->crypto_level;
+
+       return 0;
+}
+
+static int quic_storage_add(void *dbf, time_t exp_time, const gnutls_datum_t *key,
+                           const gnutls_datum_t *data)
+{
+       return 0;
+}
+
+static gnutls_anti_replay_t quic_anti_replay;
+
+static int quic_handshake_next_step(struct quic_handshake_ctx *ctx,
+                                   struct quic_handshake_step **pstep);
+
+int quic_handshake_init(gnutls_session_t session,
+                       struct quic_handshake_step **pstep)
+{
+       int sockfd = gnutls_transport_get_int(session);
+       struct quic_handshake_ctx *ctx;
+       unsigned int len;
+       uint8_t opt[128];
+       int ret;
+
+       if (pstep == NULL || *pstep != NULL)
+               return -EINVAL;
+
+       ctx = malloc(sizeof(*ctx));
+       if (!ctx) {
+               quic_log_error("ctx malloc error %d", ENOMEM);
+               return -ENOMEM;
+       }
+       memset(ctx, 0, sizeof(*ctx));
+
+       ctx->session = session;
+
+       ret = gnutls_session_ext_register(
+               session, "QUIC Transport Parameters", QUIC_TLSEXT_TP_PARAM,
+               GNUTLS_EXT_TLS, quic_tp_recv, quic_tp_send, NULL, NULL, NULL,
+               GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE);
+       if (ret) {
+               free(ctx);
+               quic_log_gnutls_error(ret);
+               return ret;
+       }
+       gnutls_ext_set_data(session, QUIC_TLSEXT_TP_PARAM, ctx);
+       gnutls_handshake_set_secret_function(session, quic_set_secret);
+       gnutls_handshake_set_read_function(session, quic_msg_read);
+       gnutls_alert_set_read_function(session, quic_alert_read);
+
+       len = sizeof(opt);
+       ret = getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_TOKEN, opt, &len);
+       ctx->is_serv = !!ret;
+
+       if (ctx->is_serv) {
+               if (!quic_anti_replay) {
+                       ret = gnutls_anti_replay_init(&quic_anti_replay);
+                       if (ret)
+                               goto deinit;
+                       gnutls_anti_replay_set_add_function(quic_anti_replay,
+                                                           quic_storage_add);
+                       gnutls_anti_replay_set_ptr(quic_anti_replay, NULL);
+               }
+               gnutls_anti_replay_enable(ctx->session, quic_anti_replay);
+       }
+
+       if (!ctx->is_serv) {
+               ret = quic_handshake_process(ctx->session,
+                                            QUIC_CRYPTO_INITIAL,
+                                            NULL, 0);
+               if (ret)
+                       goto deinit;
+       }
+
+       ret = quic_handshake_next_step(ctx, pstep);
+       if (ret)
+               goto deinit;
+       return 0;
+deinit:
+       quic_handshake_deinit(session);
+       return ret;
+}
+
+static int quic_handshake_sendmsg_process(struct quic_handshake_ctx *ctx);
+static int quic_handshake_recvmsg_process(struct quic_handshake_ctx *ctx);
+
+static int quic_handshake_next_step(struct quic_handshake_ctx *ctx,
+                                   struct quic_handshake_step **pstep)
+{
+       gnutls_memset(&ctx->next_step, 0, sizeof(ctx->next_step));
+
+       if (ctx->send_list != NULL) {
+               struct quic_smsg *smsg = ctx->send_list;
+
+               quic_prepare_sendmsg_step(ctx,
+                                         quic_handshake_sendmsg_process,
+                                         &smsg->msg,
+                                         smsg->flags);
+               goto prepared;
+       }
+
+       if (!ctx->completed) {
+               struct quic_rmsg *rmsg = &ctx->rmsg;
+
+               quic_prepare_rmsg(rmsg);
+               quic_prepare_recvmsg_step(ctx,
+                                         quic_handshake_recvmsg_process,
+                                         &rmsg->msg,
+                                         rmsg->flags);
+               goto prepared;
+       }
+
+prepared:
+       if (ctx->next_step.process_fn != NULL)
+               *pstep = &ctx->next_step.step;
+       else
+               *pstep = NULL;
+
+       return 0;
+}
+
+static int quic_handshake_sendmsg_process(struct quic_handshake_ctx *ctx)
+{
+       struct quic_handshake_step_sendmsg *s = &ctx->next_step.step.s_sendmsg;
+       struct quic_smsg *smsg = ctx->send_list;
+       ssize_t slen = s->retval;
+
+       if (slen < 0) {
+               quic_log_error("socket sendmsg(%u, %u) error %zd",
+                              smsg->iov.iov_len,
+                              slen);
+               return slen;
+       }
+       if (slen != smsg->iov.iov_len) {
+               quic_log_error("socket sendmsg(%u, %u) short %zd",
+                              smsg->iov.iov_len,
+                              slen);
+               return -EMSGSIZE;
+       }
+
+       quic_log_debug("> Handshake SENT: %zu %u", slen, smsg->level);
+       ctx->send_list = smsg->next;
+       quic_smsg_destroy(smsg);
+
+       return 0;
+}
+
+static int quic_handshake_recvmsg_process(struct quic_handshake_ctx *ctx)
+{
+       struct quic_handshake_step_recvmsg *s = &ctx->next_step.step.s_recvmsg;
+       struct quic_rmsg *rmsg = &ctx->rmsg;
+       ssize_t rlen = s->retval;
+       int ret;
+
+       if (rlen < 0) {
+               quic_log_error("socket recvmsg(%u) error %zd",
+                              rmsg->iov.iov_len,
+                              rlen);
+               return rlen;
+       }
+
+       if (rlen == 0) {
+               quic_log_error("socket recvmsg(%u) EOF",
+                              rmsg->iov.iov_len);
+               return -ECONNRESET;
+       }
+
+       if (rmsg->msg.msg_flags & MSG_TRUNC) {
+               quic_log_error("socket recvmsg(%u) got MSG_TRUNC %zd",
+                              rmsg->iov.iov_len,
+                              rlen);
+               return -EMSGSIZE;
+       }
+
+       ret = quic_check_rmsg_level(rmsg);
+       if (ret < 0) {
+               quic_log_error("socket recvmsg(%u) no QUIC_HANDSHAKE_INFO",
+                              rmsg->iov.iov_len);
+               return -EBADMSG;
+       }
+
+       quic_log_debug("> Handshake RECV: %zu %u", rlen, rmsg->level);
+       ret = quic_handshake_process(ctx->session, rmsg->level, rmsg->data, rlen);
+       if (ret != 0)
+               return ret;
+
+       return 0;
+}
+
+int quic_handshake_step(gnutls_session_t session,
+                       struct quic_handshake_step **pstep)
+{
+       struct quic_handshake_ctx *ctx = quic_handshake_ctx_get(session);
+       quic_handshake_step_process_fn_t process_fn = ctx->next_step.process_fn;
+       int ret;
+
+       ctx->next_step.process_fn = NULL;
+
+       if (pstep == NULL)
+               return -EINVAL;
+
+       if (*pstep != &ctx->next_step.step) {
+               quic_log_error("ctx invalid step[%p] != expected[%p] %d",
+                              *pstep, &ctx->next_step.step, EINVAL);
+               return -EINVAL;
+       }
+
+       if (process_fn == NULL) {
+               quic_log_error("ctx no process_fn %d",
+                              EINVAL);
+               return -EINVAL;
+       }
+
+       ret = process_fn(ctx);
+       if (ret)
+               return ret;
+
+       return quic_handshake_next_step(ctx, pstep);
+}
+
+void quic_handshake_deinit(gnutls_session_t session)
+{
+       struct quic_handshake_ctx *ctx = quic_handshake_ctx_get(session);
+       struct quic_smsg *smsg;
+
+       if (ctx == NULL)
+               return;
+
+       gnutls_ext_set_data(session, QUIC_TLSEXT_TP_PARAM, NULL);
+
+       smsg = ctx->send_list;
+       while (smsg) {
+               ctx->send_list = smsg->next;
+               quic_smsg_destroy(smsg);
+               smsg = ctx->send_list;
+       }
+
+       gnutls_memset(ctx, 0, sizeof(*ctx));
+       free(ctx);
+}
+
+/**
+ * quic_handshake - Drive the handshake interaction with TLS session
+ * @session: TLS session
+ *
+ * Return values:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_handshake(gnutls_session_t session)
+{
+       int ret, sockfd = gnutls_transport_get_int(session);
+       struct quic_handshake_step *step = NULL;
+
+       ret = quic_handshake_init(session, &step);
+       if (ret != 0)
+               return ret;
+
+       while (ret == 0 && step != NULL) {
+               switch (step->op) {
+               case QUIC_HANDSHAKE_STEP_OP_RECVMSG: {
+                       struct quic_handshake_step_recvmsg *s = &step->s_recvmsg;
+                       ssize_t rlen;
+
+                       rlen = recvmsg(sockfd, s->msg, s->flags);
+                       if (rlen == -1 && errno == EINTR)
+                               continue;
+                       if (rlen == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+                               struct pollfd pfd = {
+                                       .fd = sockfd,
+                                       .events = POLLIN,
+                               };
+                               int prc;
+
+                               prc = poll(&pfd, 1, 1000);
+                               if (prc < 0) {
+                                       quic_log_error("socket poll() error %d", errno);
+                                       ret = -errno;
+                                       goto out;
+                               }
+                               continue;
+                       }
+
+                       if (rlen == -1)
+                               rlen = -errno;
+
+                       s->retval = rlen;
+                       ret = quic_handshake_step(session, &step);
+                       if (ret)
+                               goto out;
+
+                       continue;
+                       }
+               case QUIC_HANDSHAKE_STEP_OP_SENDMSG: {
+                       struct quic_handshake_step_sendmsg *s = &step->s_sendmsg;
+                       ssize_t slen;
+
+                       slen = sendmsg(sockfd, s->msg, s->flags);
+                       if (slen == -1 && errno == EINTR)
+                               continue;
+                       if (slen == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+                               struct pollfd pfd = {
+                                       .fd = sockfd,
+                                       .events = POLLOUT,
+                               };
+                               int prc;
+
+                               prc = poll(&pfd, 1, 1000);
+                               if (prc < 0) {
+                                       quic_log_error("socket poll() error %d", errno);
+                                       ret = -errno;
+                                       goto out;
+                               }
+                               continue;
+                       }
+
+                       if (slen == -1)
+                               slen = -errno;
+
+                       s->retval = slen;
+                       ret = quic_handshake_step(session, &step);
+                       if (ret)
+                               goto out;
+
+                       continue;
+                       }
+               }
+
+               ret = -EUCLEAN;
+       }
+
+out:
+       quic_handshake_deinit(session);
+       return ret < 0 ? ret : 0;
+}
+
+/**
+ * quic_session_get_data - Get session data from a TLS session
+ * @session: TLS session
+ * @data: pre-allocated buffer to hold session data
+ * @size: session data's size
+ *
+ * Return values:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_session_get_data(gnutls_session_t session, void *data, size_t *size)
+{
+       int ret, sockfd = gnutls_transport_get_int(session);
+       unsigned int len = *size;
+
+       if (getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_SESSION_TICKET, data, &len)) {
+               quic_log_error("socket getsockopt session ticket error %d", errno);
+               return -errno;
+       }
+       if (!len) {
+               *size = 0;
+               return 0;
+       }
+
+       ret = quic_handshake_process(session, QUIC_CRYPTO_APP, data, len);
+       if (ret)
+               return ret;
+       return gnutls_session_get_data(session, data, size);
+}
+
+/**
+ * quic_session_set_data - Set session data to a TLS session
+ * @session: TLS session
+ * @data: buffer to hold the session
+ * @size: session data's size
+ *
+ * Return values:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_session_set_data(gnutls_session_t session, const void *data, size_t size)
+{
+       return gnutls_session_set_data(session, data, size);
+}
+
+/**
+ * quic_session_get_alpn - Get session alpn from a TLS session
+ * @session: TLS session
+ * @data: pre-allocated string buffer to hold session alpn
+ * @size: session alpn's size
+ *
+ * Return values:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_session_get_alpn(gnutls_session_t session, void *alpn, size_t *size)
+{
+       gnutls_datum_t alpn_data;
+       int ret;
+
+       ret = gnutls_alpn_get_selected_protocol(session, &alpn_data);
+       if (ret)
+               return ret;
+
+       if (*size < alpn_data.size)
+               return -EINVAL;
+
+       memcpy(alpn, alpn_data.data, alpn_data.size);
+       *size = alpn_data.size;
+       return 0;
+}
+
+/**
+ * quic_session_set_alpn - Set session alpn to a TLS session
+ * @session: TLS session
+ * @data: string buffer to hold the session
+ * @size: session alpn's size
+ *
+ * Return values:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_session_set_alpn(gnutls_session_t session, const void *alpns, size_t size)
+{
+       gnutls_datum_t alpn_data[5];
+       char *s, data[64] = {};
+       int count = 0;
+
+       if (size >= 64)
+               return -EINVAL;
+
+       memcpy(data, alpns, size);
+       s = strtok(data, ",");
+       while (s) {
+               while (*s == ' ')
+                       s++;
+               alpn_data[count].data = (unsigned char *)s;
+               alpn_data[count].size = strlen(s);
+               count++;
+               s = strtok(NULL, ",");
+       }
+
+       return gnutls_alpn_set_protocols(session, alpn_data, count,
+                                        GNUTLS_ALPN_MANDATORY);
+}
diff --git a/third_party/quic/libquic/libquic.pc.in b/third_party/quic/libquic/libquic.pc.in
new file mode 100644 (file)
index 0000000..2a7a740
--- /dev/null
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: quic
+Description: User-level API library for in-kernel QUIC
+Version: 1.0
+Libs: -L${libdir} -lquic
+Cflags: -I${includedir}
diff --git a/third_party/quic/libquic/netinet/quic.h b/third_party/quic/libquic/netinet/quic.h
new file mode 100644 (file)
index 0000000..6582aab
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Provide APIs for QUIC handshake.
+ *
+ * Copyright (c) 2024 Red Hat, Inc.
+ *
+ * libquic is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ */
+
+#include <gnutls/abstract.h>
+#include <sys/socket.h>
+#include <linux/quic.h>
+
+/* Socket option layer for QUIC */
+#ifndef SOL_QUIC
+#define SOL_QUIC               288
+#endif
+
+#ifndef IPPROTO_QUIC
+#define IPPROTO_QUIC           261
+#endif
+
+#define QUIC_PRIORITY \
+       "NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK:+ECDHE-PSK:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
+       "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
+       "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
+       "%DISABLE_TLS13_COMPAT_MODE"
+
+int quic_client_handshake(int sockfd, const char *pkey_file,
+                         const char *hostname, const char *alpns);
+int quic_server_handshake(int sockfd, const char *pkey_file,
+                         const char *cert_file, const char *alpns);
+
+enum quic_handshake_step_op {
+       QUIC_HANDSHAKE_STEP_OP_SENDMSG = 1,
+       QUIC_HANDSHAKE_STEP_OP_RECVMSG,
+};
+
+struct quic_handshake_step_sendmsg {
+       const struct msghdr *msg;
+       int flags;
+       ssize_t retval;
+};
+
+struct quic_handshake_step_recvmsg {
+       struct msghdr *msg;
+       int flags;
+       ssize_t retval;
+};
+
+struct quic_handshake_step {
+       enum quic_handshake_step_op op;
+
+       union {
+               struct quic_handshake_step_sendmsg s_sendmsg;
+               struct quic_handshake_step_recvmsg s_recvmsg;
+       };
+};
+
+int quic_handshake_init(gnutls_session_t session,
+                       struct quic_handshake_step **pstep);
+int quic_handshake_step(gnutls_session_t session,
+                       struct quic_handshake_step **pstep);
+void quic_handshake_deinit(gnutls_session_t session);
+
+int quic_handshake(gnutls_session_t session);
+
+int quic_session_get_data(gnutls_session_t session,
+                         void *data, size_t *size);
+int quic_session_set_data(gnutls_session_t session,
+                         const void *data, size_t size);
+
+int quic_session_get_alpn(gnutls_session_t session,
+                         void *data, size_t *size);
+int quic_session_set_alpn(gnutls_session_t session,
+                         const void *data, size_t size);
+
+ssize_t quic_sendmsg(int sockfd, const void *msg, size_t len,
+                    int64_t sid, uint32_t flags);
+ssize_t quic_recvmsg(int sockfd, void *msg, size_t len,
+                    int64_t *sid, uint32_t *flags);
+
+typedef void (*quic_set_log_func_t)(int level, const char *msg);
+quic_set_log_func_t quic_set_log_func(quic_set_log_func_t func);
+int quic_set_log_level(int level);
diff --git a/third_party/quic/libquic/quic.man b/third_party/quic/libquic/quic.man
new file mode 100644 (file)
index 0000000..1b2b223
--- /dev/null
@@ -0,0 +1,1462 @@
+.TH QUIC  7 2024-01-15 "Linux Man Page" "Linux Programmer's Manual"
+.SH NAME
+quic \- QUIC protocol.
+.SH SYNOPSIS
+.nf
+.B #include <netinet/in.h>
+.B #include <netinet/quic.h>
+.sp
+.B quic_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_QUIC);
+.B quic_socket = socket(PF_INET, SOCK_DGRAM, \ IPPROTO_QUIC);
+.fi
+.SH DESCRIPTION
+This is an implementation of the QUIC protocol as defined in RFC9000 (QUIC: A
+UDP-Based Multiplexed and Secure Transport). QUIC provides applications with
+flow-controlled streams for structured communication, low-latency connection
+establishment, and network path migration. QUIC includes security measures that
+ensure confidentiality, integrity, and availability in a range of deployment
+circumstances.
+
+.PP
+This implementation of QUIC in the kernel space enables users to utilize
+the QUIC protocol through common socket APIs in user space. Additionally,
+kernel subsystems like SMB and NFS can seamlessly operate over the QUIC
+protocol after handshake using net/handshake APIs.
+
+.PP
+In userspace, similar to TCP and SCTP, a typical server and client use the
+following system call sequence to communicate:
+.PP
+       Client                             Server
+    ------------------------------------------------------------------
+    sockfd = socket(IPPROTO_QUIC)      listenfd = socket(IPPROTO_QUIC)
+    bind(sockfd)                       bind(listenfd)
+                                       listen(listenfd)
+    connect(sockfd)
+    quic_client_handshake(sockfd)
+                                       sockfd = accept(listenfd)
+                                       quic_server_handshake(sockfd, cert)
+
+    sendmsg(sockfd)                    recvmsg(sockfd)
+    close(sockfd)                      close(sockfd)
+                                       close(listenfd)
+.PP
+On the client side, the `connect()` function initializes the keys and route,
+while `quic_client_handshake()` sends the initial packet to the server. Upon
+receiving the initial packet on the listen socket, the server creates a request
+socket, processes it through `accept()`, and subsequently generates a new common
+socket, returning it to the user. Meanwhile, the `quic_server_handshake()`
+function manages the reception and processing of the initial packet on the
+server, ensuring the handshake proceeds seamlessly until completion.
+
+.PP
+For kernel consumers, activating the tlshd service in userspace is essential.
+This service is responsible for receiving and managing kernel handshake requests
+for kernel sockets. Within the kernel space, the APIs mirror those used in
+userspace:
+
+       Client                                 Server
+    ---------------------------------------------------------------------------
+    __sock_create(IPPROTO_QUIC, &sock)     __sock_create(IPPROTO_QUIC, &sock)
+    kernel_bind(sock)                      kernel_bind(sock)
+                                           kernel_listen(sock)
+    kernel_connect(sock)
+    tls_client_hello_x509(args:{sock})
+                                           kernel_accept(sock, &newsock)
+                                           tls_server_hello_x509(args:{newsock})
+
+    kernel_sendmsg(sock)                   kernel_recvmsg(newsock)
+    sock_release(sock)                     sock_release(newsock)
+                                           sock_release(sock)
+
+Please note that `tls_client_hello_x509()` and `tls_server_hello_x509()` are
+kernel APIs located in net/handshake/. These APIs are utilized to dispatch
+handshake requests to the userspace tlshd service and wait until the operation
+is successfully completed.
+
+.SH SYSCTLS
+These variables can be accessed by the
+.B /proc/sys/net/quic/*
+files or with the
+.BR sysctl (2)
+interface.  In addition, most IP sysctls also apply to QUIC. See
+.BR ip (7).
+Please check kernel documentation for this, at
+Documentation/networking/ip-sysctl.rst.
+.PP
+Upon loading the QUIC module, it also creates /proc/net/quic under procfs,
+enabling users to access and inspect information regarding existing QUIC
+connections.
+
+.SH MSG_CONTROL STRUCTURES
+This section describes key data structures specific to QUIC that are used
+with `sendmsg()` and `recvmsg()` calls. These structures control QUIC endpoint
+operations and provide access to ancillary information and notifications.
+
+.SS The msghdr and cmsghdr Structures
+The `msghdr` structure is used in
+.B sendmsg(2)
+and
+.B recvmsg(2)
+calls to carry various control information to and from the QUIC endpoint.
+The related `cmsghdr` structure defines the format of ancillary data used
+within `msghdr`, see
+.B cmsg(3).
+
+.nf
+struct msghdr {
+  void *msg_name;           /* pointer to socket address structure */
+  socklen_t msg_namelen;    /* size of socket address structure */
+  struct iovec *msg_iov;    /* scatter/gather array */
+  int msg_iovlen;           /* number of elements in msg_iov */
+  void *msg_control;        /* ancillary data */
+  socklen_t msg_controllen; /* ancillary data buffer length */
+  int msg_flags;            /* flags on message */
+};
+
+struct cmsghdr {
+  socklen_t cmsg_len; /* number of bytes, including this header */
+  int cmsg_level;     /* originating protocol */
+  int cmsg_type;      /* protocol-specific type */
+                      /* followed by unsigned char cmsg_data[]; */
+};
+.fi
+
+.PP
+The `msg_name` field is not used when sending a message with `sendmsg()`. The
+scatter/gather buffers, or I/O vectors (pointed to by the `msg_iov` field),
+are treated by QUIC as a single user message in both `sendmsg()`
+and `recvmsg()` calls.
+
+.PP
+The QUIC stack uses the ancillary data (in the `msg_control` field) to
+communicate the attributes of the message stored in `msg_iov` to the socket
+endpoint. These attributes may include QUIC-specific information,
+such as `QUIC_STREAM_INFO` discribed in
+.B Stream Information,
+and `QUIC_HANDSHAKE_INFO` discribed in
+.B Handshake Information.
+
+.TP
+.B On Send Side:
+
+.RS 4
+.PP
+The `flags` parameter in `sendmsg()` can be set to the following values:
+
+.IP \[bu] 4
+.B `MSG_NOSIGNAL`:
+See
+.BR sendmsg (2).
+
+.IP \[bu] 4
+.B `MSG_MORE`:
+Indicates that the data will be held until the next data is sent without
+this flag.
+
+.IP \[bu] 4
+.B `MSG_DONTWAIT`:
+Prevents blocking if there is no send buffer available.
+
+.IP \[bu] 4
+.B `MSG_DATAGRAM`:
+Sends the data as an unreliable datagram.
+
+.PP
+Additionally, the `flags` can be set to values from the `stream_flags` on the
+send side if Stream `msg_control` is not being used. See
+.B Stream Information
+below.
+In this case, the most recently opened stream will be used for sending data.
+
+.PP
+Note that the `msg_flags` field of the `msghdr` structure passed to the kernel
+is ignored during sending.
+.RE
+
+.TP
+.B On Receive Side:
+
+.RS 4
+.PP
+The `flags` parameter in `recvmsg()` can be set to the following values:
+
+.IP \[bu] 4
+.B `MSG_DONTWAIT`:
+Prevents blocking if there is no data available in the receive buffer.
+
+.PP
+The `msg_flags` field of the `msghdr` structure returned from the kernel
+may be set to:
+
+.IP \[bu] 4
+.B `MSG_EOR`:
+Indicates that the received data is read completely.
+
+.IP \[bu] 4
+.B `MSG_DATAGRAM`:
+Indicates that the received data is an unreliable datagram.
+
+.IP \[bu] 4
+.B `MSG_NOTIFICATION`:
+Indicates that the received data is a notification message.
+
+.PP
+These flags might also be set to values from the `stream_flags` on the
+receive side if Stream `msg_control` is not being used. See
+.B Stream Information
+below.
+In such cases, the stream ID for the received data will not be visible
+to the user space.
+.RE
+
+.SS Stream Information
+This control message (`QUIC_STREAM_INFO` cmsg_type and `SOL_QUIC` cmsg_level)
+specifies QUIC stream options for `sendmsg()` and describes QUIC stream
+information about a received message via `recvmsg()`. It uses struct
+quic_stream_info
+
+.nf
+struct quic_stream_info {
+  uint64_t stream_id;
+  uint32_t stream_flags;
+};
+.fi
+
+The fields in the `quic_stream_info` structure are defined as follows:
+
+.TP
+.B On Send Side:
+
+.PP
+.B stream_id
+
+.RS 4
+.PP
+Value -1 is special handling based on the flags:
+
+.IP \[bu] 4
+.B If `MSG_STREAM_NEW` is set:
+Opens the next bidirectional stream and uses it for sending data.
+.IP \[bu] 4
+.B If both `MSG_STREAM_NEW` and `MSG_STREAM_UNI` are set:
+Opens the next unidirectional stream and uses it for sending data.
+.IP \[bu] 4
+.B Otherwise:
+Uses the latest opened stream for sending data.
+
+.PP
+Any other value for `stream_id` is treated as a specific stream ID, with the
+first two bits used to indicate stream type:
+
+.IP \[bu] 4
+.B `QUIC_STREAM_TYPE_SERVER_MASK` (0x1):
+Indicates a server-side stream.
+.IP \[bu] 4
+.B `QUIC_STREAM_TYPE_UNI_MASK` (0x2):
+Indicates a unidirectional stream.
+.RE
+
+.PP
+.B stream_flags
+
+.RS 4
+.IP \[bu] 4
+.B `MSG_STREAM_NEW`:
+Opens a stream and sends the first data.
+.IP \[bu] 4
+.B `MSG_STREAM_FIN`:
+Sends the last data and closes the stream.
+.IP \[bu] 4
+.B `MSG_STREAM_UNI`:
+Opens the next unidirectional stream.
+.IP \[bu] 4
+.B `MSG_STREAM_DONTWAIT`:
+Opens the stream without blocking.
+.IP \[bu] 4
+.B `MSG_STREAM_SNDBLOCK`:
+Send streams blocked when no capacity.
+.RE
+
+.TP
+.B On Receive Side:
+.PP
+.B stream_id
+
+.RS 4
+.PP
+Identifies the stream to which the received data belongs.
+.RE
+
+.PP
+.B stream_flags
+
+.RS 4
+.IP \[bu] 4
+.B `MSG_STREAM_FIN`:
+Indicates that the data received is the last one for this stream.
+.RE
+
+.PP
+This control message is specifically used for sending user stream data,
+including early or 0-RTT data. When sending unreliable user datagrams,
+this control message should not be set.
+
+.SS Handshake Information
+This control message (`QUIC_HANDSHAKE_INFO` cmsg_type and `SOL_QUIC` cmsg_level)
+provides information for sending and receiving handshake/TLS messages via
+`sendmsg()` or `recvmsg()`. It uses struct quic_handshake_info
+
+.nf
+struct quic_handshake_info {
+  uint8_t crypto_level;
+};
+.fi
+
+The fields in the `quic_handshake_info` structure are defined as follows:
+
+.PP
+.B crypto_level
+
+.PP
+Specifies the level of cryptographic data:
+
+.RS 4
+.IP \[bu] 4
+.B `QUIC_CRYPTO_INITIAL`:
+Initial level data.
+.IP \[bu] 4
+.B `QUIC_CRYPTO_HANDSHAKE`:
+Handshake level data.
+.RE
+
+.PP
+This control message is used exclusively during the handshake process and is
+critical for managing the transmission of handshake-related messages in a QUIC
+connection.
+
+
+.SH MESSAGE AND HANDSHAKE INTERFACE
+This session describes a couple of advanced functions that are used to send and
+receive user data message with stream information, or used to start a handshake
+from either from client or server side.
+
+.SS quic_sendmsg() and quic_recvmsg()
+These functions are used to send and receive data over a QUIC connection, with
+support for specifying stream IDs and flags.
+
+.PP
+.B quic_sendmsg()
+.RS 4
+.PP
+is used to transmit data to a peer over a specific stream in a QUIC connection.
+
+.nf
+ssize_t quic_sendmsg(int sd,
+                     const void *msg,
+                     size_t len,
+                     int64_t sid,
+                     uint32_t flags);
+.fi
+
+.PP
+The arguments are:
+
+.TP
+.B sd
+The socket descriptor.
+
+.TP
+.B msg
+A pointer to the message buffer that contains the data to be sent.
+
+.TP
+.B len
+The length of the message buffer.
+
+.TP
+.B sid
+The stream ID (`stream_id`) indicating the stream over which the data should
+be sent.
+
+.TP
+.B flags
+The flags controlling the behavior of the function, which include
+stream-specific flags as defined in
+.B Stream Information
+and general message flags as defined in
+.B The msghdr and cmsghdr Structures.
+
+.PP
+The function returns the number of bytes accepted by the kernel for
+transmission, or `-1` in case of an error.
+.RE
+
+.PP
+.B quic_recvmsg()
+.RS 4
+.PP
+is used to receive data from a peer over a specific stream in a QUIC connection.
+
+.nf
+ssize_t quic_recvmsg(int sd,
+                     void *msg,
+                     size_t len,
+                     int64_t *sid,
+                     uint32_t *flags);
+.fi
+
+.PP
+The arguments are:
+
+.TP
+.B sd
+The socket descriptor.
+
+.TP
+.B msg
+A pointer to the message buffer where the received data will be stored.
+
+.TP
+.B len
+The length of the message buffer.
+
+.TP
+.B sid
+A pointer to the stream ID (`stream_id`) that indicates the stream from which
+the data was received.
+
+.TP
+.B flags
+A pointer to the flags that were used when the data was received, which include
+stream-specific flags as defined in
+.B Stream Information
+and general message flags as defined in
+.B The msghdr and cmsghdr Structures.
+
+.PP
+The function returns the number of bytes received, or `-1` in case of an error.
+.RE
+
+.PP
+These two functions wrap the standard `sendmsg()` and `recvmsg()` system calls,
+adding support for stream-specific information through the use of `msg_control`.
+They are essential for applications utilizing QUIC's multiple stream
+capabilities.
+
+.SS quic_client_handshake() and quic_server_handshake()
+These functions are used to initiate a QUIC handshake either from the client or
+server side. They support both Certificate and PSK modes.
+
+.PP
+.B quic_server_handshake()
+.RS 4
+.PP
+An application uses `quic_server_handshake()` to start a QUIC handshake from the
+server side.
+
+.nf
+int quic_server_handshake(int sd,
+                          const char *pkey_file,
+                          const char *cert_file,
+                          const char *alpns);
+.fi
+
+.PP
+The arguments are:
+
+.TP
+.B sd
+The socket descriptor.
+
+.TP
+.B pkey_file
+The private key file for Certificate mode or the pre-shared key file for PSK
+mode.
+
+.TP
+.B cert_file
+The certificate file for Certificate mode or `NULL` for PSK mode.
+
+.TP
+.B alpns
+The Application-Layer Protocol Negotiation (ALPN) strings supported, separated
+by commas.
+
+.PP
+The function returns `0` on success and an error code on failure.
+.RE
+
+.PP
+.B quic_client_handshake()
+.RS 4
+.PP
+An application uses `quic_client_handshake()` to start a QUIC handshake from the
+client side.
+
+.nf
+int quic_client_handshake(int sd,
+                          const char *pkey_file,
+                          const char *hostname,
+                          const char *alpns);
+.fi
+
+.PP
+The arguments are:
+
+.TP
+.B sd
+The socket descriptor.
+
+.TP
+.B pkey_file
+The pre-shared key file for PSK mode.
+
+.TP
+.B hostname
+The server name for Certificate mode.
+
+.TP
+.B alpns
+The Application-Layer Protocol Negotiation (ALPN) strings supported, separated
+by commas.
+
+.PP
+The function returns `0` on success and an error code on failure.
+.RE
+
+.SS quic_handshake_init(), quic_handshake_step() \
+and quic_handshake_deinit()
+This group of APIs provide greater control over the configuration of the
+handshake session, allowing more detailed management of the TLS session
+and also the sendmsg(2) and recvmsg(2) scheduling.
+
+Without the need to integrate sendmsg(2) and recvmsg(3) calls into an
+existing event loop `quic_handshake()` can be used instead.
+
+.PP
+.B Step work to be done by the caller
+
+.RS 4
+The following structures represend work required to
+be one by the caller in order to step forward
+in the handshake processing:
+
+.nf
+enum quic_handshake_step_op {
+  QUIC_HANDSHAKE_STEP_OP_SENDMSG = 1,
+  QUIC_HANDSHAKE_STEP_OP_RECVMSG,
+};
+
+struct quic_handshake_step_sendmsg {
+  const struct msghdr *msg;
+  int flags;
+  ssize_t retval;
+};
+
+struct quic_handshake_step_recvmsg {
+  struct msghdr *msg;
+  int flags;
+  ssize_t retval;
+};
+
+struct quic_handshake_step {
+  enum quic_handshake_step_op op;
+
+  union {
+    struct quic_handshake_step_sendmsg s_sendmsg;
+    struct quic_handshake_step_recvmsg s_recvmsg;
+  };
+};
+.fi
+
+The callers needs to do the work described by the step
+(sendmsg(2) or recvmsg(2)) and set the `retval` to the
+return value of the syscall if it's greater or equal to zero,
+or set it to -errno on failure. Note `EINTR`, `EAGAIN` and `EWOULDBLOCK`
+should never be set, instead the operation should be retried by the caller.
+
+When the step work is done the caller needs to pass
+`struct quic_handshake_step` pointer reference back to `quic_handshake_step()`.
+.RE
+
+.PP
+.B quic_handshake_init()
+
+.RS 4
+It prepares the usage of `quic_handshake_step()` and requires
+`quic_handshake_deinit()` to cleanup.
+
+.nf
+int quic_handshake_init(void *session, struct quic_handshake_step **pstep);
+.fi
+
+The argument is:
+
+.TP
+.B session
+A pointer to a TLS session object. This is represented differently depending on
+the TLS library being used, such as `gnutls_session_t` in GnuTLS or `SSL *` in
+OpenSSL.
+
+.TP
+.B pstep
+A reference to a `struct quic_handshake_step` pointer,
+the pointer itself needs to be NULL on input. On success
+it is filled with the first step work to be done by the caller.
+
+.PP
+The function returns `0` on success and an error code on failure.
+.RE
+
+.PP
+.B quic_handshake_step()
+
+.RS 4
+It requires `quic_handshake_init()` to prepare the state and the
+first step. It should called until a `NULL` step is returned,
+which indicates the handshake is completed and `quic_handshake_deinit()` can cleanup.
+
+When the step work is done the caller needs to pass the
+`struct quic_handshake_step` pointer to `quic_handshake_step()`.
+
+.nf
+int quic_handshake_step(void *session, struct quic_handshake_step **pstep);
+.fi
+
+The argument is:
+
+.TP
+.B session
+A pointer to a TLS session object. This is represented differently depending on
+the TLS library being used, such as `gnutls_session_t` in GnuTLS or `SSL *` in
+OpenSSL.
+
+.TP
+.B pstep
+A reference to a `struct quic_handshake_step` pointer, on input
+the pointer itself needs to contain a value returned by
+`quic_handshake_init()` or `quic_handshake_step()`. On success
+it is filled with the next work to be done by the caller or
+`NULL` if the handshake is completed.
+
+.PP
+The function returns `0` on success and an error code on failure.
+.RE
+
+.PP
+.B quic_handshake_deinit()
+
+.RS 4
+It cleans up the state created by `quic_handshake_init()`.
+It should be called even if `quic_handshake_step()` returned an error.
+
+.nf
+void quic_handshake_deinit(void *session);
+.fi
+
+The argument is:
+
+.TP
+.B session
+A pointer to a TLS session object. This is represented differently depending on
+the TLS library being used, such as `gnutls_session_t` in GnuTLS or `SSL *` in
+OpenSSL.
+.RE
+
+.SS quic_handshake()
+`quic_handshake()` provides greater control over the configuration of the
+handshake session, allowing more detailed management of the TLS session.
+
+It is a simplified helper arround `quic_handshake_init()`,
+`quic_handshake_step()` and `quic_handshake_deinit()` for callers
+without the need to integrate sendmsg(2) and recvmsg(3) calls into an
+existing event loop.
+
+.PP
+.B quic_handshake()
+
+.RS 4
+.nf
+int quic_handshake(void *session);
+.fi
+
+The argument is:
+
+.TP
+.B session
+A pointer to a TLS session object. This is represented differently depending on
+the TLS library being used, such as `gnutls_session_t` in GnuTLS or `SSL *` in
+OpenSSL.
+
+.PP
+The function returns `0` on success and an error code on failure.
+.RE
+
+.SH EVENTS and NOTIFICATIONS
+A QUIC application MAY need to understand and process events and errors within
+the QUIC stack. The events are categorized under the `quic_event_type` enum:
+
+.nf
+enum quic_event_type {
+  QUIC_EVENT_NONE,
+  QUIC_EVENT_STREAM_UPDATE,
+  QUIC_EVENT_STREAM_MAX_DATA,
+  QUIC_EVENT_STREAM_MAX_STREAM,
+  QUIC_EVENT_CONNECTION_ID,
+  QUIC_EVENT_CONNECTION_CLOSE,
+  QUIC_EVENT_CONNECTION_MIGRATION,
+  QUIC_EVENT_KEY_UPDATE,
+  QUIC_EVENT_NEW_TOKEN,
+  QUIC_EVENT_NEW_SESSION_TICKET,
+};
+.fi
+
+.PP
+When a notification arrives, `recvmsg()` returns the notification in the
+application-supplied data buffer via `msg_iov`, and sets `MSG_NOTIFICATION`
+in `msg_flags` of `msghdr`. The first byte of the received data indicates the
+type of the event, corresponding to one of the values in the `quic_event_type`
+enum. The subsequent bytes contain the content of the event. To manage and
+enable these events, refer to socket option
+.B QUIC_SOCKOPT_EVENT.
+
+.SS QUIC_EVENT_STREAM_UPDATE
+Notifications are delivered to userspace for specific stream states:
+
+.IP QUIC_STREAM_SEND_STATE_RECVD
+An update when all data on the stream has been acknowledged.
+
+.IP QUIC_STREAM_SEND_STATE_RESET_SENT
+An update if a `STOP_SENDING` frame is received and a `STREAM_RESET` frame is
+sent.
+
+.IP QUIC_STREAM_SEND_STATE_RESET_RECVD
+An update when a `STREAM_RESET` frame is received and acknowledged.
+
+.IP QUIC_STREAM_RECV_STATE_RECV
+An update when the last fragment of data has not yet arrived, indicating
+pending data.
+
+.IP QUIC_STREAM_RECV_STATE_SIZE_KNOWN
+An update if data arrives out of order, indicating the size of the data is
+known.
+
+.IP QUIC_STREAM_RECV_STATE_RECVD
+An update when all data on the stream has been fully received.
+
+.IP QUIC_STREAM_RECV_STATE_RESET_RECVD
+An update when a `STREAM_RESET` frame is received, indicating that the peer has
+reset the stream.
+
+.PP
+Data format in the event:
+
+.nf
+struct quic_stream_update {
+  uint64_t id;
+  uint32_t state;
+  uint32_t errcode;
+  uint64_t finalsz;
+};
+.fi
+.TP
+id
+The stream ID.
+.TP
+state
+The new stream state. All valid states are listed above.
+.TP
+errcode
+Error code for the application protocol. It is used for the RESET_SENT or
+RESET_RECVD state update on send side, and for the RESET_RECVD update on
+receive side.
+.TP
+finalsz
+The final size of the stream. It is used for the SIZE_KNOWN, RESET_RECVD,
+or RECVD state updates on receive side.
+
+.SS QUIC_EVENT_STREAM_MAX_DATA
+Delivered when a Stream Max Data frame is received. If a stream is blocked,
+a non-blocking sendmsg() call will return ENOSPC. This event notifies the
+application when additional send space becomes available for a stream,
+allowing applications to adjust stream scheduling accordingly.
+
+.PP
+Data format in the event:
+
+.nf
+struct quic_stream_max_data {
+  int64_t  id;
+  uint64_t max_data;
+};
+.fi
+.TP
+id
+The stream ID.
+.TP
+max_data
+The updated maximum amount of data that can be sent on the stream.
+
+.SS QUIC_EVENT_STREAM_MAX_STREAM
+Delivered when a `MAX_STREAMS` frame is received. Useful when
+using `MSG_STREAM_DONTWAIT` to open a stream whose ID exceeds the current
+maximum stream count. After receiving this notification, the application
+SHOULD attempt to open the stream again.
+
+.PP
+Data format in the event:
+
+.nf
+uint64_t max_stream;
+.fi
+.TP
+max_stream
+Indicates the maximum stream limit for a specific stream byte. The stream
+type is encoded in the first 2 bits, and the maximum stream limit is calculated
+by shifting max_stream right by 2 bits.
+
+.SS QUIC_EVENT_CONNECTION_ID
+Delivered when any source or destination connection IDs are retired. This
+usually occurs during connection migration or when managing connection IDs via
+socket option
+.B QUIC_SOCKOPT_CONNECTION_ID.
+
+.PP
+Data format in the event:
+
+.nf
+struct quic_connection_id_info {
+  uint8_t  dest;
+  uint32_t active;
+  uint32_t prior_to;
+};
+.fi
+.TP
+dest
+Indicates whether to operate on destination connection IDs.
+.TP
+active
+The number of the connection ID in use.
+.TP
+prior_to
+The lowest connection ID number.
+
+.SS QUIC_EVENT_CONNECTION_CLOSE
+Delivered when a `CLOSE` frame is received from the peer. The peer MAY set the
+close information via socket option
+.B QUIC_SOCKOPT_CONNECTION_CLOSE
+before calling `close()`.
+
+.PP
+Data format in the event:
+
+.nf
+struct quic_connection_close {
+  uint32_t errcode;
+  uint8_t frame;
+  uint8_t phrase[];
+};
+.fi
+.TP
+errcode
+Error code for the application protocol.
+.TP
+phrase
+Optional string for additional details.
+.TP
+frame
+Frame type that caused the closure.
+
+.SS QUIC_EVENT_CONNECTION_MIGRATION
+Delivered when either side successfully changes its source address using the
+socket option
+.B QUIC_SOCKOPT_CONNECTION_MIGRATION,
+or when the destination address is changed by the peer's connection migration.
+The parameter indicates whether the migration was local or initiated by the
+peer.
+
+.PP
+Data format in the event:
+
+.nf
+uint8_t local_migration;
+.fi
+.TP
+local_migration
+Indicates whether the migration was local or initiated by the peer. After
+receiving this notification, the new address can be retrieved using
+getsockname() for the local address or getpeername() for the peer's address.
+
+.SS QUIC_EVENT_KEY_UPDATE
+Delivered when both sides have successfully updated to the new key phase after
+a key update via socket option
+.B QUIC_SOCKOPT_KEY_UPDATE.
+The parameter indicates which key phase is currently in use.
+
+.PP
+Data format in the event:
+
+.nf
+uint8_t key_update_phase;
+.fi
+.TP
+key_update_phase
+Indicates which key phase is currently in use.
+
+.SS QUIC_EVENT_NEW_TOKEN
+Delivered whenever a `NEW_TOKEN` frame is received from the peer. Tokens can be
+sent using socket option
+.B QUIC_SOCKOPT_TOKEN.
+
+.PP
+Data format in the event:
+
+.nf
+uint8_t token[];
+.fi
+.TP
+token
+Carries the token data.
+
+.SS QUIC_EVENT_NEW_SESSION_TICKET
+Delivered whenever a `NEW_SESSION_TICKET` message carried in crypto frame is
+received from the peer.
+
+.PP
+Data format in the event:
+
+.nf
+uint8_t ticket[];
+.fi
+.TP
+ticket
+Carries the data of the TLS session ticket message.
+
+.SH SOCKET OPTIONS
+To set or get a QUIC socket option, call
+.BR getsockopt (2)
+to read or
+.BR setsockopt (2)
+to write the option with the option level argument set to
+.BR SOL_QUIC.
+Note that all these macros and structures described for parameters are defined
+in /usr/include/linux/quic.h.
+
+.SS Read/Write Options
+
+.PP
+.B QUIC_SOCKOPT_EVENT
+
+.RS 4
+.PP
+This option is used to enable or disable a specific type of event or
+notification.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_event_option {
+  uint8_t type;
+  uint8_t on;
+};
+.fi
+.IP "type"
+Specifies the event type, as defined in Section 5.1.
+.IP "on"
+Indicates whether the event is enabled or disabled:
+.IP \[bu] 4
+.B `0`:
+disable.
+.IP \[bu] 4
+.B `!0`:
+enable.
+.PP
+By default, all events are disabled.
+.RE
+
+.PP
+.B QUIC_SOCKOPT_TRANSPORT_PARAM
+
+.RS 4
+.PP
+This option is used to configure QUIC transport parameters.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_transport_param {
+  uint8_t  remote;
+  uint8_t  disable_active_migration;         /* 0 by default */
+  uint8_t  grease_quic_bit;                  /* 0 */
+  uint8_t  stateless_reset;                  /* 0 */
+  uint8_t  disable_1rtt_encryption;          /* 0 */
+  uint8_t  disable_compatible_version;       /* 0 */
+  uint8_t  active_connection_id_limit;       /* 7 */
+  uint8_t  ack_delay_exponent;               /* 3 */
+  uint16_t max_datagram_frame_size;          /* 0 */
+  uint16_t max_udp_payload_size;             /* 65527 */
+  uint32_t max_idle_timeout;                 /* 30000000 us */
+  uint32_t max_ack_delay;                    /* 25000 */
+  uint16_t max_streams_bidi;                 /* 100 */
+  uint16_t max_streams_uni;                  /* 100 */
+  uint64_t max_data;                         /* 65536 * 32 */
+  uint64_t max_stream_data_bidi_local;       /* 65536 * 4 */
+  uint64_t max_stream_data_bidi_remote;      /* 65536 * 4 */
+  uint64_t max_stream_data_uni;              /* 65536 * 4 */
+  uint64_t reserved;
+};
+.fi
+.PP
+These parameters and descripted in [RFC9000] and their default values are
+specified in the struct code.
+.PP
+The `remote` member allows users to set remote transport parameters. When used
+in conjunction with session resumption ticket, it enables the configuration of
+remote transport parameters from the previous connection. This configuration
+is crucial for sending 0-RTT data efficiently.
+.RE
+
+.PP
+.B QUIC_SOCKOPT_CONFIG
+
+.RS 4
+.PP
+This option is used to configure various settings for QUIC connections,
+including some handshake-specific options for kernel consumers.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_config {
+  uint32_t version;
+  uint32_t plpmtud_probe_interval;
+  uint32_t initial_smoothed_rtt;
+  uint32_t payload_cipher_type;
+  uint8_t  congestion_control_algo;
+  uint8_t  validate_peer_address;
+  uint8_t  stream_data_nodelay;
+  uint8_t  receive_session_ticket;
+  uint8_t  certificate_request;
+  uint8_t  reserved[3];
+};
+.fi
+.IP "version"
+QUIC version, options include:
+.RS 8
+.IP \[bu] 4
+`QUIC_VERSION_V1` (default)
+.IP \[bu] 4
+`QUIC_VERSION_V2`
+.RE
+.IP "plpmtud_probe_interval (in usec)"
+The probe interval of Packetization Layer Path MTU Discovery. Options include:
+.RS 8
+.IP \[bu] 4
+`0`: disabled (by default)
+.IP \[bu] 4
+`!0`: at least QUIC_MIN_PROBE_TIMEOUT (5000000)
+.RE
+.IP "initial_smoothed_rtt (in usec)"
+The initial smoothed RTT. Options include:
+.RS 8
+.IP \[bu] 4
+`333000` (default)
+.IP \[bu] 4
+At least QUIC_RTO_MIN (100000) and less than QUIC_RTO_MAX (6000000)
+.RE
+.IP "congestion_control_algo"
+Congestion control algorithm. Options may include:
+.RS 8
+.IP \[bu] 4
+`NEW_RENO` (default)
+.IP \[bu] 4
+`CUBIC`
+.IP \[bu] 4
+`BBR`
+.RE
+.IP "validate_peer_address"
+Server-side only. If enabled, the server will send a retry packet to the client
+upon receiving the first handshake request to validate the client's IP address.
+Options include:
+.RS 8
+.IP \[bu] 4
+`0`: disabled (default)
+.IP \[bu] 4
+`!0`: enabled
+.RE
+.IP "payload_cipher_type"
+For kernel consumers only. Allows users to inform userspace handshake of the
+preferred cipher type. Options include:
+.RS 8
+.IP \[bu] 4
+`0`: any type (default)
+.IP \[bu] 4
+`AES_GCM_128`
+.IP \[bu] 4
+`AES_GCM_256`
+.IP \[bu] 4
+`AES_CCM_128`
+.IP \[bu] 4
+`CHACHA20_POLY1305`
+.RE
+.IP "receive_session_ticket (in sec)"
+Client-side only. Enables userspace handshake to receive session tickets either
+via `NEW_SESSION_TICKET` event or socket option `SESSION_TICKET` and then set
+it back to kernel. Options include:
+.RS 8
+.IP \[bu] 4
+`0`: disabled (default)
+.IP \[bu] 4
+`!0`: maximum time (in sec) to wait
+.RE
+.IP "certificate_request"
+Server-side only. Instructs userspace handshake whether to request a certificate
+from the client. Options include:
+.RS 8
+.IP \[bu] 4
+`0`: IGNORE (default)
+.IP \[bu] 4
+`1`: REQUEST
+.IP \[bu] 4
+`2`: REQUIRE
+.RE
+.IP "stream_data_nodelay"
+Disable the Nagle algorithm. Options include:
+.RS 8
+.IP \[bu] 4
+`0`: Enable the Nagle algorithm (default)
+.IP \[bu] 4
+`!0`: Disable the Nagle algorithm
+.RE
+.RE
+
+.PP
+.B QUIC_SOCKOPT_CONNECTION_ID
+
+.RS 4
+.PP
+This option is used to get or set the source and destination connection IDs,
+including `dest`, `active` and `prior_to`. Along with
+the `active_connection_id_limit` in the transport parameters, it helps
+determine the range of available connection IDs.
+
+.PP
+The `optval` type is:
+
+.nf
+struct quic_connection_id_info {
+  uint8_t  dest;
+  uint32_t active;
+  uint32_t prior_to;
+};
+.fi
+.IP "dest"
+Indicates whether to operate on destination connection IDs.
+.IP "active"
+The number of the connection ID in use.
+.IP "prior_to"
+The lowest connection ID number.
+
+.PP
+The `active` is used to switch the connection ID in use. The `prior_to`, for
+source connection IDs, specifies prior to which ID will be retired by
+sending `NEW_CONNECTION_ID` frames; for destination connection IDs, it
+indicates prior to which ID issued by the peer will no longer be used and
+should be retired by sending `RETIRE_CONNECTION_ID` frames.
+.RE
+
+.PP
+.B QUIC_SOCKOPT_CONNECTION_CLOSE
+
+.RS 4
+.PP
+This option is used to get or set the close context, which includes `errcode`,
+`phrase`, and `frame`.
+.IP "On the closing side"
+Set this option before calling `close()` to communicate the closing information
+to the peer.
+.IP "On the receiving side"
+Get this option to retrieve the closing information from the peer.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_connection_close {
+  uint32_t errcode;
+  uint8_t  frame;
+  uint8_t  phrase[];
+};
+.fi
+.IP "errcode"
+Error code for the application protocol. Defaults to 0.
+.IP "frame"
+Frame type that caused the closure. Defaults to 0.
+.IP "phrase"
+Optional string for additional details. Defaults to null.
+.RE
+
+.PP
+.B QUIC_SOCKOPT_TOKEN
+
+.RS 4
+.PP
+Manages tokens for address verification in QUIC connections.
+.IP "Client-Side Usage"
+The client uses this option to set a token provided by the peer server for
+address verification in subsequent connections. The token can be obtained
+from the server during the previous connection, either via `getsockopt()` with
+this option or from `NEW_TOKEN` event.
+.RS 8
+.PP
+The `optval` type is:
+
+.nf
+uint8_t *opt;
+.fi
+.RE
+.IP "Server-Side Usage"
+The server uses this option to issue a new token to the client for address
+verification in the next connection.
+.RS 8
+.PP
+The `optval` type is null.
+.RE
+.RE
+
+.PP
+.B QUIC_SOCKOPT_ALPN
+
+.RS 4
+.PP
+Used on listening sockets for kernel ALPN routing and on regular sockets for
+communicating ALPN identifiers with userspace handshake.
+.IP "On regular sockets"
+Sets the desired ALPNs before sending handshake requests to userspace. Multiple
+ALPNs can be specified, separated by commas (e.g., "smbd,h3,ksmbd"). Userspace
+handshake should return the selected ALPN to the kernel via this socket option.
+.IP "On listening sockets"
+Directs incoming requests to the appropriate application based on ALPNs if
+supported by the kernel. ALPNs must be set before calling `listen()`.
+.PP
+The `optval` type is:
+
+.nf
+char *alpn;
+.fi
+.RE
+
+.PP
+.B QUIC_SOCKOPT_SESSION_TICKET
+
+.RS 4
+.PP
+Used on listening sockets to retrieve the key for enabling session tickets on
+the server, and on regular sockets to receive session ticket messages on the
+client. Also used by client-side kernel consumers to communicate session data
+with userspace handshake.
+.IP "For userspace handshake"
+On the server side, requires a key to enable session tickets. On the client
+side, receives `NEW_SESSION_TICKET` messages to generate session data.
+.IP "For kernel consumers"
+After handling `NEW_SESSION_TICKET` messages, userspace handshake must return
+session data to the kernel via this socket option. During session resumption,
+kernel consumers use this option to inform userspace handshake about session
+data.
+.PP
+The `optval` type is:
+
+.nf
+uint8_t *opt;
+.fi
+.RE
+
+.PP
+.B QUIC_SOCKOPT_CRYPTO_SECRET
+
+.RS 4
+.PP
+Sets cryptographic secrets derived from userspace to the socket in the kernel
+during the QUIC handshake process.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_crypto_secret {
+  uint8_t  level;
+  uint16_t send;
+  uint32_t type;
+  uint8_t  secret[48];
+};
+.fi
+.IP "level"
+Specifies the QUIC cryptographic level:
+.RS 8
+.IP \[bu] 4
+`QUIC_CRYPTO_APP`: Application level
+.IP \[bu] 4
+`QUIC_CRYPTO_HANDSHAKE`: Handshake level
+.IP \[bu] 4
+`QUIC_CRYPTO_EARLY`: Early or 0-RTT level
+.RE
+.IP "send"
+Indicates the direction of the secret:
+.RS 8
+.IP \[bu] 4
+`0`: Set secret for receiving
+.IP \[bu] 4
+`!0`: Set secret for sending
+.RE
+.IP "type"
+Specifies the encryption algorithm used:
+.RS 8
+.IP \[bu] 4
+`AES_GCM_128`
+.IP \[bu] 4
+`AES_GCM_256`
+.IP \[bu] 4
+`AES_CCM_128`
+.IP \[bu] 4
+`CHACHA20_POLY1305`
+.RE
+.IP "secret"
+The cryptographic key material. Length depends on the type and should be filled
+accordingly in the kernel.
+.RE
+
+.PP
+.B QUIC_SOCKOPT_TRANSPORT_PARAM_EXT
+
+.RS 4
+.PP
+Used to retrieve or set the QUIC Transport Parameters Extension, essential for
+building TLS messages and handling extended QUIC transport parameters.
+.IP "Get Operation"
+Retrieves the QUIC Transport Parameters Extension based on local transport
+parameters configured in the kernel.
+.IP "Set Operation"
+Updates the kernel with the QUIC Transport Parameters Extension received from
+the peer's TLS message.
+.PP
+The `optval` type is:
+
+.nf
+uint8_t *opt;
+.fi
+.RE
+
+.SS Read-Only Options
+
+.PP
+.B QUIC_SOCKOPT_STREAM_OPEN
+
+.RS 4
+.PP
+Opens a new QUIC stream for data transmission within a QUIC connection.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_stream_info {
+  uint64_t stream_id;
+  uint32_t stream_flags;
+};
+.fi
+.IP "stream_id"
+Specifies the stream ID for the new stream:
+.RS 8
+.IP \[bu] 4
+`>= 0`: Open a stream with the specific stream ID.
+.IP \[bu] 4
+`-1`: Open the next available stream. The assigned stream ID will be returned to the user.
+.RE
+.IP "stream_flags"
+Specifies flags for stream creation:
+.RS 8
+.IP \[bu] 4
+`QUIC_STREAM_UNI`: Open the next unidirectional stream.
+.IP \[bu] 4
+`QUIC_STREAM_DONTWAIT`: Open the stream without blocking; allows asynchronous processing.
+.RE
+.RE
+
+.SS Write-Only Options
+
+.PP
+.B QUIC_SOCKOPT_STREAM_RESET
+
+.RS 4
+.PP
+Resets a specific QUIC stream, indicating that the endpoint will no longer
+guarantee the delivery of data associated with that stream.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_errinfo {
+  uint64_t stream_id;
+  uint32_t errcode;
+};
+.fi
+.RE
+
+.PP
+.B QUIC_SOCKOPT_STREAM_STOP_SENDING
+
+.RS 4
+.PP
+Requests that the peer stop sending data on a specified QUIC stream.
+.PP
+The `optval` type is:
+
+.nf
+struct quic_errinfo {
+  uint64_t stream_id;
+  uint32_t errcode;
+};
+.fi
+.RE
+
+.PP
+.B QUIC_SOCKOPT_CONNECTION_MIGRATION
+
+.RS 4
+.PP
+Initiates a connection migration, allowing the QUIC connection to switch to a
+new address. Can also be used on the server side to set the preferred address
+transport parameter before the handshake.
+.PP
+The `optval` type is:
+
+.nf
+struct sockaddr_in(6);
+.fi
+.RE
+
+.PP
+.B QUIC_SOCKOPT_KEY_UPDATE
+
+.RS 4
+.PP
+Initiates a key update or rekeying process for the QUIC connection.
+.PP
+The `optval` type is null.
+.fi
+.RE
+
+.SH AUTHORS
+Xin Long <lucien.xin@gmail.com>
+.SH "SEE ALSO"
+.BR socket (7),
+.BR socket (2),
+.BR ip (7),
+.BR bind (2),
+.BR listen (2),
+.BR accept (2),
+.BR connect (2),
+.BR sendmsg (2),
+.BR recvmsg (2),
+.BR sysctl (2),
+.BR getsockopt (2),
+.sp
+RFC9000 for the QUIC specification.
diff --git a/third_party/quic/libquic/server.c b/third_party/quic/libquic/server.c
new file mode 100644 (file)
index 0000000..43bc26e
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Perform a QUIC server-side handshake.
+ *
+ * Copyright (c) 2024 Red Hat, Inc.
+ *
+ * libquic is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "netinet/quic.h"
+
+static int quic_server_psk_handshake(int sockfd, const char *psk, const char *alpns)
+{
+       gnutls_psk_server_credentials_t cred;
+       gnutls_session_t session;
+       size_t alpn_len;
+       char alpn[64];
+       int ret;
+
+       ret = gnutls_psk_allocate_server_credentials(&cred);
+       if (ret)
+               goto err;
+       ret = gnutls_psk_set_server_credentials_file(cred, psk);
+       if (ret)
+               goto err_cred;
+       ret = gnutls_init(&session, GNUTLS_SERVER | GNUTLS_NO_AUTO_SEND_TICKET);
+       if (ret)
+               goto err_cred;
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_PSK, cred);
+       if (ret)
+               goto err_session;
+
+       ret = gnutls_priority_set_direct(session, QUIC_PRIORITY, NULL);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               ret = quic_session_set_alpn(session, alpns, strlen(alpns));
+               if (ret)
+                       goto err_session;
+       }
+
+       gnutls_transport_set_int(session, sockfd);
+
+       ret = quic_handshake(session);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               alpn_len = sizeof(alpn);
+               ret = quic_session_get_alpn(session, alpn, &alpn_len);
+       }
+
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_psk_free_server_credentials(cred);
+err:
+       return ret;
+}
+
+static int quic_server_x509_handshake(int sockfd, const char *pkey,
+                                     const char *cert, const char *alpns)
+{
+       gnutls_certificate_credentials_t cred;
+       gnutls_session_t session;
+       size_t alpn_len;
+       char alpn[64];
+       int ret;
+
+       ret = gnutls_certificate_allocate_credentials(&cred);
+       if (ret)
+               goto err;
+       ret = gnutls_certificate_set_x509_system_trust(cred);
+       if (ret < 0)
+               goto err_cred;
+       ret = gnutls_certificate_set_x509_key_file(cred, cert, pkey, GNUTLS_X509_FMT_PEM);
+       if (ret)
+               goto err_cred;
+       ret = gnutls_init(&session, GNUTLS_SERVER | GNUTLS_NO_AUTO_SEND_TICKET);
+       if (ret)
+               goto err_cred;
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
+       if (ret)
+               goto err_session;
+
+       ret = gnutls_priority_set_direct(session, QUIC_PRIORITY, NULL);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               ret = quic_session_set_alpn(session, alpns, strlen(alpns));
+               if (ret)
+                       goto err_session;
+       }
+
+       gnutls_transport_set_int(session, sockfd);
+
+       ret = quic_handshake(session);
+       if (ret)
+               goto err_session;
+
+       if (alpns) {
+               alpn_len = sizeof(alpn);
+               ret = quic_session_get_alpn(session, alpn, &alpn_len);
+       }
+
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_certificate_free_credentials(cred);
+err:
+       return ret;
+}
+
+/**
+ * quic_server_handshake - start a QUIC handshake with Certificate or PSK mode on server side
+ * @sockfd: IPPROTO_QUIC type socket
+ * @pkey_file: private key file for Certificate mode or pre-shared key file for PSK mode
+ * @cert_file: certificate file for Certificate mode or null for PSK mode
+ * @alpns: ALPNs supported and split by ','
+ *
+ * Return values:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_server_handshake(int sockfd, const char *pkey_file,
+                         const char *cert_file, const char *alpns)
+{
+       if (cert_file)
+               return  quic_server_x509_handshake(sockfd, pkey_file, cert_file, alpns);
+
+       return quic_server_psk_handshake(sockfd, pkey_file, alpns);
+}
diff --git a/third_party/quic/modules/include/uapi/linux/quic.h b/third_party/quic/modules/include/uapi/linux/quic.h
new file mode 100644 (file)
index 0000000..430c030
--- /dev/null
@@ -0,0 +1,245 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/* QUIC kernel implementation
+ * (C) Copyright Red Hat Corp. 2023
+ *
+ * This file is part of the QUIC kernel implementation
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#ifndef __uapi_quic_h__
+#define __uapi_quic_h__
+
+#ifdef __KERNEL__
+#include <linux/socket.h>
+#include <linux/types.h>
+#else
+#include <sys/socket.h>
+#include <stdint.h>
+#endif
+
+enum {
+       IPPROTO_QUIC = 261,             /* A UDP-Based Multiplexed and Secure Transport */
+#define IPPROTO_QUIC           IPPROTO_QUIC
+};
+
+#define SOL_QUIC       288
+
+/* NOTE: Structure descriptions are specified in:
+ * https://datatracker.ietf.org/doc/html/draft-lxin-quic-socket-apis
+ */
+
+/* Send or Receive Options APIs */
+enum quic_cmsg_type {
+       QUIC_STREAM_INFO,
+       QUIC_HANDSHAKE_INFO,
+};
+
+#define QUIC_STREAM_TYPE_SERVER_MASK   0x01
+#define QUIC_STREAM_TYPE_UNI_MASK      0x02
+#define QUIC_STREAM_TYPE_MASK          0x03
+
+enum quic_msg_flags {
+       /* flags for stream_flags */
+       MSG_STREAM_NEW          = MSG_SYN,
+       MSG_STREAM_FIN          = MSG_FIN,
+       MSG_STREAM_UNI          = MSG_CONFIRM,
+       MSG_STREAM_DONTWAIT     = MSG_WAITFORONE,
+       MSG_STREAM_SNDBLOCK     = MSG_ERRQUEUE,
+
+       /* extented flags for msg_flags */
+       MSG_DATAGRAM            = MSG_RST,
+       MSG_NOTIFICATION        = MSG_MORE,
+};
+
+enum quic_crypto_level {
+       QUIC_CRYPTO_APP,
+       QUIC_CRYPTO_INITIAL,
+       QUIC_CRYPTO_HANDSHAKE,
+       QUIC_CRYPTO_EARLY,
+       QUIC_CRYPTO_MAX,
+};
+
+struct quic_handshake_info {
+       uint8_t crypto_level;
+};
+
+struct quic_stream_info {
+       int64_t  stream_id;
+       uint32_t stream_flags;
+};
+
+/* Socket Options APIs */
+#define QUIC_SOCKOPT_EVENT                             0
+#define QUIC_SOCKOPT_STREAM_OPEN                       1
+#define QUIC_SOCKOPT_STREAM_RESET                      2
+#define QUIC_SOCKOPT_STREAM_STOP_SENDING               3
+#define QUIC_SOCKOPT_CONNECTION_ID                     4
+#define QUIC_SOCKOPT_CONNECTION_CLOSE                  5
+#define QUIC_SOCKOPT_CONNECTION_MIGRATION              6
+#define QUIC_SOCKOPT_KEY_UPDATE                                7
+#define QUIC_SOCKOPT_TRANSPORT_PARAM                   8
+#define QUIC_SOCKOPT_CONFIG                            9
+#define QUIC_SOCKOPT_TOKEN                             10
+#define QUIC_SOCKOPT_ALPN                              11
+#define QUIC_SOCKOPT_SESSION_TICKET                    12
+#define QUIC_SOCKOPT_CRYPTO_SECRET                     13
+#define QUIC_SOCKOPT_TRANSPORT_PARAM_EXT               14
+
+#define QUIC_VERSION_V1                        0x1
+#define QUIC_VERSION_V2                        0x6b3343cf
+
+struct quic_transport_param {
+       uint8_t         remote;
+       uint8_t         disable_active_migration;
+       uint8_t         grease_quic_bit;
+       uint8_t         stateless_reset;
+       uint8_t         disable_1rtt_encryption;
+       uint8_t         disable_compatible_version;
+       uint8_t         active_connection_id_limit;
+       uint8_t         ack_delay_exponent;
+       uint16_t        max_datagram_frame_size;
+       uint16_t        max_udp_payload_size;
+       uint32_t        max_idle_timeout;
+       uint32_t        max_ack_delay;
+       uint16_t        max_streams_bidi;
+       uint16_t        max_streams_uni;
+       uint64_t        max_data;
+       uint64_t        max_stream_data_bidi_local;
+       uint64_t        max_stream_data_bidi_remote;
+       uint64_t        max_stream_data_uni;
+       uint64_t        reserved;
+};
+
+struct quic_config {
+       uint32_t        version;
+       uint32_t        plpmtud_probe_interval;
+       uint32_t        initial_smoothed_rtt;
+       uint32_t        payload_cipher_type;
+       uint8_t         congestion_control_algo;
+       uint8_t         validate_peer_address;
+       uint8_t         stream_data_nodelay;
+       uint8_t         receive_session_ticket;
+       uint8_t         certificate_request;
+       uint8_t         reserved[3];
+};
+
+struct quic_crypto_secret {
+       uint8_t send;  /* send or recv */
+       uint8_t level; /* crypto level */
+       uint32_t type; /* TLS_CIPHER_* */
+#define QUIC_CRYPTO_SECRET_BUFFER_SIZE 48
+       uint8_t secret[QUIC_CRYPTO_SECRET_BUFFER_SIZE];
+};
+
+enum quic_cong_algo {
+       QUIC_CONG_ALG_RENO,
+       QUIC_CONG_ALG_CUBIC,
+       QUIC_CONG_ALG_MAX,
+};
+
+struct quic_errinfo {
+       int64_t  stream_id;
+       uint32_t errcode;
+};
+
+struct quic_connection_id_info {
+       uint8_t  dest;
+       uint32_t active;
+       uint32_t prior_to;
+};
+
+struct quic_event_option {
+       uint8_t type;
+       uint8_t on;
+};
+
+/* Event APIs */
+enum quic_event_type {
+       QUIC_EVENT_NONE,
+       QUIC_EVENT_STREAM_UPDATE,
+       QUIC_EVENT_STREAM_MAX_DATA,
+       QUIC_EVENT_STREAM_MAX_STREAM,
+       QUIC_EVENT_CONNECTION_ID,
+       QUIC_EVENT_CONNECTION_CLOSE,
+       QUIC_EVENT_CONNECTION_MIGRATION,
+       QUIC_EVENT_KEY_UPDATE,
+       QUIC_EVENT_NEW_TOKEN,
+       QUIC_EVENT_NEW_SESSION_TICKET,
+       QUIC_EVENT_END,
+       QUIC_EVENT_MAX = QUIC_EVENT_END - 1,
+};
+
+enum {
+       QUIC_STREAM_SEND_STATE_READY,
+       QUIC_STREAM_SEND_STATE_SEND,
+       QUIC_STREAM_SEND_STATE_SENT,
+       QUIC_STREAM_SEND_STATE_RECVD,
+       QUIC_STREAM_SEND_STATE_RESET_SENT,
+       QUIC_STREAM_SEND_STATE_RESET_RECVD,
+
+       QUIC_STREAM_RECV_STATE_RECV,
+       QUIC_STREAM_RECV_STATE_SIZE_KNOWN,
+       QUIC_STREAM_RECV_STATE_RECVD,
+       QUIC_STREAM_RECV_STATE_READ,
+       QUIC_STREAM_RECV_STATE_RESET_RECVD,
+       QUIC_STREAM_RECV_STATE_RESET_READ,
+};
+
+struct quic_stream_update {
+       int64_t  id;
+       uint8_t  state;
+       uint32_t errcode;
+       uint64_t finalsz;
+};
+
+struct quic_stream_max_data {
+       int64_t  id;
+       uint64_t max_data;
+};
+
+struct quic_connection_close {
+       uint32_t errcode;
+       uint8_t frame;
+       uint8_t phrase[];
+};
+
+union quic_event {
+       struct quic_stream_update update;
+       struct quic_stream_max_data max_data;
+       struct quic_connection_close close;
+       struct quic_connection_id_info info;
+       uint64_t max_stream;
+       uint8_t local_migration;
+       uint8_t key_update_phase;
+};
+
+enum {
+       QUIC_TRANSPORT_ERROR_NONE,
+       QUIC_TRANSPORT_ERROR_INTERNAL,
+       QUIC_TRANSPORT_ERROR_CONNECTION_REFUSED,
+       QUIC_TRANSPORT_ERROR_FLOW_CONTROL,
+       QUIC_TRANSPORT_ERROR_STREAM_LIMIT,
+       QUIC_TRANSPORT_ERROR_STREAM_STATE,
+       QUIC_TRANSPORT_ERROR_FINAL_SIZE,
+       QUIC_TRANSPORT_ERROR_FRAME_ENCODING,
+       QUIC_TRANSPORT_ERROR_TRANSPORT_PARAM,
+       QUIC_TRANSPORT_ERROR_CONNECTION_ID_LIMIT,
+       QUIC_TRANSPORT_ERROR_PROTOCOL_VIOLATION,
+       QUIC_TRANSPORT_ERROR_INVALID_TOKEN,
+       QUIC_TRANSPORT_ERROR_APPLICATION,
+       QUIC_TRANSPORT_ERROR_CRYPTO_BUF_EXCEEDED,
+       QUIC_TRANSPORT_ERROR_KEY_UPDATE,
+       QUIC_TRANSPORT_ERROR_AEAD_LIMIT_REACHED,
+       QUIC_TRANSPORT_ERROR_NO_VIABLE_PATH,
+
+       /* The cryptographic handshake failed. A range of 256 values is reserved
+        * for carrying error codes specific to the cryptographic handshake that
+        * is used. Codes for errors occurring when TLS is used for the
+        * cryptographic handshake are described in Section 4.8 of [QUIC-TLS].
+        */
+       QUIC_TRANSPORT_ERROR_CRYPTO = 0x0100,
+};
+
+#endif /* __uapi_quic_h__ */
diff --git a/third_party/quic/update.sh b/third_party/quic/update.sh
new file mode 100755 (executable)
index 0000000..297dc6f
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+if [[ $# -lt 1 ]]; then
+    echo "Usage: update.sh VERSION"
+    exit 1
+fi
+
+QUIC_VERSION="${1}"
+QUIC_GIT="https://github.com/lxin/quic.git"
+QUIC_UPDATE_SCRIPT="$(readlink -f "$0")"
+QUIC_SAMBA_DIR="$(dirname "${QUIC_UPDATE_SCRIPT}")"
+QUIC_TMPDIR=$(mktemp --tmpdir -d quic-XXXXXXXX)
+
+echo "VERSION:        ${QUIC_VERSION}"
+echo "GIT URL:        ${QUIC_GIT}"
+echo "QUIC SAMBA DIR: ${QUIC_SAMBA_DIR}"
+echo "QUIC TMP DIR:   ${QUIC_TMPDIR}"
+
+cleanup_tmpdir() {
+    popd 2>/dev/null || true
+    rm -rf "$QUIC_TMPDIR"
+}
+trap cleanup_tmpdir SIGINT
+
+cleanup_and_exit() {
+    cleanup_tmpdir
+    if test "$1" = 0 -o -z "$1" ; then
+        exit 0
+    else
+        exit "$1"
+    fi
+}
+
+# Checkout the git tree
+mkdir -p "${QUIC_TMPDIR}"
+pushd "${QUIC_TMPDIR}" || cleanup_and_exit 1
+
+git clone "${QUIC_GIT}"
+ret=$?
+if [ $ret -ne 0 ]; then
+    echo "ERROR: Failed to clone repository"
+    cleanup_and_exit 1
+fi
+
+pushd quic || cleanup_and_exit 1
+#git checkout -b "quic-${QUIC_VERSION}" "quic-${QUIC_VERSION}"
+PAGER= git log --pretty=oneline -1
+ret=$?
+if [ $ret -ne 0 ]; then
+    echo "ERROR: Failed to checkout quic-${QUIC_VERSION} repository"
+    cleanup_and_exit 1
+fi
+popd || cleanup_and_exit 1
+
+popd || cleanup_and_exit 1
+
+# Update src
+pushd "${QUIC_SAMBA_DIR}" || cleanup_and_exit 1
+pwd
+
+rm -rf libquic/ modules/ COPYING
+mkdir -p modules/include/uapi
+rsync -av "${QUIC_TMPDIR}/quic/libquic/" libquic/
+rsync -av "${QUIC_TMPDIR}/quic/modules/include/uapi/" modules/include/uapi/
+rsync -av "${QUIC_TMPDIR}/quic/COPYING" .
+ret=$?
+if [ $ret -ne 0 ]; then
+    echo "ERROR: Failed copy src"
+    cleanup_and_exit 1
+fi
+
+git add libquic modules/include/uapi/ COPYING
+
+popd || cleanup_and_exit 1
+
+echo
+echo "Now please change VERSION in buildtools/wafsamba/samba_third_party.py"
+echo
+
+cleanup_and_exit 0
diff --git a/third_party/quic/wscript b/third_party/quic/wscript
new file mode 100644 (file)
index 0000000..3715e35
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+import sys
+from waflib import Logs
+
+def configure(conf):
+    if not sys.platform.startswith('linux'):
+        Logs.warn('libquic requires linux')
+        conf.SET_TARGET_TYPE('quic', 'EMPTY')
+        return
+
+    if not conf.CONFIG_GET('HAVE_GNUTLS_CB_TLS_SERVER_END_POINT'):
+        Logs.warn('libquic requires gnutls 3.7.2')
+        conf.SET_TARGET_TYPE('quic', 'EMPTY')
+        return
+
+    if not conf.CHECK_HEADERS('linux/tls.h'):
+        Logs.warn('libquic requires linux/tls.h')
+        conf.SET_TARGET_TYPE('quic', 'EMPTY')
+        return
+
+    if not conf.CHECK_DECLS('TLS_CIPHER_CHACHA20_POLY1305', headers='linux/tls.h'):
+        conf.DEFINE('TLS_CIPHER_CHACHA20_POLY1305', 54)
+
+    if conf.CHECK_LIBQUIC():
+        conf.CHECK_HEADERS('netinet/quic.h', lib='quic')
+        conf.CHECK_LIB('quic', shlib=True)
+        conf.CHECK_FUNCS_IN('quic_handshake_step', 'quic', headers='netinet/quic.h')
+        conf.DEFINE('HAVE_LIBQUIC', '1')
+        conf.define('USING_SYSTEM_LIBQUIC', 1)
+        return
+
+    conf.ADD_NAMED_CFLAGS('LIBQUIC_UNPICKY_CFLAGS',
+                          '-Wno-cast-qual',
+                          testflags=True)
+    conf.ADD_NAMED_CFLAGS('LIBQUIC_UNPICKY_CFLAGS',
+                          '-Wno-error=cast-qual',
+                          testflags=True)
+
+    conf.DEFINE('HAVE_LIBQUIC', '1')
+    return
+
+def build(bld):
+    if bld.CONFIG_SET('USING_SYSTEM_LIBQUIC'):
+        return
+
+    bld.SAMBA_LIBRARY('quic',
+                      source='''
+                      libquic/handshake.c
+                      ''',
+                      includes='libquic modules/include/uapi',
+                      deps='replace',
+                      public_deps='gnutls',
+                      cflags_end=bld.env.LIBQUIC_UNPICKY_CFLAGS,
+                      private_library=True,
+                      enabled=bld.CONFIG_SET('HAVE_LIBQUIC'))
+    bld.SAMBA_SUBSYSTEM('libquic', source='', public_deps='quic',
+                        enabled=bld.CONFIG_SET('HAVE_LIBQUIC'))
index e95bc3194ce00a4d77d1188bc7d98cab473d948e..1345d77844ef95e8d96a463cc07d7f5399e652c1 100644 (file)
@@ -5,6 +5,7 @@ from waflib import Options
 def configure(conf):
     conf.RECURSE('cmocka')
     conf.RECURSE('popt')
+    conf.RECURSE('quic')
     if conf.CONFIG_GET('ENABLE_SELFTEST'):
         conf.RECURSE('socket_wrapper')
         conf.RECURSE('nss_wrapper')
@@ -17,6 +18,7 @@ def configure(conf):
 def build(bld):
     bld.RECURSE('cmocka')
     bld.RECURSE('popt')
+    bld.RECURSE('quic')
     if bld.CONFIG_GET('SOCKET_WRAPPER'):
         bld.RECURSE('socket_wrapper')
     if bld.CONFIG_GET('NSS_WRAPPER'):