Merged with the quic branch.

This commit is contained in:
Roman Arutyunyan 2023-05-19 21:46:36 +04:00
commit 4b02661748
76 changed files with 23593 additions and 51 deletions

View File

@ -5,12 +5,17 @@
if [ $OPENSSL != NONE ]; then
have=NGX_OPENSSL . auto/have
have=NGX_SSL . auto/have
if [ $USE_OPENSSL_QUIC = YES ]; then
have=NGX_QUIC . auto/have
have=NGX_QUIC_OPENSSL_COMPAT . auto/have
fi
case "$CC" in
cl | bcc32)
have=NGX_OPENSSL . auto/have
have=NGX_SSL . auto/have
CFLAGS="$CFLAGS -DNO_SYS_TYPES_H"
CORE_INCS="$CORE_INCS $OPENSSL/openssl/include"
@ -33,9 +38,6 @@ if [ $OPENSSL != NONE ]; then
;;
*)
have=NGX_OPENSSL . auto/have
have=NGX_SSL . auto/have
CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include"
CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h"
CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a"
@ -123,6 +125,35 @@ else
CORE_INCS="$CORE_INCS $ngx_feature_path"
CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
OPENSSL=YES
if [ $USE_OPENSSL_QUIC = YES ]; then
ngx_feature="OpenSSL QUIC support"
ngx_feature_name="NGX_QUIC"
ngx_feature_test="SSL_set_quic_method(NULL, NULL)"
. auto/feature
if [ $ngx_found = no ]; then
have=NGX_QUIC_OPENSSL_COMPAT . auto/have
ngx_feature="OpenSSL QUIC compatibility"
ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0,
NULL, NULL, NULL, NULL, NULL)"
. auto/feature
fi
if [ $ngx_found = no ]; then
cat << END
$0: error: certain modules require OpenSSL QUIC support.
You can either do not enable the modules, or install the OpenSSL library with
QUIC support into the system, or build the OpenSSL library with QUIC support
statically from the source with nginx by using --with-openssl=<path> option.
END
exit 1
fi
fi
fi
fi

View File

@ -6,9 +6,10 @@
echo "creating $NGX_MAKEFILE"
mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \
$NGX_OBJS/src/event/quic \
$NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \
$NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \
$NGX_OBJS/src/http/modules/perl \
$NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \
$NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \
$NGX_OBJS/src/mail \
$NGX_OBJS/src/stream \
$NGX_OBJS/src/misc

View File

@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then
fi
if [ $HTTP_V2 = YES ]; then
if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then
HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS"
fi
@ -124,6 +124,7 @@ if [ $HTTP = YES ]; then
# ngx_http_header_filter
# ngx_http_chunked_filter
# ngx_http_v2_filter
# ngx_http_v3_filter
# ngx_http_range_header_filter
# ngx_http_gzip_filter
# ngx_http_postpone_filter
@ -156,6 +157,7 @@ if [ $HTTP = YES ]; then
ngx_http_header_filter_module \
ngx_http_chunked_filter_module \
ngx_http_v2_filter_module \
ngx_http_v3_filter_module \
ngx_http_range_header_filter_module \
ngx_http_gzip_filter_module \
ngx_http_postpone_filter_module \
@ -217,6 +219,17 @@ if [ $HTTP = YES ]; then
. auto/module
fi
if [ $HTTP_V3 = YES ]; then
ngx_module_name=ngx_http_v3_filter_module
ngx_module_incs=
ngx_module_deps=
ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c
ngx_module_libs=
ngx_module_link=$HTTP_V3
. auto/module
fi
if :; then
ngx_module_name=ngx_http_range_header_filter_module
ngx_module_incs=
@ -426,6 +439,33 @@ if [ $HTTP = YES ]; then
. auto/module
fi
if [ $HTTP_V3 = YES ]; then
USE_OPENSSL_QUIC=YES
HTTP_SSL=YES
have=NGX_HTTP_V3 . auto/have
have=NGX_HTTP_HEADERS . auto/have
ngx_module_name=ngx_http_v3_module
ngx_module_incs=src/http/v3
ngx_module_deps="src/http/v3/ngx_http_v3.h \
src/http/v3/ngx_http_v3_encode.h \
src/http/v3/ngx_http_v3_parse.h \
src/http/v3/ngx_http_v3_table.h \
src/http/v3/ngx_http_v3_uni.h"
ngx_module_srcs="src/http/v3/ngx_http_v3.c \
src/http/v3/ngx_http_v3_encode.c \
src/http/v3/ngx_http_v3_parse.c \
src/http/v3/ngx_http_v3_table.c \
src/http/v3/ngx_http_v3_uni.c \
src/http/v3/ngx_http_v3_request.c \
src/http/v3/ngx_http_v3_module.c"
ngx_module_libs=
ngx_module_link=$HTTP_V3
. auto/module
fi
if :; then
ngx_module_name=ngx_http_static_module
ngx_module_incs=
@ -1272,6 +1312,63 @@ if [ $USE_OPENSSL = YES ]; then
fi
if [ $USE_OPENSSL_QUIC = YES ]; then
ngx_module_type=CORE
ngx_module_name=ngx_quic_module
ngx_module_incs=
ngx_module_deps="src/event/quic/ngx_event_quic.h \
src/event/quic/ngx_event_quic_transport.h \
src/event/quic/ngx_event_quic_protection.h \
src/event/quic/ngx_event_quic_connection.h \
src/event/quic/ngx_event_quic_frames.h \
src/event/quic/ngx_event_quic_connid.h \
src/event/quic/ngx_event_quic_migration.h \
src/event/quic/ngx_event_quic_streams.h \
src/event/quic/ngx_event_quic_ssl.h \
src/event/quic/ngx_event_quic_tokens.h \
src/event/quic/ngx_event_quic_ack.h \
src/event/quic/ngx_event_quic_output.h \
src/event/quic/ngx_event_quic_socket.h \
src/event/quic/ngx_event_quic_openssl_compat.h"
ngx_module_srcs="src/event/quic/ngx_event_quic.c \
src/event/quic/ngx_event_quic_udp.c \
src/event/quic/ngx_event_quic_transport.c \
src/event/quic/ngx_event_quic_protection.c \
src/event/quic/ngx_event_quic_frames.c \
src/event/quic/ngx_event_quic_connid.c \
src/event/quic/ngx_event_quic_migration.c \
src/event/quic/ngx_event_quic_streams.c \
src/event/quic/ngx_event_quic_ssl.c \
src/event/quic/ngx_event_quic_tokens.c \
src/event/quic/ngx_event_quic_ack.c \
src/event/quic/ngx_event_quic_output.c \
src/event/quic/ngx_event_quic_socket.c \
src/event/quic/ngx_event_quic_openssl_compat.c"
ngx_module_libs=
ngx_module_link=YES
ngx_module_order=
. auto/module
if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then
ngx_module_type=CORE
ngx_module_name=ngx_quic_bpf_module
ngx_module_incs=
ngx_module_deps=
ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \
src/event/quic/ngx_event_quic_bpf_code.c"
ngx_module_libs=
ngx_module_link=YES
ngx_module_order=
. auto/module
have=NGX_QUIC_BPF . auto/have
fi
fi
if [ $USE_PCRE = YES ]; then
ngx_module_type=CORE
ngx_module_name=ngx_regex_module

View File

@ -45,6 +45,8 @@ USE_THREADS=NO
NGX_FILE_AIO=NO
QUIC_BPF=NO
HTTP=YES
NGX_HTTP_LOG_PATH=
@ -59,6 +61,7 @@ HTTP_CHARSET=YES
HTTP_GZIP=YES
HTTP_SSL=NO
HTTP_V2=NO
HTTP_V3=NO
HTTP_SSI=YES
HTTP_REALIP=NO
HTTP_XSLT=NO
@ -149,6 +152,7 @@ PCRE_JIT=NO
PCRE2=YES
USE_OPENSSL=NO
USE_OPENSSL_QUIC=NO
OPENSSL=NONE
USE_ZLIB=NO
@ -166,6 +170,8 @@ USE_GEOIP=NO
NGX_GOOGLE_PERFTOOLS=NO
NGX_CPP_TEST=NO
SO_COOKIE_FOUND=NO
NGX_LIBATOMIC=NO
NGX_CPU_CACHE_LINE=
@ -211,6 +217,8 @@ do
--with-file-aio) NGX_FILE_AIO=YES ;;
--without-quic_bpf_module) QUIC_BPF=NONE ;;
--with-ipv6)
NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
$0: warning: the \"--with-ipv6\" option is deprecated"
@ -228,6 +236,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated"
--with-http_ssl_module) HTTP_SSL=YES ;;
--with-http_v2_module) HTTP_V2=YES ;;
--with-http_v3_module) HTTP_V3=YES ;;
--with-http_realip_module) HTTP_REALIP=YES ;;
--with-http_addition_module) HTTP_ADDITION=YES ;;
--with-http_xslt_module) HTTP_XSLT=YES ;;
@ -443,8 +452,11 @@ cat << END
--with-file-aio enable file AIO support
--without-quic_bpf_module disable ngx_quic_bpf_module
--with-http_ssl_module enable ngx_http_ssl_module
--with-http_v2_module enable ngx_http_v2_module
--with-http_v3_module enable ngx_http_v3_module
--with-http_realip_module enable ngx_http_realip_module
--with-http_addition_module enable ngx_http_addition_module
--with-http_xslt_module enable ngx_http_xslt_module

View File

@ -232,6 +232,50 @@ ngx_feature_test="struct crypt_data cd;
ngx_include="sys/vfs.h"; . auto/include
# BPF sockhash
ngx_feature="BPF sockhash"
ngx_feature_name="NGX_HAVE_BPF"
ngx_feature_run=no
ngx_feature_incs="#include <linux/bpf.h>
#include <sys/syscall.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="union bpf_attr attr = { 0 };
attr.map_flags = 0;
attr.map_type = BPF_MAP_TYPE_SOCKHASH;
syscall(__NR_bpf, 0, &attr, 0);"
. auto/feature
if [ $ngx_found = yes ]; then
CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c"
CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h"
if [ $QUIC_BPF != NONE ]; then
QUIC_BPF=YES
fi
fi
ngx_feature="SO_COOKIE"
ngx_feature_name="NGX_HAVE_SO_COOKIE"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
$NGX_INCLUDE_INTTYPES_H"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="socklen_t optlen = sizeof(uint64_t);
uint64_t cookie;
getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)"
. auto/feature
if [ $ngx_found = yes ]; then
SO_COOKIE_FOUND=YES
fi
# UDP segmentation offloading
ngx_feature="UDP_SEGMENT"

View File

@ -83,7 +83,7 @@ CORE_SRCS="src/core/nginx.c \
EVENT_MODULES="ngx_events_module ngx_event_core_module"
EVENT_INCS="src/event src/event/modules"
EVENT_INCS="src/event src/event/modules src/event/quic"
EVENT_DEPS="src/event/ngx_event.h \
src/event/ngx_event_timer.h \

View File

@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_IPV6, IPV6_RECVPKTINFO, NULL, 0)"
. auto/feature
# IP packet fragmentation
ngx_feature="IP_MTU_DISCOVER"
ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
#include <netinet/in.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="(void) IP_PMTUDISC_DO;
setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)"
. auto/feature
ngx_feature="IPV6_MTU_DISCOVER"
ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
#include <netinet/in.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="(void) IPV6_PMTUDISC_DO;
setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)"
. auto/feature
ngx_feature="IP_DONTFRAG"
ngx_feature_name="NGX_HAVE_IP_DONTFRAG"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
#include <netinet/in.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)"
. auto/feature
ngx_feature="IPV6_DONTFRAG"
ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
#include <netinet/in.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)"
. auto/feature
ngx_feature="TCP_DEFER_ACCEPT"
ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT"
ngx_feature_run=no

View File

@ -680,6 +680,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv)
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
if (ls[i].ignore) {
continue;
}
p = ngx_sprintf(p, "%ud;", ls[i].fd);
}

143
src/core/ngx_bpf.c Normal file
View File

@ -0,0 +1,143 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_BPF_LOGBUF_SIZE (16 * 1024)
static ngx_inline int
ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
{
return syscall(__NR_bpf, cmd, attr, size);
}
void
ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd)
{
ngx_uint_t i;
ngx_bpf_reloc_t *rl;
rl = program->relocs;
for (i = 0; i < program->nrelocs; i++) {
if (ngx_strcmp(rl[i].name, symbol) == 0) {
program->ins[rl[i].offset].src_reg = 1;
program->ins[rl[i].offset].imm = fd;
}
}
}
int
ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program)
{
int fd;
union bpf_attr attr;
#if (NGX_DEBUG)
char buf[NGX_BPF_LOGBUF_SIZE];
#endif
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.license = (uintptr_t) program->license;
attr.prog_type = program->type;
attr.insns = (uintptr_t) program->ins;
attr.insn_cnt = program->nins;
#if (NGX_DEBUG)
/* for verifier errors */
attr.log_buf = (uintptr_t) buf;
attr.log_size = NGX_BPF_LOGBUF_SIZE;
attr.log_level = 1;
#endif
fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
if (fd < 0) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"failed to load BPF program");
ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
"bpf verifier: %s", buf);
return -1;
}
return fd;
}
int
ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
int value_size, int max_entries, uint32_t map_flags)
{
int fd;
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_type = type;
attr.key_size = key_size;
attr.value_size = value_size;
attr.max_entries = max_entries;
attr.map_flags = map_flags;
fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
if (fd < 0) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"failed to create BPF map");
return NGX_ERROR;
}
return fd;
}
int
ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags)
{
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_fd = fd;
attr.key = (uintptr_t) key;
attr.value = (uintptr_t) value;
attr.flags = flags;
return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}
int
ngx_bpf_map_delete(int fd, const void *key)
{
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_fd = fd;
attr.key = (uintptr_t) key;
return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
}
int
ngx_bpf_map_lookup(int fd, const void *key, void *value)
{
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_fd = fd;
attr.key = (uintptr_t) key;
attr.value = (uintptr_t) value;
return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}

43
src/core/ngx_bpf.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_BPF_H_INCLUDED_
#define _NGX_BPF_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <linux/bpf.h>
typedef struct {
char *name;
int offset;
} ngx_bpf_reloc_t;
typedef struct {
char *license;
enum bpf_prog_type type;
struct bpf_insn *ins;
size_t nins;
ngx_bpf_reloc_t *relocs;
size_t nrelocs;
} ngx_bpf_program_t;
void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol,
int fd);
int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program);
int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
int value_size, int max_entries, uint32_t map_flags);
int ngx_bpf_map_update(int fd, const void *key, const void *value,
uint64_t flags);
int ngx_bpf_map_delete(int fd, const void *key);
int ngx_bpf_map_lookup(int fd, const void *key, void *value);
#endif /* _NGX_BPF_H_INCLUDED_ */

View File

@ -1013,6 +1013,78 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle)
}
}
#endif
#if (NGX_HAVE_IP_MTU_DISCOVER)
if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) {
value = IP_PMTUDISC_DO;
if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(IP_MTU_DISCOVER) "
"for %V failed, ignored",
&ls[i].addr_text);
}
}
#elif (NGX_HAVE_IP_DONTFRAG)
if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) {
value = 1;
if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(IP_DONTFRAG) "
"for %V failed, ignored",
&ls[i].addr_text);
}
}
#endif
#if (NGX_HAVE_INET6)
#if (NGX_HAVE_IPV6_MTU_DISCOVER)
if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) {
value = IPV6_PMTUDISC_DO;
if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(IPV6_MTU_DISCOVER) "
"for %V failed, ignored",
&ls[i].addr_text);
}
}
#elif (NGX_HAVE_IP_DONTFRAG)
if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) {
value = 1;
if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG,
(const void *) &value, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(IPV6_DONTFRAG) "
"for %V failed, ignored",
&ls[i].addr_text);
}
}
#endif
#endif
}
@ -1037,6 +1109,12 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle)
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
#if (NGX_QUIC)
if (ls[i].quic) {
continue;
}
#endif
c = ls[i].connection;
if (c) {

View File

@ -73,6 +73,7 @@ struct ngx_listening_s {
unsigned reuseport:1;
unsigned add_reuseport:1;
unsigned keepalive:2;
unsigned quic:1;
unsigned deferred_accept:1;
unsigned delete_deferred:1;
@ -147,6 +148,10 @@ struct ngx_connection_s {
ngx_proxy_protocol_t *proxy_protocol;
#if (NGX_QUIC || NGX_COMPAT)
ngx_quic_stream_t *quic;
#endif
#if (NGX_SSL || NGX_COMPAT)
ngx_ssl_connection_t *ssl;
#endif

View File

@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx_connection_t;
typedef struct ngx_thread_task_s ngx_thread_task_t;
typedef struct ngx_ssl_s ngx_ssl_t;
typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;
typedef struct ngx_quic_stream_s ngx_quic_stream_t;
typedef struct ngx_ssl_connection_s ngx_ssl_connection_t;
typedef struct ngx_udp_connection_s ngx_udp_connection_t;
@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
#include <ngx_resolver.h>
#if (NGX_OPENSSL)
#include <ngx_event_openssl.h>
#if (NGX_QUIC)
#include <ngx_event_quic.h>
#endif
#endif
#include <ngx_process_cycle.h>
#include <ngx_conf_file.h>
@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
#include <ngx_connection.h>
#include <ngx_syslog.h>
#include <ngx_proxy_protocol.h>
#if (NGX_HAVE_BPF)
#include <ngx_bpf.h>
#endif
#define LF (u_char) '\n'

View File

@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle)
ngx_int_t
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
#if (NGX_QUIC)
ngx_connection_t *c;
c = rev->data;
if (c->quic) {
return NGX_OK;
}
#endif
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
{
ngx_connection_t *c;
if (lowat) {
c = wev->data;
c = wev->data;
#if (NGX_QUIC)
if (c->quic) {
return NGX_OK;
}
#endif
if (lowat) {
if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
return NGX_ERROR;
}
@ -873,8 +891,16 @@ ngx_event_process_init(ngx_cycle_t *cycle)
#else
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg;
if (c->type == SOCK_STREAM) {
rev->handler = ngx_event_accept;
#if (NGX_QUIC)
} else if (ls[i].quic) {
rev->handler = ngx_quic_recvmsg;
#endif
} else {
rev->handler = ngx_event_recvmsg;
}
#if (NGX_HAVE_REUSEPORT)

View File

@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn,
#ifdef SSL_READ_EARLY_DATA_SUCCESS
static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c);
#endif
#if (NGX_DEBUG)
static void ngx_ssl_handshake_log(ngx_connection_t *c);
#endif
static void ngx_ssl_handshake_handler(ngx_event_t *ev);
#ifdef SSL_READ_EARLY_DATA_SUCCESS
static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf,
@ -2052,7 +2049,7 @@ ngx_ssl_try_early_data(ngx_connection_t *c)
#if (NGX_DEBUG)
static void
void
ngx_ssl_handshake_log(ngx_connection_t *c)
{
char buf[129], *s, *d;
@ -3202,6 +3199,13 @@ ngx_ssl_shutdown(ngx_connection_t *c)
ngx_err_t err;
ngx_uint_t tries;
#if (NGX_QUIC)
if (c->quic) {
/* QUIC streams inherit SSL object */
return NGX_OK;
}
#endif
rc = NGX_OK;
ngx_ssl_ocsp_cleanup(c);

View File

@ -24,6 +24,14 @@
#include <openssl/engine.h>
#endif
#include <openssl/evp.h>
#if (NGX_QUIC)
#ifdef OPENSSL_IS_BORINGSSL
#include <openssl/hkdf.h>
#include <openssl/chacha.h>
#else
#include <openssl/kdf.h>
#endif
#endif
#include <openssl/hmac.h>
#ifndef OPENSSL_NO_OCSP
#include <openssl/ocsp.h>
@ -302,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool,
ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
#if (NGX_DEBUG)
void ngx_ssl_handshake_log(ngx_connection_t *c);
#endif
ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit);

View File

@ -12,13 +12,6 @@
#if !(NGX_WIN32)
struct ngx_udp_connection_s {
ngx_rbtree_node_t node;
ngx_connection_t *connection;
ngx_buf_t *buffer;
};
static void ngx_close_accepted_udp_connection(ngx_connection_t *c);
static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf,
size_t size);
@ -424,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp,
udpt = (ngx_udp_connection_t *) temp;
ct = udpt->connection;
rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen,
ct->sockaddr, ct->socklen, 1);
rc = ngx_memn2cmp(udp->key.data, udpt->key.data,
udp->key.len, udpt->key.len);
if (rc == 0 && c->listening->wildcard) {
rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen,
@ -478,6 +471,8 @@ ngx_insert_udp_connection(ngx_connection_t *c)
ngx_crc32_final(hash);
udp->node.key = hash;
udp->key.data = (u_char *) c->sockaddr;
udp->key.len = c->socklen;
cln = ngx_pool_cleanup_add(c->pool, 0);
if (cln == NULL) {

View File

@ -23,6 +23,14 @@
#endif
struct ngx_udp_connection_s {
ngx_rbtree_node_t node;
ngx_connection_t *connection;
ngx_buf_t *buffer;
ngx_str_t key;
};
#if (NGX_HAVE_ADDRINFO_CMSG)
typedef union {

View File

@ -0,0 +1,113 @@
#!/bin/bash
export LANG=C
set -e
if [ $# -lt 1 ]; then
echo "Usage: PROGNAME=foo LICENSE=bar $0 <bpf object file>"
exit 1
fi
self=$0
filename=$1
funcname=$PROGNAME
generate_head()
{
cat << END
/* AUTO-GENERATED, DO NOT EDIT. */
#include <stddef.h>
#include <stdint.h>
#include "ngx_bpf.h"
END
}
generate_tail()
{
cat << END
ngx_bpf_program_t $PROGNAME = {
.relocs = bpf_reloc_prog_$funcname,
.nrelocs = sizeof(bpf_reloc_prog_$funcname)
/ sizeof(bpf_reloc_prog_$funcname[0]),
.ins = bpf_insn_prog_$funcname,
.nins = sizeof(bpf_insn_prog_$funcname)
/ sizeof(bpf_insn_prog_$funcname[0]),
.license = "$LICENSE",
.type = BPF_PROG_TYPE_SK_REUSEPORT,
};
END
}
process_relocations()
{
echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {"
objdump -r $filename | awk '{
if (enabled && $NF > 0) {
off = strtonum(sprintf("0x%s", $1));
name = $3;
printf(" { \"%s\", %d },\n", name, off/8);
}
if ($1 == "OFFSET") {
enabled=1;
}
}'
echo "};"
echo
}
process_section()
{
echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {"
echo " /* opcode dst src offset imm */"
section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname")
# dd doesn't know hex
length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3))
offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6))
for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8)
do
opcode=0x${ins:0:2}
srcdst=0x${ins:2:2}
# bytes are dumped in LE order
offset=0x${ins:6:2}${ins:4:2} # short
immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int
dst="$(($srcdst & 0xF))"
src="$(($srcdst & 0xF0))"
src="$(($src >> 4))"
opcode=$(printf "0x%x" $opcode)
dst=$(printf "BPF_REG_%d" $dst)
src=$(printf "BPF_REG_%d" $src)
offset=$(printf "%d" $offset)
immedi=$(printf "0x%x" $immedi)
printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi
done
cat << END
};
END
}
generate_head
process_relocations
process_section
generate_tail

View File

@ -0,0 +1,30 @@
CFLAGS=-O2 -Wall
LICENSE=BSD
PROGNAME=ngx_quic_reuseport_helper
RESULT=ngx_event_quic_bpf_code
DEST=../$(RESULT).c
all: $(RESULT)
$(RESULT): $(PROGNAME).o
LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@
DEFS=-DPROGNAME=\"$(PROGNAME)\" \
-DLICENSE_$(LICENSE) \
-DLICENSE=\"$(LICENSE)\" \
$(PROGNAME).o: $(PROGNAME).c
clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@
install: $(RESULT)
cp $(RESULT) $(DEST)
clean:
@rm -f $(RESULT) *.o
debug: $(PROGNAME).o
llvm-objdump -S -no-show-raw-insn $<
.DELETE_ON_ERROR:

View File

