From 831186e79880a2b0f949613e1f5eb8923b716356 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Mon, 13 Jul 2026 08:06:19 -0700 Subject: [PATCH] server: add rustls_server_config_builder_set_send_tls13_tickets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose ServerConfig::send_tls13_tickets via the C API so callers can control how many TLS 1.3 NewSessionTicket messages the server sends after a full handshake (rustls default is 2). Setting 0 disables ticket issuance. This lets benchmark harnesses driving rustls through the FFI measure a clean full handshake without NewSessionTicket issuance cost, making the handshake row equivalent to OpenSSL/rustls-Rust-API peers. rustls upstream established the ticket-issuance cost is a measurable benchmarking hazard (PR #2187: ~2% of a full handshake server-side at the default of 2), and both OpenSSL and the rustls Rust API expose a knob to suppress it — rustls-ffi was the one path that couldn't. Closes #652 --- librustls/src/rustls.h | 9 +++++ librustls/src/server.rs | 85 +++++++++++++++++++++++++++++++++++++++++ website/static/api.json | 8 ++++ 3 files changed, 102 insertions(+) diff --git a/librustls/src/rustls.h b/librustls/src/rustls.h index 43d45b04..b45cc3e3 100644 --- a/librustls/src/rustls.h +++ b/librustls/src/rustls.h @@ -2392,6 +2392,15 @@ void rustls_server_config_builder_free(struct rustls_server_config_builder *conf rustls_result rustls_server_config_builder_set_ignore_client_order(struct rustls_server_config_builder *builder, bool ignore); +/** + * Set the number of TLS 1.3 NewSessionTickets sent after a full handshake. + * + * Setting this to 0 disables session ticket issuance entirely. + * + */ +rustls_result rustls_server_config_builder_set_send_tls13_tickets(struct rustls_server_config_builder *builder, + size_t n); + /** * Set the ALPN protocol list to the given protocols. * diff --git a/librustls/src/server.rs b/librustls/src/server.rs index fac04994..f0069555 100644 --- a/librustls/src/server.rs +++ b/librustls/src/server.rs @@ -60,6 +60,7 @@ pub(crate) struct ServerConfigBuilder { session_storage: Option>, alpn_protocols: Vec>, ignore_client_order: Option, + send_tls13_tickets: Option, key_log: Option>, } @@ -98,6 +99,7 @@ impl rustls_server_config_builder { session_storage: None, alpn_protocols: vec![], ignore_client_order: None, + send_tls13_tickets: None, key_log: None, }; to_boxed_mut_ptr(builder) @@ -153,6 +155,7 @@ impl rustls_server_config_builder { session_storage: None, alpn_protocols: vec![], ignore_client_order: None, + send_tls13_tickets: None, key_log: None, }; set_boxed_mut_ptr(builder_out, builder); @@ -271,6 +274,22 @@ impl rustls_server_config_builder { } } + /// Set the number of TLS 1.3 NewSessionTickets sent after a full handshake. + /// + /// Setting this to 0 disables session ticket issuance entirely. + /// + #[no_mangle] + pub extern "C" fn rustls_server_config_builder_set_send_tls13_tickets( + builder: *mut rustls_server_config_builder, + n: usize, + ) -> rustls_result { + ffi_panic_boundary! { + let config = try_mut_from_ptr!(builder); + config.send_tls13_tickets = Some(n); + rustls_result::Ok + } + } + /// Set the ALPN protocol list to the given protocols. /// /// `protocols` must point to a buffer of `rustls_slice_bytes` (built by the caller) @@ -376,6 +395,9 @@ impl rustls_server_config_builder { if let Some(ignore_client_order) = builder.ignore_client_order { config.ignore_client_order = ignore_client_order; } + if let Some(send_tls13_tickets) = builder.send_tls13_tickets { + config.send_tls13_tickets = send_tls13_tickets; + } if let Some(key_log) = builder.key_log { config.key_log = key_log; @@ -820,6 +842,69 @@ mod tests { rustls_server_config::rustls_server_config_free(config); } + #[test] + #[cfg_attr(miri, ignore)] + fn test_server_config_builder_set_send_tls13_tickets() { + let builder = rustls_server_config_builder::rustls_server_config_builder_new(); + + let cert_pem = include_str!("../testdata/localhost/cert.pem").as_bytes(); + let key_pem = include_str!("../testdata/localhost/key.pem").as_bytes(); + let mut certified_key = null(); + let result = rustls_certified_key::rustls_certified_key_build( + cert_pem.as_ptr(), + cert_pem.len(), + key_pem.as_ptr(), + key_pem.len(), + &mut certified_key, + ); + if !matches!(result, rustls_result::Ok) { + panic!("expected RUSTLS_RESULT_OK from rustls_certified_key_build, got {result:?}"); + } + rustls_server_config_builder::rustls_server_config_builder_set_certified_keys( + builder, + &certified_key, + 1, + ); + + // 0 disables ticket issuance. + rustls_server_config_builder::rustls_server_config_builder_set_send_tls13_tickets( + builder, 0, + ); + + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); + { + let config2 = try_ref_from_ptr!(config); + assert_eq!(config2.send_tls13_tickets, 0); + } + rustls_server_config::rustls_server_config_free(config); + + // A non-zero value flows through too. + let builder = rustls_server_config_builder::rustls_server_config_builder_new(); + rustls_server_config_builder::rustls_server_config_builder_set_certified_keys( + builder, + &certified_key, + 1, + ); + rustls_server_config_builder::rustls_server_config_builder_set_send_tls13_tickets( + builder, 4, + ); + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); + { + let config2 = try_ref_from_ptr!(config); + assert_eq!(config2.send_tls13_tickets, 4); + } + rustls_server_config::rustls_server_config_free(config); + rustls_certified_key::rustls_certified_key_free(certified_key); + } + // Build a server connection and test the getters and initial values. #[test] fn test_server_config_builder_new_empty() { diff --git a/website/static/api.json b/website/static/api.json index ab55d12c..14e11147 100644 --- a/website/static/api.json +++ b/website/static/api.json @@ -1106,6 +1106,14 @@ "name": "rustls_server_config_builder_set_ignore_client_order", "text": "```c\nrustls_result rustls_server_config_builder_set_ignore_client_order(struct rustls_server_config_builder *builder,\n bool ignore);\n```" }, + { + "anchor": "rustls-server-config-builder-set-send-tls13-tickets", + "comment": "Set the number of TLS 1.3 NewSessionTickets sent after a full handshake.\n\n Setting this to 0 disables session ticket issuance entirely.\n ", + "feature": null, + "deprecation": null, + "name": "rustls_server_config_builder_set_send_tls13_tickets", + "text": "```c\nrustls_result rustls_server_config_builder_set_send_tls13_tickets(struct rustls_server_config_builder *builder,\n size_t n);\n```" + }, { "anchor": "rustls-server-config-builder-set-alpn-protocols", "comment": "Set the ALPN protocol list to the given protocols.\n\n `protocols` must point to a buffer of [`rustls_slice_bytes`](#rustls-slice-bytes) (built by the caller)\n with `len` elements. Each element of the buffer must point to a slice of bytes that\n contains a single ALPN protocol from\n .\n\n This function makes a copy of the data in `protocols` and does not retain\n any pointers, so the caller can free the pointed-to memory after calling.\n\n ",