# HG changeset patch # User Vladimir Homutov # Date 1583326332 -10800 # Node ID 0a2683df5f114d22b056e1a7f60c4ed5c56b961f # Parent 6a76d965777208c5d8ed046b37570bd6a05cb558 Implemented improved version of quic_output(). Now handshake generates frames, and they are queued in c->quic->frames. The ngx_quic_output() is called from ngx_quic_flush_flight() or manually, processes the queue and encrypts all frames according to required encryption level. 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 @@ -124,12 +124,38 @@ typedef struct { } ngx_quic_secret_t; +typedef enum ssl_encryption_level_t ngx_quic_level_t; + +typedef struct ngx_quic_frame_s ngx_quic_frame_t; + +typedef struct { + ngx_uint_t pn; + // ngx_uint_t nranges; + // ... +} ngx_quic_ack_frame_t; + +typedef ngx_str_t ngx_quic_crypto_frame_t; + +struct ngx_quic_frame_s { + ngx_uint_t type; + ngx_quic_level_t level; + ngx_quic_frame_t *next; + union { + ngx_quic_crypto_frame_t crypto; + ngx_quic_ack_frame_t ack; + // more frames + } u; + + u_char info[128]; // for debug purposes +}; + + struct ngx_quic_connection_s { ngx_quic_state_t state; ngx_ssl_t *ssl; - ngx_str_t out; // stub for some kind of output queue + ngx_quic_frame_t *frames; ngx_str_t scid; ngx_str_t dcid; @@ -149,8 +175,6 @@ struct ngx_quic_connection_s { }; -typedef enum ssl_encryption_level_t ngx_quic_level_t; - typedef struct { ngx_quic_secret_t *secret; ngx_uint_t type; @@ -240,16 +264,217 @@ ngx_quic_input(ngx_connection_t *c, ngx_ return NGX_OK; } +static ngx_int_t +ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_level_t level, ngx_str_t *payload) +{ + ngx_str_t res; + ngx_quic_header_t pkt; + + static ngx_str_t initial_token = ngx_null_string; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); + + pkt.level = level; + + if (level == ssl_encryption_initial) { + pkt.number = &qc->initial_pn; + pkt.flags = NGX_QUIC_PKT_INITIAL; + pkt.secret = &qc->server_in; + pkt.token = &initial_token; + + if (ngx_quic_create_long_packet(c, c->ssl->connection, + &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; + } + + } else if (level == ssl_encryption_handshake) { + pkt.number = &qc->handshake_pn; + pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + pkt.secret = &qc->server_hs; + + if (ngx_quic_create_long_packet(c, c->ssl->connection, + &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + pkt.number = &qc->appdata_pn; + pkt.secret = &qc->server_ad; + + if (ngx_quic_create_short_packet(c, c->ssl->connection, + &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; + } + } + + ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); + + c->send(c, res.data, res.len); // TODO: err handling + + return NGX_OK; +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) +{ + if (p == NULL) { + return 5; /* minimal ACK */ + } + + 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) +{ + u_char *start; + + if (p == NULL) { + if (crypto->len >= 64) { + return crypto->len + 4; + + } else { + return crypto->len + 3; + } // TODO: proper calculation of varint + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, crypto->len); + p = ngx_cpymem(p, crypto->data, crypto->len); + + return p - start; +} + +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); + default: + /* BUG: unsupported frame type generated */ + return 0; + } +} + + +/* pack a group of frames [start; end) into memory p and send as single packet */ +ngx_int_t +ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, + ngx_quic_frame_t *end, size_t total) +{ + u_char *p; + ngx_str_t out; + ngx_quic_frame_t *f; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending frames %p...%p", start, end); + + p = ngx_pnalloc(c->pool, total); + if (p == NULL) { + return NGX_ERROR; + } + + out.data = p; + + for (f = start; f != end; f = f->next) { + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + p += ngx_quic_create_ack(p, &f->u.ack); + break; + + case NGX_QUIC_FT_CRYPTO: + p += ngx_quic_create_crypto(p, &f->u.crypto); + break; + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } + } + + out.len = p - out.data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "packet ready: %ui bytes at level %d", + out.len, start->level); + + // IOVEC/sendmsg_chain ? + if (ngx_quic_send_packet(c, c->quic, start->level, &out) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} ngx_int_t ngx_quic_output(ngx_connection_t *c) { - /* stub for processing output queue */ + size_t len; + ngx_uint_t lvl; + ngx_quic_frame_t *f, *start; + ngx_quic_connection_t *qc; + + qc = c->quic; + + if (qc->frames == NULL) { + return NGX_OK; + } + + lvl = qc->frames->level; + start = qc->frames; + f = start; + + do { + len = 0; + + do { + /* process same-level group of frames */ - if (c->quic->out.data) { - c->send(c, c->quic->out.data, c->quic->out.len); - c->quic->out.data = NULL; - } + len += ngx_quic_frame_len(f);// TODO: handle overflow, max size + + f = f->next; + } while (f && f->level == lvl); + + + if (ngx_quic_frames_send(c, start, f, len) != NGX_OK) { + return NGX_ERROR; + } + + if (f == NULL) { + break; + } + + lvl = f->level; // TODO: must not decrease (ever, also between calls) + start = f; + + } while (1); + + qc->frames = NULL; return NGX_OK; } @@ -541,105 +766,70 @@ ngx_quic_create_short_packet(ngx_connect static void -ngx_quic_create_ack(u_char **p, uint64_t num) +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { - ngx_quic_build_int(p, NGX_QUIC_FT_ACK); - ngx_quic_build_int(p, num); - ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, num); -} - + ngx_quic_frame_t *f; -static void -ngx_quic_create_crypto(u_char **p, u_char *data, size_t len) -{ - ngx_quic_build_int(p, NGX_QUIC_FT_CRYPTO); - ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, len); - *p = ngx_cpymem(*p, data, len); + if (qc->frames == NULL) { + qc->frames = frame; + return; + } + + for (f = qc->frames; f->next; f = f->next) { /* void */ } + + f->next = frame; } - 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; - ngx_str_t payload, res; + ngx_quic_frame_t *frame; ngx_connection_t *c; - ngx_quic_header_t pkt; ngx_quic_connection_t *qc; - ngx_str_t initial_token = ngx_null_string; - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = c->quic; //ngx_ssl_handshake_log(c); // TODO: enable again - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data"); - pkt.level = level; + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return 0; + } - payload.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); - if (payload.data == 0) { + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { return 0; } - p = payload.data; + + ngx_memcpy(p, data, len); - ngx_quic_create_crypto(&p, (u_char *) data, len); + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.len = len; + frame->u.crypto.data = p; + + ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); + + ngx_quic_queue_frame(qc, frame); if (level == ssl_encryption_initial) { - ngx_quic_create_ack(&p, 0); - - pkt.number = &qc->initial_pn; - pkt.flags = NGX_QUIC_PKT_INITIAL; - pkt.secret = &qc->server_in; - - pkt.token = &initial_token; - - } else if (level == ssl_encryption_handshake) { - pkt.number = &qc->handshake_pn; - pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->server_hs; - - pkt.token = NULL; - - } else { - pkt.number = &qc->appdata_pn; - pkt.secret = &qc->server_ad; - } - - payload.len = p - payload.data; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data: clear_len:%uz", - payload.len); - - if (level == ssl_encryption_application) { - - if (ngx_quic_create_short_packet(c, ssl_conn, &pkt, &payload, &res) - != NGX_OK) - { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { return 0; } - - } else { + frame->level = level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.pn = 0; + ngx_sprintf(frame->info, "ACK for PN=0 at initial, added manually from add_handshake_data"); - if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res) - != NGX_OK) - { - return 0; - } - } - - // TODO: save state of data to send into qc (push into queue) - qc->out = res; - - if (ngx_quic_output(c) != NGX_OK) { - return 0; + ngx_quic_queue_frame(qc, frame); } return 1; @@ -655,6 +845,10 @@ ngx_quic_flush_flight(ngx_ssl_conn_t *ss ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()"); + if (ngx_quic_output(c) != NGX_OK) { + return 0; + } + return 1; } @@ -1187,32 +1381,19 @@ ngx_quic_handshake_input(ngx_connection_ // ACK Client Finished - ngx_str_t payload, res; - ngx_quic_header_t pkt; - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_quic_frame_t *frame; - pkt.level = ssl_encryption_handshake; - pkt.number = &qc->handshake_pn; - pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->server_hs; - - payload.data = ngx_alloc(5 /*minimal ACK*/, c->log); - if (payload.data == 0) { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { return 0; } - p = payload.data; - ngx_quic_create_ack(&p, pn); - - payload.len = p - payload.data; + frame->level = ssl_encryption_handshake; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.pn = pn; - if (ngx_quic_create_long_packet(c, c->ssl->connection, &pkt, &payload, &res) - != NGX_OK) - { - return 0; - } - - qc->out = res; + ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pn); + ngx_quic_queue_frame(qc, frame); if (ngx_quic_output(c) != NGX_OK) { return 0;