@ -0,0 +1,140 @@
#include <errno.h>
#include <linux/string.h>
#include <linux/udp.h>
#include <linux/bpf.h>
/*
* the bpf_helpers.h is not included into linux-headers, only available
* with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf.
*/
#include <bpf/bpf_helpers.h>
#if !defined(SEC)
#define SEC(NAME) __attribute__((section(NAME), used))
#endif
#if defined(LICENSE_GPL)
/*
* To see debug:
*
* echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable
* cat /sys/kernel/debug/tracing/trace_pipe
* echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable
*/
#define debugmsg(fmt, ...) \
do { \
char __buf[] = fmt; \
bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \
} while (0)
#else
#define debugmsg(fmt, ...)
#endif
char _license[] SEC("license") = LICENSE;
/*****************************************************************************/
#define NGX_QUIC_PKT_LONG 0x80 /* header form */
#define NGX_QUIC_SERVER_CID_LEN 20
#define advance_data(nbytes) \
offset += nbytes; \
if (start + offset > end) { \
debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \
goto failed; \
} \
data = start + offset - 1;
#define ngx_quic_parse_uint64(p) \
(((__u64)(p)[0] << 56) | \
((__u64)(p)[1] << 48) | \
((__u64)(p)[2] << 40) | \
((__u64)(p)[3] << 32) | \
((__u64)(p)[4] << 24) | \
((__u64)(p)[5] << 16) | \
((__u64)(p)[6] << 8) | \
((__u64)(p)[7]))
/*
* actual map object is created by the "bpf" system call,
* all pointers to this variable are replaced by the bpf loader
*/
struct bpf_map_def SEC("maps") ngx_quic_sockmap;
SEC(PROGNAME)
int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx)
{
int rc;
__u64 key;
size_t len, offset;
unsigned char *start, *end, *data, *dcid;
start = ctx->data;
end = (unsigned char *) ctx->data_end;
offset = 0;
advance_data(sizeof(struct udphdr)); /* data at UDP header */
advance_data(1); /* data at QUIC flags */
if (data[0] & NGX_QUIC_PKT_LONG) {
advance_data(4); /* data at QUIC version */
advance_data(1); /* data at DCID len */
len = data[0]; /* read DCID length */
if (len < 8) {
/* it's useless to search for key in such short DCID */
return SK_PASS;
}
} else {
len = NGX_QUIC_SERVER_CID_LEN;
}
dcid = &data[1];
advance_data(len); /* we expect the packet to have full DCID */
/* make verifier happy */
if (dcid + sizeof(__u64) > end) {
goto failed;
}
key = ngx_quic_parse_uint64(dcid);
rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0);
switch (rc) {
case 0:
debugmsg("nginx quic socket selected by key 0x%llx", key);
return SK_PASS;
/* kernel returns positive error numbers, errno.h defines positive */
case -ENOENT:
debugmsg("nginx quic default route for key 0x%llx", key);
/* let the default reuseport logic decide which socket to choose */
return SK_PASS;
default:
debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx",
rc, key);
goto failed;
}
failed:
/*
* SK_DROP will generate ICMP, but we may want to process "invalid" packet
* in userspace quic to investigate further and finally react properly
* (maybe ignore, maybe send something in response or close connection)
*/
return SK_PASS;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
#define _NGX_EVENT_QUIC_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527
#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32
#define NGX_QUIC_SR_KEY_LEN 32
#define NGX_QUIC_AV_KEY_LEN 32
#define NGX_QUIC_SR_TOKEN_LEN 16
#define NGX_QUIC_MIN_INITIAL_SIZE 1200
#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01
#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02
typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c);
typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c);
typedef enum {
NGX_QUIC_STREAM_SEND_READY = 0,
NGX_QUIC_STREAM_SEND_SEND,
NGX_QUIC_STREAM_SEND_DATA_SENT,
NGX_QUIC_STREAM_SEND_DATA_RECVD,
NGX_QUIC_STREAM_SEND_RESET_SENT,
NGX_QUIC_STREAM_SEND_RESET_RECVD
} ngx_quic_stream_send_state_e;
typedef enum {
NGX_QUIC_STREAM_RECV_RECV = 0,
NGX_QUIC_STREAM_RECV_SIZE_KNOWN,
NGX_QUIC_STREAM_RECV_DATA_RECVD,
NGX_QUIC_STREAM_RECV_DATA_READ,
NGX_QUIC_STREAM_RECV_RESET_RECVD,
NGX_QUIC_STREAM_RECV_RESET_READ
} ngx_quic_stream_recv_state_e;
typedef struct {
uint64_t size;
uint64_t offset;
uint64_t last_offset;
ngx_chain_t *chain;
ngx_chain_t *last_chain;
} ngx_quic_buffer_t;
typedef struct {
ngx_ssl_t *ssl;
ngx_flag_t retry;
ngx_flag_t gso_enabled;
ngx_flag_t disable_active_migration;
ngx_msec_t timeout;
ngx_str_t host_key;
size_t stream_buffer_size;
ngx_uint_t max_concurrent_streams_bidi;
ngx_uint_t max_concurrent_streams_uni;
ngx_uint_t active_connection_id_limit;
ngx_int_t stream_close_code;
ngx_int_t stream_reject_code_uni;
ngx_int_t stream_reject_code_bidi;
ngx_quic_init_pt init;
ngx_quic_shutdown_pt shutdown;
u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
} ngx_quic_conf_t;
struct ngx_quic_stream_s {
ngx_rbtree_node_t node;
ngx_queue_t queue;
ngx_connection_t *parent;
ngx_connection_t *connection;
uint64_t id;
uint64_t sent;
uint64_t acked;
uint64_t send_max_data;
uint64_t send_offset;
uint64_t send_final_size;
uint64_t recv_max_data;
uint64_t recv_offset;
uint64_t recv_window;
uint64_t recv_last;
uint64_t recv_final_size;
ngx_quic_buffer_t send;
ngx_quic_buffer_t recv;
ngx_quic_stream_send_state_e send_state;
ngx_quic_stream_recv_state_e recv_state;
unsigned cancelable:1;
unsigned fin_acked:1;
};
void ngx_quic_recvmsg(ngx_event_t *ev);
void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf);
ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi);
void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
const char *reason);
void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err,
const char *reason);
ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err);
ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how);
void ngx_quic_cancelable_stream(ngx_connection_t *c);
ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len,
ngx_str_t *dcid);
ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label,
ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len);
#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_
#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_frame_t *f);
void ngx_quic_congestion_ack(ngx_connection_t *c,
ngx_quic_frame_t *frame);
void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
void ngx_quic_set_lost_timer(ngx_connection_t *c);
void ngx_quic_pto_handler(ngx_event_t *ev);
ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c,
ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx);
#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */

View File

@ -0,0 +1,657 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS"
#define NGX_QUIC_BPF_VARSEP ';'
#define NGX_QUIC_BPF_ADDRSEP '#'
#define ngx_quic_bpf_get_conf(cycle) \
(ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module)
#define ngx_quic_bpf_get_old_conf(cycle) \
cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \
: NULL
#define ngx_core_get_conf(cycle) \
(ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module)
typedef struct {
ngx_queue_t queue;
int map_fd;
struct sockaddr *sockaddr;
socklen_t socklen;
ngx_uint_t unused; /* unsigned unused:1; */
} ngx_quic_sock_group_t;
typedef struct {
ngx_flag_t enabled;
ngx_uint_t map_size;
ngx_queue_t groups; /* of ngx_quic_sock_group_t */
} ngx_quic_bpf_conf_t;
static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle);
static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle);
static void ngx_quic_bpf_cleanup(void *data);
static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd,
const char *name);
static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf,
ngx_listening_t *ls);
static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle,
struct sockaddr *sa, socklen_t socklen);
static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle,
ngx_listening_t *ls);
static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle,
ngx_listening_t *ls);
static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle,
ngx_listening_t *ls);
static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log);
static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle);
static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle);
extern ngx_bpf_program_t ngx_quic_reuseport_helper;
static ngx_command_t ngx_quic_bpf_commands[] = {
{ ngx_string("quic_bpf"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_quic_bpf_conf_t, enabled),
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_quic_bpf_module_ctx = {
ngx_string("quic_bpf"),
ngx_quic_bpf_create_conf,
NULL
};
ngx_module_t ngx_quic_bpf_module = {
NGX_MODULE_V1,
&ngx_quic_bpf_module_ctx, /* module context */
ngx_quic_bpf_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
ngx_quic_bpf_module_init, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void *
ngx_quic_bpf_create_conf(ngx_cycle_t *cycle)
{
ngx_quic_bpf_conf_t *bcf;
bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t));
if (bcf == NULL) {
return NULL;
}
bcf->enabled = NGX_CONF_UNSET;
bcf->map_size = NGX_CONF_UNSET_UINT;
ngx_queue_init(&bcf->groups);
return bcf;
}
static ngx_int_t
ngx_quic_bpf_module_init(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_listening_t *ls;
ngx_core_conf_t *ccf;
ngx_pool_cleanup_t *cln;
ngx_quic_bpf_conf_t *bcf;
if (ngx_test_config) {
/*
* during config test, SO_REUSEPORT socket option is
* not set, thus making further processing meaningless
*/
return NGX_OK;
}
ccf = ngx_core_get_conf(cycle);
bcf = ngx_quic_bpf_get_conf(cycle);
ngx_conf_init_value(bcf->enabled, 0);
bcf->map_size = ccf->worker_processes * 4;
cln = ngx_pool_cleanup_add(cycle->pool, 0);
if (cln == NULL) {
goto failed;
}
cln->data = bcf;
cln->handler = ngx_quic_bpf_cleanup;
if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) {
if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) {
goto failed;
}
}
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
if (ls[i].quic && ls[i].reuseport) {
if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) {
goto failed;
}
}
}
if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) {
goto failed;
}
return NGX_OK;
failed:
if (ngx_is_init_cycle(cycle->old_cycle)) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"ngx_quic_bpf_module failed to initialize, check limits");
/* refuse to start */
return NGX_ERROR;
}
/*
* returning error now will lead to master process exiting immediately
* leaving worker processes orphaned, what is really unexpected.
* Instead, just issue a not about failed initialization and try
* to cleanup a bit. Still program can be already loaded to kernel
* for some reuseport groups, and there is no way to revert, so
* behaviour may be inconsistent.
*/
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"ngx_quic_bpf_module failed to initialize properly, ignored."
"please check limits and note that nginx state now "
"can be inconsistent and restart may be required");
return NGX_OK;
}
static void
ngx_quic_bpf_cleanup(void *data)
{
ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data;
ngx_queue_t *q;
ngx_quic_sock_group_t *grp;
for (q = ngx_queue_head(&bcf->groups);
q != ngx_queue_sentinel(&bcf->groups);
q = ngx_queue_next(q))
{
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map");
}
}
static ngx_inline void
ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name)
{
if (close(fd) != -1) {
return;
}
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"quic bpf close %s fd:%d failed", name, fd);
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls)
{
ngx_queue_t *q;
ngx_quic_sock_group_t *grp;
for (q = ngx_queue_head(&bcf->groups);
q != ngx_queue_sentinel(&bcf->groups);
q = ngx_queue_next(q))
{
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen,
grp->sockaddr, grp->socklen, 1)
== NGX_OK)
{
return grp;
}
}
return NULL;
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa,
socklen_t socklen)
{
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
bcf = ngx_quic_bpf_get_conf(cycle);
grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t));
if (grp == NULL) {
return NULL;
}
grp->socklen = socklen;
grp->sockaddr = ngx_palloc(cycle->pool, socklen);
if (grp->sockaddr == NULL) {
return NULL;
}
ngx_memcpy(grp->sockaddr, sa, socklen);
ngx_queue_insert_tail(&bcf->groups, &grp->queue);
return grp;
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
int progfd, failed, flags, rc;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
bcf = ngx_quic_bpf_get_conf(cycle);
if (!bcf->enabled) {
return NULL;
}
grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
if (grp == NULL) {
return NULL;
}
grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH,
sizeof(uint64_t), sizeof(uint64_t),
bcf->map_size, 0);
if (grp->map_fd == -1) {
goto failed;
}
flags = fcntl(grp->map_fd, F_GETFD);
if (flags == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
"quic bpf getfd failed");
goto failed;
}
/* need to inherit map during binary upgrade after exec */
flags &= ~FD_CLOEXEC;
rc = fcntl(grp->map_fd, F_SETFD, flags);
if (rc == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
"quic bpf setfd failed");
goto failed;
}
ngx_bpf_program_link(&ngx_quic_reuseport_helper,
"ngx_quic_sockmap", grp->map_fd);
progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper);
if (progfd < 0) {
goto failed;
}
failed = 0;
if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF,
&progfd, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
"quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed");
failed = 1;
}
ngx_quic_bpf_close(cycle->log, progfd, "program");
if (failed) {
goto failed;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap created fd:%d", grp->map_fd);
return grp;
failed:
if (grp->map_fd != -1) {
ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
}
ngx_queue_remove(&grp->queue);
return NULL;
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
ngx_quic_bpf_conf_t *bcf, *old_bcf;
ngx_quic_sock_group_t *grp, *ogrp;
bcf = ngx_quic_bpf_get_conf(cycle);
grp = ngx_quic_bpf_find_group(bcf, ls);
if (grp) {
return grp;
}
old_bcf = ngx_quic_bpf_get_old_conf(cycle);
if (old_bcf == NULL) {
return ngx_quic_bpf_create_group(cycle, ls);
}
ogrp = ngx_quic_bpf_find_group(old_bcf, ls);
if (ogrp == NULL) {
return ngx_quic_bpf_create_group(cycle, ls);
}
grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
if (grp == NULL) {
return NULL;
}
grp->map_fd = dup(ogrp->map_fd);
if (grp->map_fd == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"quic bpf failed to duplicate bpf map descriptor");
ngx_queue_remove(&grp->queue);
return NULL;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap fd duplicated old:%d new:%d",
ogrp->map_fd, grp->map_fd);
return grp;
}
static ngx_int_t
ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
uint64_t cookie;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
bcf = ngx_quic_bpf_get_conf(cycle);
grp = ngx_quic_bpf_get_group(cycle, ls);
if (grp == NULL) {
if (!bcf->enabled) {
return NGX_OK;
}
return NGX_ERROR;
}
grp->unused = 0;
cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log);
if (cookie == (uint64_t) NGX_ERROR) {
return NGX_ERROR;
}
/* map[cookie] = socket; for use in kernel helper */
if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"quic bpf failed to update socket map key=%xL", cookie);
return NGX_ERROR;
}
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui",
grp->map_fd, ls->fd, cookie, ls->worker);
/* do not inherit this socket */
ls->ignore = 1;
return NGX_OK;
}
static uint64_t
ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log)
{
uint64_t cookie;
socklen_t optlen;
optlen = sizeof(cookie);
if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"quic bpf getsockopt(SO_COOKIE) failed");
return (ngx_uint_t) NGX_ERROR;
}
return cookie;
}
static ngx_int_t
ngx_quic_bpf_export_maps(ngx_cycle_t *cycle)
{
u_char *p, *buf;
size_t len;
ngx_str_t *var;
ngx_queue_t *q;
ngx_core_conf_t *ccf;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
ccf = ngx_core_get_conf(cycle);
bcf = ngx_quic_bpf_get_conf(cycle);
len = sizeof(NGX_QUIC_BPF_VARNAME) + 1;
q = ngx_queue_head(&bcf->groups);
while (q != ngx_queue_sentinel(&bcf->groups)) {
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
q = ngx_queue_next(q);
if (grp->unused) {
/*
* map was inherited, but it is not used in this configuration;
* do not pass such map further and drop the group to prevent
* interference with changes during reload
*/
ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
ngx_queue_remove(&grp->queue);
continue;
}
len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1;
}
len++;
buf = ngx_palloc(cycle->pool, len);
if (buf == NULL) {
return NGX_ERROR;
}
p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=",
sizeof(NGX_QUIC_BPF_VARNAME));
for (q = ngx_queue_head(&bcf->groups);
q != ngx_queue_sentinel(&bcf->groups);
q = ngx_queue_next(q))
{
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
p = ngx_sprintf(p, "%ud", grp->map_fd);
*p++ = NGX_QUIC_BPF_ADDRSEP;
p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p,
NGX_SOCKADDR_STRLEN, 1);
*p++ = NGX_QUIC_BPF_VARSEP;
}
*p = '\0';
var = ngx_array_push(&ccf->env);
if (var == NULL) {
return NGX_ERROR;
}
var->data = buf;
var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1;
return NGX_OK;
}
static ngx_int_t
ngx_quic_bpf_import_maps(ngx_cycle_t *cycle)
{
int s;
u_char *inherited, *p, *v;
ngx_uint_t in_fd;
ngx_addr_t tmp;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME);
if (inherited == NULL) {
return NGX_OK;
}
bcf = ngx_quic_bpf_get_conf(cycle);
#if (NGX_SUPPRESS_WARN)
s = -1;
#endif
in_fd = 1;
for (p = inherited, v = p; *p; p++) {
switch (*p) {
case NGX_QUIC_BPF_ADDRSEP:
if (!in_fd) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited env");
return NGX_ERROR;
}
in_fd = 0;
s = ngx_atoi(v, p - v);
if (s == NGX_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited map fd");
return NGX_ERROR;
}
v = p + 1;
break;
case NGX_QUIC_BPF_VARSEP:
if (in_fd) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited env");
return NGX_ERROR;
}
in_fd = 1;
grp = ngx_pcalloc(cycle->pool,
sizeof(ngx_quic_sock_group_t));
if (grp == NULL) {
return NGX_ERROR;
}
grp->map_fd = s;
if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited"
" address '%*s'", p - v , v);
ngx_quic_bpf_close(cycle->log, s, "inherited map");
return NGX_ERROR;
}
grp->sockaddr = tmp.sockaddr;
grp->socklen = tmp.socklen;
grp->unused = 1;
ngx_queue_insert_tail(&bcf->groups, &grp->queue);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap inherited with "
"fd:%d address:%*s",
grp->map_fd, p - v, v);
v = p + 1;
break;
default:
break;
}
}
return NGX_OK;
}

View File

@ -0,0 +1,88 @@
/* AUTO-GENERATED, DO NOT EDIT. */
#include <stddef.h>
#include <stdint.h>
#include "ngx_bpf.h"
static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = {
{ "ngx_quic_sockmap", 55 },
};
static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = {
/* opcode dst src offset imm */
{ 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 },
{ 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 },
{ 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 },
{ 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 },
{ 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 },
{ 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 },
{ 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 },
{ 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 },
{ 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
{ 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
{ 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff },
{ 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd },
{ 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 },
{ 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe },
{ 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 },
{ 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe },
{ 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 },
{ 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 },
{ 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 },
{ 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 },
{ 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 },
{ 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 },
{ 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 },
{ 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 },
{ 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 },
{ 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 },
{ 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 },
{ 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 },
{ 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
};
ngx_bpf_program_t ngx_quic_reuseport_helper = {
.relocs = bpf_reloc_prog_ngx_quic_reuseport_helper,
.nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper)
/ sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]),
.ins = bpf_insn_prog_ngx_quic_reuseport_helper,
.nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper)
/ sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]),
.license = "BSD",
.type = BPF_PROG_TYPE_SK_REUSEPORT,
};

View File

@ -0,0 +1,283 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */
/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */
/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */
/* #define NGX_QUIC_DEBUG_CRYPTO */
typedef struct ngx_quic_connection_s ngx_quic_connection_t;
typedef struct ngx_quic_server_id_s ngx_quic_server_id_t;
typedef struct ngx_quic_client_id_s ngx_quic_client_id_t;
typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t;
typedef struct ngx_quic_socket_s ngx_quic_socket_t;
typedef struct ngx_quic_path_s ngx_quic_path_t;
typedef struct ngx_quic_keys_s ngx_quic_keys_t;
#if (NGX_QUIC_OPENSSL_COMPAT)
#include <ngx_event_quic_openssl_compat.h>
#endif
#include <ngx_event_quic_transport.h>
#include <ngx_event_quic_protection.h>
#include <ngx_event_quic_frames.h>
#include <ngx_event_quic_migration.h>
#include <ngx_event_quic_connid.h>
#include <ngx_event_quic_streams.h>
#include <ngx_event_quic_ssl.h>
#include <ngx_event_quic_tokens.h>
#include <ngx_event_quic_ack.h>
#include <ngx_event_quic_output.h>
#include <ngx_event_quic_socket.h>
/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */
#define NGX_QUIC_INITIAL_RTT 333 /* ms */
#define NGX_QUIC_UNSET_PN (uint64_t) -1
#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1)
/* 0-RTT and 1-RTT data exist in the same packet number space,
* so we have 3 packet number spaces:
*
* 0 - Initial
* 1 - Handshake
* 2 - 0-RTT and 1-RTT
*/
#define ngx_quic_get_send_ctx(qc, level) \
((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \
: (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \
: &((qc)->send_ctx[2]))
#define ngx_quic_get_connection(c) \
(((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL)
#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp))
struct ngx_quic_client_id_s {
ngx_queue_t queue;
uint64_t seqnum;
size_t len;
u_char id[NGX_QUIC_CID_LEN_MAX];
u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
ngx_uint_t used; /* unsigned used:1; */
};
struct ngx_quic_server_id_s {
uint64_t seqnum;
size_t len;
u_char id[NGX_QUIC_CID_LEN_MAX];
};
struct ngx_quic_path_s {
ngx_queue_t queue;
struct sockaddr *sockaddr;
ngx_sockaddr_t sa;
socklen_t socklen;
ngx_quic_client_id_t *cid;
ngx_msec_t expires;
ngx_uint_t tries;
ngx_uint_t tag;
off_t sent;
off_t received;
u_char challenge1[8];
u_char challenge2[8];
uint64_t seqnum;
ngx_str_t addr_text;
u_char text[NGX_SOCKADDR_STRLEN];
unsigned validated:1;
unsigned validating:1;
unsigned limited:1;
};
struct ngx_quic_socket_s {
ngx_udp_connection_t udp;
ngx_quic_connection_t *quic;
ngx_queue_t queue;
ngx_quic_server_id_t sid;
ngx_sockaddr_t sockaddr;
socklen_t socklen;
ngx_uint_t used; /* unsigned used:1; */
};
typedef struct {
ngx_rbtree_t tree;
ngx_rbtree_node_t sentinel;
ngx_queue_t uninitialized;
ngx_queue_t free;
uint64_t sent;
uint64_t recv_offset;
uint64_t recv_window;
uint64_t recv_last;
uint64_t recv_max_data;
uint64_t send_offset;
uint64_t send_max_data;
uint64_t server_max_streams_uni;
uint64_t server_max_streams_bidi;
uint64_t server_streams_uni;
uint64_t server_streams_bidi;
uint64_t client_max_streams_uni;
uint64_t client_max_streams_bidi;
uint64_t client_streams_uni;
uint64_t client_streams_bidi;
ngx_uint_t initialized;
/* unsigned initialized:1; */
} ngx_quic_streams_t;
typedef struct {
size_t in_flight;
size_t window;
size_t ssthresh;
ngx_msec_t recovery_start;
} ngx_quic_congestion_t;
/*
* RFC 9000, 12.3. Packet Numbers
*
* Conceptually, a packet number space is the context in which a packet
* can be processed and acknowledged. Initial packets can only be sent
* with Initial packet protection keys and acknowledged in packets that
* are also Initial packets.
*/
struct ngx_quic_send_ctx_s {
enum ssl_encryption_level_t level;
ngx_quic_buffer_t crypto;
uint64_t crypto_sent;
uint64_t pnum; /* to be sent */
uint64_t largest_ack; /* received from peer */
uint64_t largest_pn; /* received from peer */
ngx_queue_t frames; /* generated frames */
ngx_queue_t sending; /* frames assigned to pkt */
ngx_queue_t sent; /* frames waiting ACK */
uint64_t pending_ack; /* non sent ack-eliciting */
uint64_t largest_range;
uint64_t first_range;
ngx_msec_t largest_received;
ngx_msec_t ack_delay_start;
ngx_uint_t nranges;
ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES];
ngx_uint_t send_ack;
};
struct ngx_quic_connection_s {
uint32_t version;
ngx_quic_path_t *path;
ngx_queue_t sockets;
ngx_queue_t paths;
ngx_queue_t client_ids;
ngx_queue_t free_sockets;
ngx_queue_t free_paths;
ngx_queue_t free_client_ids;
ngx_uint_t nsockets;
ngx_uint_t nclient_ids;
uint64_t max_retired_seqnum;
uint64_t client_seqnum;
uint64_t server_seqnum;
uint64_t path_seqnum;
ngx_quic_tp_t tp;
ngx_quic_tp_t ctp;
ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST];
ngx_quic_keys_t *keys;
ngx_quic_conf_t *conf;
ngx_event_t push;
ngx_event_t pto;
ngx_event_t close;
ngx_event_t path_validation;
ngx_msec_t last_cc;
ngx_msec_t first_rtt;
ngx_msec_t latest_rtt;
ngx_msec_t avg_rtt;
ngx_msec_t min_rtt;
ngx_msec_t rttvar;
ngx_uint_t pto_count;
ngx_queue_t free_frames;
ngx_buf_t *free_bufs;
ngx_buf_t *free_shadow_bufs;
ngx_uint_t nframes;
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_uint_t nbufs;
ngx_uint_t nshadowbufs;
#endif
#if (NGX_QUIC_OPENSSL_COMPAT)
ngx_quic_compat_t *compat;
#endif
ngx_quic_streams_t streams;
ngx_quic_congestion_t congestion;
off_t received;
ngx_uint_t error;
enum ssl_encryption_level_t error_level;
ngx_uint_t error_ftype;
const char *error_reason;
ngx_uint_t shutdown_code;
const char *shutdown_reason;
unsigned error_app:1;
unsigned send_timer_set:1;
unsigned closing:1;
unsigned shutdown:1;
unsigned draining:1;
unsigned key_phase:1;
unsigned validated:1;
unsigned client_tp_done:1;
};
ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c,
ngx_quic_tp_t *ctp);
void ngx_quic_discard_ctx(ngx_connection_t *c,
enum ssl_encryption_level_t level);
void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc);
void ngx_quic_shutdown_quic(ngx_connection_t *c);
#if (NGX_DEBUG)
void ngx_quic_connstate_dbg(ngx_connection_t *c);
#else
#define ngx_quic_connstate_dbg(c)
#endif
#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */

View File

