From 33a03850a3d5b4a0eb38e647942fef1f6e419f49 Mon Sep 17 00:00:00 2001 From: Lynne Date: Thu, 28 Mar 2024 02:21:36 +0100 Subject: [PATCH] WIP QUIC protocol via OpenSSL, specify ALPN --- draft-avtransport-spec.bs | 7 +- libavtransport/address.c | 3 + libavtransport/address.h | 1 + libavtransport/io_common.c | 2 +- libavtransport/meson.build | 4 + libavtransport/protocol_common.c | 9 +- libavtransport/protocol_quic.c | 193 +++++++++++++++++++++++++++++++ meson.build | 2 +- 8 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 libavtransport/protocol_quic.c diff --git a/draft-avtransport-spec.bs b/draft-avtransport-spec.bs index 3a88137..0f1644e 100644 --- a/draft-avtransport-spec.bs +++ b/draft-avtransport-spec.bs @@ -4229,8 +4229,9 @@ as possible. As such, it uses both reliable and unreliable streams, as well as bidirectionality features of the transport mechanism. All data packets with descriptors ''0x01**'', ''0xFF'', ''0xFE'', ''0xFD'' and ''0xFC'' -MUST be sent over in an unreliable QUIC DATAGRAM stream, as per -[[RFC9221]]. +must be sent over in an unreliable QUIC datagram stream, as per +[[RFC9221]]. Each stream must map to a different QUIC stream, though their AVTransport +and QUIC stream IDs do not have to match. All other packets must be sent over a reliable steam. FEC data for those packets should not be signalled. @@ -4242,6 +4243,8 @@ The minimum network MTU required for the protocol is 384 bytes, as to allow [[#video-information-packets]] or any future large packets to be sent without fragmentation. +The ALPN must contain the extension 0x61, 0x76, 0x74, 0x30 (''avt0''). + Jumbograms may be used where supported to reduce overhead and increase efficiency. [[#reverse-signalling]] is natively supported on QUIC. diff --git a/libavtransport/address.c b/libavtransport/address.c index 37bca40..8ac7c66 100644 --- a/libavtransport/address.c +++ b/libavtransport/address.c @@ -172,6 +172,9 @@ static int parse_host_addr(void *log_ctx, AVTAddress *addr, char *host, return AVT_ERROR(EINVAL); } + strncpy(addr->host, host, sizeof(addr->host) - 1); + addr->host[255] = '\0'; + memcpy(addr->ip, res[0].ai_addr, 16); freeaddrinfo(res); } else { diff --git a/libavtransport/address.h b/libavtransport/address.h index 2324f2e..123d66b 100644 --- a/libavtransport/address.h +++ b/libavtransport/address.h @@ -70,6 +70,7 @@ typedef struct AVTAddress { int fd; /** NETWORK **/ + uint8_t host[256]; uint8_t uuid[16]; bool listen; /* Server or client */ diff --git a/libavtransport/io_common.c b/libavtransport/io_common.c index 9da0db4..e8e2a92 100644 --- a/libavtransport/io_common.c +++ b/libavtransport/io_common.c @@ -93,7 +93,7 @@ static inline enum AVTIOType map_addr_to_io(AVTAddress *addr) case AVT_ADDRESS_URL: [[fallthrough]]; case AVT_ADDRESS_SOCKET: - if (addr->proto == AVT_PROTOCOL_UDP) + if (addr->proto == AVT_PROTOCOL_UDP || addr->proto == AVT_PROTOCOL_QUIC) return AVT_IO_UDP; else if (addr->proto == AVT_PROTOCOL_UDP_LITE) return AVT_IO_UDP_LITE; diff --git a/libavtransport/meson.build b/libavtransport/meson.build index 4c3158b..1394a5c 100644 --- a/libavtransport/meson.build +++ b/libavtransport/meson.build @@ -68,6 +68,10 @@ sources = [ fallback: 'release') ] +if openssl_dep.found() + sources += 'protocol_quic.c' +endif + if host_machine.system() != 'windows' sources += 'io_fd.c' sources += 'io_mmap.c' diff --git a/libavtransport/protocol_common.c b/libavtransport/protocol_common.c index 55cbae7..9cdd6f0 100644 --- a/libavtransport/protocol_common.c +++ b/libavtransport/protocol_common.c @@ -28,16 +28,21 @@ extern const AVTProtocol avt_protocol_datagram; extern const AVTProtocol avt_protocol_stream; -extern const AVTProtocol avt_protocol_quic; extern const AVTProtocol avt_protocol_pkt; +#ifdef CONFIG_HAVE_OPENSSL +extern const AVTProtocol avt_protocol_quic; +#endif + static const AVTProtocol *avt_protocol_list[AVT_PROTOCOL_MAX] = { [AVT_PROTOCOL_DATAGRAM] = &avt_protocol_datagram, [AVT_PROTOCOL_UDP] = &avt_protocol_datagram, [AVT_PROTOCOL_UDP_LITE] = &avt_protocol_datagram, [AVT_PROTOCOL_STREAM] = &avt_protocol_stream, [AVT_PROTOCOL_FILE] = &avt_protocol_stream, -// [AVT_PROTOCOL_QUIC] = &avt_protocol_quic, +#ifdef CONFIG_HAVE_OPENSSL + [AVT_PROTOCOL_QUIC] = &avt_protocol_quic, +#endif // [AVT_PROTOCOL_CALLBACK_PKT] = &avt_protocol_pkt, }; diff --git a/libavtransport/protocol_quic.c b/libavtransport/protocol_quic.c new file mode 100644 index 0000000..c65ad7d --- /dev/null +++ b/libavtransport/protocol_quic.c @@ -0,0 +1,193 @@ +/* + * Copyright © 2024, Lynne + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include + +#include "protocol_common.h" +#include "io_common.h" + +struct AVTProtocolCtx { + const AVTIO *io; + AVTIOCtx *io_ctx; + + SSL_CTX *ssl_ctx; + + BIO *bio; + SSL *ssl; +}; + +static COLD int quic_proto_close(AVTProtocolCtx **_p) +{ + AVTProtocolCtx *p = *_p; + int err = p->io->close(&p->io_ctx); + + BIO_free_all(p->bio); + SSL_CTX_free(p->ssl_ctx); + + free(p); + *_p = NULL; + return err; +} + +static COLD int quic_proto_init(AVTContext *ctx, AVTProtocolCtx **_p, AVTAddress *addr) +{ + AVTProtocolCtx *p = malloc(sizeof(*p)); + if (!p) + return AVT_ERROR(ENOMEM); + + int err = avt_io_init(ctx, &p->io, &p->io_ctx, addr); + if (err < 0) + free(p); + + p->ssl_ctx = SSL_CTX_new(OSSL_QUIC_client_method()); + if (!p->ssl_ctx) { + quic_proto_close(&p); + return AVT_ERROR(ENOMEM); + } + + /* Enable trust chain verification */ + SSL_CTX_set_verify(p->ssl_ctx, SSL_VERIFY_PEER, NULL); + + /* Load default root CA store */ + if (!SSL_CTX_set_default_verify_paths(p->ssl_ctx)) { + avt_log(p, AVT_LOG_ERROR, "Unable to load default root CA store\n"); + quic_proto_close(&p); + } + + p->bio = BIO_new_ssl_connect(p->ssl_ctx); + if (!p->bio) { + quic_proto_close(&p); + return AVT_ERROR(EINVAL); + } + + if (!BIO_get_ssl(p->bio, &p->ssl)) { + avt_log(p, AVT_LOG_ERROR, "Unable to get SSL context\n"); + quic_proto_close(&p); + return AVT_ERROR(EINVAL); + } + + if (strlen(addr->host)) { + /* Tell the SSL object the hostname to check certificates against. */ + if (SSL_set1_host(p->ssl, addr->host) <= 0) { + avt_log(p, AVT_LOG_ERROR, "Unable to set SSL hostname\n"); + quic_proto_close(&p); + return AVT_ERROR(EINVAL); + } + } + + /* Configure ALPN, which is required for QUIC */ + static const uint8_t alpn[] = { 4, 'a', 'v', 't', '0' }; + if (SSL_set_alpn_protos(p->ssl, alpn, sizeof(alpn))) { + /* Note: SSL_set_alpn_protos returns 1 for failure */ + avt_log(p, AVT_LOG_ERROR, "Unable to set ALPN\n"); + quic_proto_close(&p); + return AVT_ERROR(EINVAL); + } + + *_p = p; + return err; +} + +static int quic_proto_add_dst(AVTProtocolCtx *p, AVTAddress *addr) +{ + if (!p->io->add_dst) + return AVT_ERROR(ENOTSUP); + return p->io->add_dst(p->io_ctx, addr); +} + +static int quic_proto_rm_dst(AVTProtocolCtx *p, AVTAddress *addr) +{ + if (!p->io->del_dst) + return AVT_ERROR(ENOTSUP); + return p->io->del_dst(p->io_ctx, addr); +} + +static int64_t quic_proto_send_packet(AVTProtocolCtx *p, AVTPktd *pkt, + int64_t timeout) +{ + return p->io->write_pkt(p->io_ctx, pkt, timeout); +} + +static int64_t quic_proto_send_seq(AVTProtocolCtx *p, AVTPacketFifo *seq, + int64_t timeout) +{ + return p->io->write_vec(p->io_ctx, seq->data, seq->nb, timeout); +} + +static int64_t quic_proto_receive_packet(AVTProtocolCtx *p, + union AVTPacketData *pkt, AVTBuffer **pl, + int64_t timeout) +{ + AVTBuffer *buf; + int64_t err = p->io->read_input(p->io_ctx, &buf, 0, timeout); + if (err < 0) + return err; + + // TODO - deserialize packet here + + return err; +} + +static int64_t quic_proto_max_pkt_len(AVTProtocolCtx *p) +{ + return p->io->get_max_pkt_len(p->io_ctx); +} + +static int64_t quic_proto_seek(AVTProtocolCtx *p, + int64_t off, uint32_t seq, + int64_t ts, bool ts_is_dts) +{ + if (p->io->seek) + return p->io->seek(p->io_ctx, off); + return AVT_ERROR(ENOTSUP); +} + +static int quic_proto_flush(AVTProtocolCtx *p, int64_t timeout) +{ + if (p->io->flush) + return p->io->flush(p->io_ctx, timeout); + return AVT_ERROR(ENOTSUP); +} + +const AVTProtocol avt_protocol_quic = { + .name = "quic", + .type = AVT_PROTOCOL_QUIC, + .init = quic_proto_init, + .add_dst = quic_proto_add_dst, + .rm_dst = quic_proto_rm_dst, + .get_max_pkt_len = quic_proto_max_pkt_len, + .send_packet = quic_proto_send_packet, + .send_seq = quic_proto_send_seq, + .update_packet = NULL, + .receive_packet = quic_proto_receive_packet, + .seek = quic_proto_seek, + .flush = quic_proto_flush, + .close = quic_proto_close, +}; diff --git a/meson.build b/meson.build index 306cf79..3a24823 100644 --- a/meson.build +++ b/meson.build @@ -119,7 +119,7 @@ endif uring_dep = dependency('liburing', required: false) zstd_dep = dependency('libzstd', required: false) cbor_dep = dependency('libcbor', required: false) -openssl_dep = dependency('openssl', required: false) +openssl_dep = dependency('openssl', required: false, version : '3.3.0') brotlienc_dep = dependency('', required:false) brotlidec_dep = dependency('', required:false)