Mercurial > hg > nginx-quic
diff src/event/ngx_event_quic_transport.c @ 7690:ae35ccba7aa6 quic
Extracted transport part of the code into separate file.
All code dealing with serializing/deserializing
is moved int srv/event/ngx_event_quic_transport.c/h file.
All macros for dealing with data are internal to source file.
The header file exposes frame types and error codes.
The exported functions are currently packet header parsers and writers
and frames parser/writer.
The ngx_quic_header_t structure is updated with 'log' member. This avoids
passing extra argument to parsing functions that need to report errors.
author | Vladimir Homutov <vl@nginx.com> |
---|---|
date | Wed, 18 Mar 2020 12:58:27 +0300 |
parents | |
children | 78540e2160d0 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/event/ngx_event_quic_transport.c @@ -0,0 +1,588 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#define ngx_quic_varint_len(value) \ + ((value) <= 63 ? 1 \ + : ((uint32_t) value) <= 16383 ? 2 \ + : ((uint64_t) value) <= 1073741823 ? 4 \ + : 8) + + +static uint64_t ngx_quic_parse_int(u_char **pos); +static void ngx_quic_build_int(u_char **pos, uint64_t value); + +static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); +static size_t ngx_quic_create_crypto(u_char *p, + ngx_quic_crypto_frame_t *crypto); +static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); + + +/* literal errors indexed by corresponding value */ +static char *ngx_quic_errors[] = { + "NO_ERROR", + "INTERNAL_ERROR", + "SERVER_BUSY", + "FLOW_CONTROL_ERROR", + "STREAM_LIMIT_ERROR", + "STREAM_STATE_ERROR", + "FINAL_SIZE_ERROR", + "FRAME_ENCODING_ERROR", + "TRANSPORT_PARAMETER_ERROR", + "CONNECTION_ID_LIMIT_ERROR", + "PROTOCOL_VIOLATION", + "INVALID_TOKEN", + "", + "CRYPTO_BUFFER_EXCEEDED", + "CRYPTO_ERROR", +}; + + +static uint64_t +ngx_quic_parse_int(u_char **pos) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + p = *pos; + len = 1 << ((*p & 0xc0) >> 6); + value = *p++ & 0x3f; + + while (--len) { + value = (value << 8) + *p++; + } + + *pos = p; + return value; +} + + +static void +ngx_quic_build_int(u_char **pos, uint64_t value) +{ + u_char *p; + ngx_uint_t len;//, len2; + + p = *pos; + len = 0; + + while (value >> ((1 << len) * 8 - 2)) { + len++; + } + + *p = len << 6; + +// len2 = + len = (1 << len); + len--; + *p |= value >> (len * 8); + p++; + + while (len) { + *p++ = value >> ((len-- - 1) * 8); + } + + *pos = p; +// return len2; +} + + +u_char * +ngx_quic_error_text(uint64_t error_code) +{ + return (u_char *) ngx_quic_errors[error_code]; +} + + +ngx_int_t +ngx_quic_parse_long_header(ngx_quic_header_t *pkt) +{ + u_char *p; + + p = pkt->data; + + ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len); + + if (!(p[0] & NGX_QUIC_PKT_LONG)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a long packet"); + return NGX_ERROR; + } + + pkt->flags = *p++; + + pkt->version = ngx_quic_parse_uint32(p); + p += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic flags:%xi version:%xD", pkt->flags, pkt->version); + + if (pkt->version != quic_version) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unsupported quic version"); + return NGX_ERROR; + } + + pkt->dcid.len = *p++; + pkt->dcid.data = p; + p += pkt->dcid.len; + + pkt->scid.len = *p++; + pkt->scid.data = p; + p += pkt->scid.len; + + pkt->raw->pos = p; + + return NGX_OK; +} + + +size_t +ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, + size_t pkt_len, u_char **pnp) +{ + u_char *p, *start; + + p = start = out->data; + + *p++ = pkt->flags; + + p = ngx_quic_write_uint32(p, quic_version); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, pkt->token.len); + } + + ngx_quic_build_int(&p, pkt_len + 1); // length (inc. pnl) + + *pnp = p; + + *p++ = (uint64_t) (*pkt->number); + + return p - start; +} + + +ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) +{ + u_char *p; + + p = pkt->data; + + ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len); + + if ((p[0] & NGX_QUIC_PKT_LONG)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a short packet"); + return NGX_ERROR; + } + + pkt->flags = *p++; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic flags:%xi", pkt->flags); + + if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid"); + return NGX_ERROR; + } + + pkt->dcid.len = dcid->len; + pkt->dcid.data = p; + p += pkt->dcid.len; + + pkt->raw->pos = p; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) +{ + u_char *p; + ngx_int_t plen; + + p = pkt->raw->pos; + + pkt->token.len = ngx_quic_parse_int(&p); + pkt->token.data = p; + + p += pkt->token.len; + + plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + if (plen > pkt->data + pkt->len - p) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = plen; + + ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len); + ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len); + ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) +{ + u_char *p; + ngx_int_t plen; + + p = pkt->raw->pos; + + plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + if (plen > pkt->data + pkt->len - p) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = plen; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + return NGX_OK; +} + + +#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) +#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) +#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) + +ssize_t +ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) +{ + u_char *p; + + size_t npad; + + p = start; + + frame->type = *p++; // TODO: check overflow (p < end) + + switch (frame->type) { + + case NGX_QUIC_FT_CRYPTO: + frame->u.crypto.offset = *p++; + frame->u.crypto.len = ngx_quic_parse_int(&p); + frame->u.crypto.data = p; + p += frame->u.crypto.len; + + break; + + case NGX_QUIC_FT_PADDING: + npad = 0; + while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX + p++; npad++; + } + + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + frame->u.ack.largest = ngx_quic_parse_int(&p); + frame->u.ack.delay = ngx_quic_parse_int(&p); + frame->u.ack.range_count =ngx_quic_parse_int(&p); + frame->u.ack.first_range =ngx_quic_parse_int(&p); + + if (frame->u.ack.range_count) { + frame->u.ack.ranges[0] = ngx_quic_parse_int(&p); + } + + if (frame->type ==NGX_QUIC_FT_ACK_ECN) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + frame->u.ncid.seqnum = ngx_quic_parse_int(&p); + frame->u.ncid.retire = ngx_quic_parse_int(&p); + frame->u.ncid.len = *p++; + ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len); + p += frame->u.ncid.len; + + ngx_memcpy(frame->u.ncid.srt, p, 16); + p += 16; + + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + + frame->u.close.error_code = ngx_quic_parse_int(&p); + frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC + frame->u.close.reason.len = ngx_quic_parse_int(&p); + frame->u.close.reason.data = p; + p += frame->u.close.reason.len; + + if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) { + frame->u.close.error_code = NGX_QUIC_ERR_LAST; + } + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + frame->u.stream.type = frame->type; + + frame->u.stream.off = ngx_quic_stream_bit_off(frame->type); + frame->u.stream.len = ngx_quic_stream_bit_len(frame->type); + frame->u.stream.fin = ngx_quic_stream_bit_fin(frame->type); + + frame->u.stream.stream_id = ngx_quic_parse_int(&p); + if (frame->type & 0x04) { + frame->u.stream.offset = ngx_quic_parse_int(&p); + } else { + frame->u.stream.offset = 0; + } + + if (frame->type & 0x02) { + frame->u.stream.length = ngx_quic_parse_int(&p); + } else { + frame->u.stream.length = end - p; /* up to packet end */ + } + + frame->u.stream.data = p; + + p += frame->u.stream.length; + + break; + + default: + return NGX_ERROR; + } + + return p - start; +} + + +ssize_t +ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) +{ + // TODO: handle end arg + + switch (f->type) { + case NGX_QUIC_FT_ACK: + return ngx_quic_create_ack(p, &f->u.ack); + + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(p, &f->u.crypto); + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + return ngx_quic_create_stream(p, &f->u.stream); + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } +} + + +size_t +ngx_quic_frame_len(ngx_quic_frame_t *frame) +{ + switch (frame->type) { + case NGX_QUIC_FT_ACK: + return ngx_quic_create_ack(NULL, &frame->u.ack); + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(NULL, &frame->u.crypto); + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + return ngx_quic_create_stream(NULL, &frame->u.stream); + default: + /* BUG: unsupported frame type generated */ + return 0; + } +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) +{ + size_t len; + + /* minimal ACK packet */ + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); + len += ngx_quic_varint_len(ack->pn); + len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->pn); + + return len; + } + + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(&p, ack->pn); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->pn); + + return 5; +} + + +static size_t +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); + len += ngx_quic_varint_len(crypto->offset); + len += ngx_quic_varint_len(crypto->len); + len += crypto->len; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, crypto->offset); + ngx_quic_build_int(&p, crypto->len); + p = ngx_cpymem(p, crypto->data, crypto->len); + + return p - start; +} + + +static size_t +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) +{ + size_t len; + u_char *start; + + if (!sf->len) { +#if 0 + ngx_log_error(NGX_LOG_INFO, log, 0, + "attempt to generate a stream frame without length"); +#endif + // XXX: handle error in caller + return NGX_ERROR; + } + + if (p == NULL) { + len = ngx_quic_varint_len(sf->type); + + if (sf->off) { + len += ngx_quic_varint_len(sf->offset); + } + + len += ngx_quic_varint_len(sf->stream_id); + + /* length is always present in generated frames */ + len += ngx_quic_varint_len(sf->length); + + len += sf->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, sf->type); + ngx_quic_build_int(&p, sf->stream_id); + + if (sf->off) { + ngx_quic_build_int(&p, sf->offset); + } + + /* length is always present in generated frames */ + ngx_quic_build_int(&p, sf->length); + + p = ngx_cpymem(p, sf->data, sf->length); + + return p - start; +}