@ -0,0 +1,502 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_MAX_SERVER_IDS 8
#if (NGX_QUIC_BPF)
static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id);
#endif
static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c,
ngx_quic_client_id_t *cid);
static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c,
ngx_quic_connection_t *qc);
static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c,
ngx_quic_server_id_t *sid);
ngx_int_t
ngx_quic_create_server_id(ngx_connection_t *c, u_char *id)
{
if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) {
return NGX_ERROR;
}
#if (NGX_QUIC_BPF)
if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"quic bpf failed to generate socket key");
/* ignore error, things still may work */
}
#endif
return NGX_OK;
}
#if (NGX_QUIC_BPF)
static ngx_int_t
ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id)
{
int fd;
uint64_t cookie;
socklen_t optlen;
fd = c->listening->fd;
optlen = sizeof(cookie);
if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno,
"quic getsockopt(SO_COOKIE) failed");
return NGX_ERROR;
}
ngx_quic_dcid_encode_key(id, cookie);
return NGX_OK;
}
#endif
ngx_int_t
ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
ngx_quic_new_conn_id_frame_t *f)
{
ngx_str_t id;
ngx_queue_t *q;
ngx_quic_frame_t *frame;
ngx_quic_client_id_t *cid, *item;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (f->seqnum < qc->max_retired_seqnum) {
/*
* RFC 9000, 19.15. NEW_CONNECTION_ID Frame
*
* An endpoint that receives a NEW_CONNECTION_ID frame with
* a sequence number smaller than the Retire Prior To field
* of a previously received NEW_CONNECTION_ID frame MUST send
* a corresponding RETIRE_CONNECTION_ID frame that retires
* the newly received connection ID, unless it has already
* done so for that sequence number.
*/
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
frame->u.retire_cid.sequence_number = f->seqnum;
ngx_quic_queue_frame(qc, frame);
goto retire;
}
cid = NULL;
for (q = ngx_queue_head(&qc->client_ids);
q != ngx_queue_sentinel(&qc->client_ids);
q = ngx_queue_next(q))
{
item = ngx_queue_data(q, ngx_quic_client_id_t, queue);
if (item->seqnum == f->seqnum) {
cid = item;
break;
}
}
if (cid) {
/*
* Transmission errors, timeouts, and retransmissions might cause the
* same NEW_CONNECTION_ID frame to be received multiple times.
*/
if (cid->len != f->len
|| ngx_strncmp(cid->id, f->cid, f->len) != 0
|| ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0)
{
/*
* ..if a sequence number is used for different connection IDs,
* the endpoint MAY treat that receipt as a connection error
* of type PROTOCOL_VIOLATION.
*/
qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
qc->error_reason = "seqnum refers to different connection id/token";
return NGX_ERROR;
}
} else {
id.data = f->cid;
id.len = f->len;
if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) {
return NGX_ERROR;
}
}
retire:
if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) {
/*
* Once a sender indicates a Retire Prior To value, smaller values sent
* in subsequent NEW_CONNECTION_ID frames have no effect. A receiver
* MUST ignore any Retire Prior To fields that do not increase the
* largest received Retire Prior To value.
*/
goto done;
}
qc->max_retired_seqnum = f->retire;
q = ngx_queue_head(&qc->client_ids);
while (q != ngx_queue_sentinel(&qc->client_ids)) {
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
q = ngx_queue_next(q);
if (cid->seqnum >= f->retire) {
continue;
}
if (ngx_quic_retire_client_id(c, cid) != NGX_OK) {
return NGX_ERROR;
}
}
done:
if (qc->nclient_ids > qc->tp.active_connection_id_limit) {
/*
* RFC 9000, 5.1.1. Issuing Connection IDs
*
* After processing a NEW_CONNECTION_ID frame and
* adding and retiring active connection IDs, if the number of active
* connection IDs exceeds the value advertised in its
* active_connection_id_limit transport parameter, an endpoint MUST
* close the connection with an error of type CONNECTION_ID_LIMIT_ERROR.
*/
qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
qc->error_reason = "too many connection ids received";
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
{
ngx_queue_t *q;
ngx_quic_path_t *path;
ngx_quic_client_id_t *new_cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!cid->used) {
return ngx_quic_free_client_id(c, cid);
}
/* we are going to retire client id which is in use */
q = ngx_queue_head(&qc->paths);
while (q != ngx_queue_sentinel(&qc->paths)) {
path = ngx_queue_data(q, ngx_quic_path_t, queue);
q = ngx_queue_next(q);
if (path->cid != cid) {
continue;
}
if (path == qc->path) {
/* this is the active path: update it with new CID */
new_cid = ngx_quic_next_client_id(c);
if (new_cid == NULL) {
return NGX_ERROR;
}
qc->path->cid = new_cid;
new_cid->used = 1;
return ngx_quic_free_client_id(c, cid);
}
return ngx_quic_free_path(c, path);
}
return NGX_OK;
}
static ngx_quic_client_id_t *
ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc)
{
ngx_queue_t *q;
ngx_quic_client_id_t *cid;
if (!ngx_queue_empty(&qc->free_client_ids)) {
q = ngx_queue_head(&qc->free_client_ids);
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
ngx_queue_remove(&cid->queue);
ngx_memzero(cid, sizeof(ngx_quic_client_id_t));
} else {
cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t));
if (cid == NULL) {
return NULL;
}
}
return cid;
}
ngx_quic_client_id_t *
ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id,
uint64_t seqnum, u_char *token)
{
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
cid = ngx_quic_alloc_client_id(c, qc);
if (cid == NULL) {
return NULL;
}
cid->seqnum = seqnum;
cid->len = id->len;
ngx_memcpy(cid->id, id->data, id->len);
if (token) {
ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN);
}
ngx_queue_insert_tail(&qc->client_ids, &cid->queue);
qc->nclient_ids++;
if (seqnum > qc->client_seqnum) {
qc->client_seqnum = seqnum;
}
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic cid seq:%uL received id:%uz:%xV:%*xs",
cid->seqnum, id->len, id,
(size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token);
return cid;
}
ngx_quic_client_id_t *
ngx_quic_next_client_id(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
for (q = ngx_queue_head(&qc->client_ids);
q != ngx_queue_sentinel(&qc->client_ids);
q = ngx_queue_next(q))
{
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
if (!cid->used) {
return cid;
}
}
return NULL;
}
ngx_int_t
ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
ngx_quic_retire_cid_frame_t *f)
{
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (f->sequence_number >= qc->server_seqnum) {
/*
* RFC 9000, 19.16.
*
* Receipt of a RETIRE_CONNECTION_ID frame containing a sequence
* number greater than any previously sent to the peer MUST be
* treated as a connection error of type PROTOCOL_VIOLATION.
*/
qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
qc->error_reason = "sequence number of id to retire was never issued";
return NGX_ERROR;
}
qsock = ngx_quic_get_socket(c);
if (qsock->sid.seqnum == f->sequence_number) {
/*
* RFC 9000, 19.16.
*
* The sequence number specified in a RETIRE_CONNECTION_ID frame MUST
* NOT refer to the Destination Connection ID field of the packet in
* which the frame is contained. The peer MAY treat this as a
* connection error of type PROTOCOL_VIOLATION.
*/
qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
qc->error_reason = "sequence number of id to retire refers DCID";
return NGX_ERROR;
}
qsock = ngx_quic_find_socket(c, f->sequence_number);
if (qsock == NULL) {
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%uL is retired", qsock->sid.seqnum);
ngx_quic_close_socket(c, qsock);
/* restore socket count up to a limit after deletion */
if (ngx_quic_create_sockets(c) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_quic_create_sockets(ngx_connection_t *c)
{
ngx_uint_t n;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic create sockets has:%ui max:%ui", qc->nsockets, n);
while (qc->nsockets < n) {
qsock = ngx_quic_create_socket(c, qc);
if (qsock == NULL) {
return NGX_ERROR;
}
if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid)
{
ngx_str_t dcid;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
dcid.len = sid->len;
dcid.data = sid->id;
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID;
frame->u.ncid.seqnum = sid->seqnum;
frame->u.ncid.retire = 0;
frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN;
ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN);
if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key,
frame->u.ncid.srt)
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
ngx_int_t
ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
{
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
frame->u.retire_cid.sequence_number = cid->seqnum;
ngx_quic_queue_frame(qc, frame);
/* we are no longer going to use this client id */
ngx_queue_remove(&cid->queue);
ngx_queue_insert_head(&qc->free_client_ids, &cid->queue);
qc->nclient_ids--;
return NGX_OK;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
ngx_quic_retire_cid_frame_t *f);
ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
ngx_quic_new_conn_id_frame_t *f);
ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c);
ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id);
ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c,
ngx_str_t *id, uint64_t seqnum, u_char *token);
ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c);
ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c,
ngx_quic_client_id_t *cid);
#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */

View File

@ -0,0 +1,894 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_BUFFER_SIZE 4096
#define ngx_quic_buf_refs(b) (b)->shadow->num
#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++
#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)--
#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v
static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c);
static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b);
static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b);
static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl,
off_t offset);
static ngx_buf_t *
ngx_quic_alloc_buf(ngx_connection_t *c)
{
u_char *p;
ngx_buf_t *b;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
b = qc->free_bufs;
if (b) {
qc->free_bufs = b->shadow;
p = b->start;
} else {
b = qc->free_shadow_bufs;
if (b) {
qc->free_shadow_bufs = b->shadow;
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic use shadow buffer n:%ui %ui",
++qc->nbufs, --qc->nshadowbufs);
#endif
} else {
b = ngx_palloc(c->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NULL;
}
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic new buffer n:%ui", ++qc->nbufs);
#endif
}
p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE);
if (p == NULL) {
return NULL;
}
}
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b);
#endif
ngx_memzero(b, sizeof(ngx_buf_t));
b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf;
b->temporary = 1;
b->shadow = b;
b->start = p;
b->pos = p;
b->last = p;
b->end = p + NGX_QUIC_BUFFER_SIZE;
ngx_quic_buf_set_refs(b, 1);
return b;
}
static void
ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b)
{
ngx_buf_t *shadow;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_quic_buf_dec_refs(b);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic free buffer %p r:%ui",
b, (ngx_uint_t) ngx_quic_buf_refs(b));
#endif
shadow = b->shadow;
if (ngx_quic_buf_refs(b) == 0) {
shadow->shadow = qc->free_bufs;
qc->free_bufs = shadow;
}
if (b != shadow) {
b->shadow = qc->free_shadow_bufs;
qc->free_shadow_bufs = b;
}
}
static ngx_buf_t *
ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b)
{
ngx_buf_t *nb;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
nb = qc->free_shadow_bufs;
if (nb) {
qc->free_shadow_bufs = nb->shadow;
} else {
nb = ngx_palloc(c->pool, sizeof(ngx_buf_t));
if (nb == NULL) {
return NULL;
}
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic new shadow buffer n:%ui", ++qc->nshadowbufs);
#endif
}
*nb = *b;
ngx_quic_buf_inc_refs(b);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic clone buffer %p %p r:%ui",
b, nb, (ngx_uint_t) ngx_quic_buf_refs(b));
#endif
return nb;
}
static ngx_int_t
ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset)
{
ngx_buf_t *b, *tb;
ngx_chain_t *tail;
b = cl->buf;
tail = ngx_alloc_chain_link(c->pool);
if (tail == NULL) {
return NGX_ERROR;
}
tb = ngx_quic_clone_buf(c, b);
if (tb == NULL) {
return NGX_ERROR;
}
tail->buf = tb;
tb->pos += offset;
b->last = tb->pos;
b->last_buf = 0;
tail->next = cl->next;
cl->next = tail;
return NGX_OK;
}
ngx_quic_frame_t *
ngx_quic_alloc_frame(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!ngx_queue_empty(&qc->free_frames)) {
q = ngx_queue_head(&qc->free_frames);
frame = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_queue_remove(&frame->queue);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic reuse frame n:%ui", qc->nframes);
#endif
} else if (qc->nframes < 10000) {
frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t));
if (frame == NULL) {
return NULL;
}
++qc->nframes;
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic alloc frame n:%ui", qc->nframes);
#endif
} else {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected");
return NULL;
}
ngx_memzero(frame, sizeof(ngx_quic_frame_t));
return frame;
}
void
ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (frame->data) {
ngx_quic_free_chain(c, frame->data);
}
ngx_queue_insert_head(&qc->free_frames, &frame->queue);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic free frame n:%ui", qc->nframes);
#endif
}
void
ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in)
{
ngx_chain_t *cl;
while (in) {
cl = in;
in = in->next;
ngx_quic_free_buf(c, cl->buf);
ngx_free_chain(c->pool, cl);
}
}
void
ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames)
{
ngx_queue_t *q;
ngx_quic_frame_t *f;
do {
q = ngx_queue_head(frames);
if (q == ngx_queue_sentinel(frames)) {
break;
}
ngx_queue_remove(q);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_quic_free_frame(c, f);
} while (1);
}
void
ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame)
{
ngx_quic_send_ctx_t *ctx;
ctx = ngx_quic_get_send_ctx(qc, frame->level);
ngx_queue_insert_tail(&ctx->frames, &frame->queue);
frame->len = ngx_quic_create_frame(NULL, frame);
/* always succeeds */
if (qc->closing) {
return;
}
ngx_post_event(&qc->push, &ngx_posted_events);
}
ngx_int_t
ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len)
{
size_t shrink;
ngx_chain_t *out;
ngx_quic_frame_t *nf;
ngx_quic_buffer_t qb;
ngx_quic_ordered_frame_t *of, *onf;
switch (f->type) {
case NGX_QUIC_FT_CRYPTO:
case NGX_QUIC_FT_STREAM:
break;
default:
return NGX_DECLINED;
}
if ((size_t) f->len <= len) {
return NGX_OK;
}
shrink = f->len - len;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic split frame now:%uz need:%uz shrink:%uz",
f->len, len, shrink);
of = &f->u.ord;
if (of->length <= shrink) {
return NGX_DECLINED;
}
of->length -= shrink;
f->len = ngx_quic_create_frame(NULL, f);
if ((size_t) f->len > len) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame");
return NGX_ERROR;
}
ngx_memzero(&qb, sizeof(ngx_quic_buffer_t));
qb.chain = f->data;
out = ngx_quic_read_buffer(c, &qb, of->length);
if (out == NGX_CHAIN_ERROR) {
return NGX_ERROR;
}
f->data = out;
nf = ngx_quic_alloc_frame(c);
if (nf == NULL) {
return NGX_ERROR;
}
*nf = *f;
onf = &nf->u.ord;
onf->offset += of->length;
onf->length = shrink;
nf->len = ngx_quic_create_frame(NULL, nf);
nf->data = qb.chain;
if (f->type == NGX_QUIC_FT_STREAM) {
f->u.stream.fin = 0;
}
ngx_queue_insert_after(&f->queue, &nf->queue);
return NGX_OK;
}
ngx_chain_t *
ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, size_t len)
{
ngx_buf_t buf;
ngx_chain_t cl, *out;
ngx_quic_buffer_t qb;
ngx_memzero(&buf, sizeof(ngx_buf_t));
buf.pos = data;
buf.last = buf.pos + len;
buf.temporary = 1;
cl.buf = &buf;
cl.next = NULL;
ngx_memzero(&qb, sizeof(ngx_quic_buffer_t));
if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) {
return NGX_CHAIN_ERROR;
}
out = ngx_quic_read_buffer(c, &qb, len);
if (out == NGX_CHAIN_ERROR) {
return NGX_CHAIN_ERROR;
}
ngx_quic_free_buffer(c, &qb);
return out;
}
ngx_chain_t *
ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit)
{
uint64_t n;
ngx_buf_t *b;
ngx_chain_t *out, **ll;
out = qb->chain;
for (ll = &out; *ll; ll = &(*ll)->next) {
b = (*ll)->buf;
if (b->sync) {
/* hole */
break;
}
if (limit == 0) {
break;
}
n = b->last - b->pos;
if (n > limit) {
if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
n = limit;
}
limit -= n;
qb->offset += n;
}
if (qb->offset >= qb->last_offset) {
qb->last_chain = NULL;
}
qb->chain = *ll;
*ll = NULL;
return out;
}
void
ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
uint64_t offset)
{
size_t n;
ngx_buf_t *b;
ngx_chain_t *cl;
while (qb->chain) {
if (qb->offset >= offset) {
break;
}
cl = qb->chain;
b = cl->buf;
n = b->last - b->pos;
if (qb->offset + n > offset) {
n = offset - qb->offset;
b->pos += n;
qb->offset += n;
break;
}
qb->offset += n;
qb->chain = cl->next;
cl->next = NULL;
ngx_quic_free_chain(c, cl);
}
if (qb->chain == NULL) {
qb->offset = offset;
}
if (qb->offset >= qb->last_offset) {
qb->last_chain = NULL;
}
}
ngx_chain_t *
ngx_quic_alloc_chain(ngx_connection_t *c)
{
ngx_chain_t *cl;
cl = ngx_alloc_chain_link(c->pool);
if (cl == NULL) {
return NULL;
}
cl->buf = ngx_quic_alloc_buf(c);
if (cl->buf == NULL) {
return NULL;
}
return cl;
}
ngx_chain_t *
ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
ngx_chain_t *in, uint64_t limit, uint64_t offset)
{
u_char *p;
uint64_t n, base;
ngx_buf_t *b;
ngx_chain_t *cl, **chain;
if (qb->last_chain && offset >= qb->last_offset) {
base = qb->last_offset;
chain = &qb->last_chain;
} else {
base = qb->offset;
chain = &qb->chain;
}
while (in && limit) {
if (offset < base) {
n = ngx_min((uint64_t) (in->buf->last - in->buf->pos),
ngx_min(base - offset, limit));
in->buf->pos += n;
offset += n;
limit -= n;
if (in->buf->pos == in->buf->last) {
in = in->next;
}
continue;
}
cl = *chain;
if (cl == NULL) {
cl = ngx_quic_alloc_chain(c);
if (cl == NULL) {
return NGX_CHAIN_ERROR;
}
cl->buf->last = cl->buf->end;
cl->buf->sync = 1; /* hole */
cl->next = NULL;
*chain = cl;
}
b = cl->buf;
n = b->last - b->pos;
if (base + n <= offset) {
base += n;
chain = &cl->next;
continue;
}
if (b->sync && offset > base) {
if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
continue;
}
p = b->pos + (offset - base);
while (in) {
if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) {
in = in->next;
continue;
}
if (p == b->last || limit == 0) {
break;
}
n = ngx_min(b->last - p, in->buf->last - in->buf->pos);
n = ngx_min(n, limit);
if (b->sync) {
ngx_memcpy(p, in->buf->pos, n);
qb->size += n;
}
p += n;
in->buf->pos += n;
offset += n;
limit -= n;
}
if (b->sync && p == b->last) {
b->sync = 0;
continue;
}
if (b->sync && p != b->pos) {
if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
b->sync = 0;
}
}
qb->last_offset = base;
qb->last_chain = *chain;
return in;
}
void
ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb)
{
ngx_quic_free_chain(c, qb->chain);
qb->chain = NULL;
}
#if (NGX_DEBUG)
void
ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx)
{
u_char *p, *last, *pos, *end;
ssize_t n;
uint64_t gap, range, largest, smallest;
ngx_uint_t i;
u_char buf[NGX_MAX_ERROR_STR];
p = buf;
last = buf + sizeof(buf);
switch (f->type) {
case NGX_QUIC_FT_CRYPTO:
p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL",
f->u.crypto.length, f->u.crypto.offset);
#ifdef NGX_QUIC_DEBUG_FRAMES
{
ngx_chain_t *cl;
p = ngx_slprintf(p, last, " data:");
for (cl = f->data; cl; cl = cl->next) {
p = ngx_slprintf(p, last, "%*xs",
cl->buf->last - cl->buf->pos, cl->buf->pos);
}
}
#endif
break;
case NGX_QUIC_FT_PADDING:
p = ngx_slprintf(p, last, "PADDING");
break;
case NGX_QUIC_FT_ACK:
case NGX_QUIC_FT_ACK_ECN:
p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ",
f->u.ack.range_count, f->u.ack.delay);
if (f->data) {
pos = f->data->buf->pos;
end = f->data->buf->last;
} else {
pos = NULL;
end = NULL;
}
largest = f->u.ack.largest;
smallest = f->u.ack.largest - f->u.ack.first_range;
if (largest == smallest) {
p = ngx_slprintf(p, last, "%uL", largest);
} else {
p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest);
}
for (i = 0; i < f->u.ack.range_count; i++) {
n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range);
if (n == NGX_ERROR) {
break;
}
pos += n;
largest = smallest - gap - 2;
smallest = largest - range;
if (largest == smallest) {
p = ngx_slprintf(p, last, " %uL", largest);
} else {
p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest);
}
}
if (f->type == NGX_QUIC_FT_ACK_ECN) {
p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL",
f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
}
break;
case NGX_QUIC_FT_PING:
p = ngx_slprintf(p, last, "PING");
break;
case NGX_QUIC_FT_NEW_CONNECTION_ID:
p = ngx_slprintf(p, last,
"NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud",
f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len);
break;
case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL",
f->u.retire_cid.sequence_number);
break;
case NGX_QUIC_FT_CONNECTION_CLOSE:
case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui",
f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP",
f->u.close.error_code);
if (f->u.close.reason.len) {
p = ngx_slprintf(p, last, " %V", &f->u.close.reason);
}
if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type);
}
break;
case NGX_QUIC_FT_STREAM:
p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id);
if (f->u.stream.off) {
p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset);
}
if (f->u.stream.len) {
p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length);
}
if (f->u.stream.fin) {
p = ngx_slprintf(p, last, " fin:1");
}
#ifdef NGX_QUIC_DEBUG_FRAMES
{
ngx_chain_t *cl;
p = ngx_slprintf(p, last, " data:");
for (cl = f->data; cl; cl = cl->next) {
p = ngx_slprintf(p, last, "%*xs",
cl->buf->last - cl->buf->pos, cl->buf->pos);
}
}
#endif
break;
case NGX_QUIC_FT_MAX_DATA:
p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv",
f->u.max_data.max_data);
break;
case NGX_QUIC_FT_RESET_STREAM:
p = ngx_slprintf(p, last, "RESET_STREAM"
" id:0x%xL error_code:0x%xL final_size:0x%xL",
f->u.reset_stream.id, f->u.reset_stream.error_code,
f->u.reset_stream.final_size);
break;
case NGX_QUIC_FT_STOP_SENDING:
p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL",
f->u.stop_sending.id, f->u.stop_sending.error_code);
break;
case NGX_QUIC_FT_STREAMS_BLOCKED:
case NGX_QUIC_FT_STREAMS_BLOCKED2:
p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui",
f->u.streams_blocked.limit, f->u.streams_blocked.bidi);
break;
case NGX_QUIC_FT_MAX_STREAMS:
case NGX_QUIC_FT_MAX_STREAMS2:
p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui",
f->u.max_streams.limit, f->u.max_streams.bidi);
break;
case NGX_QUIC_FT_MAX_STREAM_DATA:
p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL",
f->u.max_stream_data.id, f->u.max_stream_data.limit);
break;
case NGX_QUIC_FT_DATA_BLOCKED:
p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL",
f->u.data_blocked.limit);
break;
case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL",
f->u.stream_data_blocked.id,
f->u.stream_data_blocked.limit);
break;
case NGX_QUIC_FT_PATH_CHALLENGE:
p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs",
sizeof(f->u.path_challenge.data),
f->u.path_challenge.data);
break;
case NGX_QUIC_FT_PATH_RESPONSE:
p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs",
sizeof(f->u.path_challenge.data),
f->u.path_challenge.data);
break;
case NGX_QUIC_FT_NEW_TOKEN:
p = ngx_slprintf(p, last, "NEW_TOKEN");
#ifdef NGX_QUIC_DEBUG_FRAMES
{
ngx_chain_t *cl;
p = ngx_slprintf(p, last, " token:");
for (cl = f->data; cl; cl = cl->next) {
p = ngx_slprintf(p, last, "%*xs",
cl->buf->last - cl->buf->pos, cl->buf->pos);
}
}
#endif
break;
case NGX_QUIC_FT_HANDSHAKE_DONE:
p = ngx_slprintf(p, last, "HANDSHAKE DONE");
break;
default:
p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type);
break;
}
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s",
tx ? "tx" : "rx", ngx_quic_level_name(f->level),
p - buf, buf);
}
#endif

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c,
ngx_quic_frame_t *frame, void *data);
ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c);
void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame);
void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames);
void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame);
ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f,
size_t len);
ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c);
void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in);
ngx_chain_t *ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data,
size_t len);
ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
uint64_t limit);
ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
ngx_chain_t *in, uint64_t limit, uint64_t offset);
void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
uint64_t offset);
void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb);
#if (NGX_DEBUG)
void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx);
#else
#define ngx_quic_log_frame(log, f, tx)
#endif
#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */

View File

