On Thu, Oct 07, 2021 at 02:36:13PM +0300, Roman Arutyunyan wrote: > This series adds support for flood detection in QUIC and HTTP/3 smilar to > HTTP/2. > > - patch 1 removes client-side encoder support from HTTP/3 for simplicity > - patch 2 fixes a minor issue with $request_length calculation > - patch 3 adds HTTP/3 traffic-based flood detection > - patch 4 adds QUIC traffic-based flood detection > - patch 5 adds a limit on frames number similar to HTTP/2 > > As for the patch 3, both input and output traffic is analyzed similar to > HTTP/2. > Probably only input should be analyzed because current HTTP/3 implementation > does not seem to allow amplification (the only exception is Stream > Cancellation, > but keepalive_requests limits the damage anyway). Also, we can never be sure > the output traffic we counted actually reached the client and was not rejected > by stream reset. We can discuss this later.
Testing: I patched nghttp3/ngtcp2 to enable flooding: examples/client --http3-flood=10000000 127.0.0.1 8443 https://example.com:8443/bar examples/client --quic-flood=10000000 127.0.0.1 8443 https://example.com:8443/bar Patches (quite dirty) are attached. With --http3-flood, a big reserved (0x1f + 0x21, see quic-http 34, section 7.2.8) frame is sent before HEADERS frame. With --quic-flood, a big number of PING frames are sent. -- Roman Arutyunyan
diff --git a/lib/nghttp3_conn.c b/lib/nghttp3_conn.c index ab608a2..93f8b52 100644 --- a/lib/nghttp3_conn.c +++ b/lib/nghttp3_conn.c @@ -2012,7 +2012,7 @@ int nghttp3_conn_add_ack_offset(nghttp3_conn *conn, int64_t stream_id, static int conn_submit_headers_data(nghttp3_conn *conn, nghttp3_stream *stream, const nghttp3_nv *nva, size_t nvlen, const nghttp3_data_reader *dr) { - int rv; + int rv, i; nghttp3_nv *nnva; nghttp3_frame_entry frent; @@ -2021,6 +2021,22 @@ static int conn_submit_headers_data(nghttp3_conn *conn, nghttp3_stream *stream, return rv; } + for (i = 0; i < nvlen; i++) { + if (strcmp((char *) nva[i].name, "x-flood") == 0) { + break; + } + } + + if (i < nvlen) { + frent.fr.hd.type = NGHTTP3_FRAME_FLOOD; + frent.fr.flood.size = atoi((char *) nva[i].value); + + rv = nghttp3_stream_frq_add(stream, &frent); + if (rv != 0) { + return rv; + } + } + frent.fr.hd.type = NGHTTP3_FRAME_HEADERS; frent.fr.headers.nva = nnva; frent.fr.headers.nvlen = nvlen; diff --git a/lib/nghttp3_frame.c b/lib/nghttp3_frame.c index 38c395e..dd27f36 100644 --- a/lib/nghttp3_frame.c +++ b/lib/nghttp3_frame.c @@ -79,6 +79,17 @@ uint8_t *nghttp3_frame_write_goaway(uint8_t *p, return p; } +uint8_t *nghttp3_frame_write_flood(uint8_t *p, + const nghttp3_frame_flood *fr) { + p = nghttp3_frame_write_hd(p, &fr->hd); + + for (int i = 0; i < fr->size; i++) { + *p++ = 0xfe; + } + + return p; +} + size_t nghttp3_frame_write_goaway_len(int64_t *ppayloadlen, const nghttp3_frame_goaway *fr) { size_t payloadlen = nghttp3_put_varint_len(fr->id); @@ -89,6 +100,16 @@ size_t nghttp3_frame_write_goaway_len(int64_t *ppayloadlen, nghttp3_put_varint_len((int64_t)payloadlen) + payloadlen; } +size_t nghttp3_frame_write_flood_len(int64_t *ppayloadlen, + const nghttp3_frame_flood *fr) { + size_t payloadlen = fr->size; + + *ppayloadlen = (int64_t)payloadlen; + + return nghttp3_put_varint_len(NGHTTP3_FRAME_FLOOD) + + nghttp3_put_varint_len((int64_t)payloadlen) + payloadlen; +} + uint8_t * nghttp3_frame_write_priority_update(uint8_t *p, const nghttp3_frame_priority_update *fr) { diff --git a/lib/nghttp3_frame.h b/lib/nghttp3_frame.h index 216b346..06861a0 100644 --- a/lib/nghttp3_frame.h +++ b/lib/nghttp3_frame.h @@ -46,6 +46,7 @@ typedef enum nghttp3_frame_type { https://tools.ietf.org/html/draft-ietf-httpbis-priority-03 */ NGHTTP3_FRAME_PRIORITY_UPDATE = 0x0f0700, NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID = 0x0f0701, + NGHTTP3_FRAME_FLOOD = 0x1f + 0x21 } nghttp3_frame_type; typedef enum nghttp3_h2_reserved_type { @@ -95,6 +96,11 @@ typedef struct nghttp3_frame_goaway { int64_t id; } nghttp3_frame_goaway; +typedef struct nghttp3_frame_flood { + nghttp3_frame_hd hd; + size_t size; +} nghttp3_frame_flood; + typedef struct nghttp3_frame_priority_update { nghttp3_frame_hd hd; /* pri_elem_id is stream ID if hd.type == @@ -111,6 +117,7 @@ typedef union nghttp3_frame { nghttp3_frame_headers headers; nghttp3_frame_settings settings; nghttp3_frame_goaway goaway; + nghttp3_frame_flood flood; nghttp3_frame_priority_update priority_update; } nghttp3_frame; @@ -154,6 +161,15 @@ size_t nghttp3_frame_write_settings_len(int64_t *pppayloadlen, uint8_t *nghttp3_frame_write_goaway(uint8_t *dest, const nghttp3_frame_goaway *fr); +/* + * nghttp3_frame_write_flood writes FLOOD frame |fr| to |dest|. + * This function assumes that |dest| has enough space to write |fr|. + * + * This function returns |dest| plus the number of bytes written. + */ +uint8_t *nghttp3_frame_write_flood(uint8_t *dest, + const nghttp3_frame_flood *fr); + /* * nghttp3_frame_write_goaway_len returns the number of bytes required * to write |fr|. fr->hd.length is ignored. This function stores @@ -162,6 +178,14 @@ uint8_t *nghttp3_frame_write_goaway(uint8_t *dest, size_t nghttp3_frame_write_goaway_len(int64_t *ppayloadlen, const nghttp3_frame_goaway *fr); +/* + * nghttp3_frame_write_flood_len returns the number of bytes required + * to write |fr|. fr->hd.length is ignored. This function stores + * payload length in |*ppayloadlen|. + */ +size_t nghttp3_frame_write_flood_len(int64_t *ppayloadlen, + const nghttp3_frame_flood *fr); + /* * nghttp3_frame_write_priority_update writes PRIORITY_UPDATE frame * |fr| to |dest|. This function assumes that |dest| has enough space diff --git a/lib/nghttp3_stream.c b/lib/nghttp3_stream.c index c91be7e..19ddc46 100644 --- a/lib/nghttp3_stream.c +++ b/lib/nghttp3_stream.c @@ -281,6 +281,12 @@ int nghttp3_stream_fill_outq(nghttp3_stream *stream) { return rv; } break; + case NGHTTP3_FRAME_FLOOD: + rv = nghttp3_stream_write_flood(stream, frent); + if (rv != 0) { + return rv; + } + break; case NGHTTP3_FRAME_PRIORITY_UPDATE: rv = nghttp3_stream_write_priority_update(stream, frent); if (rv != 0) { @@ -390,6 +396,31 @@ int nghttp3_stream_write_goaway(nghttp3_stream *stream, return nghttp3_stream_outq_add(stream, &tbuf); } +int nghttp3_stream_write_flood(nghttp3_stream *stream, + nghttp3_frame_entry *frent) { + nghttp3_frame_flood *fr = &frent->fr.flood; + size_t len; + int rv; + nghttp3_buf *chunk; + nghttp3_typed_buf tbuf; + + len = nghttp3_frame_write_flood_len(&fr->hd.length, fr); + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_frame_write_flood(chunk->last, fr); + + tbuf.buf.last = chunk->last; + + return nghttp3_stream_outq_add(stream, &tbuf); +} + int nghttp3_stream_write_priority_update(nghttp3_stream *stream, nghttp3_frame_entry *frent) { nghttp3_frame_priority_update *fr = &frent->fr.priority_update; diff --git a/lib/nghttp3_stream.h b/lib/nghttp3_stream.h index 047475e..a07a763 100644 --- a/lib/nghttp3_stream.h +++ b/lib/nghttp3_stream.h @@ -301,6 +301,9 @@ int nghttp3_stream_write_settings(nghttp3_stream *stream, int nghttp3_stream_write_goaway(nghttp3_stream *stream, nghttp3_frame_entry *frent); +int nghttp3_stream_write_flood(nghttp3_stream *stream, + nghttp3_frame_entry *frent); + int nghttp3_stream_write_priority_update(nghttp3_stream *stream, nghttp3_frame_entry *frent);
diff --git a/examples/client.cc b/examples/client.cc index 990688eb..556a5168 100644 --- a/examples/client.cc +++ b/examples/client.cc @@ -1611,7 +1611,7 @@ int Client::submit_http_request(const Stream *stream) { const auto &req = stream->req; - std::array<nghttp3_nv, 6> nva{ + std::array<nghttp3_nv, 7> nva{ util::make_nv(":method", config.http_method), util::make_nv(":scheme", req.scheme), util::make_nv(":authority", req.authority), @@ -1623,6 +1623,11 @@ int Client::submit_http_request(const Stream *stream) { content_length_str = std::to_string(config.datalen); nva[nvlen++] = util::make_nv("content-length", content_length_str); } + if (config.http3_flood) { + static char buf[1024]; + snprintf(buf, sizeof(buf), "%d", (int) config.http3_flood); + nva[nvlen++] = util::make_nv("x-flood", std::string(buf)); + } if (!config.quiet) { debug::print_http_request_headers(stream->stream_id, nva.data(), nvlen); @@ -1937,6 +1942,10 @@ int Client::setup_httpconn() { return -1; } + if (config.quic_flood) { + ngxtcp2_conn_flood(conn_, config.quic_flood); + } + nghttp3_callbacks callbacks{ ::http_acked_stream_data, ::http_stream_close, ::http_recv_data, ::http_deferred_consume, ::http_begin_headers, ::http_recv_header, @@ -2384,6 +2393,8 @@ int main(int argc, char **argv) { {"max-window", required_argument, &flag, 32}, {"max-stream-window", required_argument, &flag, 33}, {"scid", required_argument, &flag, 34}, + {"http3-flood", required_argument, &flag, 35}, + {"quic-flood", required_argument, &flag, 36}, {nullptr, 0, nullptr, 0}, }; @@ -2672,6 +2683,24 @@ int main(int argc, char **argv) { config.scid_present = true; break; } + case 35: + // --http3-flood + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "http3-flood: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.http3_flood = *n; + } + break; + case 36: + // --quic-flood + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "quic-flood: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.quic_flood = *n; + } + break; } break; default: diff --git a/examples/client_base.h b/examples/client_base.h index e118bac2..87ba246a 100644 --- a/examples/client_base.h +++ b/examples/client_base.h @@ -163,6 +163,10 @@ struct Config { std::string_view sni; // initial_rtt is an initial RTT. ngtcp2_duration initial_rtt; + // HTTP/3 flood + uint64_t http3_flood; + // QUIC flood + uint64_t quic_flood; }; struct Buffer { diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h index 235d6e6b..e737215a 100644 --- a/lib/includes/ngtcp2/ngtcp2.h +++ b/lib/includes/ngtcp2/ngtcp2.h @@ -5173,6 +5173,13 @@ NGTCP2_EXTERN void ngtcp2_path_copy(ngtcp2_path *dest, const ngtcp2_path *src); */ NGTCP2_EXTERN int ngtcp2_path_eq(const ngtcp2_path *a, const ngtcp2_path *b); +/** + * @function + * + * `ngtcp2_conn_flood` floods with PING frames. + */ +NGTCP2_EXTERN int ngxtcp2_conn_flood(ngtcp2_conn *conn, size_t size); + #ifdef __cplusplus } #endif diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c index b4b4145b..2ea05132 100644 --- a/lib/ngtcp2_conn.c +++ b/lib/ngtcp2_conn.c @@ -8844,6 +8844,28 @@ static int conn_enqueue_handshake_done(ngtcp2_conn *conn) { return 0; } +/* + * ngtcp2_conn_flood floods with PING frames + */ +int ngxtcp2_conn_flood(ngtcp2_conn *conn, size_t size) { + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_frame_chain *nfrc; + int rv; + + while (size--) { + rv = ngtcp2_frame_chain_new(&nfrc, conn->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_PING; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + } + + return 0; +} + /** * @function *
_______________________________________________ nginx-devel mailing list nginx-devel@nginx.org http://mailman.nginx.org/mailman/listinfo/nginx-devel