diff --git a/.github/workflows/plot-goodput.py b/.github/workflows/plot-goodput.py
new file mode 100644
index 00000000..1116c115
--- /dev/null
+++ b/.github/workflows/plot-goodput.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+import json
+import matplotlib.pyplot as plt
+import numpy as np
+
+# QUIC implementes
+IMPLS = ["tquic", "lsquic", "picoquic", "quiche"]
+
+# Different file sizes
+SIZES = ["10m", "1m", "100k"]
+
+# Different loss rates
+RATES = [0, 1, 3, 5]
+
+# Running count for each test
+COUNT = 10
+
+
+# Read a measurement file generated by goodput testing
+def read_data(data_dir, impl, cc, size, loss):
+ dirname = "goodput%s-%s-%s" % (size, cc, impl)
+ filename = "goodput%s-%s-%s-%s.json" % (size, loss, cc, impl)
+ path = os.path.join(data_dir, dirname, filename)
+ try:
+ with open(path) as f:
+ data = json.load(f)
+ return data["measurements"][0][0]["data"]
+ except:
+ return None
+
+
+# Plot the throughput graph for the specified CC algorithm under different file
+# sizes and packet loss rates.
+def plot(data_dir, cc):
+ fig, axs = plt.subplots(len(SIZES), len(RATES), figsize=(15,10))
+ x = np.linspace(0, COUNT, COUNT)
+ for i in range(len(SIZES)):
+ for j in range(len(RATES)):
+ for impl in IMPLS:
+ data = read_data(data_dir, impl, cc, SIZES[i], RATES[j])
+ if data is None or len(data) != COUNT:
+ continue
+ axs[i, j].plot(x, data, label=impl, marker="o")
+ axs[i, j].set_xlabel("Run #")
+ axs[i, j].set_ylabel("Goodput")
+ axs[i, j].set_title("%s loss rate %s%%" % (SIZES[i], RATES[j]))
+ axs[i, j].legend()
+ plt.suptitle(cc.upper())
+ plt.tight_layout()
+ plt.savefig("goodput-%s.png" % (cc), dpi=300)
+
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Usage: %s [data_dir]" % (sys.argv[0]))
+ exit(1)
+
+ data_dir= sys.argv[1]
+ plot(data_dir, "bbr")
+ plot(data_dir, "cubic")
+
diff --git a/.github/workflows/plot-interop.py b/.github/workflows/plot-interop.py
new file mode 100644
index 00000000..e0877970
--- /dev/null
+++ b/.github/workflows/plot-interop.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+import json
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.colors import ListedColormap
+
+# QUIC implementes
+CLIENT_IMPLS = ["tquic", "lsquic", "quiche", "picoquic", "ngtcp2", "msquic",
+ "s2n-quic", "quinn", "neqo", "kwik", "aioquic", "chrome",
+ "go-x-net", "quic-go", "mvfst"]
+
+SERVER_IMPLS = ["lsquic", "quiche", "picoquic", "ngtcp2", "msquic", "s2n-quic",
+ "quinn", "neqo", "kwik", "aioquic", "nginx", "haproxy",
+ "go-x-net", "quic-go", "mvfst"]
+
+# Interop test cases
+INTEROP_TESTS = ["handshake", "retry", "resumption", "zerortt", "amplificationlimit",
+ "http3", "ipv6", "transfer", "multiplexing", "longrtt", "blackhole",
+ "handshakeloss", "handshakecorruption", "transferloss","transfercorruption"]
+
+
+# Read a interop file generated by interop testing
+def read_data(data_dir, server, client):
+ dirname = "%s-%s/%s-%s-logs" % (server, client, server, client)
+ path = os.path.join(data_dir, dirname, "interop.json")
+ try:
+ with open(path) as f:
+ data = json.load(f)
+ return convert_data(data["results"][0])
+ except:
+ return [0] * len(INTEROP_TESTS)
+
+
+# Convert interop results to a vector
+def convert_data(result):
+ data = [0] * len(INTEROP_TESTS)
+ for i, name in enumerate(INTEROP_TESTS):
+ for item in result:
+ if item["name"] != name:
+ continue
+ if item["result"] == "succeeded":
+ data[i] = 3
+ elif item["result"] == "unsupported":
+ data[i] = 2
+ else:
+ data[i] = 1
+ break
+ return data
+
+
+# Convert test result to a letter
+def convert_text(value):
+ if value == 3:
+ return "Y" # success
+ elif value == 2:
+ return "U" # unsupported
+ elif value == 1:
+ return "N" # failure
+ else:
+ return "-" # unknown
+
+
+# Plot the interop graph
+def plot(data_dir, is_tquic_server):
+ impls = CLIENT_IMPLS if is_tquic_server else SERVER_IMPLS
+ name = "server" if is_tquic_server else "client"
+ if is_tquic_server:
+ data = [read_data(data_dir, "tquic", impl) for impl in impls]
+ else:
+ data = [read_data(data_dir, impl, "tquic") for impl in impls]
+ interop_result = np.array(data)
+ print(interop_result)
+
+ fig, ax = plt.subplots()
+ im = ax.imshow(interop_result, cmap=ListedColormap(['gray', 'red', 'blue', 'green']),
+ interpolation='nearest')
+ ax.set_xticks(np.arange(len(INTEROP_TESTS)), labels=INTEROP_TESTS)
+ ax.set_yticks(np.arange(len(impls)), labels=impls)
+ plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
+ rotation_mode="anchor")
+ for i in range(len(INTEROP_TESTS)):
+ for j in range(len(impls)):
+ text = ax.text(j, i, convert_text(interop_result[i, j]),
+ ha="center", va="center", color="w")
+ ax.set_title("TQUIC %s interop results" % (name))
+ fig.tight_layout()
+
+ filename = "interop-tquic-%s.png" % (name)
+ plt.savefig(filename, dpi=300)
+
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Usage: %s [data_dir]" % (sys.argv[0]))
+ exit(1)
+
+ data_dir= sys.argv[1]
+ plot(data_dir, True)
+ plot(data_dir, False)
+
diff --git a/.github/workflows/tquic-goodput.yml b/.github/workflows/tquic-goodput.yml
index 90b495aa..0460cf97 100644
--- a/.github/workflows/tquic-goodput.yml
+++ b/.github/workflows/tquic-goodput.yml
@@ -23,6 +23,13 @@ jobs:
# You can manually trigger it if necessary.
if: ${{ ( github.event_name == 'schedule' && github.repository == 'tencent/tquic' ) || github.event_name == 'workflow_dispatch' }}
steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: 'recursive'
+
+ - name: Build docker image
+ run: docker build -t tquic_interop:v1 -f interop/Dockerfile .
+
- name: Install quic-interop-runner
run: |
git clone https://github.com/tquic-group/quic-interop-runner.git
@@ -38,15 +45,15 @@ jobs:
- name: Run the interop tests
run: |
cd quic-interop-runner
- python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=0 --rate_to_client=0" -j ${{ matrix.case }}-0-${{ matrix.cc }}-${{ matrix.impl }}.json
- python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=1 --rate_to_client=1" -j ${{ matrix.case }}-1-${{ matrix.cc }}-${{ matrix.impl }}.json
- python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=3 --rate_to_client=3" -j ${{ matrix.case }}-3-${{ matrix.cc }}-${{ matrix.impl }}.json
- python3 run.py -r $QUIC_IMAGES -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=5 --rate_to_client=5" -j ${{ matrix.case }}-5-${{ matrix.cc }}-${{ matrix.impl }}.json
+ python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=0 --rate_to_client=0" -j ${{ matrix.case }}-0-${{ matrix.cc }}-${{ matrix.impl }}.json
+ python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=1 --rate_to_client=1" -j ${{ matrix.case }}-1-${{ matrix.cc }}-${{ matrix.impl }}.json
+ python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=3 --rate_to_client=3" -j ${{ matrix.case }}-3-${{ matrix.cc }}-${{ matrix.impl }}.json
+ python3 run.py -r "$QUIC_IMAGES,tquic=tquic_interop:v1" -s ${{ matrix.impl }} -c ${{ matrix.impl }} -t ${{ matrix.case }} -a ${{ matrix.cc }} -d -n "drop-rate --delay=15ms --bandwidth=10Mbps --queue=25 --rate_to_server=5 --rate_to_client=5" -j ${{ matrix.case }}-5-${{ matrix.cc }}-${{ matrix.impl }}.json
- name: Store measurement results
uses: actions/upload-artifact@v4
with:
- name: ${{ matrix.impl }}-${{ matrix.case }}-${{ matrix.cc }}
+ name: ${{ matrix.case }}-${{ matrix.cc }}-${{ matrix.impl }}
path: quic-interop-runner/goodput*.json
result:
@@ -55,8 +62,25 @@ jobs:
steps:
- name: Download all workflow run artifacts
uses: actions/download-artifact@v4
+
- name: Display structure of downloaded files
run: ls -R
+
+ - name: Display all measurement details
+ run: grep "details.*" . -Ro
+
+ - name: Download plot tools
+ uses: actions/checkout@v4
+ with:
+ path: tools
+
+ - name: Install dependences
+ run: |
+ sudo apt install python3-matplotlib
+
+ - name: Plot all measurement results
+ run: python3 tools/.github/workflows/plot-goodput.py .
+
- name: Store all measurement results
uses: actions/upload-artifact@v4
with:
diff --git a/.github/workflows/tquic-interop-all.yml b/.github/workflows/tquic-interop-all.yml
index 88af0f36..c5f33655 100644
--- a/.github/workflows/tquic-interop-all.yml
+++ b/.github/workflows/tquic-interop-all.yml
@@ -113,7 +113,7 @@ jobs:
python3 -m json.tool ${{ matrix.server }}-${{ matrix.client }}-logs/interop.json
- name: Store interop logs
- if: ${{ failure() }}
+ if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.server }}-${{ matrix.client }}
@@ -121,3 +121,38 @@ jobs:
quic-interop-runner/*logs/*
!quic-interop-runner/*logs/**/crosstraffic/
!quic-interop-runner/*logs/**/goodput/
+
+ result:
+ runs-on: ubuntu-latest
+ needs: tquic_interop_testing
+ if: ${{ (( github.event_name == 'schedule' && github.repository == 'tencent/tquic' ) || github.event_name == 'workflow_dispatch') && !cancelled() }}
+ steps:
+ - name: Download all workflow run artifacts
+ uses: actions/download-artifact@v4
+
+ - name: Display structure of downloaded files
+ run: ls -R
+
+ - name: Display failed interop tests
+ run: grep -Ho "{[^{]*failed" */*/*.json
+
+ - name: Download plot tools
+ uses: actions/checkout@v4
+ with:
+ path: tools
+
+ - name: Install dependences
+ run: |
+ sudo apt install python3-matplotlib
+
+ - name: Plot all interop results
+ run: python3 tools/.github/workflows/plot-interop.py ./
+
+ - name: Store all interop results
+ uses: actions/upload-artifact@v4
+ with:
+ name: tquic-interop-all-result
+ path: |
+ interop*.png
+ */*logs/*
+ !*/*logs/**/**/sim/
diff --git a/.github/workflows/tquic-interop-main.yaml b/.github/workflows/tquic-interop-main.yaml
index 48fb01ee..06beec80 100644
--- a/.github/workflows/tquic-interop-main.yaml
+++ b/.github/workflows/tquic-interop-main.yaml
@@ -27,8 +27,6 @@ jobs:
client: tquic
- server: tquic
client: ngtcp2
- - server: ngtcp2
- client: tquic
steps:
- uses: actions/checkout@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c374d3d..d3ccf103 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,17 +11,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [v0.9.0] - 2024-04-10
+
+### Added
+- Improve FFI for quic_tls_config_t
+- Update the handling of probe timeout to conform with RFC 9002
+- Update limit of the output buffer for Connection::send()
+- Add plot tools for goodput and interop testing
+
+### Changed
+- Change `quic_config_set_tls_config()` in FFI
+- Change `quic_tls_config_select_methods_t` in FFI
+
+### Fixed
+- Fix NewToken frame in qlog
+- Fix the unit test case `conn_write_qlog` that fails with low probability
+
+### Security
+- limit the number of queued RETIRE_CONNECTION_ID frames
+
+
## [v0.8.1] - 2024-03-18
### Removed
-
- Remove the sfv feature flag from h3 (to resolve a build issue at docs.rs)
## [v0.8.0] - 2024-03-15
### Added
-
- Support anti-amplification limit for server
- Support customized config when initiating a connection
- Add callback based FFI for writing the keylog and qlog
@@ -88,7 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- tquic_client: output the stats first and then exit when it receives an SIGINT signal.
### Changed
-- Simplify FFI quic_set_logger() to avoid from return unnessary errors
+- Simplify FFI quic_set_logger() to avoid from return unnecessary errors
- Rename set_multipath() in Config to enable_multipath()
- Rename set_multipath_algor() in Config to set_multipath_algorithm()
- Change default congestion control algorithm to BBR
@@ -164,6 +182,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Provide example clients and servers.
+[v0.9.0]: https://github.com/tencent/tquic/compare/v0.8.1...v0.9.0
[v0.8.1]: https://github.com/tencent/tquic/compare/v0.8.0...v0.8.1
[v0.8.0]: https://github.com/tencent/tquic/compare/v0.7.0...v0.8.0
[v0.7.0]: https://github.com/tencent/tquic/compare/v0.6.0...v0.7.0
diff --git a/Cargo.toml b/Cargo.toml
index 12d38b38..cd03ec04 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tquic"
-version = "0.8.1"
+version = "0.9.0"
edition = "2021"
rust-version = "1.70.0"
license = "Apache-2.0"
diff --git a/include/tquic.h b/include/tquic.h
index aa592877..69878b61 100644
--- a/include/tquic.h
+++ b/include/tquic.h
@@ -137,9 +137,11 @@ typedef struct http3_conn_t http3_conn_t;
*/
typedef struct http3_headers_t http3_headers_t;
+typedef struct quic_tls_config_t quic_tls_config_t;
+
typedef struct quic_tls_config_select_methods_t {
- SSL_CTX *(*get_default)(void *ctx);
- SSL_CTX *(*select)(void *ctx, const uint8_t *server_name, size_t server_name_len);
+ struct quic_tls_config_t *(*get_default)(void *ctx);
+ struct quic_tls_config_t *(*select)(void *ctx, const uint8_t *server_name, size_t server_name_len);
} quic_tls_config_select_methods_t;
typedef void *quic_tls_config_select_context_t;
@@ -317,8 +319,9 @@ void quic_config_set_max_handshake_timeout(struct quic_config_t *config, uint64_
void quic_config_set_recv_udp_payload_size(struct quic_config_t *config, uint16_t v);
/**
- * Set the maximum outgoing UDP payload size.
+ * Set the initial maximum outgoing UDP payload size.
* The default and minimum value is `1200`.
+ *
* The configuration should be changed with caution. The connection may
* not work properly if an inappropriate value is set.
*/
@@ -488,6 +491,86 @@ void quic_config_set_cid_len(struct quic_config_t *config, uint8_t v);
*/
void quic_config_set_send_batch_size(struct quic_config_t *config, uint16_t v);
+/**
+ * Create a new TlsConfig.
+ * The caller is responsible for the memory of the TlsConfig and should properly
+ * destroy it by calling `quic_tls_config_free`.
+ */
+struct quic_tls_config_t *quic_tls_config_new(void);
+
+/**
+ * Create a new TlsConfig with SSL_CTX.
+ * When using raw SSL_CTX, TlsSession::session() and TlsSession::set_keylog() won't take effect.
+ * The caller is responsible for the memory of TlsConfig and SSL_CTX when use this function.
+ */
+struct quic_tls_config_t *quic_tls_config_new_with_ssl_ctx(SSL_CTX *ssl_ctx);
+
+/**
+ * Create a new client side TlsConfig.
+ * The caller is responsible for the memory of the TlsConfig and should properly
+ * destroy it by calling `quic_tls_config_free`.
+ */
+struct quic_tls_config_t *quic_tls_config_new_client_config(const char *const *protos,
+ intptr_t proto_num,
+ bool enable_early_data);
+
+/**
+ * Create a new server side TlsConfig.
+ * The caller is responsible for the memory of the TlsConfig and should properly
+ * destroy it by calling `quic_tls_config_free`.
+ */
+struct quic_tls_config_t *quic_tls_config_new_server_config(const char *cert_file,
+ const char *key_file,
+ const char *const *protos,
+ intptr_t proto_num,
+ bool enable_early_data);
+
+/**
+ * Destroy a TlsConfig instance.
+ */
+void quic_tls_config_free(struct quic_tls_config_t *tls_config);
+
+/**
+ * Set whether early data is allowed.
+ */
+void quic_tls_config_set_early_data_enabled(struct quic_tls_config_t *tls_config, bool enable);
+
+/**
+ * Set the list of supported application protocols.
+ */
+int quic_tls_config_set_application_protos(struct quic_tls_config_t *tls_config,
+ const char *const *protos,
+ intptr_t proto_num);
+
+/**
+ * Set session ticket key for server.
+ */
+int quic_tls_config_set_ticket_key(struct quic_tls_config_t *tls_config,
+ const uint8_t *ticket_key,
+ size_t ticket_key_len);
+
+/**
+ * Set the certificate verification behavior.
+ */
+void quic_tls_config_set_verify(struct quic_tls_config_t *tls_config, bool verify);
+
+/**
+ * Set the PEM-encoded certificate file.
+ */
+int quic_tls_config_set_certificate_file(struct quic_tls_config_t *tls_config,
+ const char *cert_file);
+
+/**
+ * Set the PEM-encoded private key file.
+ */
+int quic_tls_config_set_private_key_file(struct quic_tls_config_t *tls_config,
+ const char *key_file);
+
+/**
+ * Set CA certificates.
+ */
+int quic_tls_config_set_ca_certs(struct quic_tls_config_t *tls_config, const char *ca_path);
+
/**
* Set TLS config selector.
*/
@@ -497,9 +580,11 @@ void quic_config_set_tls_selector(struct quic_config_t *config,
/**
* Set TLS config.
- * The caller is responsible for the memory of SSL_CTX when use this function.
+ *
+ * Note: Config doesn't own the TlsConfig when using this function.
+ * It is the responsibility of the caller to release it.
*/
-void quic_config_set_tls_config(struct quic_config_t *config, SSL_CTX *ssl_ctx);
+void quic_config_set_tls_config(struct quic_config_t *config, struct quic_tls_config_t *tls_config);
/**
* Create a QUIC endpoint.
@@ -630,6 +715,11 @@ void quic_conn_server_name(struct quic_conn_t *conn, const uint8_t **out, size_t
*/
void quic_conn_session(struct quic_conn_t *conn, const uint8_t **out, size_t *out_len);
+/**
+ * Return details why 0-RTT was accepted or rejected.
+ */
+int quic_conn_early_data_reason(struct quic_conn_t *conn, const uint8_t **out, size_t *out_len);
+
/**
* Add a new path on the client connection.
*/
diff --git a/interop/run_endpoint.sh b/interop/run_endpoint.sh
index 3a72685c..1e05edab 100644
--- a/interop/run_endpoint.sh
+++ b/interop/run_endpoint.sh
@@ -59,7 +59,11 @@ COPA)
;;
esac
-COMMON_ARGS="--keylog-file $SSLKEYLOGFILE --qlog-dir $QLOG_DIR --log-level TRACE --log-file $LOG_DIR/$ROLE.log --idle-timeout 30000 --handshake-timeout 30000 --congestion-control-algor $CC_ALGOR"
+COMMON_ARGS="--keylog-file $SSLKEYLOGFILE --log-level DEBUG --log-file $LOG_DIR/$ROLE.log --idle-timeout 30000 --handshake-timeout 30000 --initial-rtt 100 --congestion-control-algor $CC_ALGOR"
+
+if [ "$TESTCASE" != "transfer" ]; then
+ COMMON_ARGS="$COMMON_ARGS --qlog-dir $QLOG_DIR"
+fi
if [ "$ROLE" == "client" ]; then
# Wait for the simulator to start up.
@@ -89,7 +93,6 @@ if [ "$ROLE" == "client" ]; then
case $TESTCASE in
multiconnect|resumption)
- CLIENT_ARGS="$CLIENT_ARGS --initial-rtt 100"
for REQ in $REQUESTS
do
$TQUIC_DIR/$TQUIC_CLIENT $CLIENT_ARGS $REQ
@@ -109,9 +112,6 @@ elif [ "$ROLE" == "server" ]; then
retry)
SERVER_ARGS="$SERVER_ARGS --enable-retry"
;;
- multiconnect)
- SERVER_ARGS="$SERVER_ARGS --initial-rtt 100"
- ;;
transfer)
SERVER_ARGS="$SERVER_ARGS --send-udp-payload-size 1400"
;;
diff --git a/src/connection/cid.rs b/src/connection/cid.rs
index 435270cf..92f35808 100644
--- a/src/connection/cid.rs
+++ b/src/connection/cid.rs
@@ -130,13 +130,17 @@ impl ConnectionIdDeque {
/// ConnectionIdMgr maintains all the Connection IDs on a QUIC connection
#[derive(Default)]
pub struct ConnectionIdMgr {
- /// All the Destination Connection IDs provided by our peer.
+ /// All the destination connection IDs provided by our peer.
dcids: ConnectionIdDeque,
- /// All the Source Connection IDs we provide to our peer.
+ /// All the source connection IDs we provide to our peer.
scids: ConnectionIdDeque,
- /// The maximum number of source Connection IDs our peer allows us.
+ /// The maximum number of destination connection IDs from peer that this
+ /// endpoint is willing to store.
+ dcid_limit: usize,
+
+ /// The maximum number of source connection IDs our peer allows us.
scid_limit: usize,
/// Whether the host use zero-length Source Connection ID.
@@ -185,6 +189,7 @@ impl ConnectionIdMgr {
ConnectionIdMgr {
scids,
dcids,
+ dcid_limit,
scid_limit,
zero_length_scid: initial_scid.is_empty(),
next_scid_seq: 1,
@@ -386,6 +391,7 @@ impl ConnectionIdMgr {
// (RFC 9000 Section 19.15)
if seq < self.largest_peer_retire_prior_to && !self.dcids_to_retire.contains(&seq) {
self.dcids_to_retire.push_back(seq);
+ self.check_dcids_to_retire()?;
return Ok(retired_path_ids);
}
@@ -407,6 +413,7 @@ impl ConnectionIdMgr {
}
});
self.largest_peer_retire_prior_to = retire_prior_to;
+ self.check_dcids_to_retire()?;
}
// After processing a NEW_CONNECTION_ID frame and adding and retiring
@@ -419,6 +426,22 @@ impl ConnectionIdMgr {
Ok(retired_path_ids)
}
+ /// Check and limit the total number of dcids to be retried.
+ fn check_dcids_to_retire(&self) -> Result<()> {
+ // An attacker might flood the server with NEW_CONNECTION_ID frames, and
+ // force it to respond with numerous RETIRE_CONNECTION_ID frames. If the
+ // attacker ignores these responses, a large queue of unacknowledged
+ // RETIRE_CONNECTION_ID frames will accumulate on the server. Over time,
+ // this could exhaust the server's memory.
+ //
+ // The endpoint should limit the number of queued RETIRE_CONNECTION)ID
+ // frames and break the connection if the peer exceeds this limit.
+ if self.dcids_to_retire.len() > self.dcid_limit * 4 {
+ return Err(Error::ProtocolViolation);
+ }
+ Ok(())
+ }
+
/// Retire the Source CID from a RETIRE_CONNECTION_ID frame
pub fn retire_scid(&mut self, seq: u64, pkt_dcid: &ConnectionId) -> Result