@ -0,0 +1,711 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
static void ngx_quic_set_connection_path(ngx_connection_t *c,
ngx_quic_path_t *path);
static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
ngx_quic_path_t *path);
static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
ngx_quic_path_t *path);
static void ngx_quic_set_path_timer(ngx_connection_t *c);
static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
ngx_int_t
ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
{
ngx_quic_frame_t frame, *fp;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
frame.level = ssl_encryption_application;
frame.type = NGX_QUIC_FT_PATH_RESPONSE;
frame.u.path_response = *f;
/*
* RFC 9000, 8.2.2. Path Validation Responses
*
* A PATH_RESPONSE frame MUST be sent on the network path where the
* PATH_CHALLENGE frame was received.
*/
/*
* An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
* to at least the smallest allowed maximum datagram size of 1200 bytes.
*/
if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) {
return NGX_ERROR;
}
if (pkt->path == qc->path) {
/*
* RFC 9000, 9.3.3. Off-Path Packet Forwarding
*
* An endpoint that receives a PATH_CHALLENGE on an active path SHOULD
* send a non-probing packet in response.
*/
fp = ngx_quic_alloc_frame(c);
if (fp == NULL) {
return NGX_ERROR;
}
fp->level = ssl_encryption_application;
fp->type = NGX_QUIC_FT_PING;
ngx_quic_queue_frame(qc, fp);
}
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_path_response_frame(ngx_connection_t *c,
ngx_quic_path_challenge_frame_t *f)
{
ngx_uint_t rst;
ngx_queue_t *q;
ngx_quic_path_t *path, *prev;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
/*
* RFC 9000, 8.2.3. Successful Path Validation
*
* A PATH_RESPONSE frame received on any network path validates the path
* on which the PATH_CHALLENGE was sent.
*/
for (q = ngx_queue_head(&qc->paths);
q != ngx_queue_sentinel(&qc->paths);
q = ngx_queue_next(q))
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
if (!path->validating) {
continue;
}
if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0
|| ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0)
{
goto valid;
}
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stale PATH_RESPONSE ignored");
return NGX_OK;
valid:
/*
* RFC 9000, 9.4. Loss Detection and Congestion Control
*
* On confirming a peer's ownership of its new address,
* an endpoint MUST immediately reset the congestion controller
* and round-trip time estimator for the new path to initial values
* unless the only change in the peer's address is its port number.
*/
rst = 1;
prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
if (prev != NULL) {
if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen,
path->sockaddr, path->socklen, 0)
== NGX_OK)
{
/* address did not change */
rst = 0;
}
}
if (rst) {
ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t));
qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
ngx_max(2 * qc->tp.max_udp_payload_size,
14720));
qc->congestion.ssthresh = (size_t) -1;
qc->congestion.recovery_start = ngx_current_msec;
}
/*
* RFC 9000, 9.3. Responding to Connection Migration
*
* After verifying a new client address, the server SHOULD
* send new address validation tokens (Section 8) to the client.
*/
if (ngx_quic_send_new_token(c, path) != NGX_OK) {
return NGX_ERROR;
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic path seq:%uL addr:%V successfully validated",
path->seqnum, &path->addr_text);
ngx_quic_path_dbg(c, "is validated", path);
path->validated = 1;
path->validating = 0;
path->limited = 0;
ngx_quic_set_path_timer(c);
return NGX_OK;
}
ngx_quic_path_t *
ngx_quic_new_path(ngx_connection_t *c,
struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid)
{
ngx_queue_t *q;
ngx_quic_path_t *path;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!ngx_queue_empty(&qc->free_paths)) {
q = ngx_queue_head(&qc->free_paths);
path = ngx_queue_data(q, ngx_quic_path_t, queue);
ngx_queue_remove(&path->queue);
ngx_memzero(path, sizeof(ngx_quic_path_t));
} else {
path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t));
if (path == NULL) {
return NULL;
}
}
ngx_queue_insert_tail(&qc->paths, &path->queue);
path->cid = cid;
cid->used = 1;
path->limited = 1;
path->seqnum = qc->path_seqnum++;
path->sockaddr = &path->sa.sockaddr;
path->socklen = socklen;
ngx_memcpy(path->sockaddr, sockaddr, socklen);
path->addr_text.data = path->text;
path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
NGX_SOCKADDR_STRLEN, 1);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%uL created addr:%V",
path->seqnum, &path->addr_text);
return path;
}
static ngx_quic_path_t *
ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag)
{
ngx_queue_t *q;
ngx_quic_path_t *path;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
for (q = ngx_queue_head(&qc->paths);
q != ngx_queue_sentinel(&qc->paths);
q = ngx_queue_next(q))
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
if (path->tag == tag) {
return path;
}
}
return NULL;
}
ngx_int_t
ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
off_t len;
ngx_queue_t *q;
ngx_quic_path_t *path, *probe;
ngx_quic_socket_t *qsock;
ngx_quic_send_ctx_t *ctx;
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
qsock = ngx_quic_get_socket(c);
len = pkt->raw->last - pkt->raw->start;
if (c->udp->buffer == NULL) {
/* first ever packet in connection, path already exists */
path = qc->path;
goto update;
}
probe = NULL;
for (q = ngx_queue_head(&qc->paths);
q != ngx_queue_sentinel(&qc->paths);
q = ngx_queue_next(q))
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen,
path->sockaddr, path->socklen, 1)
== NGX_OK)
{
goto update;
}
if (path->tag == NGX_QUIC_PATH_PROBE) {
probe = path;
}
}
/* packet from new path, drop current probe, if any */
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
/*
* only accept highest-numbered packets to prevent connection id
* exhaustion by excessive probing packets from unknown paths
*/
if (pkt->pn != ctx->largest_pn) {
return NGX_DONE;
}
if (probe && ngx_quic_free_path(c, probe) != NGX_OK) {
return NGX_ERROR;
}
/* new path requires new client id */
cid = ngx_quic_next_client_id(c);
if (cid == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic no available client ids for new path");
/* stop processing of this datagram */
return NGX_DONE;
}
path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid);
if (path == NULL) {
return NGX_ERROR;
}
path->tag = NGX_QUIC_PATH_PROBE;
/*
* client arrived using new path and previously seen DCID,
* this indicates NAT rebinding (or bad client)
*/
if (qsock->used) {
pkt->rebound = 1;
}
update:
qsock->used = 1;
pkt->path = path;
/* TODO: this may be too late in some cases;
* for example, if error happens during decrypt(), we cannot
* send CC, if error happens in 1st packet, due to amplification
* limit, because path->received = 0
*
* should we account garbage as received or only decrypting packets?
*/
path->received += len;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet len:%O via sock seq:%L path seq:%uL",
len, (int64_t) qsock->sid.seqnum, path->seqnum);
ngx_quic_path_dbg(c, "status", path);
return NGX_OK;
}
ngx_int_t
ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_queue_remove(&path->queue);
ngx_queue_insert_head(&qc->free_paths, &path->queue);
/*
* invalidate CID that is no longer usable for any other path;
* this also requests new CIDs from client
*/
if (path->cid) {
if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) {
return NGX_ERROR;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%uL addr:%V retired",
path->seqnum, &path->addr_text);
return NGX_OK;
}
static void
ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen);
c->socklen = path->socklen;
if (c->addr_text.data) {
c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
c->addr_text.data,
c->listening->addr_text_max_len, 0);
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic send path set to seq:%uL addr:%V",
path->seqnum, &path->addr_text);
}
ngx_int_t
ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
ngx_quic_path_t *next, *bkp;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
/* got non-probing packet via non-active path */
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
/*
* RFC 9000, 9.3. Responding to Connection Migration
*
* An endpoint only changes the address to which it sends packets in
* response to the highest-numbered non-probing packet.
*/
if (pkt->pn != ctx->largest_pn) {
return NGX_OK;
}
next = pkt->path;
/*
* RFC 9000, 9.3.3:
*
* In response to an apparent migration, endpoints MUST validate the
* previously active path using a PATH_CHALLENGE frame.
*/
if (pkt->rebound) {
/* NAT rebinding: client uses new path with old SID */
if (ngx_quic_validate_path(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
if (qc->path->validated) {
if (next->tag != NGX_QUIC_PATH_BACKUP) {
/* can delete backup path, if any */
bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) {
return NGX_ERROR;
}
}
qc->path->tag = NGX_QUIC_PATH_BACKUP;
ngx_quic_path_dbg(c, "is now backup", qc->path);
} else {
if (ngx_quic_free_path(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
/* switch active path to migrated */
qc->path = next;
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
ngx_quic_set_connection_path(c, next);
if (!next->validated && !next->validating) {
if (ngx_quic_validate_path(c, next) != NGX_OK) {
return NGX_ERROR;
}
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic migrated to path seq:%uL addr:%V",
qc->path->seqnum, &qc->path->addr_text);
ngx_quic_path_dbg(c, "is now active", qc->path);
return NGX_OK;
}
static ngx_int_t
ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_msec_t pto;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic initiated validation of path seq:%uL", path->seqnum);
path->validating = 1;
path->tries = 0;
if (RAND_bytes(path->challenge1, 8) != 1) {
return NGX_ERROR;
}
if (RAND_bytes(path->challenge2, 8) != 1) {
return NGX_ERROR;
}
if (ngx_quic_send_path_challenge(c, path) != NGX_OK) {
return NGX_ERROR;
}
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
pto = ngx_max(ngx_quic_pto(c, ctx), 1000);
path->expires = ngx_current_msec + pto;
ngx_quic_set_path_timer(c);
return NGX_OK;
}
static ngx_int_t
ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_quic_frame_t frame;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%uL send path_challenge tries:%ui",
path->seqnum, path->tries);
ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
frame.level = ssl_encryption_application;
frame.type = NGX_QUIC_FT_PATH_CHALLENGE;
ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8);
/*
* RFC 9000, 8.2.1. Initiating Path Validation
*
* An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame
* to at least the smallest allowed maximum datagram size of 1200 bytes,
* unless the anti-amplification limit for the path does not permit
* sending a datagram of this size.
*/
/* same applies to PATH_RESPONSE frames */
if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
return NGX_ERROR;
}
ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8);
if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
static void
ngx_quic_set_path_timer(ngx_connection_t *c)
{
ngx_msec_t now;
ngx_queue_t *q;
ngx_msec_int_t left, next;
ngx_quic_path_t *path;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
now = ngx_current_msec;
next = -1;
for (q = ngx_queue_head(&qc->paths);
q != ngx_queue_sentinel(&qc->paths);
q = ngx_queue_next(q))
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
if (!path->validating) {
continue;
}
left = path->expires - now;
left = ngx_max(left, 1);
if (next == -1 || left < next) {
next = left;
}
}
if (next != -1) {
ngx_add_timer(&qc->path_validation, next);
} else if (qc->path_validation.timer_set) {
ngx_del_timer(&qc->path_validation);
}
}
void
ngx_quic_path_validation_handler(ngx_event_t *ev)
{
ngx_msec_t now;
ngx_queue_t *q;
ngx_msec_int_t left, next, pto;
ngx_quic_path_t *path, *bkp;
ngx_connection_t *c;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
c = ev->data;
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
next = -1;
now = ngx_current_msec;
q = ngx_queue_head(&qc->paths);
while (q != ngx_queue_sentinel(&qc->paths)) {
path = ngx_queue_data(q, ngx_quic_path_t, queue);
q = ngx_queue_next(q);
if (!path->validating) {
continue;
}
left = path->expires - now;
if (left > 0) {
if (next == -1 || left < next) {
next = left;
}
continue;
}
if (++path->tries < NGX_QUIC_PATH_RETRIES) {
pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries;
path->expires = ngx_current_msec + pto;
if (next == -1 || pto < next) {
next = pto;
}
/* retransmit */
(void) ngx_quic_send_path_challenge(c, path);
continue;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"quic path seq:%uL validation failed", path->seqnum);
/* found expired path */
path->validated = 0;
path->validating = 0;
path->limited = 1;
/* RFC 9000, 9.3.2. On-Path Address Spoofing
*
* To protect the connection from failing due to such a spurious
* migration, an endpoint MUST revert to using the last validated
* peer address when validation of a new peer address fails.
*/
if (qc->path == path) {
/* active path validation failed */
bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
if (bkp == NULL) {
qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
qc->error_reason = "no viable path";
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
qc->path = bkp;
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
ngx_quic_set_connection_path(c, qc->path);
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic path seq:%uL addr:%V is restored from backup",
qc->path->seqnum, &qc->path->addr_text);
ngx_quic_path_dbg(c, "is active", qc->path);
}
if (ngx_quic_free_path(c, path) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
}
if (next != -1) {
ngx_add_timer(&qc->path_validation, next);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_PATH_RETRIES 3
#define NGX_QUIC_PATH_PROBE 0
#define NGX_QUIC_PATH_ACTIVE 1
#define NGX_QUIC_PATH_BACKUP 2
#define ngx_quic_path_dbg(c, msg, path) \
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \
"quic path seq:%uL %s sent:%O recvd:%O state:%s%s%s", \
path->seqnum, msg, path->sent, path->received, \
path->limited ? "L" : "", path->validated ? "V": "N", \
path->validating ? "R": "");
ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f);
ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c,
ngx_quic_path_challenge_frame_t *f);
ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c,
struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid);
ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path);
ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c,
ngx_quic_header_t *pkt);
void ngx_quic_path_validation_handler(ngx_event_t *ev);
#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */

View File

@ -0,0 +1,646 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#if (NGX_QUIC_OPENSSL_COMPAT)
#define NGX_QUIC_COMPAT_RECORD_SIZE 1024
#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39
#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET"
#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET"
#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0"
#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0"
typedef struct {
ngx_quic_secret_t secret;
ngx_uint_t cipher;
} ngx_quic_compat_keys_t;
typedef struct {
ngx_log_t *log;
u_char type;
ngx_str_t payload;
uint64_t number;
ngx_quic_compat_keys_t *keys;
enum ssl_encryption_level_t level;
} ngx_quic_compat_record_t;
struct ngx_quic_compat_s {
const SSL_QUIC_METHOD *method;
enum ssl_encryption_level_t write_level;
enum ssl_encryption_level_t read_level;
uint64_t read_record;
ngx_quic_compat_keys_t keys;
ngx_str_t tp;
ngx_str_t ctp;
};
static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line);
static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len);
static int ngx_quic_compat_add_transport_params_callback(SSL *ssl,
unsigned int ext_type, unsigned int context, const unsigned char **out,
size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg);
static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl,
unsigned int ext_type, unsigned int context, const unsigned char *in,
size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg);
static void ngx_quic_compat_message_callback(int write_p, int version,
int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec,
u_char *out, ngx_uint_t plain);
static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec,
ngx_str_t *res);
ngx_int_t
ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx)
{
SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback);
if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) {
return NGX_OK;
}
if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT,
SSL_EXT_CLIENT_HELLO
|SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
ngx_quic_compat_add_transport_params_callback,
NULL,
NULL,
ngx_quic_compat_parse_transport_params_callback,
NULL)
== 0)
{
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"SSL_CTX_add_custom_ext() failed");
return NGX_ERROR;
}
return NGX_OK;
}
static void
ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line)
{
u_char ch, *p, *start, value;
size_t n;
ngx_uint_t write;
const SSL_CIPHER *cipher;
ngx_quic_compat_t *com;
ngx_connection_t *c;
ngx_quic_connection_t *qc;
enum ssl_encryption_level_t level;
u_char secret[EVP_MAX_MD_SIZE];
c = ngx_ssl_get_connection(ssl);
if (c->type != SOCK_DGRAM) {
return;
}
p = (u_char *) line;
for (start = p; *p && *p != ' '; p++);
n = p - start;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic compat secret %*s", n, start);
if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1
&& ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0)
{
level = ssl_encryption_handshake;
write = 0;
} else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1
&& ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0)
{
level = ssl_encryption_handshake;
write = 1;
} else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1
&& ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n)
== 0)
{
level = ssl_encryption_application;
write = 0;
} else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1
&& ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n)
== 0)
{
level = ssl_encryption_application;
write = 1;
} else {
return;
}
if (*p++ == '\0') {
return;
}
for ( /* void */ ; *p && *p != ' '; p++);
if (*p++ == '\0') {
return;
}
for (n = 0, start = p; *p; p++) {
ch = *p;
if (ch >= '0' && ch <= '9') {
value = ch - '0';
goto next;
}
ch = (u_char) (ch | 0x20);
if (ch >= 'a' && ch <= 'f') {
value = ch - 'a' + 10;
goto next;
}
ngx_log_error(NGX_LOG_EMERG, c->log, 0,
"invalid OpenSSL QUIC secret format");
return;
next:
if ((p - start) % 2) {
secret[n++] += value;
} else {
if (n >= EVP_MAX_MD_SIZE) {
ngx_log_error(NGX_LOG_EMERG, c->log, 0,
"too big OpenSSL QUIC secret");
return;
}
secret[n] = (value << 4);
}
}
qc = ngx_quic_get_connection(c);
com = qc->compat;
cipher = SSL_get_current_cipher(ssl);
if (write) {
com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n);
com->write_level = level;
} else {
com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n);
com->read_level = level;
com->read_record = 0;
(void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level,
cipher, secret, n);
}
}
static ngx_int_t
ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
{
ngx_int_t key_len;
ngx_str_t secret_str;
ngx_uint_t i;
ngx_quic_hkdf_t seq[2];
ngx_quic_secret_t *peer_secret;
ngx_quic_ciphers_t ciphers;
peer_secret = &keys->secret;
keys->cipher = SSL_CIPHER_get_id(cipher);
key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
if (key_len == NGX_ERROR) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher");
return NGX_ERROR;
}
if (sizeof(peer_secret->secret.data) < secret_len) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"unexpected secret len: %uz", secret_len);
return NGX_ERROR;
}
peer_secret->secret.len = secret_len;
ngx_memcpy(peer_secret->secret.data, secret, secret_len);
peer_secret->key.len = key_len;
peer_secret->iv.len = NGX_QUIC_IV_LEN;
secret_str.len = secret_len;
secret_str.data = (u_char *) secret;
ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str);
ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str);
for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
static int
ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type,
unsigned int context, const unsigned char **out, size_t *outlen, X509 *x,
size_t chainidx, int *al, void *add_arg)
{
ngx_connection_t *c;
ngx_quic_compat_t *com;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection(ssl);
if (c->type != SOCK_DGRAM) {
return 0;
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic compat add transport params");
qc = ngx_quic_get_connection(c);
com = qc->compat;
*out = com->tp.data;
*outlen = com->tp.len;
return 1;
}
static int
ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type,
unsigned int context, const unsigned char *in, size_t inlen, X509 *x,
size_t chainidx, int *al, void *parse_arg)
{
u_char *p;
ngx_connection_t *c;
ngx_quic_compat_t *com;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection(ssl);
if (c->type != SOCK_DGRAM) {
return 0;
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic compat parse transport params");
qc = ngx_quic_get_connection(c);
com = qc->compat;
p = ngx_pnalloc(c->pool, inlen);
if (p == NULL) {
return 0;
}
ngx_memcpy(p, in, inlen);
com->ctp.data = p;
com->ctp.len = inlen;
return 1;
}
int
SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method)
{
BIO *rbio, *wbio;
ngx_connection_t *c;
ngx_quic_compat_t *com;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection(ssl);
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method");
qc = ngx_quic_get_connection(c);
qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t));
if (qc->compat == NULL) {
return 0;
}
com = qc->compat;
com->method = quic_method;
rbio = BIO_new(BIO_s_mem());
if (rbio == NULL) {
return 0;
}
wbio = BIO_new(BIO_s_null());
if (wbio == NULL) {
return 0;
}
SSL_set_bio(ssl, rbio, wbio);
SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback);
/* early data is not supported */
SSL_set_max_early_data(ssl, 0);
return 1;
}
static void
ngx_quic_compat_message_callback(int write_p, int version, int content_type,
const void *buf, size_t len, SSL *ssl, void *arg)
{
ngx_uint_t alert;
ngx_connection_t *c;
ngx_quic_compat_t *com;
ngx_quic_connection_t *qc;
enum ssl_encryption_level_t level;
if (!write_p) {
return;
}
c = ngx_ssl_get_connection(ssl);
qc = ngx_quic_get_connection(c);
if (qc == NULL) {
/* closing */
return;
}
com = qc->compat;
level = com->write_level;
switch (content_type) {
case SSL3_RT_HANDSHAKE:
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic compat tx %s len:%uz ",
ngx_quic_level_name(level), len);
(void) com->method->add_handshake_data(ssl, level, buf, len);
break;
case SSL3_RT_ALERT:
if (len >= 2) {
alert = ((u_char *) buf)[1];
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic compat %s alert:%ui len:%uz ",
ngx_quic_level_name(level), alert, len);
(void) com->method->send_alert(ssl, level, alert);
}
break;
}
}
int
SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
const uint8_t *data, size_t len)
{
BIO *rbio;
size_t n;
u_char *p;
ngx_str_t res;
ngx_connection_t *c;
ngx_quic_compat_t *com;
ngx_quic_connection_t *qc;
ngx_quic_compat_record_t rec;
u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1];
u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1
+ SSL3_RT_HEADER_LENGTH
+ EVP_GCM_TLS_TAG_LEN];
c = ngx_ssl_get_connection(ssl);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz",
ngx_quic_level_name(level), len);
qc = ngx_quic_get_connection(c);
com = qc->compat;
rbio = SSL_get_rbio(ssl);
while (len) {
ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t));
rec.type = SSL3_RT_HANDSHAKE;
rec.log = c->log;
rec.number = com->read_record++;
rec.keys = &com->keys;
if (level == ssl_encryption_initial) {
n = ngx_min(len, 65535);
rec.payload.len = n;
rec.payload.data = (u_char *) data;
ngx_quic_compat_create_header(&rec, out, 1);
BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH);
BIO_write(rbio, data, n);
#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic compat record len:%uz %*xs%*xs",
n + SSL3_RT_HEADER_LENGTH,
(size_t) SSL3_RT_HEADER_LENGTH, out, n, data);
#endif
} else {
n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE);
p = ngx_cpymem(in, data, n);
*p++ = SSL3_RT_HANDSHAKE;
rec.payload.len = p - in;
rec.payload.data = in;
res.data = out;
if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) {
return 0;
}
#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic compat record len:%uz %xV", res.len, &res);
#endif
BIO_write(rbio, res.data, res.len);
}
data += n;
len -= n;
}
return 1;
}
static size_t
ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out,
ngx_uint_t plain)
{
u_char type;
size_t len;
len = rec->payload.len;
if (plain) {
type = rec->type;
} else {
type = SSL3_RT_APPLICATION_DATA;
len += EVP_GCM_TLS_TAG_LEN;
}
out[0] = type;
out[1] = 0x03;
out[2] = 0x03;
out[3] = (len >> 8);
out[4] = len;
return 5;
}
static ngx_int_t
ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res)
{
ngx_str_t ad, out;
ngx_quic_secret_t *secret;
ngx_quic_ciphers_t ciphers;
u_char nonce[NGX_QUIC_IV_LEN];
ad.data = res->data;
ad.len = ngx_quic_compat_create_header(rec, ad.data, 0);
out.len = rec->payload.len + EVP_GCM_TLS_TAG_LEN;
out.data = res->data + ad.len;
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0,
"quic compat ad len:%uz %xV", ad.len, &ad);
#endif
if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == NGX_ERROR)
{
return NGX_ERROR;
}
secret = &rec->keys->secret;
ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number);
if (ngx_quic_tls_seal(ciphers.c, secret, &out,
nonce, &rec->payload, &ad, rec->log)
!= NGX_OK)
{
return NGX_ERROR;
}
res->len = ad.len + out.len;
return NGX_OK;
}
enum ssl_encryption_level_t
SSL_quic_read_level(const SSL *ssl)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection(ssl);
qc = ngx_quic_get_connection(c);
return qc->compat->read_level;
}
enum ssl_encryption_level_t
SSL_quic_write_level(const SSL *ssl)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection(ssl);
qc = ngx_quic_get_connection(c);
return qc->compat->write_level;
}
int
SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
size_t params_len)
{
ngx_connection_t *c;
ngx_quic_compat_t *com;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection(ssl);
qc = ngx_quic_get_connection(c);
com = qc->compat;
com->tp.len = params_len;
com->tp.data = (u_char *) params;
return 1;
}
void
SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params,
size_t *out_params_len)
{
ngx_connection_t *c;
ngx_quic_compat_t *com;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection(ssl);
qc = ngx_quic_get_connection(c);
com = qc->compat;
*out_params = com->ctp.data;
*out_params_len = com->ctp.len;
}
#endif /* NGX_QUIC_OPENSSL_COMPAT */

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_
#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_
#ifdef TLSEXT_TYPE_quic_transport_parameters
#undef NGX_QUIC_OPENSSL_COMPAT
#else
#include <ngx_config.h>
#include <ngx_core.h>
typedef struct ngx_quic_compat_s ngx_quic_compat_t;
enum ssl_encryption_level_t {
ssl_encryption_initial = 0,
ssl_encryption_early_data,
ssl_encryption_handshake,
ssl_encryption_application
};
typedef struct ssl_quic_method_st {
int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher,
const uint8_t *rsecret, size_t secret_len);
int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher,
const uint8_t *wsecret, size_t secret_len);
int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level,
const uint8_t *data, size_t len);
int (*flush_flight)(SSL *ssl);
int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level,
uint8_t alert);
} SSL_QUIC_METHOD;
ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx);
int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method);
int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
const uint8_t *data, size_t len);
enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl);
enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl);
int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
size_t params_len);
void SSL_get_peer_quic_transport_params(const SSL *ssl,
const uint8_t **out_params, size_t *out_params_len);
#endif /* TLSEXT_TYPE_quic_transport_parameters */
#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
ngx_int_t ngx_quic_output(ngx_connection_t *c);
ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
ngx_quic_header_t *inpkt);
ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c,
ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_send_cc(ngx_connection_t *c);
ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c,
ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason);
ngx_int_t ngx_quic_send_retry(ngx_connection_t *c,
ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path);
ngx_int_t ngx_quic_send_ack(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx);
ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest);
ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
size_t min, ngx_quic_path_t *path);
#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event_quic_transport.h>
#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1)
/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */
#define NGX_QUIC_IV_LEN 12
/* largest hash used in TLS is SHA-384 */
#define NGX_QUIC_MAX_MD_SIZE 48
#ifdef OPENSSL_IS_BORINGSSL
#define ngx_quic_cipher_t EVP_AEAD
#else
#define ngx_quic_cipher_t EVP_CIPHER
#endif
typedef struct {
size_t len;
u_char data[NGX_QUIC_MAX_MD_SIZE];
} ngx_quic_md_t;
typedef struct {
size_t len;
u_char data[NGX_QUIC_IV_LEN];
} ngx_quic_iv_t;
typedef struct {
ngx_quic_md_t secret;
ngx_quic_md_t key;
ngx_quic_iv_t iv;
ngx_quic_md_t hp;
} ngx_quic_secret_t;
typedef struct {
ngx_quic_secret_t client;
ngx_quic_secret_t server;
} ngx_quic_secrets_t;
struct ngx_quic_keys_s {
ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST];
ngx_quic_secrets_t next_key;
ngx_uint_t cipher;
};
typedef struct {
const ngx_quic_cipher_t *c;
const EVP_CIPHER *hp;
const EVP_MD *d;
} ngx_quic_ciphers_t;
typedef struct {
size_t out_len;
u_char *out;
size_t prk_len;
const uint8_t *prk;
size_t label_len;
const u_char *label;
} ngx_quic_hkdf_t;
#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \
(seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \
(seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \
(seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label);
ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys,
ngx_str_t *secret, ngx_log_t *log);
ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log,
ngx_uint_t is_write, ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *secret, size_t secret_len);
ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level);
void ngx_quic_keys_discard(ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level);
void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys);
ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys);
ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res);
ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn);
void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers,
enum ssl_encryption_level_t level);
ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher,
ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
ngx_str_t *ad, ngx_log_t *log);
ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest,
ngx_log_t *log);
#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */

View File

