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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+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}
--- /dev/null
+/*
+ * 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);
--- /dev/null
+.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.
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+#!/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
--- /dev/null
+#!/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'))
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')
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'):