summaryrefslogtreecommitdiffhomepage
path: root/src/event/quic/ngx_event_quic_migration.c
diff options
context:
space:
mode:
authorVladimir Homutov <vl@nginx.com>2022-01-19 22:39:24 +0300
committerVladimir Homutov <vl@nginx.com>2022-01-19 22:39:24 +0300
commit8a4a267d74fa31e4693691a1a8788b0773329481 (patch)
tree9977ce66a227ef0369b9e5e6541b175b70c892e4 /src/event/quic/ngx_event_quic_migration.c
parent1f97aa71ecaa75dfd646495a13534b10405b500c (diff)
downloadnginx-8a4a267d74fa31e4693691a1a8788b0773329481.tar.gz
nginx-8a4a267d74fa31e4693691a1a8788b0773329481.tar.bz2
QUIC: reworked migration handling.
The quic connection now holds active, backup and probe paths instead of sockets. The number of migration paths is now limited and cannot be inflated by a bad client or an attacker. The client id is now associated with path rather than socket. This allows to simplify processing of output and connection ids handling. New migration abandons any previously started migrations. This allows to free consumed client ids and request new for use in future migrations and make progress in case when connection id limit is hit during migration. A path now can be revalidated without losing its state. The patch also fixes various issues with NAT rebinding case handling: - paths are now validated (previously, there was no validation and paths were left in limited state) - attempt to reuse id on different path is now again verified (this was broken in 40445fc7c403) - former path is now validated in case of apparent migration
Diffstat (limited to 'src/event/quic/ngx_event_quic_migration.c')
-rw-r--r--src/event/quic/ngx_event_quic_migration.c402
1 files changed, 187 insertions, 215 deletions
diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c
index ac787e31d..e66a402c8 100644
--- a/src/event/quic/ngx_event_quic_migration.c
+++ b/src/event/quic/ngx_event_quic_migration.c
@@ -16,17 +16,14 @@ static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
ngx_quic_path_t *path);
static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
ngx_quic_path_t *path);
-static ngx_int_t ngx_quic_path_restore(ngx_connection_t *c);
-static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c);
+static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
ngx_int_t
ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
- ngx_quic_path_challenge_frame_t *f)
+ ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
{
- ngx_quic_path_t *path;
ngx_quic_frame_t frame, *fp;
- ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
@@ -43,18 +40,16 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
* A PATH_RESPONSE frame MUST be sent on the network path where the
* PATH_CHALLENGE frame was received.
*/
- qsock = ngx_quic_get_socket(c);
- path = qsock->path;
/*
* An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
* to at least the smallest allowed maximum datagram size of 1200 bytes.
*/
- if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
+ if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) {
return NGX_ERROR;
}
- if (qsock == qc->socket) {
+ if (pkt->path == qc->path) {
/*
* RFC 9000, 9.3.3. Off-Path Packet Forwarding
*
@@ -101,7 +96,7 @@ ngx_quic_handle_path_response_frame(ngx_connection_t *c,
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
- if (path->state != NGX_QUIC_PATH_VALIDATING) {
+ if (!path->validating) {
continue;
}
@@ -112,7 +107,7 @@ ngx_quic_handle_path_response_frame(ngx_connection_t *c,
}
}
- ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stale PATH_RESPONSE ignored");
return NGX_OK;
@@ -130,8 +125,9 @@ valid:
rst = 1;
- if (qc->backup) {
- prev = qc->backup->path;
+ prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
+
+ if (prev != NULL) {
if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen,
path->sockaddr, path->socklen, 0)
@@ -164,20 +160,24 @@ valid:
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "quic path #%uL successfully validated", path->seqnum);
+ "quic path #%uL addr:%V successfully validated",
+ path->seqnum, &path->addr_text);
+
+ ngx_quic_path_dbg(c, "is validated", path);
- path->state = NGX_QUIC_PATH_VALIDATED;
+ path->validated = 1;
+ path->validating = 0;
path->limited = 0;
return NGX_OK;
}
-static ngx_quic_path_t *
-ngx_quic_alloc_path(ngx_connection_t *c)
+ngx_quic_path_t *
+ngx_quic_new_path(ngx_connection_t *c,
+ struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid)
{
ngx_queue_t *q;
- struct sockaddr *sa;
ngx_quic_path_t *path;
ngx_quic_connection_t *qc;
@@ -190,9 +190,7 @@ ngx_quic_alloc_path(ngx_connection_t *c)
ngx_queue_remove(&path->queue);
- sa = path->sockaddr;
ngx_memzero(path, sizeof(ngx_quic_path_t));
- path->sockaddr = sa;
} else {
@@ -200,37 +198,18 @@ ngx_quic_alloc_path(ngx_connection_t *c)
if (path == NULL) {
return NULL;
}
-
- path->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN);
- if (path->sockaddr == NULL) {
- return NULL;
- }
}
- return path;
-}
-
-
-ngx_quic_path_t *
-ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr,
- socklen_t socklen)
-{
- ngx_quic_path_t *path;
- ngx_quic_connection_t *qc;
-
- qc = ngx_quic_get_connection(c);
+ ngx_queue_insert_tail(&qc->paths, &path->queue);
- path = ngx_quic_alloc_path(c);
- if (path == NULL) {
- return NULL;
- }
+ path->cid = cid;
+ cid->used = 1;
- path->state = NGX_QUIC_PATH_NEW;
path->limited = 1;
path->seqnum = qc->path_seqnum++;
- path->last_seen = ngx_current_msec;
+ path->sockaddr = &path->sa.sockaddr;
path->socklen = socklen;
ngx_memcpy(path->sockaddr, sockaddr, socklen);
@@ -238,19 +217,15 @@ ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr,
path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
NGX_SOCKADDR_STRLEN, 1);
- ngx_queue_insert_tail(&qc->paths, &path->queue);
-
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "quic path #%uL created src:%V",
+ "quic path #%uL created addr:%V",
path->seqnum, &path->addr_text);
-
return path;
}
-ngx_quic_path_t *
-ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr,
- socklen_t socklen)
+static ngx_quic_path_t *
+ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag)
{
ngx_queue_t *q;
ngx_quic_path_t *path;
@@ -264,10 +239,7 @@ ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr,
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
- if (ngx_cmp_sockaddr(sockaddr, socklen,
- path->sockaddr, path->socklen, 1)
- == NGX_OK)
- {
+ if (path->tag == tag) {
return path;
}
}
@@ -277,10 +249,11 @@ ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr,
ngx_int_t
-ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt)
+ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
off_t len;
- ngx_quic_path_t *path;
+ ngx_queue_t *q;
+ ngx_quic_path_t *path, *probe;
ngx_quic_socket_t *qsock;
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
@@ -288,72 +261,69 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt)
qc = ngx_quic_get_connection(c);
qsock = ngx_quic_get_socket(c);
+ len = pkt->raw->last - pkt->raw->start;
+
if (c->udp->dgram == NULL) {
- /* 1st ever packet in connection, path already exists */
- path = qsock->path;
+ /* first ever packet in connection, path already exists */
+ path = qc->path;
goto update;
}
- path = ngx_quic_find_path(c, c->udp->dgram->sockaddr,
- c->udp->dgram->socklen);
-
- if (path == NULL) {
- path = ngx_quic_add_path(c, c->udp->dgram->sockaddr,
- c->udp->dgram->socklen);
- if (path == NULL) {
- return NGX_ERROR;
- }
-
- if (qsock->path) {
- /* NAT rebinding case: packet to same CID, but from new address */
+ probe = NULL;
- ngx_quic_unref_path(c, qsock->path);
-
- qsock->path = path;
- path->refcnt++;
+ for (q = ngx_queue_head(&qc->paths);
+ q != ngx_queue_sentinel(&qc->paths);
+ q = ngx_queue_next(q))
+ {
+ path = ngx_queue_data(q, ngx_quic_path_t, queue);
+ if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr, c->udp->dgram->socklen,
+ path->sockaddr, path->socklen, 1)
+ == NGX_OK)
+ {
goto update;
}
- } else if (qsock->path) {
- goto update;
+ if (path->tag == NGX_QUIC_PATH_PROBE) {
+ probe = path;
+ }
}
- /* prefer unused client IDs if available */
- cid = ngx_quic_next_client_id(c);
- if (cid == NULL) {
-
- /* try to reuse connection ID used on the same path */
- cid = ngx_quic_used_client_id(c, path);
- if (cid == NULL) {
-
- qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
- qc->error_reason = "no available client ids for new path";
+ /* packet from new path, drop current probe, if any */
- ngx_log_error(NGX_LOG_ERR, c->log, 0,
- "no available client ids for new path");
+ if (probe && ngx_quic_free_path(c, probe) != NGX_OK) {
+ return NGX_ERROR;
+ }
- return NGX_ERROR;
- }
+ /* new path requires new client id */
+ cid = ngx_quic_next_client_id(c);
+ if (cid == NULL) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "quic no available client ids for new path");
+ /* stop processing of this datagram */
+ return NGX_DONE;
}
- ngx_quic_connect(c, qsock, path, cid);
+ path = ngx_quic_new_path(c, c->udp->dgram->sockaddr,
+ c->udp->dgram->socklen, cid);
+ if (path == NULL) {
+ return NGX_ERROR;
+ }
-update:
+ path->tag = NGX_QUIC_PATH_PROBE;
- if (path->state != NGX_QUIC_PATH_NEW) {
- /* force limits/revalidation for paths that were not seen recently */
- if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) {
- path->state = NGX_QUIC_PATH_NEW;
- path->limited = 1;
- path->sent = 0;
- path->received = 0;
- }
+ /*
+ * client arrived using new path and previously seen DCID,
+ * this indicates NAT rebinding (or bad client)
+ */
+ if (qsock->used) {
+ pkt->rebound = 1;
}
- path->last_seen = ngx_current_msec;
+update:
- len = pkt->raw->last - pkt->raw->start;
+ qsock->used = 1;
+ pkt->path = path;
/* TODO: this may be too late in some cases;
* for example, if error happens during decrypt(), we cannot
@@ -364,11 +334,38 @@ update:
*/
path->received += len;
- ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "quic packet via #%uL:%uL:%uL"
- " size:%O path recvd:%O sent:%O limited:%ui",
- qsock->sid.seqnum, qsock->cid->seqnum, path->seqnum,
- len, path->received, path->sent, path->limited);
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic packet len:%O via sock#%uL path#%uL",
+ len, qsock->sid.seqnum, path->seqnum);
+ ngx_quic_path_dbg(c, "status", path);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path)
+{
+ ngx_quic_connection_t *qc;
+
+ qc = ngx_quic_get_connection(c);
+
+ ngx_queue_remove(&path->queue);
+ ngx_queue_insert_head(&qc->free_paths, &path->queue);
+
+ /*
+ * invalidate CID that is no longer usable for any other path;
+ * this also requests new CIDs from client
+ */
+ if (path->cid) {
+ if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic path #%uL addr:%V retired",
+ path->seqnum, &path->addr_text);
return NGX_OK;
}
@@ -398,35 +395,14 @@ ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path)
ngx_int_t
ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
- ngx_quic_path_t *next;
- ngx_quic_socket_t *qsock;
+ ngx_quic_path_t *next, *bkp;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
- /* got non-probing packet via non-active socket with different path */
+ /* got non-probing packet via non-active path */
qc = ngx_quic_get_connection(c);
- /* current socket, different from active */
- qsock = ngx_quic_get_socket(c);
-
- next = qsock->path; /* going to migrate to this path... */
-
- ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "quic migration from #%uL:%uL:%uL (%s)"
- " to #%uL:%uL:%uL (%s)",
- qc->socket->sid.seqnum, qc->socket->cid->seqnum,
- qc->socket->path->seqnum,
- ngx_quic_path_state_str(qc->socket->path),
- qsock->sid.seqnum, qsock->cid->seqnum, next->seqnum,
- ngx_quic_path_state_str(next));
-
- if (next->state == NGX_QUIC_PATH_NEW) {
- if (ngx_quic_validate_path(c, qsock->path) != NGX_OK) {
- return NGX_ERROR;
- }
- }
-
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
/*
@@ -439,39 +415,59 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt)
return NGX_OK;
}
- /* switching connection to new path */
-
- ngx_quic_set_connection_path(c, next);
+ next = pkt->path;
/*
- * RFC 9000, 9.5. Privacy Implications of Connection Migration
+ * RFC 9000, 9.3.3:
*
- * An endpoint MUST NOT reuse a connection ID when sending to
- * more than one destination address.
+ * In response to an apparent migration, endpoints MUST validate the
+ * previously active path using a PATH_CHALLENGE frame.
*/
+ if (pkt->rebound) {
+
+ /* NAT rebinding: client uses new path with old SID */
+ if (ngx_quic_validate_path(c, qc->path) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (qc->path->validated) {
- /* preserve valid path we are migrating from */
- if (qc->socket->path->state == NGX_QUIC_PATH_VALIDATED) {
+ if (next->tag != NGX_QUIC_PATH_BACKUP) {
+ /* can delete backup path, if any */
+ bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
- if (qc->backup) {
- ngx_quic_close_socket(c, qc->backup);
+ if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) {
+ return NGX_ERROR;
+ }
}
- qc->backup = qc->socket;
+ qc->path->tag = NGX_QUIC_PATH_BACKUP;
+ ngx_quic_path_dbg(c, "is now backup", qc->path);
- ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "quic backup socket is now #%uL:%uL:%uL (%s)",
- qc->backup->sid.seqnum, qc->backup->cid->seqnum,
- qc->backup->path->seqnum,
- ngx_quic_path_state_str(qc->backup->path));
+ } else {
+ if (ngx_quic_free_path(c, qc->path) != NGX_OK) {
+ return NGX_ERROR;
+ }
}
- qc->socket = qsock;
+ /* switch active path to migrated */
+ qc->path = next;
+ qc->path->tag = NGX_QUIC_PATH_ACTIVE;
+
+ ngx_quic_set_connection_path(c, next);
+
+ if (!next->validated && !next->validating) {
+ if (ngx_quic_validate_path(c, next) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "quic active socket is now #%uL:%uL:%uL (%s)",
- qsock->sid.seqnum, qsock->cid->seqnum,
- qsock->path->seqnum, ngx_quic_path_state_str(qsock->path));
+ "quic migrated to path#%uL addr:%V",
+ qc->path->seqnum, &qc->path->addr_text);
+
+ ngx_quic_path_dbg(c, "is now active", qc->path);
return NGX_OK;
}
@@ -487,10 +483,9 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path)
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "quic initiated validation of new path #%uL",
- path->seqnum);
+ "quic initiated validation of path #%uL", path->seqnum);
- path->state = NGX_QUIC_PATH_VALIDATING;
+ path->validating = 1;
if (RAND_bytes(path->challenge1, 8) != 1) {
return NGX_ERROR;
@@ -524,7 +519,7 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path)
ngx_quic_frame_t frame;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "quic path #%uL send path challenge tries:%ui",
+ "quic path #%uL send path_challenge tries:%ui",
path->seqnum, path->tries);
ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
@@ -564,7 +559,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev)
ngx_msec_t now;
ngx_queue_t *q;
ngx_msec_int_t left, next, pto;
- ngx_quic_path_t *path;
+ ngx_quic_path_t *path, *bkp;
ngx_connection_t *c;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
@@ -578,13 +573,14 @@ ngx_quic_path_validation_handler(ngx_event_t *ev)
next = -1;
now = ngx_current_msec;
- for (q = ngx_queue_head(&qc->paths);
- q != ngx_queue_sentinel(&qc->paths);
- q = ngx_queue_next(q))
- {
+ q = ngx_queue_head(&qc->paths);
+
+ while (q != ngx_queue_sentinel(&qc->paths)) {
+
path = ngx_queue_data(q, ngx_quic_path_t, queue);
+ q = ngx_queue_next(q);
- if (path->state != NGX_QUIC_PATH_VALIDATING) {
+ if (!path->validating) {
continue;
}
@@ -593,7 +589,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev)
if (left > 0) {
if (next == -1 || left < next) {
- next = path->expires;
+ next = left;
}
continue;
@@ -617,26 +613,43 @@ ngx_quic_path_validation_handler(ngx_event_t *ev)
/* found expired path */
- path->state = NGX_QUIC_PATH_NEW;
+ path->validated = 0;
+ path->validating = 0;
path->limited = 1;
- /*
- * RFC 9000, 9.4. Loss Detection and Congestion Control
+
+ /* RFC 9000, 9.3.2. On-Path Address Spoofing
*
- * If the timer fires before the PATH_RESPONSE is received, the
- * endpoint might send a new PATH_CHALLENGE and restart the timer for
- * a longer period of time. This timer SHOULD be set as described in
- * Section 6.2.1 of [QUIC-RECOVERY] and MUST NOT be more aggressive.
+ * To protect the connection from failing due to such a spurious
+ * migration, an endpoint MUST revert to using the last validated
+ * peer address when validation of a new peer address fails.
*/
- if (qc->socket->path != path) {
- /* the path was not actually used */
- continue;
+ if (qc->path == path) {
+ /* active path validation failed */
+
+ bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
+
+ if (bkp == NULL) {
+ qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
+ qc->error_reason = "no viable path";
+ ngx_quic_close_connection(c, NGX_ERROR);
+ return;
+ }
+
+ qc->path = bkp;
+ qc->path->tag = NGX_QUIC_PATH_ACTIVE;
+
+ ngx_quic_set_connection_path(c, qc->path);
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic path #%uL addr:%V is restored from backup",
+ qc->path->seqnum, &qc->path->addr_text);
+
+ ngx_quic_path_dbg(c, "is active", qc->path);
}
- if (ngx_quic_path_restore(c) != NGX_OK) {
- qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
- qc->error_reason = "no viable path";
+ if (ngx_quic_free_path(c, path) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
@@ -646,44 +659,3 @@ ngx_quic_path_validation_handler(ngx_event_t *ev)
ngx_add_timer(&qc->path_validation, next);
}
}
-
-
-static ngx_int_t
-ngx_quic_path_restore(ngx_connection_t *c)
-{
- ngx_quic_socket_t *qsock;
- ngx_quic_connection_t *qc;
-
- qc = ngx_quic_get_connection(c);
-
- /*
- * RFC 9000, 9.1. Probing a New Path
- *
- * Failure to validate a path does not cause the connection to end
- *
- * RFC 9000, 9.3.2. On-Path Address Spoofing
- *
- * To protect the connection from failing due to such a spurious
- * migration, an endpoint MUST revert to using the last validated
- * peer address when validation of a new peer address fails.
- */
-
- if (qc->backup == NULL) {
- return NGX_ERROR;
- }
-
- qc->socket = qc->backup;
- qc->backup = NULL;
-
- qsock = qc->socket;
-
- ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "quic active socket is restored to #%uL:%uL:%uL"
- " (%s), no backup",
- qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum,
- ngx_quic_path_state_str(qsock->path));
-
- ngx_quic_set_connection_path(c, qsock->path);
-
- return NGX_OK;
-}