@ -0,0 +1,238 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
ngx_int_t
ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc,
ngx_quic_header_t *pkt)
{
ngx_quic_socket_t *qsock, *tmp;
ngx_quic_client_id_t *cid;
/*
* qc->path = NULL
*
* qc->nclient_ids = 0
* qc->nsockets = 0
* qc->max_retired_seqnum = 0
* qc->client_seqnum = 0
*/
ngx_queue_init(&qc->sockets);
ngx_queue_init(&qc->free_sockets);
ngx_queue_init(&qc->paths);
ngx_queue_init(&qc->free_paths);
ngx_queue_init(&qc->client_ids);
ngx_queue_init(&qc->free_client_ids);
qc->tp.original_dcid.len = pkt->odcid.len;
qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid);
if (qc->tp.original_dcid.data == NULL) {
return NGX_ERROR;
}
/* socket to use for further processing (id auto-generated) */
qsock = ngx_quic_create_socket(c, qc);
if (qsock == NULL) {
return NGX_ERROR;
}
/* socket is listening at new server id */
if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
return NGX_ERROR;
}
qsock->used = 1;
qc->tp.initial_scid.len = qsock->sid.len;
qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len);
if (qc->tp.initial_scid.data == NULL) {
goto failed;
}
ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len);
/* for all packets except first, this is set at udp layer */
c->udp = &qsock->udp;
/* ngx_quic_get_connection(c) macro is now usable */
/* we have a client identified by scid */
cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL);
if (cid == NULL) {
goto failed;
}
/* path of the first packet is our initial active path */
qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid);
if (qc->path == NULL) {
goto failed;
}
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
if (pkt->validated) {
qc->path->validated = 1;
qc->path->limited = 0;
}
ngx_quic_path_dbg(c, "set active", qc->path);
tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
if (tmp == NULL) {
goto failed;
}
tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */
ngx_memcpy(tmp->sid.id, pkt->odcid.data, pkt->odcid.len);
tmp->sid.len = pkt->odcid.len;
if (ngx_quic_listen(c, qc, tmp) != NGX_OK) {
goto failed;
}
return NGX_OK;
failed:
ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
c->udp = NULL;
return NGX_ERROR;
}
ngx_quic_socket_t *
ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc)
{
ngx_queue_t *q;
ngx_quic_socket_t *sock;
if (!ngx_queue_empty(&qc->free_sockets)) {
q = ngx_queue_head(&qc->free_sockets);
sock = ngx_queue_data(q, ngx_quic_socket_t, queue);
ngx_queue_remove(&sock->queue);
ngx_memzero(sock, sizeof(ngx_quic_socket_t));
} else {
sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
if (sock == NULL) {
return NULL;
}
}
sock->sid.len = NGX_QUIC_SERVER_CID_LEN;
if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) {
return NULL;
}
sock->sid.seqnum = qc->server_seqnum++;
return sock;
}
void
ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_queue_remove(&qsock->queue);
ngx_queue_insert_head(&qc->free_sockets, &qsock->queue);
ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
qc->nsockets--;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%L closed nsock:%ui",
(int64_t) qsock->sid.seqnum, qc->nsockets);
}
ngx_int_t
ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
ngx_quic_socket_t *qsock)
{
ngx_str_t id;
ngx_quic_server_id_t *sid;
sid = &qsock->sid;
id.data = sid->id;
id.len = sid->len;
qsock->udp.connection = c;
qsock->udp.node.key = ngx_crc32_long(id.data, id.len);
qsock->udp.key = id;
ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node);
ngx_queue_insert_tail(&qc->sockets, &qsock->queue);
qc->nsockets++;
qsock->quic = qc;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%L listening at sid:%xV nsock:%ui",
(int64_t) sid->seqnum, &id, qc->nsockets);
return NGX_OK;
}
void
ngx_quic_close_sockets(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
while (!ngx_queue_empty(&qc->sockets)) {
q = ngx_queue_head(&qc->sockets);
qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
ngx_quic_close_socket(c, qsock);
}
}
ngx_quic_socket_t *
ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum)
{
ngx_queue_t *q;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
for (q = ngx_queue_head(&qc->sockets);
q != ngx_queue_sentinel(&qc->sockets);
q = ngx_queue_next(q))
{
qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
if (qsock->sid.seqnum == seqnum) {
return qsock;
}
}
return NULL;
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c,
ngx_quic_connection_t *qc, ngx_quic_header_t *pkt);
void ngx_quic_close_sockets(ngx_connection_t *c);
ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c,
ngx_quic_connection_t *qc);
ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
ngx_quic_socket_t *qsock);
void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock);
ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum);
#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */

View File

@ -0,0 +1,600 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#if defined OPENSSL_IS_BORINGSSL \
|| defined LIBRESSL_VERSION_NUMBER \
|| NGX_QUIC_OPENSSL_COMPAT
#define NGX_QUIC_BORINGSSL_API 1
#endif
/*
* RFC 9000, 7.5. Cryptographic Message Buffering
*
* Implementations MUST support buffering at least 4096 bytes of data
*/
#define NGX_QUIC_MAX_BUFFERED 65535
#if (NGX_QUIC_BORINGSSL_API)
static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *secret, size_t secret_len);
static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *secret, size_t secret_len);
#else
static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *read_secret,
const uint8_t *write_secret, size_t secret_len);
#endif
static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *data, size_t len);
static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, uint8_t alert);
static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data);
#if (NGX_QUIC_BORINGSSL_API)
static int
ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *rsecret, size_t secret_len)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_set_read_secret() level:%d", level);
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic read secret len:%uz %*xs", secret_len,
secret_len, rsecret);
#endif
if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level,
cipher, rsecret, secret_len)
!= NGX_OK)
{
return 0;
}
return 1;
}
static int
ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *wsecret, size_t secret_len)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_set_write_secret() level:%d", level);
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic write secret len:%uz %*xs", secret_len,
secret_len, wsecret);
#endif
if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
cipher, wsecret, secret_len)
!= NGX_OK)
{
return 0;
}
return 1;
}
#else
static int
ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *rsecret,
const uint8_t *wsecret, size_t secret_len)
{
ngx_connection_t *c;
const SSL_CIPHER *cipher;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_set_encryption_secrets() level:%d", level);
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic read secret len:%uz %*xs", secret_len,
secret_len, rsecret);
#endif
cipher = SSL_get_current_cipher(ssl_conn);
if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level,
cipher, rsecret, secret_len)
!= NGX_OK)
{
return 0;
}
if (level == ssl_encryption_early_data) {
return 1;
}
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic write secret len:%uz %*xs", secret_len,
secret_len, wsecret);
#endif
if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
cipher, wsecret, secret_len)
!= NGX_OK)
{
return 0;
}
return 1;
}
#endif
static int
ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *data, size_t len)
{
u_char *p, *end;
size_t client_params_len;
ngx_chain_t *out;
const uint8_t *client_params;
ngx_quic_tp_t ctp;
ngx_quic_frame_t *frame;
ngx_connection_t *c;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
unsigned int alpn_len;
const unsigned char *alpn_data;
#endif
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_add_handshake_data");
if (!qc->client_tp_done) {
/*
* things to do once during handshake: check ALPN and transport
* parameters; we want to break handshake if something is wrong
* here;
*/
#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len);
if (alpn_len == 0) {
qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL);
qc->error_reason = "unsupported protocol in ALPN extension";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic unsupported protocol in ALPN extension");
return 0;
}
#endif
SSL_get_peer_quic_transport_params(ssl_conn, &client_params,
&client_params_len);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic SSL_get_peer_quic_transport_params():"
" params_len:%ui", client_params_len);
if (client_params_len == 0) {
/* RFC 9001, 8.2. QUIC Transport Parameters Extension */
qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION);
qc->error_reason = "missing transport parameters";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"missing transport parameters");
return 0;
}
p = (u_char *) client_params;
end = p + client_params_len;
/* defaults for parameters not sent by client */
ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t));
if (ngx_quic_parse_transport_params(p, end, &ctp, c->log)
!= NGX_OK)
{
qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
qc->error_reason = "failed to process transport parameters";
return 0;
}
if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) {
return 0;
}
qc->client_tp_done = 1;
}
ctx = ngx_quic_get_send_ctx(qc, level);
out = ngx_quic_copy_buffer(c, (u_char *) data, len);
if (out == NGX_CHAIN_ERROR) {
return 0;
}
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return 0;
}
frame->data = out;
frame->level = level;
frame->type = NGX_QUIC_FT_CRYPTO;
frame->u.crypto.offset = ctx->crypto_sent;
frame->u.crypto.length = len;
ctx->crypto_sent += len;
ngx_quic_queue_frame(qc, frame);
return 1;
}
static int
ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
{
#if (NGX_DEBUG)
ngx_connection_t *c;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_flush_flight()");
#endif
return 1;
}
static int
ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
uint8_t alert)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_send_alert() level:%s alert:%d",
ngx_quic_level_name(level), (int) alert);
/* already closed on regular shutdown */
qc = ngx_quic_get_connection(c);
if (qc == NULL) {
return 1;
}
qc->error = NGX_QUIC_ERR_CRYPTO(alert);
qc->error_reason = "handshake failed";
return 1;
}
ngx_int_t
ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
ngx_quic_frame_t *frame)
{
uint64_t last;
ngx_chain_t *cl;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
ngx_quic_crypto_frame_t *f;
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
f = &frame->u.crypto;
/* no overflow since both values are 62-bit */
last = f->offset + f->length;
if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) {
qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED;
return NGX_ERROR;
}
if (last <= ctx->crypto.offset) {
if (pkt->level == ssl_encryption_initial) {
/* speeding up handshake completion */
if (!ngx_queue_empty(&ctx->sent)) {
ngx_quic_resend_frames(c, ctx);
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
while (!ngx_queue_empty(&ctx->sent)) {
ngx_quic_resend_frames(c, ctx);
}
}
}
return NGX_OK;
}
if (f->offset == ctx->crypto.offset) {
if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) {
return NGX_ERROR;
}
ngx_quic_skip_buffer(c, &ctx->crypto, last);
} else {
if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length,
f->offset)
== NGX_CHAIN_ERROR)
{
return NGX_ERROR;
}
}
cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1);
if (cl) {
if (ngx_quic_crypto_input(c, cl) != NGX_OK) {
return NGX_ERROR;
}
ngx_quic_free_chain(c, cl);
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data)
{
int n, sslerr;
ngx_buf_t *b;
ngx_chain_t *cl;
ngx_ssl_conn_t *ssl_conn;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ssl_conn = c->ssl->connection;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
(int) SSL_quic_read_level(ssl_conn),
(int) SSL_quic_write_level(ssl_conn));
for (cl = data; cl; cl = cl->next) {
b = cl->buf;
if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn),
b->pos, b->last - b->pos))
{
ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
"SSL_provide_quic_data() failed");
return NGX_ERROR;
}
}
n = SSL_do_handshake(ssl_conn);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
(int) SSL_quic_read_level(ssl_conn),
(int) SSL_quic_write_level(ssl_conn));
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
if (n <= 0) {
sslerr = SSL_get_error(ssl_conn, n);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
sslerr);
if (sslerr != SSL_ERROR_WANT_READ) {
if (c->ssl->handshake_rejected) {
ngx_connection_error(c, 0, "handshake rejected");
ERR_clear_error();
return NGX_ERROR;
}
ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
return NGX_ERROR;
}
}
if (n <= 0 || SSL_in_init(ssl_conn)) {
if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data)
&& qc->client_tp_done)
{
if (ngx_quic_init_streams(c) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
#if (NGX_DEBUG)
ngx_ssl_handshake_log(c);
#endif
c->ssl->handshaked = 1;
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_HANDSHAKE_DONE;
ngx_quic_queue_frame(qc, frame);
if (qc->conf->retry) {
if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
/*
* RFC 9001, 9.5. Header Protection Timing Side Channels
*
* Generating next keys before a key update is received.
*/
if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) {
return NGX_ERROR;
}
/*
* RFC 9001, 4.9.2. Discarding Handshake Keys
*
* An endpoint MUST discard its Handshake keys
* when the TLS handshake is confirmed.
*/
ngx_quic_discard_ctx(c, ssl_encryption_handshake);
/* start accepting clients on negotiated number of server ids */
if (ngx_quic_create_sockets(c) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_init_streams(c) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_quic_init_connection(ngx_connection_t *c)
{
u_char *p;
size_t clen;
ssize_t len;
ngx_str_t dcid;
ngx_ssl_conn_t *ssl_conn;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
static SSL_QUIC_METHOD quic_method;
qc = ngx_quic_get_connection(c);
if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) {
return NGX_ERROR;
}
c->ssl->no_wait_shutdown = 1;
ssl_conn = c->ssl->connection;
if (!quic_method.send_alert) {
#if (NGX_QUIC_BORINGSSL_API)
quic_method.set_read_secret = ngx_quic_set_read_secret;
quic_method.set_write_secret = ngx_quic_set_write_secret;
#else
quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets;
#endif
quic_method.add_handshake_data = ngx_quic_add_handshake_data;
quic_method.flush_flight = ngx_quic_flush_flight;
quic_method.send_alert = ngx_quic_send_alert;
}
if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic SSL_set_quic_method() failed");
return NGX_ERROR;
}
#ifdef OPENSSL_INFO_QUIC
if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) {
SSL_set_quic_early_data_enabled(ssl_conn, 1);
}
#endif
qsock = ngx_quic_get_socket(c);
dcid.data = qsock->sid.id;
dcid.len = qsock->sid.len;
if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token)
!= NGX_OK)
{
return NGX_ERROR;
}
len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen);
/* always succeeds */
p = ngx_pnalloc(c->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL);
if (len < 0) {
return NGX_ERROR;
}
#ifdef NGX_QUIC_DEBUG_PACKETS
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic transport parameters len:%uz %*xs", len, len, p);
#endif
if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic SSL_set_quic_transport_params() failed");
return NGX_ERROR;
}
#ifdef OPENSSL_IS_BORINGSSL
if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic SSL_set_quic_early_data_context() failed");
return NGX_ERROR;
}
#endif
return NGX_OK;
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_
#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
void ngx_quic_handle_stream_ack(ngx_connection_t *c,
ngx_quic_frame_t *f);
ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c,
ngx_quic_max_data_frame_t *f);
ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f);
ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f);
ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f);
ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f);
ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f);
ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f);
ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f);
ngx_int_t ngx_quic_init_streams(ngx_connection_t *c);
void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree,
uint64_t id);
ngx_int_t ngx_quic_close_streams(ngx_connection_t *c,
ngx_quic_connection_t *qc);
#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */

View File

@ -0,0 +1,289 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_sha1.h>
#include <ngx_event_quic_connection.h>
static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
ngx_uint_t no_port, u_char buf[20]);
ngx_int_t
ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret,
u_char *token)
{
ngx_str_t tmp;
tmp.data = secret;
tmp.len = NGX_QUIC_SR_KEY_LEN;
if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token,
NGX_QUIC_SR_TOKEN_LEN)
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stateless reset token %*xs",
(size_t) NGX_QUIC_SR_TOKEN_LEN, token);
return NGX_OK;
}
ngx_int_t
ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr,
socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
time_t exp, ngx_uint_t is_retry)
{
int len, iv_len;
u_char *p, *iv;
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher;
u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
ngx_quic_address_hash(sockaddr, socklen, !is_retry, in);
p = in + 20;
p = ngx_cpymem(p, &exp, sizeof(time_t));
*p++ = is_retry ? 1 : 0;
if (odcid) {
*p++ = odcid->len;
p = ngx_cpymem(p, odcid->data, odcid->len);
} else {
*p++ = 0;
}
len = p - in;
cipher = EVP_aes_256_cbc();
iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
if ((size_t) (iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) > token->len)
{
ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small");
return NGX_ERROR;
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
return NGX_ERROR;
}
iv = token->data;
if (RAND_bytes(iv, iv_len) <= 0
|| !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv))
{
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
token->len = iv_len;
if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) {
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
token->len += len;
if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) {
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
token->len += len;
EVP_CIPHER_CTX_free(ctx);
#ifdef NGX_QUIC_DEBUG_PACKETS
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
"quic new token len:%uz %xV", token->len, token);
#endif
return NGX_OK;
}
static void
ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
ngx_uint_t no_port, u_char buf[20])
{
size_t len;
u_char *data;
ngx_sha1_t sha1;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
len = (size_t) socklen;
data = (u_char *) sockaddr;
if (no_port) {
switch (sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sockaddr;
len = sizeof(struct in6_addr);
data = sin6->sin6_addr.s6_addr;
break;
#endif
case AF_INET:
sin = (struct sockaddr_in *) sockaddr;
len = sizeof(in_addr_t);
data = (u_char *) &sin->sin_addr;
break;
}
}
ngx_sha1_init(&sha1);
ngx_sha1_update(&sha1, data, len);
ngx_sha1_final(buf, &sha1);
}
ngx_int_t
ngx_quic_validate_token(ngx_connection_t *c, u_char *key,
ngx_quic_header_t *pkt)
{
int len, tlen, iv_len;
u_char *iv, *p;
time_t now, exp;
size_t total;
ngx_str_t odcid;
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher;
u_char addr_hash[20];
u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
#if NGX_SUPPRESS_WARN
ngx_str_null(&odcid);
#endif
/* Retry token or NEW_TOKEN in a previous connection */
cipher = EVP_aes_256_cbc();
iv = pkt->token.data;
iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
/* sanity checks */
if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) {
goto garbage;
}
if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
goto garbage;
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
return NGX_ERROR;
}
if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
p = pkt->token.data + iv_len;
len = pkt->token.len - iv_len;
if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) {
EVP_CIPHER_CTX_free(ctx);
goto garbage;
}
total = len;
if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
EVP_CIPHER_CTX_free(ctx);
goto garbage;
}
total += tlen;
EVP_CIPHER_CTX_free(ctx);
if (total < (20 + sizeof(time_t) + 2)) {
goto garbage;
}
p = tdec + 20;
ngx_memcpy(&exp, p, sizeof(time_t));
p += sizeof(time_t);
pkt->retried = (*p++ == 1);
ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash);
if (ngx_memcmp(tdec, addr_hash, 20) != 0) {
goto bad_token;
}
odcid.len = *p++;
if (odcid.len) {
if (odcid.len > NGX_QUIC_MAX_CID_LEN) {
goto bad_token;
}
if ((size_t)(tdec + total - p) < odcid.len) {
goto bad_token;
}
odcid.data = p;
}
now = ngx_time();
if (now > exp) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token");
return NGX_DECLINED;
}
if (odcid.len) {
pkt->odcid.len = odcid.len;
pkt->odcid.data = pkt->odcid_buf;
ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len);
} else {
pkt->odcid = pkt->dcid;
}
pkt->validated = 1;
return NGX_OK;
garbage:
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token");
return NGX_ABORT;
bad_token:
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
return NGX_DECLINED;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_MAX_TOKEN_SIZE 64
/* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */
/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */
#define NGX_QUIC_AES_256_CBC_IV_LEN 16
#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16
#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_CBC_IV_LEN \
+ NGX_QUIC_MAX_TOKEN_SIZE \
+ NGX_QUIC_AES_256_CBC_BLOCK_SIZE)
ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid,
u_char *secret, u_char *token);
ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr,
socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
time_t expires, ngx_uint_t is_retry);
ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
u_char *key, ngx_quic_header_t *pkt);
#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,397 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
/*
* RFC 9000, 17.2. Long Header Packets
* 17.3. Short Header Packets
*
* QUIC flags in first byte
*/
#define NGX_QUIC_PKT_LONG 0x80 /* header form */
#define NGX_QUIC_PKT_FIXED_BIT 0x40
#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */
#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */
#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG)
#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0)
/* Long packet types */
#define NGX_QUIC_PKT_INITIAL 0x00
#define NGX_QUIC_PKT_ZRTT 0x10
#define NGX_QUIC_PKT_HANDSHAKE 0x20
#define NGX_QUIC_PKT_RETRY 0x30
#define ngx_quic_pkt_in(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL)
#define ngx_quic_pkt_zrtt(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT)
#define ngx_quic_pkt_hs(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE)
#define ngx_quic_pkt_retry(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY)
#define ngx_quic_pkt_rb_mask(flags) \
(ngx_quic_long_pkt(flags) ? 0x0C : 0x18)
#define ngx_quic_pkt_hp_mask(flags) \
(ngx_quic_long_pkt(flags) ? 0x0F : 0x1F)
#define ngx_quic_level_name(lvl) \
(lvl == ssl_encryption_application) ? "app" \
: (lvl == ssl_encryption_initial) ? "init" \
: (lvl == ssl_encryption_handshake) ? "hs" : "early"
#define NGX_QUIC_MAX_CID_LEN 20
#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN
/* 12.4. Frames and Frame Types */
#define NGX_QUIC_FT_PADDING 0x00
#define NGX_QUIC_FT_PING 0x01
#define NGX_QUIC_FT_ACK 0x02
#define NGX_QUIC_FT_ACK_ECN 0x03
#define NGX_QUIC_FT_RESET_STREAM 0x04
#define NGX_QUIC_FT_STOP_SENDING 0x05
#define NGX_QUIC_FT_CRYPTO 0x06
#define NGX_QUIC_FT_NEW_TOKEN 0x07
#define NGX_QUIC_FT_STREAM 0x08
#define NGX_QUIC_FT_STREAM1 0x09
#define NGX_QUIC_FT_STREAM2 0x0A
#define NGX_QUIC_FT_STREAM3 0x0B
#define NGX_QUIC_FT_STREAM4 0x0C
#define NGX_QUIC_FT_STREAM5 0x0D
#define NGX_QUIC_FT_STREAM6 0x0E
#define NGX_QUIC_FT_STREAM7 0x0F
#define NGX_QUIC_FT_MAX_DATA 0x10
#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11
#define NGX_QUIC_FT_MAX_STREAMS 0x12
#define NGX_QUIC_FT_MAX_STREAMS2 0x13
#define NGX_QUIC_FT_DATA_BLOCKED 0x14
#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15
#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16
#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17
#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18
#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19
#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A
#define NGX_QUIC_FT_PATH_RESPONSE 0x1B
#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C
#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D
#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E
#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE
/* 22.5. QUIC Transport Error Codes Registry */
#define NGX_QUIC_ERR_NO_ERROR 0x00
#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01
#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02
#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03
#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04
#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05
#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06
#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07
#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08
#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09
#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A
#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B
#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C
#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D
#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E
#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F
#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10
#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100
#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e))
/* 22.3. QUIC Transport Parameters Registry */
#define NGX_QUIC_TP_ORIGINAL_DCID 0x00
#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01
#define NGX_QUIC_TP_SR_TOKEN 0x02
#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03
#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04
#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05
#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06
#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07
#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08
#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09
#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A
#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B
#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C
#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D
#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E
#define NGX_QUIC_TP_INITIAL_SCID 0x0F
#define NGX_QUIC_TP_RETRY_SCID 0x10
#define NGX_QUIC_CID_LEN_MIN 8
#define NGX_QUIC_CID_LEN_MAX 20
#define NGX_QUIC_MAX_RANGES 10
typedef struct {
uint64_t gap;
uint64_t range;
} ngx_quic_ack_range_t;
typedef struct {
uint64_t largest;
uint64_t delay;
uint64_t range_count;
uint64_t first_range;
uint64_t ect0;
uint64_t ect1;
uint64_t ce;
uint64_t ranges_length;
} ngx_quic_ack_frame_t;
typedef struct {
uint64_t seqnum;
uint64_t retire;
uint8_t len;
u_char cid[NGX_QUIC_CID_LEN_MAX];
u_char srt[NGX_QUIC_SR_TOKEN_LEN];
} ngx_quic_new_conn_id_frame_t;
typedef struct {
uint64_t length;
} ngx_quic_new_token_frame_t;
/*
* common layout for CRYPTO and STREAM frames;
* conceptually, CRYPTO frame is also a stream
* frame lacking some properties
*/
typedef struct {
uint64_t offset;
uint64_t length;
} ngx_quic_ordered_frame_t;
typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t;
typedef struct {
/* initial fields same as in ngx_quic_ordered_frame_t */
uint64_t offset;
uint64_t length;
uint64_t stream_id;
unsigned off:1;
unsigned len:1;
unsigned fin:1;
} ngx_quic_stream_frame_t;
typedef struct {
uint64_t max_data;
} ngx_quic_max_data_frame_t;
typedef struct {
uint64_t error_code;
uint64_t frame_type;
ngx_str_t reason;
} ngx_quic_close_frame_t;
typedef struct {
uint64_t id;
uint64_t error_code;
uint64_t final_size;
} ngx_quic_reset_stream_frame_t;
typedef struct {
uint64_t id;
uint64_t error_code;
} ngx_quic_stop_sending_frame_t;
typedef struct {
uint64_t limit;
ngx_uint_t bidi; /* unsigned: bidi:1 */
} ngx_quic_streams_blocked_frame_t;
typedef struct {
uint64_t limit;
ngx_uint_t bidi; /* unsigned: bidi:1 */
} ngx_quic_max_streams_frame_t;
typedef struct {
uint64_t id;
uint64_t limit;
} ngx_quic_max_stream_data_frame_t;
typedef struct {
uint64_t limit;
} ngx_quic_data_blocked_frame_t;
typedef struct {
uint64_t id;
uint64_t limit;
} ngx_quic_stream_data_blocked_frame_t;
typedef struct {
uint64_t sequence_number;
} ngx_quic_retire_cid_frame_t;
typedef struct {
u_char data[8];
} ngx_quic_path_challenge_frame_t;
typedef struct ngx_quic_frame_s ngx_quic_frame_t;
struct ngx_quic_frame_s {
ngx_uint_t type;
enum ssl_encryption_level_t level;
ngx_queue_t queue;
uint64_t pnum;
size_t plen;
ngx_msec_t first;
ngx_msec_t last;
ssize_t len;
unsigned need_ack:1;
unsigned pkt_need_ack:1;
unsigned flush:1;
ngx_chain_t *data;
union {
ngx_quic_ack_frame_t ack;
ngx_quic_crypto_frame_t crypto;
ngx_quic_ordered_frame_t ord;
ngx_quic_new_conn_id_frame_t ncid;
ngx_quic_new_token_frame_t token;
ngx_quic_stream_frame_t stream;
ngx_quic_max_data_frame_t max_data;
ngx_quic_close_frame_t close;
ngx_quic_reset_stream_frame_t reset_stream;
ngx_quic_stop_sending_frame_t stop_sending;
ngx_quic_streams_blocked_frame_t streams_blocked;
ngx_quic_max_streams_frame_t max_streams;
ngx_quic_max_stream_data_frame_t max_stream_data;
ngx_quic_data_blocked_frame_t data_blocked;
ngx_quic_stream_data_blocked_frame_t stream_data_blocked;
ngx_quic_retire_cid_frame_t retire_cid;
ngx_quic_path_challenge_frame_t path_challenge;
ngx_quic_path_challenge_frame_t path_response;
} u;
};
typedef struct {
ngx_log_t *log;
ngx_quic_path_t *path;
ngx_quic_keys_t *keys;
ngx_msec_t received;
uint64_t number;
uint8_t num_len;
uint32_t trunc;
uint8_t flags;
uint32_t version;
ngx_str_t token;
enum ssl_encryption_level_t level;
ngx_uint_t error;
/* filled in by parser */
ngx_buf_t *raw; /* udp datagram */
u_char *data; /* quic packet */
size_t len;
/* cleartext fields */
ngx_str_t odcid; /* retry packet tag */
u_char odcid_buf[NGX_QUIC_MAX_CID_LEN];
ngx_str_t dcid;
ngx_str_t scid;
uint64_t pn;
u_char *plaintext;
ngx_str_t payload; /* decrypted data */
unsigned need_ack:1;
unsigned key_phase:1;
unsigned key_update:1;
unsigned parsed:1;
unsigned decrypted:1;
unsigned validated:1;
unsigned retried:1;
unsigned first:1;
unsigned rebound:1;
} ngx_quic_header_t;
typedef struct {
ngx_msec_t max_idle_timeout;
ngx_msec_t max_ack_delay;
size_t max_udp_payload_size;
size_t initial_max_data;
size_t initial_max_stream_data_bidi_local;
size_t initial_max_stream_data_bidi_remote;
size_t initial_max_stream_data_uni;
ngx_uint_t initial_max_streams_bidi;
ngx_uint_t initial_max_streams_uni;
ngx_uint_t ack_delay_exponent;
ngx_uint_t active_connection_id_limit;
ngx_flag_t disable_active_migration;
ngx_str_t original_dcid;
ngx_str_t initial_scid;
ngx_str_t retry_scid;
u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
/* TODO */
void *preferred_address;
} ngx_quic_tp_t;
ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt);
size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out);
size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len);
size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out,
u_char **pnp);
size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
u_char **start);
ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
ngx_quic_frame_t *frame);
ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f);
ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start,
u_char *end, uint64_t *gap, uint64_t *range);
size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range);
ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp,
ngx_quic_conf_t *qcf);
ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end,
ngx_quic_tp_t *tp, ngx_log_t *log);
ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end,
ngx_quic_tp_t *tp, size_t *clen);
void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key);
#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */

