# HG changeset patch # User Vladimir Homutov # Date 1584013404 -10800 # Node ID 8d6ac639feac0b0a03b5b47ad7f53ce535608e52 # Parent a5423632d67b8290749b60497ed9a0fa63c3691b Added support of multiple QUIC packets in single datagram. - now NEW_CONNECTION_ID frames can be received and parsed The packet structure is created in ngx_quic_input() and passed to all handlers (initial, handshake and application data). The UDP datagram buffer is saved as pkt->raw; The QUIC packet is stored as pkt->data and pkt->len (instead of pkt->buf) (pkt->len is adjusted after parsing headers to actual length) The pkt->pos is removed, pkt->raw->pos is used instead. diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -166,6 +166,15 @@ typedef struct { } ngx_quic_crypto_frame_t; +typedef struct { + uint64_t seqnum; + uint64_t retire; + uint64_t len; + u_char cid[20]; + u_char srt[16]; +} ngx_quic_ncid_t; + + struct ngx_quic_frame_s { ngx_uint_t type; ngx_quic_level_t level; @@ -173,6 +182,7 @@ struct ngx_quic_frame_s { union { ngx_quic_crypto_frame_t crypto; ngx_quic_ack_frame_t ack; + ngx_quic_ncid_t ncid; // more frames } u; @@ -215,8 +225,10 @@ typedef struct { ngx_quic_level_t level; /* filled in by parser */ - ngx_str_t buf; /* quic packet from wire */ - u_char *pos; /* current parser position */ + ngx_buf_t *raw; /* udp datagram from wire */ + + u_char *data; /* quic packet */ + size_t len; /* cleartext fields */ ngx_str_t dcid; @@ -230,9 +242,13 @@ typedef struct { static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_buf_t *b); -static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b); -static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b); + ngx_quic_header_t *pkt); + +static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); + #if BORINGSSL_API_VERSION >= 10 static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, @@ -319,16 +335,46 @@ ngx_quic_init_ssl_methods(SSL_CTX* ctx) ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) { + u_char *p; + ngx_quic_header_t pkt; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + pkt.raw = b; + pkt.data = b->start; + pkt.len = b->last - b->start; + if (c->quic == NULL) { - return ngx_quic_new_connection(c, ssl, b); + return ngx_quic_new_connection(c, ssl, &pkt); } - if (b->start[0] & NGX_QUIC_PKT_LONG) { - // TODO: check current state - return ngx_quic_handshake_input(c, b); - } - - return ngx_quic_app_input(c, b); + p = b->start; + + do { + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.raw = b; + pkt.data = p; + pkt.len = b->last - p; + + if (p[0] & NGX_QUIC_PKT_LONG) { + // TODO: check current state + if (ngx_quic_handshake_input(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + } else { + + if (ngx_quic_app_input(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + } + + /* b->pos is at header end, adjust by actual packet length */ + p = b->pos + pkt.len; + b->pos = p; /* reset b->pos to the next packet start */ + + } while (p < b->last); + + return NGX_OK; } static ngx_int_t @@ -1018,15 +1064,14 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_ } -/* TODO: stub for short packet header processing */ static ngx_int_t ngx_quic_process_short_header(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *p; - p = pkt->buf.data; - - ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len); + p = pkt->data; + + ngx_quic_hexdump0(c->log, "short input", pkt->data, pkt->len); if ((p[0] & NGX_QUIC_PKT_LONG)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a short packet"); @@ -1043,10 +1088,11 @@ ngx_quic_process_short_header(ngx_connec return NGX_ERROR; } + pkt->dcid.len = c->quic->dcid.len; pkt->dcid.data = p; - p += c->quic->dcid.len; - - pkt->pos = p; + p += pkt->dcid.len; + + pkt->raw->pos = p; return NGX_OK; } @@ -1057,9 +1103,9 @@ ngx_quic_process_long_header(ngx_connect { u_char *p; - p = pkt->buf.data; - - ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len); + p = pkt->data; + + ngx_quic_hexdump0(c->log, "long input", pkt->data, pkt->len); if (!(p[0] & NGX_QUIC_PKT_LONG)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet"); @@ -1087,7 +1133,7 @@ ngx_quic_process_long_header(ngx_connect pkt->scid.data = p; p += pkt->scid.len; - pkt->pos = p; + pkt->raw->pos = p; return NGX_OK; } @@ -1099,7 +1145,7 @@ ngx_quic_process_initial_header(ngx_conn u_char *p; ngx_int_t plen; - p = pkt->pos; + p = pkt->raw->pos; pkt->token.len = ngx_quic_parse_int(&p); pkt->token.data = p; @@ -1111,13 +1157,13 @@ ngx_quic_process_initial_header(ngx_conn ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet length: %d", plen); - if (plen > pkt->buf.data + pkt->buf.len - p) { + if (plen > pkt->data + pkt->len - p) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet"); return NGX_ERROR; } - pkt->pos = p; - pkt->buf.len = plen; + pkt->raw->pos = p; + pkt->len = plen; ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len); ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len); @@ -1135,20 +1181,20 @@ ngx_quic_process_handshake_header(ngx_co u_char *p; ngx_int_t plen; - p = pkt->pos; + p = pkt->raw->pos; plen = ngx_quic_parse_int(&p); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet length: %d", plen); - if (plen > pkt->buf.data + pkt->buf.len - p) { + if (plen > pkt->data + pkt->len - p) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet"); return NGX_ERROR; } - pkt->pos = p; - pkt->buf.len = plen; + pkt->raw->pos = p; + pkt->len = plen; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet length: %d", plen); @@ -1280,7 +1326,7 @@ ngx_quic_decrypt(ngx_connection_t *c, ng return NGX_ERROR; } - p = pkt->pos; + p = pkt->raw->pos; /* draft-ietf-quic-tls-23#section-5.4.2: * the Packet Number field is assumed to be 4 bytes long @@ -1321,19 +1367,19 @@ ngx_quic_decrypt(ngx_connection_t *c, ng in.data = p; if (pkt->flags & NGX_QUIC_PKT_LONG) { - in.len = pkt->buf.len - pnl; + in.len = pkt->len - pnl; } else { - in.len = pkt->buf.data + pkt->buf.len - p; + in.len = pkt->data + pkt->len - p; } - ad.len = p - pkt->buf.data; + ad.len = p - pkt->data; ad.data = ngx_pnalloc(c->pool, ad.len); if (ad.data == NULL) { return NGX_ERROR; } - ngx_memcpy(ad.data, pkt->buf.data, ad.len); + ngx_memcpy(ad.data, pkt->data, ad.len); ad.data[0] = clearflags; ad.data[ad.len - pnl] = (u_char) pn; @@ -1349,24 +1395,21 @@ ngx_quic_decrypt(ngx_connection_t *c, ng ngx_quic_hexdump0(c->log, "packet payload", pkt->payload.data, pkt->payload.len); - pkt->pos = pkt->payload.data; - return rc; } -ngx_int_t -ngx_quic_read_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, +ssize_t +ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, ngx_quic_frame_t *frame) { - u_char *p, *end; + u_char *p; size_t npad; - p = pkt->pos; - end = pkt->payload.data + pkt->payload.len; - - frame->type = *p++; + p = start; + + frame->type = *p++; // TODO: check overflow (p < end) switch (frame->type) { @@ -1420,15 +1463,35 @@ ngx_quic_read_frame(ngx_connection_t *c, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "PING frame"); p++; break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "NCID frame"); + + 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: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "connection close frame => NGX_ERROR"); + + // TODO: parse connection close here + return NGX_ERROR; + break; + default: ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "unknown frame type %xi", frame->type); return NGX_ERROR; } - pkt->pos = p; - - return NGX_OK; + return p - start; } @@ -1539,22 +1602,28 @@ ngx_quic_init_connection(ngx_connection_ static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) { - u_char *end; + u_char *end, *p; + ssize_t len; ngx_uint_t ack_this; ngx_quic_frame_t frame, *ack_frame; ngx_quic_connection_t *qc; qc = c->quic; - end = pkt->payload.data + pkt->payload.len; + + p = pkt->payload.data; + end = p + pkt->payload.len; ack_this = 0; - while (pkt->pos < end) { - - if (ngx_quic_read_frame(c, pkt, &frame) != NGX_OK) { + while (p < end) { + + len = ngx_quic_read_frame(c, p, end, &frame); + if (len < 0) { return NGX_ERROR; } + p += len; + switch (frame.type) { case NGX_QUIC_FT_ACK: @@ -1594,6 +1663,15 @@ ngx_quic_payload_handler(ngx_connection_ ack_this = 1; continue; + case NGX_QUIC_FT_NEW_CONNECTION_ID: + ack_this = 1; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "NCID: { seq=%ui retire=%ui len=%ui}", + frame.u.ncid.seqnum, + frame.u.ncid.retire, + frame.u.ncid.len); + continue; + default: ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected frame type 0x%xd in packet", frame.type); @@ -1601,6 +1679,13 @@ ngx_quic_payload_handler(ngx_connection_ } } + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "trailing garbage in payload: %ui bytes", end - p); + return NGX_ERROR; + } + + if (ack_this == 0) { /* do not ack packets with ACKs and PADDING */ return NGX_OK; @@ -1626,31 +1711,27 @@ ngx_quic_payload_handler(ngx_connection_ static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; - ngx_quic_header_t pkt = { 0 }; - - pkt.buf.data = b->start; - pkt.buf.len = b->last - b->pos; - - if (ngx_buf_size(b) < 1200) { + if (ngx_buf_size(pkt->raw) < 1200) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); return NGX_ERROR; } - if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { + if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid initial packet: 0x%xi", pkt.flags); + "invalid initial packet: 0x%xi", pkt->flags); return NGX_ERROR; } - if (ngx_quic_process_initial_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_initial_header(c, pkt) != NGX_OK) { return NGX_ERROR; } @@ -1662,109 +1743,104 @@ ngx_quic_new_connection(ngx_connection_t c->quic = qc; qc->ssl = ssl; - qc->dcid.len = pkt.dcid.len; - qc->dcid.data = ngx_pnalloc(c->pool, pkt.dcid.len); + qc->dcid.len = pkt->dcid.len; + qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); if (qc->dcid.data == NULL) { return NGX_ERROR; } - ngx_memcpy(qc->dcid.data, pkt.dcid.data, qc->dcid.len); - - qc->scid.len = pkt.scid.len; + ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len); + + qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); if (qc->scid.data == NULL) { return NGX_ERROR; } - ngx_memcpy(qc->scid.data, pkt.scid.data, qc->scid.len); - - qc->token.len = pkt.token.len; + ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); + + qc->token.len = pkt->token.len; qc->token.data = ngx_pnalloc(c->pool, qc->token.len); if (qc->token.data == NULL) { return NGX_ERROR; } - ngx_memcpy(qc->token.data, pkt.token.data, qc->token.len); + ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); if (ngx_quic_initial_secret(c) != NGX_OK) { return NGX_ERROR; } - pkt.secret = &qc->client_in; - pkt.level = ssl_encryption_initial; - - if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + pkt->secret = &qc->client_in; + pkt->level = ssl_encryption_initial; + + if (ngx_quic_decrypt(c, pkt) != NGX_OK) { return NGX_ERROR; } - if (ngx_quic_init_connection(c, &pkt) != NGX_OK) { + if (ngx_quic_init_connection(c, pkt) != NGX_OK) { return NGX_ERROR; } - return ngx_quic_payload_handler(c, &pkt); + return ngx_quic_payload_handler(c, pkt); } static ngx_int_t -ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b) +ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; - ngx_quic_header_t pkt = { 0 }; - qc = c->quic; ssl_conn = c->ssl->connection; - pkt.buf.data = b->start; - pkt.buf.len = b->last - b->pos; - /* extract cleartext data into pkt */ - if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - if (pkt.dcid.len != qc->dcid.len) { + if (pkt->dcid.len != qc->dcid.len) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); return NGX_ERROR; } - if (ngx_memcmp(pkt.dcid.data, qc->dcid.data, qc->dcid.len) != 0) { + if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); return NGX_ERROR; } - if (pkt.scid.len != qc->scid.len) { + if (pkt->scid.len != qc->scid.len) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); return NGX_ERROR; } - if (ngx_memcmp(pkt.scid.data, qc->scid.data, qc->scid.len) != 0) { + if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); return NGX_ERROR; } - if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { + if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid packet type: 0x%xi", pkt.flags); + "invalid packet type: 0x%xi", pkt->flags); return NGX_ERROR; } - if (ngx_quic_process_handshake_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_handshake_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - pkt.secret = &qc->client_hs; - pkt.level = ssl_encryption_handshake; - - if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + pkt->secret = &qc->client_hs; + pkt->level = ssl_encryption_handshake; + + if (ngx_quic_decrypt(c, pkt) != NGX_OK) { return NGX_ERROR; } - return ngx_quic_payload_handler(c, &pkt); + return ngx_quic_payload_handler(c, pkt); } static ngx_int_t -ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b) +ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; @@ -1772,23 +1848,18 @@ ngx_quic_app_input(ngx_connection_t *c, /* TODO: this is a stub, untested */ - ngx_quic_header_t pkt = { 0 }; - - pkt.buf.data = b->start; - pkt.buf.len = b->last - b->pos; - - if (ngx_quic_process_short_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_short_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - pkt.secret = &qc->client_ad; - pkt.level = ssl_encryption_application; - - if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + pkt->secret = &qc->client_ad; + pkt->level = ssl_encryption_application; + + if (ngx_quic_decrypt(c, pkt) != NGX_OK) { return NGX_ERROR; } - return ngx_quic_payload_handler(c, &pkt); + return ngx_quic_payload_handler(c, pkt); }