summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorVladimir Homutov <vl@nginx.com>2020-03-04 15:52:12 +0300
committerVladimir Homutov <vl@nginx.com>2020-03-04 15:52:12 +0300
commit309cdf496d686be504b1c684e480eb8edf5a0f43 (patch)
tree5300a904064b19f1ad4213cef17663b447846934 /src
parent3c1707212273182c60560d980cb2b3f95f702d74 (diff)
downloadnginx-309cdf496d686be504b1c684e480eb8edf5a0f43.tar.gz
nginx-309cdf496d686be504b1c684e480eb8edf5a0f43.tar.bz2
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.
Diffstat (limited to 'src')
-rw-r--r--src/event/ngx_event_quic.c371
1 files changed, 276 insertions, 95 deletions
diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c
index 20d91117c..75947a633 100644
--- 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,17 +264,218 @@ ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
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;
- if (c->quic->out.data) {
- c->send(c, c->quic->out.data, c->quic->out.len);
- c->quic->out.data = NULL;
+ 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 */
+
+ 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,25 +766,19 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn,
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;
+ if (qc->frames == NULL) {
+ qc->frames = frame;
+ return;
+ }
-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);
-}
+ for (f = qc->frames; f->next; f = f->next) { /* void */ }
+ f->next = frame;
+}
static int
@@ -567,79 +786,50 @@ 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));
-
- pkt.level = level;
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "ngx_quic_add_handshake_data");
- payload.data = ngx_alloc(4 + len + 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_crypto(&p, (u_char *) data, len);
-
- 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;
+ p = ngx_pnalloc(c->pool, len);
+ if (p == NULL) {
+ return 0;
}
- payload.len = p - payload.data;
+ ngx_memcpy(p, data, len);
- ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "ngx_quic_add_handshake_data: clear_len:%uz",
- payload.len);
+ frame->level = level;
+ frame->type = NGX_QUIC_FT_CRYPTO;
+ frame->u.crypto.len = len;
+ frame->u.crypto.data = p;
- if (level == ssl_encryption_application) {
+ ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level);
- if (ngx_quic_create_short_packet(c, ssl_conn, &pkt, &payload, &res)
- != NGX_OK)
- {
- return 0;
- }
+ ngx_quic_queue_frame(qc, frame);
- } else {
-
- if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res)
- != NGX_OK)
- {
+ if (level == ssl_encryption_initial) {
+ frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t));
+ if (frame == NULL) {
return 0;
}
- }
+ 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");
- // 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 *ssl_conn)
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_t *c, ngx_buf_t *bb)
// 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;
-
- if (ngx_quic_create_long_packet(c, c->ssl->connection, &pkt, &payload, &res)
- != NGX_OK)
- {
- return 0;
- }
+ frame->level = ssl_encryption_handshake;
+ frame->type = NGX_QUIC_FT_ACK;
+ frame->u.ack.pn = pn;
- 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;