View File

@ -0,0 +1,420 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
static void ngx_quic_close_accepted_connection(ngx_connection_t *c);
static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls,
ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen);
void
ngx_quic_recvmsg(ngx_event_t *ev)
{
ssize_t n;
ngx_str_t key;
ngx_buf_t buf;
ngx_log_t *log;
ngx_err_t err;
socklen_t socklen, local_socklen;
ngx_event_t *rev, *wev;
struct iovec iov[1];
struct msghdr msg;
ngx_sockaddr_t sa, lsa;
struct sockaddr *sockaddr, *local_sockaddr;
ngx_listening_t *ls;
ngx_event_conf_t *ecf;
ngx_connection_t *c, *lc;
ngx_quic_socket_t *qsock;
static u_char buffer[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
#if (NGX_HAVE_ADDRINFO_CMSG)
u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
#endif
if (ev->timedout) {
if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
return;
}
ev->timedout = 0;
}
ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
ev->available = ecf->multi_accept;
}
lc = ev->data;
ls = lc->listening;
ev->ready = 0;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"quic recvmsg on %V, ready: %d",
&ls->addr_text, ev->available);
do {
ngx_memzero(&msg, sizeof(struct msghdr));
iov[0].iov_base = (void *) buffer;
iov[0].iov_len = sizeof(buffer);
msg.msg_name = &sa;
msg.msg_namelen = sizeof(ngx_sockaddr_t);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
#if (NGX_HAVE_ADDRINFO_CMSG)
if (ls->wildcard) {
msg.msg_control = &msg_control;
msg.msg_controllen = sizeof(msg_control);
ngx_memzero(&msg_control, sizeof(msg_control));
}
#endif
n = recvmsg(lc->fd, &msg, 0);
if (n == -1) {
err = ngx_socket_errno;
if (err == NGX_EAGAIN) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
"quic recvmsg() not ready");
return;
}
ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed");
return;
}
#if (NGX_HAVE_ADDRINFO_CMSG)
if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
"quic recvmsg() truncated data");
continue;
}
#endif
sockaddr = msg.msg_name;
socklen = msg.msg_namelen;
if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
socklen = sizeof(ngx_sockaddr_t);
}
#if (NGX_HAVE_UNIX_DOMAIN)
if (sockaddr->sa_family == AF_UNIX) {
struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr;
if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path)
|| saun->sun_path[0] == '\0')
{
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
"unbound unix socket");
goto next;
}
}
#endif
local_sockaddr = ls->sockaddr;
local_socklen = ls->socklen;
#if (NGX_HAVE_ADDRINFO_CMSG)
if (ls->wildcard) {
struct cmsghdr *cmsg;
ngx_memcpy(&lsa, local_sockaddr, local_socklen);
local_sockaddr = &lsa.sockaddr;
for (cmsg = CMSG_FIRSTHDR(&msg);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg))
{
if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) {
break;
}
}
}
#endif
if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) {
goto next;
}
c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen);
if (c) {
#if (NGX_DEBUG)
if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
ngx_log_handler_pt handler;
handler = c->log->handler;
c->log->handler = NULL;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic recvmsg: fd:%d n:%z", c->fd, n);
c->log->handler = handler;
}
#endif
ngx_memzero(&buf, sizeof(ngx_buf_t));
buf.pos = buffer;
buf.last = buffer + n;
buf.start = buf.pos;
buf.end = buffer + sizeof(buffer);
qsock = ngx_quic_get_socket(c);
ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen);
qsock->socklen = socklen;
c->udp->buffer = &buf;
rev = c->read;
rev->ready = 1;
rev->active = 0;
rev->handler(rev);
if (c->udp) {
c->udp->buffer = NULL;
}
rev->ready = 0;
rev->active = 1;
goto next;
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
#endif
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
c = ngx_get_connection(lc->fd, ev->log);
if (c == NULL) {
return;
}
c->shared = 1;
c->type = SOCK_DGRAM;
c->socklen = socklen;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
c->pool = ngx_create_pool(ls->pool_size, ev->log);
if (c->pool == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN);
if (c->sockaddr == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
ngx_memcpy(c->sockaddr, sockaddr, socklen);
log = ngx_palloc(c->pool, sizeof(ngx_log_t));
if (log == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
*log = ls->log;
c->log = log;
c->pool->log = log;
c->listening = ls;
if (local_sockaddr == &lsa.sockaddr) {
local_sockaddr = ngx_palloc(c->pool, local_socklen);
if (local_sockaddr == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
ngx_memcpy(local_sockaddr, &lsa, local_socklen);
}
c->local_sockaddr = local_sockaddr;
c->local_socklen = local_socklen;
c->buffer = ngx_create_temp_buf(c->pool, n);
if (c->buffer == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n);
rev = c->read;
wev = c->write;
rev->active = 1;
wev->ready = 1;
rev->log = log;
wev->log = log;
/*
* TODO: MT: - ngx_atomic_fetch_add()
* or protection by critical section or light mutex
*
* TODO: MP: - allocated in a shared memory
* - ngx_atomic_fetch_add()
* or protection by critical section or light mutex
*/
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
c->start_time = ngx_current_msec;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
#endif
if (ls->addr_ntop) {
c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
if (c->addr_text.data == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
c->addr_text.data,
ls->addr_text_max_len, 0);
if (c->addr_text.len == 0) {
ngx_quic_close_accepted_connection(c);
return;
}
}
#if (NGX_DEBUG)
{
ngx_str_t addr;
u_char text[NGX_SOCKADDR_STRLEN];
ngx_debug_accepted_connection(ecf, c);
if (log->log_level & NGX_LOG_DEBUG_EVENT) {
addr.data = text;
addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
NGX_SOCKADDR_STRLEN, 1);
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0,
"*%uA quic recvmsg: %V fd:%d n:%z",
c->number, &addr, c->fd, n);
}
}
#endif
log->data = NULL;
log->handler = NULL;
ls->handler(c);
next:
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
ev->available -= n;
}
} while (ev->available);
}
static void
ngx_quic_close_accepted_connection(ngx_connection_t *c)
{
ngx_free_connection(c);
c->fd = (ngx_socket_t) -1;
if (c->pool) {
ngx_destroy_pool(c->pool);
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif
}
static ngx_connection_t *
ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key,
struct sockaddr *local_sockaddr, socklen_t local_socklen)
{
uint32_t hash;
ngx_int_t rc;
ngx_connection_t *c;
ngx_rbtree_node_t *node, *sentinel;
ngx_quic_socket_t *qsock;
if (key->len == 0) {
return NULL;
}
node = ls->rbtree.root;
sentinel = ls->rbtree.sentinel;
hash = ngx_crc32_long(key->data, key->len);
while (node != sentinel) {
if (hash < node->key) {
node = node->left;
continue;
}
if (hash > node->key) {
node = node->right;
continue;
}
/* hash == node->key */
qsock = (ngx_quic_socket_t *) node;
rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len);
c = qsock->udp.connection;
if (rc == 0 && ls->wildcard) {
rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen,
c->local_sockaddr, c->local_socklen, 1);
}
if (rc == 0) {
c->udp = &qsock->udp;
return c;
}
node = (rc < 0) ? node->left : node->right;
}
return NULL;
}

View File

@ -9,6 +9,10 @@
#include <ngx_core.h>
#include <ngx_http.h>
#if (NGX_QUIC_OPENSSL_COMPAT)
#include <ngx_event_quic_openssl_compat.h>
#endif
typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
ngx_pool_t *pool, ngx_str_t *s);
@ -52,6 +56,10 @@ static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post,
void *data);
static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
#if (NGX_QUIC_OPENSSL_COMPAT)
static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf,
ngx_http_conf_addr_t *addr);
#endif
static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = {
@ -419,16 +427,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned int inlen,
void *arg)
{
unsigned int srvlen;
unsigned char *srv;
unsigned int srvlen;
unsigned char *srv;
#if (NGX_DEBUG)
unsigned int i;
unsigned int i;
#endif
#if (NGX_HTTP_V2)
ngx_http_connection_t *hc;
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
ngx_http_connection_t *hc;
#endif
#if (NGX_HTTP_V2 || NGX_DEBUG)
ngx_connection_t *c;
#if (NGX_HTTP_V3)
ngx_http_v3_srv_conf_t *h3scf;
#endif
#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG)
ngx_connection_t *c;
c = ngx_ssl_get_connection(ssl_conn);
#endif
@ -441,13 +452,40 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
}
#endif
#if (NGX_HTTP_V2)
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
hc = c->data;
#endif
#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS;
srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1;
} else
#endif
#if (NGX_HTTP_V3)
if (hc->addr_conf->quic) {
h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
if (h3scf->enable && h3scf->enable_hq) {
srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO
NGX_HTTP_V3_HQ_ALPN_PROTO;
srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO)
- 1;
} else if (h3scf->enable_hq) {
srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO;
srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1;
} else if (h3scf->enable) {
srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO;
srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1;
} else {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} else
#endif
{
srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS;
@ -1241,6 +1279,7 @@ static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
{
ngx_uint_t a, p, s;
const char *name;
ngx_http_conf_addr_t *addr;
ngx_http_conf_port_t *port;
ngx_http_ssl_srv_conf_t *sscf;
@ -1290,22 +1329,44 @@ ngx_http_ssl_init(ngx_conf_t *cf)
addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {
if (!addr[a].opt.ssl) {
if (!addr[a].opt.ssl && !addr[a].opt.quic) {
continue;
}
if (addr[a].opt.quic) {
name = "quic";
#if (NGX_QUIC_OPENSSL_COMPAT)
if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) {
return NGX_ERROR;
}
#endif
} else {
name = "ssl";
}
cscf = addr[a].default_server;
sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
if (sscf->certificates) {
if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"\"ssl_protocols\" must enable TLSv1.3 for "
"the \"listen ... %s\" directive in %s:%ui",
name, cscf->file_name, cscf->line);
return NGX_ERROR;
}
continue;
}
if (!sscf->reject_handshake) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"no \"ssl_certificate\" is defined for "
"the \"listen ... ssl\" directive in %s:%ui",
cscf->file_name, cscf->line);
"the \"listen ... %s\" directive in %s:%ui",
name, cscf->file_name, cscf->line);
return NGX_ERROR;
}
@ -1326,8 +1387,8 @@ ngx_http_ssl_init(ngx_conf_t *cf)
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"no \"ssl_certificate\" is defined for "
"the \"listen ... ssl\" directive in %s:%ui",
cscf->file_name, cscf->line);
"the \"listen ... %s\" directive in %s:%ui",
name, cscf->file_name, cscf->line);
return NGX_ERROR;
}
}
@ -1335,3 +1396,31 @@ ngx_http_ssl_init(ngx_conf_t *cf)
return NGX_OK;
}
#if (NGX_QUIC_OPENSSL_COMPAT)
static ngx_int_t
ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
ngx_uint_t s;
ngx_http_ssl_srv_conf_t *sscf;
ngx_http_core_srv_conf_t **cscfp, *cscf;
cscfp = addr->servers.elts;
for (s = 0; s < addr->servers.nelts; s++) {
cscf = cscfp[s];
sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
if (sscf->certificates || sscf->reject_handshake) {
if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) {
return NGX_ERROR;
}
}
}
return NGX_OK;
}
#endif

View File

@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
port = cmcf->ports->elts;
for (i = 0; i < cmcf->ports->nelts; i++) {
if (p != port[i].port || sa->sa_family != port[i].family) {
if (p != port[i].port
|| lsopt->type != port[i].type
|| sa->sa_family != port[i].family)
{
continue;
}
@ -1217,6 +1220,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
}
port->family = sa->sa_family;
port->type = lsopt->type;
port->port = p;
port->addrs.elts = NULL;
@ -1237,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
ngx_uint_t http2;
#endif
#if (NGX_HTTP_V3)
ngx_uint_t quic;
#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@ -1278,6 +1285,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
protocols |= lsopt->http2 << 2;
protocols_prev |= addr[i].opt.http2 << 2;
#endif
#if (NGX_HTTP_V3)
quic = lsopt->quic || addr[i].opt.quic;
#endif
if (lsopt->set) {
@ -1365,6 +1375,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
addr[i].opt.http2 = http2;
#endif
#if (NGX_HTTP_V3)
addr[i].opt.quic = quic;
#endif
return NGX_OK;
}
@ -1831,6 +1844,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
}
#endif
ls->type = addr->opt.type;
ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf;
@ -1866,6 +1880,12 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
ls->reuseport = addr->opt.reuseport;
#endif
ls->wildcard = addr->opt.wildcard;
#if (NGX_HTTP_V3)
ls->quic = addr->opt.quic;
#endif
return ls;
}
@ -1897,6 +1917,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
#endif
#if (NGX_HTTP_V2)
addrs[i].conf.http2 = addr[i].opt.http2;
#endif
#if (NGX_HTTP_V3)
addrs[i].conf.quic = addr[i].opt.quic;
#endif
addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
@ -1962,6 +1985,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
#endif
#if (NGX_HTTP_V2)
addrs6[i].conf.http2 = addr[i].opt.http2;
#endif
#if (NGX_HTTP_V3)
addrs6[i].conf.quic = addr[i].opt.quic;
#endif
addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;

View File

@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ngx_http_file_cache_t;
typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t;
typedef struct ngx_http_chunked_s ngx_http_chunked_t;
typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t;
typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t;
typedef struct ngx_http_v3_session_s ngx_http_v3_session_t;
typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
#if (NGX_HTTP_V2)
#include <ngx_http_v2.h>
#endif
#if (NGX_HTTP_V3)
#include <ngx_http_v3.h>
#endif
#if (NGX_HTTP_CACHE)
#include <ngx_http_cache.h>
#endif
@ -124,6 +129,11 @@ void ngx_http_handler(ngx_http_request_t *r);
void ngx_http_run_posted_requests(ngx_connection_t *c);
ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
ngx_http_posted_request_t *pr);
ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
ngx_str_t *host);
ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
ngx_uint_t alloc);
void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc);
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc);
@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *);
#endif
#if (NGX_HTTP_V2)
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len,
u_char **dst, ngx_uint_t last, ngx_log_t *log);
size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,

View File

@ -3005,6 +3005,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
lsopt.socklen = sizeof(struct sockaddr_in);
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.type = SOCK_STREAM;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
@ -3986,6 +3987,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.type = SOCK_STREAM;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
@ -4184,6 +4186,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
if (ngx_strcmp(value[n].data, "quic") == 0) {
#if (NGX_HTTP_V3)
lsopt.quic = 1;
lsopt.type = SOCK_DGRAM;
continue;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the \"quic\" parameter requires "
"ngx_http_v3_module");
return NGX_CONF_ERROR;
#endif
}
if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) {
if (ngx_strcmp(&value[n].data[13], "on") == 0) {
@ -4285,6 +4300,28 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_ERROR;
}
#if (NGX_HTTP_V3)
if (lsopt.quic) {
#if (NGX_HTTP_SSL)
if (lsopt.ssl) {
return "\"ssl\" parameter is incompatible with \"quic\"";
}
#endif
#if (NGX_HTTP_V2)
if (lsopt.http2) {
return "\"http2\" parameter is incompatible with \"quic\"";
}
#endif
if (lsopt.proxy_protocol) {
return "\"proxy_protocol\" parameter is incompatible with \"quic\"";
}
}
#endif
for (n = 0; n < u.naddrs; n++) {
for (i = 0; i < n; i++) {

View File

@ -75,6 +75,7 @@ typedef struct {
unsigned wildcard:1;
unsigned ssl:1;
unsigned http2:1;
unsigned quic:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@ -86,6 +87,7 @@ typedef struct {
int backlog;
int rcvbuf;
int sndbuf;
int type;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
@ -237,6 +239,7 @@ struct ngx_http_addr_conf_s {
unsigned ssl:1;
unsigned http2:1;
unsigned quic:1;
unsigned proxy_protocol:1;
};
@ -266,6 +269,7 @@ typedef struct {
typedef struct {
ngx_int_t family;
ngx_int_t type;
in_port_t port;
ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;

View File

@ -29,10 +29,6 @@ static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r,
static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
ngx_uint_t alloc);
static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
ngx_str_t *host);
static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
@ -50,7 +46,6 @@ static void ngx_http_keepalive_handler(ngx_event_t *ev);
static void ngx_http_set_lingering_close(ngx_connection_t *c);
static void ngx_http_lingering_close_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
static void ngx_http_log_request(ngx_http_request_t *r);
static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
@ -329,6 +324,13 @@ ngx_http_init_connection(ngx_connection_t *c)
}
#endif
#if (NGX_HTTP_V3)
if (hc->addr_conf->quic) {
ngx_http_v3_init_stream(c);
return;
}
#endif
#if (NGX_HTTP_SSL)
{
ngx_http_ssl_srv_conf_t *sscf;
@ -949,6 +951,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
#ifdef SSL_OP_NO_RENEGOTIATION
SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION);
#endif
#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
#if (NGX_HTTP_V3)
if (c->listening->quic) {
SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
}
#endif
#endif
}
@ -2095,7 +2105,7 @@ ngx_http_process_request(ngx_http_request_t *r)
}
static ngx_int_t
ngx_int_t
ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
{
u_char *h, ch;
@ -2187,7 +2197,7 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
}
static ngx_int_t
ngx_int_t
ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
{
ngx_int_t rc;
@ -2710,6 +2720,13 @@ ngx_http_finalize_connection(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->connection->quic) {
ngx_http_close_request(r, 0);
return;
}
#endif
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->main->count != 1) {
@ -2925,6 +2942,20 @@ ngx_http_test_reading(ngx_http_request_t *r)
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
if (rev->error) {
c->error = 1;
err = 0;
goto closed;
}
return;
}
#endif
#if (NGX_HAVE_KQUEUE)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
@ -3590,7 +3621,7 @@ ngx_http_post_action(ngx_http_request_t *r)
}
static void
void
ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_connection_t *c;
@ -3677,7 +3708,12 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
log->action = "closing request";
if (r->connection->timedout) {
if (r->connection->timedout
#if (NGX_HTTP_V3)
&& r->connection->quic == NULL
#endif
)
{
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->reset_timedout_connection) {
@ -3750,6 +3786,12 @@ ngx_http_close_connection(ngx_connection_t *c)
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
ngx_http_v3_reset_stream(c);
}
#endif
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif

View File

@ -24,6 +24,7 @@
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
#define NGX_HTTP_VERSION_20 2000
#define NGX_HTTP_VERSION_30 3000
#define NGX_HTTP_UNKNOWN 0x00000001
#define NGX_HTTP_GET 0x00000002
@ -323,6 +324,10 @@ typedef struct {
#endif
#endif
#if (NGX_HTTP_V3 || NGX_COMPAT)
ngx_http_v3_session_t *v3_session;
#endif
ngx_chain_t *busy;
ngx_int_t nbusy;
@ -451,6 +456,7 @@ struct ngx_http_request_s {
ngx_http_connection_t *http_connection;
ngx_http_v2_stream_t *stream;
ngx_http_v3_parse_t *v3_parse;
ngx_http_log_handler_pt log_handler;
@ -543,6 +549,7 @@ struct ngx_http_request_s {
unsigned request_complete:1;
unsigned request_output:1;
unsigned header_sent:1;
unsigned response_sent:1;
unsigned expect_tested:1;
unsigned root_tested:1;
unsigned done:1;

View File

@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_http_request_t *r,
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
rc = ngx_http_v3_read_request_body(r);
goto done;
}
#endif
preread = r->header_in->last - r->header_in->pos;
if (preread) {
@ -238,6 +245,18 @@ ngx_http_read_unbuffered_request_body(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
rc = ngx_http_v3_read_unbuffered_request_body(r);
if (rc == NGX_OK) {
r->reading_body = 0;
}
return rc;
}
#endif
if (r->connection->read->timedout) {
r->connection->timedout = 1;
return NGX_HTTP_REQUEST_TIME_OUT;
@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
return NGX_OK;
}
#endif
if (ngx_http_test_expect(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
@ -920,6 +945,9 @@ ngx_http_test_expect(ngx_http_request_t *r)
|| r->http_version < NGX_HTTP_VERSION_11
#if (NGX_HTTP_V2)
|| r->stream != NULL
#endif
#if (NGX_HTTP_V3)
|| r->connection->quic != NULL
#endif
)
{

View File

@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
ngx_http_upstream_init_request(r);
return;
}
#endif
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
@ -1354,6 +1361,19 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
}
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
if (c->write->error) {
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_CLIENT_CLOSED_REQUEST);
}
return;
}
#endif
#if (NGX_HAVE_KQUEUE)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {

View File

@ -240,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
r->out = NULL;
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
if (last) {
r->response_sent = 1;
}
return NGX_OK;
}
@ -346,6 +350,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
if (last) {
r->response_sent = 1;
}
if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
return NGX_AGAIN;
}

109
src/http/v3/ngx_http_v3.c Normal file
View File

@ -0,0 +1,109 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static void ngx_http_v3_keepalive_handler(ngx_event_t *ev);
static void ngx_http_v3_cleanup_session(void *data);
ngx_int_t
ngx_http_v3_init_session(ngx_connection_t *c)
{
ngx_pool_cleanup_t *cln;
ngx_http_connection_t *hc;
ngx_http_v3_session_t *h3c;
hc = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t));
if (h3c == NULL) {
goto failed;
}
ngx_queue_init(&h3c->blocked);
h3c->keepalive.log = c->log;
h3c->keepalive.data = c;
h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
h3c->table.send_insert_count.log = c->log;
h3c->table.send_insert_count.data = c;
h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler;
cln = ngx_pool_cleanup_add(c->pool, 0);
if (cln == NULL) {
goto failed;
}
cln->handler = ngx_http_v3_cleanup_session;
cln->data = h3c;
hc->v3_session = h3c;
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session");
return NGX_ERROR;
}
static void
ngx_http_v3_keepalive_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
c = ev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
"keepalive timeout");
}
static void
ngx_http_v3_cleanup_session(void *data)
{
ngx_http_v3_session_t *h3c = data;
ngx_http_v3_cleanup_table(h3c);
if (h3c->keepalive.timer_set) {
ngx_del_timer(&h3c->keepalive);
}
if (h3c->table.send_insert_count.posted) {
ngx_delete_posted_event(&h3c->table.send_insert_count);
}
}
ngx_int_t
ngx_http_v3_check_flood(ngx_connection_t *c)
{
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(c);
if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
"HTTP/3 flood detected");
return NGX_ERROR;
}
return NGX_OK;
}

157
src/http/v3/ngx_http_v3.h Normal file
View File

