Mercurial > hg > nginx
diff src/event/quic/ngx_event_quic_output.c @ 8763:4117aa7fa38e quic
QUIC: connection migration.
The patch adds proper transitions between multiple networking addresses that
can be used by a single quic connection. New networking paths are validated
using PATH_CHALLENGE/PATH_RESPONSE frames.
author | Vladimir Homutov <vl@nginx.com> |
---|---|
date | Thu, 29 Apr 2021 15:35:02 +0300 |
parents | bc910a5ec737 |
children | 4715f3e669f1 |
line wrap: on
line diff
--- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -35,10 +35,14 @@ #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ +static ngx_int_t ngx_quic_socket_output(ngx_connection_t *c, + ngx_quic_socket_t *qsock); static ssize_t ngx_quic_output_packet(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, + ngx_quic_socket_t *qsock); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); -static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); @@ -61,11 +65,29 @@ ngx_quic_max_udp_payload(ngx_connection_ ngx_int_t ngx_quic_output(ngx_connection_t *c) { + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (ngx_quic_socket_output(c, qc->socket) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ off_t max; size_t len, min, in_flight; ssize_t n; u_char *p; ngx_uint_t i, pad; + ngx_quic_path_t *path; ngx_quic_send_ctx_t *ctx; ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; @@ -78,15 +100,18 @@ ngx_quic_output(ngx_connection_t *c) in_flight = cg->in_flight; + path = qsock->path; + for ( ;; ) { p = dst; len = ngx_min(qc->ctp.max_udp_payload_size, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - if (!qc->validated) { - max = qc->received * 3; - max = (c->sent >= max) ? 0 : max - c->sent; + if (path->state != NGX_QUIC_PATH_VALIDATED) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + len = ngx_min(len, (size_t) max); } @@ -103,7 +128,7 @@ ngx_quic_output(ngx_connection_t *c) min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; - n = ngx_quic_output_packet(c, ctx, p, len, min); + n = ngx_quic_output_packet(c, ctx, p, len, min, qsock); if (n == NGX_ERROR) { return NGX_ERROR; } @@ -117,10 +142,13 @@ ngx_quic_output(ngx_connection_t *c) break; } - n = ngx_quic_send(c, dst, len); + n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen); + if (n == NGX_ERROR) { return NGX_ERROR; } + + path->sent += len; } if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { @@ -128,7 +156,6 @@ ngx_quic_output(ngx_connection_t *c) ngx_add_timer(c->read, qc->tp.max_idle_timeout); } - ngx_quic_set_lost_timer(c); return NGX_OK; } @@ -176,14 +203,14 @@ ngx_quic_get_padding_level(ngx_connectio static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - u_char *data, size_t max, size_t min) + u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock) { size_t len, hlen, pad_len; u_char *p; ssize_t flen; ngx_str_t out, res; ngx_int_t rc; - ngx_uint_t nframes; + ngx_uint_t nframes, has_pr; ngx_msec_t now; ngx_queue_t *q; ngx_quic_frame_t *f; @@ -196,9 +223,10 @@ ngx_quic_output_packet(ngx_connection_t return 0; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic output %s packet max:%uz min:%uz", - ngx_quic_level_name(ctx->level), max, min); + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output sock #%uL %s packet max:%uz min:%uz", + qsock->sid.seqnum, ngx_quic_level_name(ctx->level), + max, min); qc = ngx_quic_get_connection(c); cg = &qc->congestion; @@ -208,7 +236,7 @@ ngx_quic_output_packet(ngx_connection_t : NGX_QUIC_MAX_LONG_HEADER; hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; + hlen -= NGX_QUIC_MAX_CID_LEN - qsock->cid->len; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); @@ -216,6 +244,7 @@ ngx_quic_output_packet(ngx_connection_t nframes = 0; p = src; len = 0; + has_pr = 0; for (q = ngx_queue_head(&ctx->frames); q != ngx_queue_sentinel(&ctx->frames); @@ -227,6 +256,12 @@ ngx_quic_output_packet(ngx_connection_t max = cg->window; } + if (f->type == NGX_QUIC_FT_PATH_RESPONSE + || f->type == NGX_QUIC_FT_PATH_CHALLENGE) + { + has_pr = 1; + } + if (hlen + len >= max) { break; } @@ -296,15 +331,33 @@ ngx_quic_output_packet(ngx_connection_t pkt.version = qc->version; pkt.log = c->log; pkt.level = ctx->level; - pkt.dcid = qc->scid; - pkt.scid = qc->dcid; + + pkt.dcid.data = qsock->cid->id; + pkt.dcid.len = qsock->cid->len; + + pkt.scid.data = qsock->sid.id; + pkt.scid.len = qsock->sid.len; pad_len = 4; - if (min) { + if (min || has_pr) { hlen = EVP_GCM_TLS_TAG_LEN + ngx_quic_create_header(&pkt, NULL, out.len, NULL); + /* + * An endpoint MUST expand datagrams that contain a + * PATH_CHALLENGE frame to at least the smallest allowed + * maximum datagram size of 1200 bytes, unless the + * anti-amplification limit for the path does not permit + * sending a datagram of this size. + * + * (same applies to PATH_RESPONSE frames) + */ + + if (has_pr) { + min = ngx_max(1200, min); + } + if (min > hlen + pad_len) { pad_len = min - hlen; } @@ -364,11 +417,14 @@ ngx_quic_output_packet(ngx_connection_t } -ssize_t -ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen) { - ngx_buf_t b; - ngx_chain_t cl, *res; + ngx_buf_t b; + socklen_t orig_socklen; + ngx_chain_t cl, *res; + struct sockaddr *orig_sockaddr; ngx_memzero(&b, sizeof(ngx_buf_t)); @@ -380,7 +436,17 @@ ngx_quic_send(ngx_connection_t *c, u_cha cl.buf = &b; cl.next= NULL; + orig_socklen = c->socklen; + orig_sockaddr = c->sockaddr; + + c->sockaddr = sockaddr; + c->socklen = socklen; + res = c->send_chain(c, &cl, 0); + + c->sockaddr = orig_sockaddr; + c->socklen = orig_socklen; + if (res == NGX_CHAIN_ERROR) { return NGX_ERROR; } @@ -441,7 +507,7 @@ ngx_quic_negotiate_version(ngx_connectio "quic vnego packet to send len:%uz %*xs", len, len, buf); #endif - (void) ngx_quic_send(c, buf, len); + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); return NGX_ERROR; } @@ -524,7 +590,7 @@ ngx_quic_send_stateless_reset(ngx_connec return NGX_ERROR; } - (void) ngx_quic_send(c, buf, len); + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); return NGX_DECLINED; } @@ -642,7 +708,9 @@ ngx_quic_send_early_cc(ngx_connection_t return NGX_ERROR; } - if (ngx_quic_send(c, res.data, res.len) == NGX_ERROR) { + if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) + == NGX_ERROR) + { return NGX_ERROR; } @@ -664,8 +732,8 @@ ngx_quic_send_retry(ngx_connection_t *c, expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, conf->av_token_key, &token, &inpkt->dcid, - expires, 1) + if (ngx_quic_new_token(c, c->sockaddr, c->socklen, conf->av_token_key, + &token, &inpkt->dcid, expires, 1) != NGX_OK) { return NGX_ERROR; @@ -700,7 +768,7 @@ ngx_quic_send_retry(ngx_connection_t *c, "quic packet to send len:%uz %xV", res.len, &res); #endif - len = ngx_quic_send(c, res.data, res.len); + len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); if (len == NGX_ERROR) { return NGX_ERROR; } @@ -718,7 +786,7 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_int_t -ngx_quic_send_new_token(ngx_connection_t *c) +ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) { time_t expires; ngx_str_t token; @@ -727,13 +795,10 @@ ngx_quic_send_new_token(ngx_connection_t qc = ngx_quic_get_connection(c); - if (!qc->conf->retry) { - return NGX_OK; - } - expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, qc->conf->av_token_key, &token, NULL, expires, 0) + if (ngx_quic_new_token(c, path->sockaddr, path->socklen, + qc->conf->av_token_key, &token, NULL, expires, 0) != NGX_OK) { return NGX_ERROR; @@ -849,3 +914,75 @@ ngx_quic_send_ack_range(ngx_connection_t return NGX_OK; } + + +ssize_t +ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, struct sockaddr *sockaddr, socklen_t socklen) +{ + ssize_t len; + ngx_str_t res; + ngx_quic_header_t pkt; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + len = ngx_quic_create_frame(NULL, frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return -1; + } + + ngx_quic_log_frame(c->log, frame, 1); + + len = ngx_quic_create_frame(src, frame); + if (len == -1) { + return -1; + } + + if (len < (ssize_t) min) { + ngx_memset(src + len, NGX_QUIC_FT_PADDING, min - len); + len = min; + } + + pkt.keys = qc->keys; + pkt.flags = NGX_QUIC_PKT_FIXED_BIT; + + if (qc->key_phase) { + pkt.flags |= NGX_QUIC_PKT_KPHASE; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + ngx_quic_set_packet_number(&pkt, ctx); + + pkt.version = qc->version; + pkt.log = c->log; + pkt.level = ctx->level; + + pkt.dcid.data = qc->socket->cid->id; + pkt.dcid.len = qc->socket->cid->len; + + pkt.scid.data = qc->socket->sid.id; + pkt.scid.len = qc->socket->sid.len; + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return -1; + } + + ctx->pnum++; + + len = ngx_quic_send(c, res.data, res.len, sockaddr, socklen); + + return len; +}