@ -0,0 +1,157 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_H_INCLUDED_
#define _NGX_HTTP_V3_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_http_v3_parse.h>
#include <ngx_http_v3_encode.h>
#include <ngx_http_v3_uni.h>
#include <ngx_http_v3_table.h>
#define NGX_HTTP_V3_ALPN_PROTO "\x02h3"
#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop"
#define NGX_HTTP_V3_HQ_PROTO "hq-interop"
#define NGX_HTTP_V3_VARLEN_INT_LEN 4
#define NGX_HTTP_V3_PREFIX_INT_LEN 11
#define NGX_HTTP_V3_STREAM_CONTROL 0x00
#define NGX_HTTP_V3_STREAM_PUSH 0x01
#define NGX_HTTP_V3_STREAM_ENCODER 0x02
#define NGX_HTTP_V3_STREAM_DECODER 0x03
#define NGX_HTTP_V3_FRAME_DATA 0x00
#define NGX_HTTP_V3_FRAME_HEADERS 0x01
#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03
#define NGX_HTTP_V3_FRAME_SETTINGS 0x04
#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05
#define NGX_HTTP_V3_FRAME_GOAWAY 0x07
#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d
#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01
#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06
#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07
#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096
#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0
#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1
#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2
#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3
#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4
#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5
#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6
#define NGX_HTTP_V3_MAX_UNI_STREAMS 3
/* HTTP/3 errors */
#define NGX_HTTP_V3_ERR_NO_ERROR 0x100
#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101
#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102
#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103
#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104
#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105
#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106
#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107
#define NGX_HTTP_V3_ERR_ID_ERROR 0x108
#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109
#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a
#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b
#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c
#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d
#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f
#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110
/* QPACK errors */
#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200
#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201
#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202
#define ngx_http_quic_get_connection(c) \
((ngx_http_connection_t *) ((c)->quic ? (c)->quic->parent->data \
: (c)->data))
#define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session
#define ngx_http_v3_get_module_loc_conf(c, module) \
ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \
module)
#define ngx_http_v3_get_module_srv_conf(c, module) \
ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \
module)
#define ngx_http_v3_finalize_connection(c, code, reason) \
ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \
code, reason)
#define ngx_http_v3_shutdown_connection(c, code, reason) \
ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \
code, reason)
typedef struct {
ngx_flag_t enable;
ngx_flag_t enable_hq;
size_t max_table_capacity;
ngx_uint_t max_blocked_streams;
ngx_uint_t max_concurrent_streams;
ngx_quic_conf_t quic;
} ngx_http_v3_srv_conf_t;
struct ngx_http_v3_parse_s {
size_t header_limit;
ngx_http_v3_parse_headers_t headers;
ngx_http_v3_parse_data_t body;
ngx_array_t *cookies;
};
struct ngx_http_v3_session_s {
ngx_http_v3_dynamic_table_t table;
ngx_event_t keepalive;
ngx_uint_t nrequests;
ngx_queue_t blocked;
ngx_uint_t nblocked;
uint64_t next_request_id;
off_t total_bytes;
off_t payload_bytes;
unsigned goaway:1;
unsigned hq:1;
ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
};
void ngx_http_v3_init_stream(ngx_connection_t *c);
void ngx_http_v3_reset_stream(ngx_connection_t *c);
ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c);
ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c);
ngx_int_t ngx_http_v3_init(ngx_connection_t *c);
void ngx_http_v3_shutdown(ngx_connection_t *c);
ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r);
ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r);
extern ngx_module_t ngx_http_v3_module;
#endif /* _NGX_HTTP_V3_H_INCLUDED_ */

View File

@ -0,0 +1,304 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
uintptr_t
ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value)
{
if (value <= 63) {
if (p == NULL) {
return 1;
}
*p++ = value;
return (uintptr_t) p;
}
if (value <= 16383) {
if (p == NULL) {
return 2;
}
*p++ = 0x40 | (value >> 8);
*p++ = value;
return (uintptr_t) p;
}
if (value <= 1073741823) {
if (p == NULL) {
return 4;
}
*p++ = 0x80 | (value >> 24);
*p++ = (value >> 16);
*p++ = (value >> 8);
*p++ = value;
return (uintptr_t) p;
}
if (p == NULL) {
return 8;
}
*p++ = 0xc0 | (value >> 56);
*p++ = (value >> 48);
*p++ = (value >> 40);
*p++ = (value >> 32);
*p++ = (value >> 24);
*p++ = (value >> 16);
*p++ = (value >> 8);
*p++ = value;
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix)
{
ngx_uint_t thresh, n;
thresh = (1 << prefix) - 1;
if (value < thresh) {
if (p == NULL) {
return 1;
}
*p++ |= value;
return (uintptr_t) p;
}
value -= thresh;
if (p == NULL) {
for (n = 2; value >= 128; n++) {
value >>= 7;
}
return n;
}
*p++ |= thresh;
while (value >= 128) {
*p++ = 0x80 | value;
value >>= 7;
}
*p++ = value;
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count,
ngx_uint_t sign, ngx_uint_t delta_base)
{
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8)
+ ngx_http_v3_encode_prefix_int(NULL, delta_base, 7);
}
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8);
*p = sign ? 0x80 : 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7);
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index)
{
/* Indexed Field Line */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 6);
}
*p = dynamic ? 0x80 : 0xc0;
return ngx_http_v3_encode_prefix_int(p, index, 6);
}
uintptr_t
ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index,
u_char *data, size_t len)
{
size_t hlen;
u_char *p1, *p2;
/* Literal Field Line With Name Reference */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 4)
+ ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ len;
}
*p = dynamic ? 0x40 : 0x50;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4);
p1 = p;
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
if (data) {
p2 = p;
hlen = ngx_http_huff_encode(data, len, p, 0);
if (hlen) {
p = p1;
*p = 0x80;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
p = ngx_cpymem(p, data, len);
}
}
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value)
{
size_t hlen;
u_char *p1, *p2;
/* Literal Field Line With Literal Name */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, name->len, 3)
+ name->len
+ ngx_http_v3_encode_prefix_int(NULL, value->len, 7)
+ value->len;
}
p1 = p;
*p = 0x20;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3);
p2 = p;
hlen = ngx_http_huff_encode(name->data, name->len, p, 1);
if (hlen) {
p = p1;
*p = 0x28;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
ngx_strlow(p, name->data, name->len);
p += name->len;
}
p1 = p;
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);
p2 = p;
hlen = ngx_http_huff_encode(value->data, value->len, p, 0);
if (hlen) {
p = p1;
*p = 0x80;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
p = ngx_cpymem(p, value->data, value->len);
}
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index)
{
/* Indexed Field Line With Post-Base Index */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 4);
}
*p = 0x10;
return ngx_http_v3_encode_prefix_int(p, index, 4);
}
uintptr_t
ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data,
size_t len)
{
size_t hlen;
u_char *p1, *p2;
/* Literal Field Line With Post-Base Name Reference */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 3)
+ ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ len;
}
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3);
p1 = p;
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
if (data) {
p2 = p;
hlen = ngx_http_huff_encode(data, len, p, 0);
if (hlen) {
p = p1;
*p = 0x80;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
p = ngx_cpymem(p, data, len);
}
}
return (uintptr_t) p;
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_
#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value);
uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value,
ngx_uint_t prefix);
uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p,
ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base);
uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic,
ngx_uint_t index);
uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic,
ngx_uint_t index, u_char *data, size_t len);
uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name,
ngx_str_t *value);
uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index);
uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index,
u_char *data, size_t len);
#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */

View File

@ -0,0 +1,851 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/* static table indices */
#define NGX_HTTP_V3_HEADER_AUTHORITY 0
#define NGX_HTTP_V3_HEADER_PATH_ROOT 1
#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4
#define NGX_HTTP_V3_HEADER_DATE 6
#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10
#define NGX_HTTP_V3_HEADER_LOCATION 12
#define NGX_HTTP_V3_HEADER_METHOD_GET 17
#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22
#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23
#define NGX_HTTP_V3_HEADER_STATUS_200 25
#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31
#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53
#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59
#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72
#define NGX_HTTP_V3_HEADER_SERVER 92
#define NGX_HTTP_V3_HEADER_USER_AGENT 95
typedef struct {
ngx_chain_t *free;
ngx_chain_t *busy;
} ngx_http_v3_filter_ctx_t;
static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r,
ngx_chain_t *in);
static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r,
ngx_http_v3_filter_ctx_t *ctx);
static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf);
static ngx_http_module_t ngx_http_v3_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_v3_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_v3_filter_module = {
NGX_MODULE_V1,
&ngx_http_v3_filter_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static ngx_int_t
ngx_http_v3_header_filter(ngx_http_request_t *r)
{
u_char *p;
size_t len, n;
ngx_buf_t *b;
ngx_str_t host, location;
ngx_uint_t i, port;
ngx_chain_t *out, *hl, *cl, **ll;
ngx_list_part_t *part;
ngx_table_elt_t *header;
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_v3_filter_ctx_t *ctx;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
u_char addr[NGX_SOCKADDR_STRLEN];
if (r->http_version != NGX_HTTP_VERSION_30) {
return ngx_http_next_header_filter(r);
}
if (r->header_sent) {
return NGX_OK;
}
r->header_sent = 1;
if (r != r->main) {
return NGX_OK;
}
h3c = ngx_http_v3_get_session(r->connection);
if (r->method == NGX_HTTP_HEAD) {
r->header_only = 1;
}
if (r->headers_out.last_modified_time != -1) {
if (r->headers_out.status != NGX_HTTP_OK
&& r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
&& r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
{
r->headers_out.last_modified_time = -1;
r->headers_out.last_modified = NULL;
}
}
if (r->headers_out.status == NGX_HTTP_NO_CONTENT) {
r->header_only = 1;
ngx_str_null(&r->headers_out.content_type);
r->headers_out.last_modified_time = -1;
r->headers_out.last_modified = NULL;
r->headers_out.content_length = NULL;
r->headers_out.content_length_n = -1;
}
if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
r->header_only = 1;
}
c = r->connection;
out = NULL;
ll = &out;
len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
if (r->headers_out.status == NGX_HTTP_OK) {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_STATUS_200);
} else {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_STATUS_200,
NULL, 3);
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->headers_out.server == NULL) {
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
n = sizeof(NGINX_VER) - 1;
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
n = sizeof(NGINX_VER_BUILD) - 1;
} else {
n = sizeof("nginx") - 1;
}
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_SERVER,
NULL, n);
}
if (r->headers_out.date == NULL) {
len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE,
NULL, ngx_cached_http_time.len);
}
if (r->headers_out.content_type.len) {
n = r->headers_out.content_type.len;
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
}
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
NULL, n);
}
if (r->headers_out.content_length == NULL) {
if (r->headers_out.content_length_n > 0) {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
NULL, NGX_OFF_T_LEN);
} else if (r->headers_out.content_length_n == 0) {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
}
}
if (r->headers_out.last_modified == NULL
&& r->headers_out.last_modified_time != -1)
{
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL,
sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
}
if (r->headers_out.location && r->headers_out.location->value.len) {
if (r->headers_out.location->value.data[0] == '/'
&& clcf->absolute_redirect)
{
if (clcf->server_name_in_redirect) {
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
host = cscf->server_name;
} else if (r->headers_in.server.len) {
host = r->headers_in.server;
} else {
host.len = NGX_SOCKADDR_STRLEN;
host.data = addr;
if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
return NGX_ERROR;
}
}
port = ngx_inet_get_port(c->local_sockaddr);
location.len = sizeof("https://") - 1 + host.len
+ r->headers_out.location->value.len;
if (clcf->port_in_redirect) {
port = (port == 443) ? 0 : port;
} else {
port = 0;
}
if (port) {
location.len += sizeof(":65535") - 1;
}
location.data = ngx_pnalloc(r->pool, location.len);
if (location.data == NULL) {
return NGX_ERROR;
}
p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1);
p = ngx_cpymem(p, host.data, host.len);
if (port) {
p = ngx_sprintf(p, ":%ui", port);
}
p = ngx_cpymem(p, r->headers_out.location->value.data,
r->headers_out.location->value.len);
/* update r->headers_out.location->value for possible logging */
r->headers_out.location->value.len = p - location.data;
r->headers_out.location->value.data = location.data;
ngx_str_set(&r->headers_out.location->key, "Location");
}
r->headers_out.location->hash = 0;
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_LOCATION, NULL,
r->headers_out.location->value.len);
}
#if (NGX_HTTP_GZIP)
if (r->gzip_vary) {
if (clcf->gzip_vary) {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
} else {
r->gzip_vary = 0;
}
}
#endif
part = &r->headers_out.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
len += ngx_http_v3_encode_field_l(NULL, &header[i].key,
&header[i].value);
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last,
0, 0, 0);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \":status: %03ui\"",
r->headers_out.status);
if (r->headers_out.status == NGX_HTTP_OK) {
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_STATUS_200);
} else {
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_STATUS_200,
NULL, 3);
b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status);
}
if (r->headers_out.server == NULL) {
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
p = (u_char *) NGINX_VER;
n = sizeof(NGINX_VER) - 1;
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
p = (u_char *) NGINX_VER_BUILD;
n = sizeof(NGINX_VER_BUILD) - 1;
} else {
p = (u_char *) "nginx";
n = sizeof("nginx") - 1;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"server: %*s\"", n, p);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_SERVER,
p, n);
}
if (r->headers_out.date == NULL) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"date: %V\"",
&ngx_cached_http_time);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_DATE,
ngx_cached_http_time.data,
ngx_cached_http_time.len);
}
if (r->headers_out.content_type.len) {
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
n = r->headers_out.content_type.len + sizeof("; charset=") - 1
+ r->headers_out.charset.len;
p = ngx_pnalloc(r->pool, n);
if (p == NULL) {
return NGX_ERROR;
}
p = ngx_cpymem(p, r->headers_out.content_type.data,
r->headers_out.content_type.len);
p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
p = ngx_cpymem(p, r->headers_out.charset.data,
r->headers_out.charset.len);
/* updated r->headers_out.content_type is also needed for logging */
r->headers_out.content_type.len = n;
r->headers_out.content_type.data = p - n;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"content-type: %V\"",
&r->headers_out.content_type);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
r->headers_out.content_type.data,
r->headers_out.content_type.len);
}
if (r->headers_out.content_length == NULL
&& r->headers_out.content_length_n >= 0)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"content-length: %O\"",
r->headers_out.content_length_n);
if (r->headers_out.content_length_n > 0) {
p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n);
n = p - b->last;
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
NULL, n);
b->last = ngx_sprintf(b->last, "%O",
r->headers_out.content_length_n);
} else {
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
}
}
if (r->headers_out.last_modified == NULL
&& r->headers_out.last_modified_time != -1)
{
n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
p = ngx_pnalloc(r->pool, n);
if (p == NULL) {
return NGX_ERROR;
}
ngx_http_time(p, r->headers_out.last_modified_time);
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"last-modified: %*s\"", n, p);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_LAST_MODIFIED,
p, n);
}
if (r->headers_out.location && r->headers_out.location->value.len) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"location: %V\"",
&r->headers_out.location->value);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_LOCATION,
r->headers_out.location->value.data,
r->headers_out.location->value.len);
}
#if (NGX_HTTP_GZIP)
if (r->gzip_vary) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"vary: Accept-Encoding\"");
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
}
#endif
part = &r->headers_out.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"%V: %V\"",
&header[i].key, &header[i].value);
b->last = (u_char *) ngx_http_v3_encode_field_l(b->last,
&header[i].key,
&header[i].value);
}
if (r->header_only) {
b->last_buf = 1;
}
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = NULL;
n = b->last - b->pos;
h3c->payload_bytes += n;
len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS)
+ ngx_http_v3_encode_varlen_int(NULL, n);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
NGX_HTTP_V3_FRAME_HEADERS);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
hl = ngx_alloc_chain_link(r->pool);
if (hl == NULL) {
return NGX_ERROR;
}
hl->buf = b;
hl->next = cl;
*ll = hl;
ll = &cl->next;
if (r->headers_out.content_length_n >= 0
&& !r->header_only && !r->expect_trailers)
{
len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA)
+ ngx_http_v3_encode_varlen_int(NULL,
r->headers_out.content_length_n);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
NGX_HTTP_V3_FRAME_DATA);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
r->headers_out.content_length_n);
h3c->payload_bytes += r->headers_out.content_length_n;
h3c->total_bytes += r->headers_out.content_length_n;
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = NULL;
*ll = cl;
} else {
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module);
}
for (cl = out; cl; cl = cl->next) {
h3c->total_bytes += cl->buf->last - cl->buf->pos;
}
return ngx_http_write_filter(r, out);
}
static ngx_int_t
ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
u_char *chunk;
off_t size;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t *out, *cl, *tl, **ll;
ngx_http_v3_session_t *h3c;
ngx_http_v3_filter_ctx_t *ctx;
if (in == NULL) {
return ngx_http_next_body_filter(r, in);
}
ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module);
if (ctx == NULL) {
return ngx_http_next_body_filter(r, in);
}
h3c = ngx_http_v3_get_session(r->connection);
out = NULL;
ll = &out;
size = 0;
cl = in;
for ( ;; ) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 chunk: %O", ngx_buf_size(cl->buf));
size += ngx_buf_size(cl->buf);
if (cl->buf->flush
|| cl->buf->sync
|| ngx_buf_in_memory(cl->buf)
|| cl->buf->in_file)
{
tl = ngx_alloc_chain_link(r->pool);
if (tl == NULL) {
return NGX_ERROR;
}
tl->buf = cl->buf;
*ll = tl;
ll = &tl->next;
}
if (cl->next == NULL) {
break;
}
cl = cl->next;
}
if (size) {
tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (tl == NULL) {
return NGX_ERROR;
}
b = tl->buf;
chunk = b->start;
if (chunk == NULL) {
chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
if (chunk == NULL) {
return NGX_ERROR;
}
b->start = chunk;
b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
}
b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
b->memory = 0;
b->temporary = 1;
b->pos = chunk;
b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk,
NGX_HTTP_V3_FRAME_DATA);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size);
tl->next = out;
out = tl;
h3c->payload_bytes += size;
}
if (cl->buf->last_buf) {
tl = ngx_http_v3_create_trailers(r, ctx);
if (tl == NULL) {
return NGX_ERROR;
}
cl->buf->last_buf = 0;
*ll = tl;
} else {
*ll = NULL;
}
for (cl = out; cl; cl = cl->next) {
h3c->total_bytes += cl->buf->last - cl->buf->pos;
}
rc = ngx_http_next_body_filter(r, out);
ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
(ngx_buf_tag_t) &ngx_http_v3_filter_module);
return rc;
}
static ngx_chain_t *
ngx_http_v3_create_trailers(ngx_http_request_t *r,
ngx_http_v3_filter_ctx_t *ctx)
{
size_t len, n;
u_char *p;
ngx_buf_t *b;
ngx_uint_t i;
ngx_chain_t *cl, *hl;
ngx_list_part_t *part;
ngx_table_elt_t *header;
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(r->connection);
len = 0;
part = &r->headers_out.trailers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
len += ngx_http_v3_encode_field_l(NULL, &header[i].key,
&header[i].value);
}
cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (cl == NULL) {
return NULL;
}
b = cl->buf;
b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
b->memory = 0;
b->last_buf = 1;
if (len == 0) {
b->temporary = 0;
b->pos = b->last = NULL;
return cl;
}
b->temporary = 1;
len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
b->pos = ngx_palloc(r->pool, len);
if (b->pos == NULL) {
return NULL;
}
b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos,
0, 0, 0);
part = &r->headers_out.trailers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 output trailer: \"%V: %V\"",
&header[i].key, &header[i].value);
b->last = (u_char *) ngx_http_v3_encode_field_l(b->last,
&header[i].key,
&header[i].value);
}
n = b->last - b->pos;
h3c->payload_bytes += n;
hl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (hl == NULL) {
return NULL;
}
b = hl->buf;
p = b->start;
if (p == NULL) {
p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
if (p == NULL) {
return NULL;
}
b->start = p;
b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
}
b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
b->memory = 0;
b->temporary = 1;
b->pos = p;
b->last = (u_char *) ngx_http_v3_encode_varlen_int(p,
NGX_HTTP_V3_FRAME_HEADERS);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
hl->next = cl;
return hl;
}
static ngx_int_t
ngx_http_v3_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_v3_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_v3_body_filter;
return NGX_OK;
}

View File

@ -0,0 +1,389 @@
/*
* Copyright (C) Nginx, Inc.
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf);
static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf);
static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent,
void *child);
static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_command_t ngx_http_v3_commands[] = {
{ ngx_string("http3"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, enable),
NULL },
{ ngx_string("http3_hq"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, enable_hq),
NULL },
{ ngx_string("http3_max_concurrent_streams"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams),
NULL },
{ ngx_string("http3_stream_buffer_size"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size),
NULL },
{ ngx_string("quic_retry"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.retry),
NULL },
{ ngx_string("quic_gso"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled),
NULL },
{ ngx_string("quic_host_key"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_http_quic_host_key,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
{ ngx_string("quic_active_connection_id_limit"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit),
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_v3_module_ctx = {
ngx_http_v3_add_variables, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
ngx_http_v3_create_srv_conf, /* create server configuration */
ngx_http_v3_merge_srv_conf, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_v3_module = {
NGX_MODULE_V1,
&ngx_http_v3_module_ctx, /* module context */
ngx_http_v3_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_variable_t ngx_http_v3_vars[] = {
{ ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 },
ngx_http_null_variable
};
static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic");
static ngx_int_t
ngx_http_v3_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_v3_session_t *h3c;
if (r->connection->quic) {
h3c = ngx_http_v3_get_session(r->connection);
if (h3c->hq) {
v->len = sizeof("hq") - 1;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = (u_char *) "hq";
return NGX_OK;
}
v->len = sizeof("h3") - 1;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = (u_char *) "h3";
return NGX_OK;
}
*v = ngx_http_variable_null_value;
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_add_variables(ngx_conf_t *cf)
{
ngx_http_variable_t *var, *v;
for (v = ngx_http_v3_vars; v->name.len; v++) {
var = ngx_http_add_variable(cf, &v->name, v->flags);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = v->get_handler;
var->data = v->data;
}
return NGX_OK;
}
static void *
ngx_http_v3_create_srv_conf(ngx_conf_t *cf)
{
ngx_http_v3_srv_conf_t *h3scf;
h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
if (h3scf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* h3scf->quic.host_key = { 0, NULL }
* h3scf->quic.stream_reject_code_uni = 0;
* h3scf->quic.disable_active_migration = 0;
* h3scf->quic.timeout = 0;
* h3scf->max_blocked_streams = 0;
*/
h3scf->enable = NGX_CONF_UNSET;
h3scf->enable_hq = NGX_CONF_UNSET;
h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY;
h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT;
h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE;
h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT;
h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS;
h3scf->quic.retry = NGX_CONF_UNSET;
h3scf->quic.gso_enabled = NGX_CONF_UNSET;
h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR;
h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED;
h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
h3scf->quic.init = ngx_http_v3_init;
h3scf->quic.shutdown = ngx_http_v3_shutdown;
return h3scf;
}
static char *
ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_v3_srv_conf_t *prev = parent;
ngx_http_v3_srv_conf_t *conf = child;
ngx_http_ssl_srv_conf_t *sscf;
ngx_conf_merge_value(conf->enable, prev->enable, 1);
ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0);
ngx_conf_merge_uint_value(conf->max_concurrent_streams,
prev->max_concurrent_streams, 128);
conf->max_blocked_streams = conf->max_concurrent_streams;
ngx_conf_merge_size_value(conf->quic.stream_buffer_size,
prev->quic.stream_buffer_size,
65536);
conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams;
ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0);
ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0);
ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, "");
ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
prev->quic.active_connection_id_limit,
2);
if (conf->quic.host_key.len == 0) {
conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN;
conf->quic.host_key.data = ngx_palloc(cf->pool,
conf->quic.host_key.len);
if (conf->quic.host_key.data == NULL) {
return NGX_CONF_ERROR;
}
if (RAND_bytes(conf->quic.host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN)
<= 0)
{
return NGX_CONF_ERROR;
}
}
if (ngx_quic_derive_key(cf->log, "av_token_key",
&conf->quic.host_key, &ngx_http_quic_salt,
conf->quic.av_token_key, NGX_QUIC_AV_KEY_LEN)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
if (ngx_quic_derive_key(cf->log, "sr_token_key",
&conf->quic.host_key, &ngx_http_quic_salt,
conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module);
conf->quic.ssl = &sscf->ssl;
return NGX_CONF_OK;
}
static char *
ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_v3_srv_conf_t *h3scf = conf;
u_char *buf;
size_t size;
ssize_t n;
ngx_str_t *value;
ngx_file_t file;
ngx_file_info_t fi;
ngx_quic_conf_t *qcf;
qcf = &h3scf->quic;
if (qcf->host_key.len) {
return "is duplicate";
}
buf = NULL;
#if (NGX_SUPPRESS_WARN)
size = 0;
#endif
value = cf->args->elts;
if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) {
return NGX_CONF_ERROR;
}
ngx_memzero(&file, sizeof(ngx_file_t));
file.name = value[1];
file.log = cf->log;
file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
if (file.fd == NGX_INVALID_FILE) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
ngx_open_file_n " \"%V\" failed", &file.name);
return NGX_CONF_ERROR;
}
if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
ngx_fd_info_n " \"%V\" failed", &file.name);
goto failed;
}
size = ngx_file_size(&fi);
if (size == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%V\" zero key size", &file.name);
goto failed;
}
buf = ngx_pnalloc(cf->pool, size);
if (buf == NULL) {
goto failed;
}
n = ngx_read_file(&file, buf, size, 0);
if (n == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
ngx_read_file_n " \"%V\" failed", &file.name);
goto failed;
}
if ((size_t) n != size) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
ngx_read_file_n " \"%V\" returned only "
"%z bytes instead of %uz", &file.name, n, size);
goto failed;
}
qcf->host_key.data = buf;
qcf->host_key.len = n;
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &file.name);
}
return NGX_CONF_OK;
failed:
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &file.name);
}
if (buf) {
ngx_explicit_memzero(buf, size);
}
return NGX_CONF_ERROR;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_
#define _NGX_HTTP_V3_PARSE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_uint_t state;
uint64_t value;
} ngx_http_v3_parse_varlen_int_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t shift;
uint64_t value;
} ngx_http_v3_parse_prefix_int_t;
typedef struct {
ngx_uint_t state;
uint64_t id;
ngx_http_v3_parse_varlen_int_t vlint;
} ngx_http_v3_parse_settings_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t insert_count;
ngx_uint_t delta_base;
ngx_uint_t sign;
ngx_uint_t base;
ngx_http_v3_parse_prefix_int_t pint;
} ngx_http_v3_parse_field_section_prefix_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t length;
ngx_uint_t huffman;
ngx_str_t value;
u_char *last;
u_char huffstate;
} ngx_http_v3_parse_literal_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t index;
ngx_uint_t base;
ngx_uint_t dynamic;
ngx_str_t name;
ngx_str_t value;
ngx_http_v3_parse_prefix_int_t pint;
ngx_http_v3_parse_literal_t literal;
} ngx_http_v3_parse_field_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_field_t field;
} ngx_http_v3_parse_field_rep_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t type;
ngx_uint_t length;
ngx_http_v3_parse_varlen_int_t vlint;
ngx_http_v3_parse_field_section_prefix_t prefix;
ngx_http_v3_parse_field_rep_t field_rep;
} ngx_http_v3_parse_headers_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_field_t field;
ngx_http_v3_parse_prefix_int_t pint;
} ngx_http_v3_parse_encoder_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_prefix_int_t pint;
} ngx_http_v3_parse_decoder_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t type;
ngx_uint_t length;
ngx_http_v3_parse_varlen_int_t vlint;
ngx_http_v3_parse_settings_t settings;
} ngx_http_v3_parse_control_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_varlen_int_t vlint;
union {
ngx_http_v3_parse_encoder_t encoder;
ngx_http_v3_parse_decoder_t decoder;
ngx_http_v3_parse_control_t control;
} u;
} ngx_http_v3_parse_uni_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t type;
ngx_uint_t length;
ngx_http_v3_parse_varlen_int_t vlint;
} ngx_http_v3_parse_data_t;
/*
* Parse functions return codes:
* NGX_DONE - parsing done
* NGX_OK - sub-element done
* NGX_AGAIN - more data expected
* NGX_BUSY - waiting for external event
* NGX_ERROR - internal error
* NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error
*/
ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c,
ngx_http_v3_parse_headers_t *st, ngx_buf_t *b);
ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c,
ngx_http_v3_parse_data_t *st, ngx_buf_t *b);
ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c,
ngx_http_v3_parse_uni_t *st, ngx_buf_t *b);
#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,715 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target);
static void ngx_http_v3_unblock(void *data);
static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c);
typedef struct {
ngx_queue_t queue;
ngx_connection_t *connection;
ngx_uint_t *nblocked;
} ngx_http_v3_block_t;
static ngx_http_v3_field_t ngx_http_v3_static_table[] = {
{ ngx_string(":authority"), ngx_string("") },
{ ngx_string(":path"), ngx_string("/") },
{ ngx_string("age"), ngx_string("0") },
{ ngx_string("content-disposition"), ngx_string("") },
{ ngx_string("content-length"), ngx_string("0") },
{ ngx_string("cookie"), ngx_string("") },
{ ngx_string("date"), ngx_string("") },
{ ngx_string("etag"), ngx_string("") },
{ ngx_string("if-modified-since"), ngx_string("") },
{ ngx_string("if-none-match"), ngx_string("") },
{ ngx_string("last-modified"), ngx_string("") },
{ ngx_string("link"), ngx_string("") },
{ ngx_string("location"), ngx_string("") },
{ ngx_string("referer"), ngx_string("") },
{ ngx_string("set-cookie"), ngx_string("") },
{ ngx_string(":method"), ngx_string("CONNECT") },
{ ngx_string(":method"), ngx_string("DELETE") },
{ ngx_string(":method"), ngx_string("GET") },
{ ngx_string(":method"), ngx_string("HEAD") },
{ ngx_string(":method"), ngx_string("OPTIONS") },
{ ngx_string(":method"), ngx_string("POST") },
{ ngx_string(":method"), ngx_string("PUT") },
{ ngx_string(":scheme"), ngx_string("http") },
{ ngx_string(":scheme"), ngx_string("https") },
{ ngx_string(":status"), ngx_string("103") },
{ ngx_string(":status"), ngx_string("200") },
{ ngx_string(":status"), ngx_string("304") },
{ ngx_string(":status"), ngx_string("404") },
{ ngx_string(":status"), ngx_string("503") },
{ ngx_string("accept"), ngx_string("*/*") },
{ ngx_string("accept"),
ngx_string("application/dns-message") },
{ ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") },
{ ngx_string("accept-ranges"), ngx_string("bytes") },
{ ngx_string("access-control-allow-headers"),
ngx_string("cache-control") },
{ ngx_string("access-control-allow-headers"),
ngx_string("content-type") },
{ ngx_string("access-control-allow-origin"),
ngx_string("*") },
{ ngx_string("cache-control"), ngx_string("max-age=0") },
{ ngx_string("cache-control"), ngx_string("max-age=2592000") },
{ ngx_string("cache-control"), ngx_string("max-age=604800") },
{ ngx_string("cache-control"), ngx_string("no-cache") },
{ ngx_string("cache-control"), ngx_string("no-store") },
{ ngx_string("cache-control"),
ngx_string("public, max-age=31536000") },
{ ngx_string("content-encoding"), ngx_string("br") },
{ ngx_string("content-encoding"), ngx_string("gzip") },
{ ngx_string("content-type"),
ngx_string("application/dns-message") },
{ ngx_string("content-type"),
ngx_string("application/javascript") },
{ ngx_string("content-type"), ngx_string("application/json") },
{ ngx_string("content-type"),
ngx_string("application/x-www-form-urlencoded") },
{ ngx_string("content-type"), ngx_string("image/gif") },
{ ngx_string("content-type"), ngx_string("image/jpeg") },
{ ngx_string("content-type"), ngx_string("image/png") },
{ ngx_string("content-type"), ngx_string("text/css") },
{ ngx_string("content-type"),
ngx_string("text/html;charset=utf-8") },
{ ngx_string("content-type"), ngx_string("text/plain") },
{ ngx_string("content-type"),
ngx_string("text/plain;charset=utf-8") },
{ ngx_string("range"), ngx_string("bytes=0-") },
{ ngx_string("strict-transport-security"),
ngx_string("max-age=31536000") },
{ ngx_string("strict-transport-security"),
ngx_string("max-age=31536000;includesubdomains") },
{ ngx_string("strict-transport-security"),
ngx_string("max-age=31536000;includesubdomains;preload") },
{ ngx_string("vary"), ngx_string("accept-encoding") },
{ ngx_string("vary"), ngx_string("origin") },
{ ngx_string("x-content-type-options"),
ngx_string("nosniff") },
{ ngx_string("x-xss-protection"), ngx_string("1;mode=block") },
{ ngx_string(":status"), ngx_string("100") },
{ ngx_string(":status"), ngx_string("204") },
{ ngx_string(":status"), ngx_string("206") },
{ ngx_string(":status"), ngx_string("302") },
{ ngx_string(":status"), ngx_string("400") },
{ ngx_string(":status"), ngx_string("403") },
{ ngx_string(":status"), ngx_string("421") },
{ ngx_string(":status"), ngx_string("425") },
{ ngx_string(":status"), ngx_string("500") },
{ ngx_string("accept-language"), ngx_string("") },
{ ngx_string("access-control-allow-credentials"),
ngx_string("FALSE") },
{ ngx_string("access-control-allow-credentials"),
ngx_string("TRUE") },
{ ngx_string("access-control-allow-headers"),
ngx_string("*") },
{ ngx_string("access-control-allow-methods"),
ngx_string("get") },
{ ngx_string("access-control-allow-methods"),
ngx_string("get, post, options") },
{ ngx_string("access-control-allow-methods"),
ngx_string("options") },
{ ngx_string("access-control-expose-headers"),
ngx_string("content-length") },
{ ngx_string("access-control-request-headers"),
ngx_string("content-type") },
{ ngx_string("access-control-request-method"),
ngx_string("get") },
{ ngx_string("access-control-request-method"),
ngx_string("post") },
{ ngx_string("alt-svc"), ngx_string("clear") },
{ ngx_string("authorization"), ngx_string("") },
{ ngx_string("content-security-policy"),
ngx_string("script-src 'none';object-src 'none';base-uri 'none'") },
{ ngx_string("early-data"), ngx_string("1") },
{ ngx_string("expect-ct"), ngx_string("") },
{ ngx_string("forwarded"), ngx_string("") },
{ ngx_string("if-range"), ngx_string("") },
{ ngx_string("origin"), ngx_string("") },
{ ngx_string("purpose"), ngx_string("prefetch") },
{ ngx_string("server"), ngx_string("") },
{ ngx_string("timing-allow-origin"), ngx_string("*") },
{ ngx_string("upgrade-insecure-requests"),
ngx_string("1") },
{ ngx_string("user-agent"), ngx_string("") },
{ ngx_string("x-forwarded-for"), ngx_string("") },
{ ngx_string("x-frame-options"), ngx_string("deny") },
{ ngx_string("x-frame-options"), ngx_string("sameorigin") }
};
ngx_int_t
ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
ngx_uint_t index, ngx_str_t *value)
{
ngx_str_t name;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
if (dynamic) {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 ref insert dynamic[%ui] \"%V\"", index, value);
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->base + dt->nelts <= index) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
index = dt->base + dt->nelts - 1 - index;
if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
} else {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 ref insert static[%ui] \"%V\"", index, value);
if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
}
return ngx_http_v3_insert(c, &name, value);
}
ngx_int_t
ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value)
{
u_char *p;
size_t size;
ngx_http_v3_field_t *field;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
size = ngx_http_v3_table_entry_size(name, value);
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (size > dt->capacity) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"not enough dynamic table capacity");
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 insert [%ui] \"%V\":\"%V\", size:%uz",
dt->base + dt->nelts, name, value, size);
p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len,
c->log);
if (p == NULL) {
return NGX_ERROR;
}
field = (ngx_http_v3_field_t *) p;
field->name.data = p + sizeof(ngx_http_v3_field_t);
field->name.len = name->len;
field->value.data = ngx_cpymem(field->name.data, name->data, name->len);
field->value.len = value->len;
ngx_memcpy(field->value.data, value->data, value->len);
dt->elts[dt->nelts++] = field;
dt->size += size;
dt->insert_count++;
if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) {
return NGX_ERROR;
}
ngx_post_event(&dt->send_insert_count, &ngx_posted_events);
if (ngx_http_v3_new_entry(c) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
void
ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
c = ev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 inc insert count handler");
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->insert_count > dt->ack_insert_count) {
if (ngx_http_v3_send_inc_insert_count(c,
dt->insert_count - dt->ack_insert_count)
!= NGX_OK)
{
return;
}
dt->ack_insert_count = dt->insert_count;
}
}
ngx_int_t
ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
{
ngx_uint_t max, prev_max;
ngx_http_v3_field_t **elts;
ngx_http_v3_session_t *h3c;
ngx_http_v3_srv_conf_t *h3scf;
ngx_http_v3_dynamic_table_t *dt;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 set capacity %ui", capacity);
h3c = ngx_http_v3_get_session(c);
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
if (capacity > h3scf->max_table_capacity) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client exceeded http3_max_table_capacity limit");
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
if (ngx_http_v3_evict(c, capacity) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
dt = &h3c->table;
max = capacity / 32;
prev_max = dt->capacity / 32;
if (max > prev_max) {
elts = ngx_alloc(max * sizeof(void *), c->log);
if (elts == NULL) {
return NGX_ERROR;
}
if (dt->elts) {
ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *));
ngx_free(dt->elts);
}
dt->elts = elts;
}
dt->capacity = capacity;
return NGX_OK;
}
void
ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c)
{
ngx_uint_t n;
ngx_http_v3_dynamic_table_t *dt;
dt = &h3c->table;
if (dt->elts == NULL) {
return;
}
for (n = 0; n < dt->nelts; n++) {
ngx_free(dt->elts[n]);
}
ngx_free(dt->elts);
}
static ngx_int_t
ngx_http_v3_evict(ngx_connection_t *c, size_t target)
{
size_t size;
ngx_uint_t n;
ngx_http_v3_field_t *field;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
n = 0;
while (dt->size > target) {
field = dt->elts[n++];
size = ngx_http_v3_table_entry_size(&field->name, &field->value);
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 evict [%ui] \"%V\":\"%V\" size:%uz",
dt->base, &field->name, &field->value, size);
ngx_free(field);
dt->size -= size;
}
if (n) {
dt->nelts -= n;
dt->base += n;
ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *));
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
{
ngx_str_t name, value;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->base + dt->nelts <= index) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
index = dt->base + dt->nelts - 1 - index;
if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
return ngx_http_v3_insert(c, &name, &value);
}
ngx_int_t
ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 ack section %ui", stream_id);
/* we do not use dynamic tables */
return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
}
ngx_int_t
ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 increment insert count %ui", inc);
/* we do not use dynamic tables */
return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
}
ngx_int_t
ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
ngx_str_t *name, ngx_str_t *value)
{
ngx_uint_t nelts;
ngx_http_v3_field_t *field;
nelts = sizeof(ngx_http_v3_static_table)
/ sizeof(ngx_http_v3_static_table[0]);
if (index >= nelts) {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 static[%ui] lookup out of bounds: %ui",
index, nelts);
return NGX_ERROR;
}
field = &ngx_http_v3_static_table[index];
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 static[%ui] lookup \"%V\":\"%V\"",
index, &field->name, &field->value);
if (name) {
*name = field->name;
}
if (value) {
*value = field->value;
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name,
ngx_str_t *value)
{
ngx_http_v3_field_t *field;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (index < dt->base || index - dt->base >= dt->nelts) {
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]",
index, dt->base, dt->base + dt->nelts);
return NGX_ERROR;
}
field = dt->elts[index - dt->base];
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 dynamic[%ui] lookup \"%V\":\"%V\"",
index, &field->name, &field->value);
if (name) {
*name = field->name;
}
if (value) {
*value = field->value;
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count)
{
ngx_uint_t max_entries, full_range, max_value,
max_wrapped, req_insert_count;
ngx_http_v3_srv_conf_t *h3scf;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
/* QPACK 4.5.1.1. Required Insert Count */
if (*insert_count == 0) {
return NGX_OK;
}
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
max_entries = h3scf->max_table_capacity / 32;
full_range = 2 * max_entries;
if (*insert_count > full_range) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
max_value = dt->base + dt->nelts + max_entries;
max_wrapped = (max_value / full_range) * full_range;
req_insert_count = max_wrapped + *insert_count - 1;
if (req_insert_count > max_value) {
if (req_insert_count <= full_range) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
req_insert_count -= full_range;
}
if (req_insert_count == 0) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 decode insert_count %ui -> %ui",
*insert_count, req_insert_count);
*insert_count = req_insert_count;
return NGX_OK;
}
ngx_int_t
ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
{
size_t n;
ngx_pool_cleanup_t *cln;
ngx_http_v3_block_t *block;
ngx_http_v3_session_t *h3c;
ngx_http_v3_srv_conf_t *h3scf;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
n = dt->base + dt->nelts;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 check insert count req:%ui, have:%ui",
insert_count, n);
if (n >= insert_count) {
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream");
block = NULL;
for (cln = c->pool->cleanup; cln; cln = cln->next) {
if (cln->handler == ngx_http_v3_unblock) {
block = cln->data;
break;
}
}
if (block == NULL) {
cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t));
if (cln == NULL) {
return NGX_ERROR;
}
cln->handler = ngx_http_v3_unblock;
block = cln->data;
block->queue.prev = NULL;
block->connection = c;
block->nblocked = &h3c->nblocked;
}
if (block->queue.prev == NULL) {
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
if (h3c->nblocked == h3scf->max_blocked_streams) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client exceeded http3_max_blocked_streams limit");
ngx_http_v3_finalize_connection(c,
NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED,
"too many blocked streams");
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
h3c->nblocked++;
ngx_queue_insert_tail(&h3c->blocked, &block->queue);
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 blocked:%ui", h3c->nblocked);
return NGX_BUSY;
}
void
ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count)
{
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->ack_insert_count < insert_count) {
dt->ack_insert_count = insert_count;
}
}
static void
ngx_http_v3_unblock(void *data)
{
ngx_http_v3_block_t *block = data;
if (block->queue.prev) {
ngx_queue_remove(&block->queue);
block->queue.prev = NULL;
(*block->nblocked)--;
}
}
static ngx_int_t
ngx_http_v3_new_entry(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_connection_t *bc;
ngx_http_v3_block_t *block;
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(c);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 new dynamic entry, blocked:%ui", h3c->nblocked);
while (!ngx_queue_empty(&h3c->blocked)) {
q = ngx_queue_head(&h3c->blocked);
block = (ngx_http_v3_block_t *) q;
bc = block->connection;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream");
ngx_http_v3_unblock(block);
ngx_post_event(bc->read, &ngx_posted_events);
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value)
{
switch (id) {
case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value);
break;
case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL",
value);
break;
case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param QPACK_BLOCKED_STREAMS:%uL", value);
break;
default:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param #%uL:%uL", id, value);
}
return NGX_OK;
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_
#define _NGX_HTTP_V3_TABLE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_str_t name;
ngx_str_t value;
} ngx_http_v3_field_t;
typedef struct {
ngx_http_v3_field_t **elts;
ngx_uint_t nelts;
ngx_uint_t base;
size_t size;
size_t capacity;
uint64_t insert_count;
uint64_t ack_insert_count;
ngx_event_t send_insert_count;
} ngx_http_v3_dynamic_table_t;
void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev);
void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c);
ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
ngx_uint_t index, ngx_str_t *value);
ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
ngx_str_t *value);
ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity);
ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index);
ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc);
ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
ngx_str_t *name, ngx_str_t *value);
ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index,
ngx_str_t *name, ngx_str_t *value);
ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c,
ngx_uint_t *insert_count);
ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
ngx_uint_t insert_count);
void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count);
ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
uint64_t value);
#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */

View File

@ -0,0 +1,624 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_http_v3_parse_uni_t parse;
ngx_int_t index;
} ngx_http_v3_uni_stream_t;
static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev);
static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev);
static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
ngx_uint_t type);
void
ngx_http_v3_init_uni_stream(ngx_connection_t *c)
{
uint64_t n;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
h3c = ngx_http_v3_get_session(c);
if (h3c->hq) {
ngx_http_v3_finalize_connection(c,
NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
"uni stream in hq mode");
c->data = NULL;
ngx_http_v3_close_uni_stream(c);
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream");
n = c->quic->id >> 2;
if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) {
ngx_http_v3_finalize_connection(c,
NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
"reached maximum number of uni streams");
c->data = NULL;
ngx_http_v3_close_uni_stream(c);
return;
}
ngx_quic_cancelable_stream(c);
us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
if (us == NULL) {
ngx_http_v3_finalize_connection(c,
NGX_HTTP_V3_ERR_INTERNAL_ERROR,
"memory allocation error");
c->data = NULL;
ngx_http_v3_close_uni_stream(c);
return;
}
us->index = -1;
c->data = us;
c->read->handler = ngx_http_v3_uni_read_handler;
c->write->handler = ngx_http_v3_uni_dummy_write_handler;
ngx_http_v3_uni_read_handler(c->read);
}
static void
ngx_http_v3_close_uni_stream(ngx_connection_t *c)
{
ngx_pool_t *pool;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");
us = c->data;
if (us && us->index >= 0) {
h3c = ngx_http_v3_get_session(c);
h3c->known_streams[us->index] = NULL;
}
c->destroyed = 1;
pool = c->pool;
ngx_close_connection(c);
ngx_destroy_pool(pool);
}
ngx_int_t
ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type)
{
ngx_int_t index;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
h3c = ngx_http_v3_get_session(c);
switch (type) {
case NGX_HTTP_V3_STREAM_ENCODER:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 encoder stream");
index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER;
break;
case NGX_HTTP_V3_STREAM_DECODER:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 decoder stream");
index = NGX_HTTP_V3_STREAM_CLIENT_DECODER;
break;
case NGX_HTTP_V3_STREAM_CONTROL:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 control stream");
index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL;
break;
default:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 stream 0x%02xL", type);
if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL
|| h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL
|| h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream");
return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
}
index = -1;
}
if (index >= 0) {
if (h3c->known_streams[index]) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
}
h3c->known_streams[index] = c;
us = c->data;
us->index = index;
}
return NGX_OK;
}
static void
ngx_http_v3_uni_read_handler(ngx_event_t *rev)
{
u_char buf[128];
ssize_t n;
ngx_buf_t b;
ngx_int_t rc;
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
c = rev->data;
us = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler");
if (c->close) {
ngx_http_v3_close_uni_stream(c);
return;
}
ngx_memzero(&b, sizeof(ngx_buf_t));
while (rev->ready) {
n = c->recv(c, buf, sizeof(buf));
if (n == NGX_ERROR) {
rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
goto failed;
}
if (n == 0) {
if (us->index >= 0) {
rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM;
goto failed;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof");
ngx_http_v3_close_uni_stream(c);
return;
}
if (n == NGX_AGAIN) {
break;
}
b.pos = buf;
b.last = buf + n;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (ngx_http_v3_check_flood(c) != NGX_OK) {
ngx_http_v3_close_uni_stream(c);
return;
}
rc = ngx_http_v3_parse_uni(c, &us->parse, &b);
if (rc == NGX_DONE) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 read done");
ngx_http_v3_close_uni_stream(c);
return;
}
if (rc > 0) {
goto failed;
}
if (rc != NGX_AGAIN) {
rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
goto failed;
}
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
goto failed;
}
return;
failed:
ngx_http_v3_finalize_connection(c, rc, "stream error");
ngx_http_v3_close_uni_stream(c);
}
static void
ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev)
{
u_char ch;
ngx_connection_t *c;
c = rev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler");
if (c->close) {
ngx_http_v3_close_uni_stream(c);
return;
}
if (rev->ready) {
if (c->recv(c, &ch, 1) != 0) {
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL);
ngx_http_v3_close_uni_stream(c);
return;
}
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
NULL);
ngx_http_v3_close_uni_stream(c);
}
}
static void
ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev)
{
ngx_connection_t *c;
c = wev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
NULL);
ngx_http_v3_close_uni_stream(c);
}
}
static ngx_connection_t *
ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type)
{
u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN];
size_t n;
ngx_int_t index;
ngx_connection_t *sc;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
switch (type) {
case NGX_HTTP_V3_STREAM_ENCODER:
index = NGX_HTTP_V3_STREAM_SERVER_ENCODER;
break;
case NGX_HTTP_V3_STREAM_DECODER:
index = NGX_HTTP_V3_STREAM_SERVER_DECODER;
break;
case NGX_HTTP_V3_STREAM_CONTROL:
index = NGX_HTTP_V3_STREAM_SERVER_CONTROL;
break;
default:
index = -1;
}
h3c = ngx_http_v3_get_session(c);
if (index >= 0) {
if (h3c->known_streams[index]) {
return h3c->known_streams[index];
}
}
sc = ngx_quic_open_stream(c, 0);
if (sc == NULL) {
goto failed;
}
ngx_quic_cancelable_stream(sc);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 create uni stream, type:%ui", type);
us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
if (us == NULL) {
goto failed;
}
us->index = index;
sc->data = us;
sc->read->handler = ngx_http_v3_uni_dummy_read_handler;
sc->write->handler = ngx_http_v3_uni_dummy_write_handler;
if (index >= 0) {
h3c->known_streams[index] = sc;
}
n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (sc->send(sc, buf, n) != (ssize_t) n) {
goto failed;
}
ngx_post_event(sc->read, &ngx_posted_events);
return sc;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
"failed to create server stream");
if (sc) {
ngx_http_v3_close_uni_stream(sc);
}
return NULL;
}
ngx_int_t
ngx_http_v3_send_settings(ngx_connection_t *c)
{
u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6];
size_t n;
ngx_connection_t *cc;
ngx_http_v3_session_t *h3c;
ngx_http_v3_srv_conf_t *h3scf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings");
cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
if (cc == NULL) {
return NGX_ERROR;
}
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
n = ngx_http_v3_encode_varlen_int(NULL,
NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity);
n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams);
p = (u_char *) ngx_http_v3_encode_varlen_int(buf,
NGX_HTTP_V3_FRAME_SETTINGS);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
p = (u_char *) ngx_http_v3_encode_varlen_int(p,
NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity);
p = (u_char *) ngx_http_v3_encode_varlen_int(p,
NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams);
n = p - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (cc->send(cc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send settings");
ngx_http_v3_close_uni_stream(cc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id)
{
u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3];
size_t n;
ngx_connection_t *cc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id);
cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
if (cc == NULL) {
return NGX_ERROR;
}
n = ngx_http_v3_encode_varlen_int(NULL, id);
p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, id);
n = p - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (cc->send(cc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send goaway");
ngx_http_v3_close_uni_stream(cc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
{
u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
size_t n;
ngx_connection_t *dc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 send section acknowledgement %ui", stream_id);
dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
if (dc == NULL) {
return NGX_ERROR;
}
buf[0] = 0x80;
n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (dc->send(dc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"failed to send section acknowledgement");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send section acknowledgement");
ngx_http_v3_close_uni_stream(dc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
{
u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
size_t n;
ngx_connection_t *dc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 send stream cancellation %ui", stream_id);
dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
if (dc == NULL) {
return NGX_ERROR;
}
buf[0] = 0x40;
n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (dc->send(dc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send stream cancellation");
ngx_http_v3_close_uni_stream(dc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
{
u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
size_t n;
ngx_connection_t *dc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 send insert count increment %ui", inc);
dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
if (dc == NULL) {
return NGX_ERROR;
}
buf[0] = 0;
n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (dc->send(dc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"failed to send insert count increment");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send insert count increment");
ngx_http_v3_close_uni_stream(dc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 cancel stream %ui", stream_id);
/* we do not use dynamic tables */
return NGX_OK;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_
#define _NGX_HTTP_V3_UNI_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
void ngx_http_v3_init_uni_stream(ngx_connection_t *c);
ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type);
ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id);
ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c,
ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c,
ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c,
ngx_uint_t inc);
#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */

View File

@ -13,6 +13,8 @@
#define NGX_WRITE_SHUTDOWN SHUT_WR
#define NGX_READ_SHUTDOWN SHUT_RD
#define NGX_RDWR_SHUTDOWN SHUT_RDWR
typedef int ngx_socket_t;

View File

@ -14,6 +14,8 @@
#define NGX_WRITE_SHUTDOWN SD_SEND
#define NGX_READ_SHUTDOWN SD_RECEIVE
#define NGX_RDWR_SHUTDOWN SD_BOTH
typedef SOCKET ngx_socket_t;