Skip to content

Commit 6fde5b6

Browse files
committed
httpd: support scheduling group for TLS handshakes
Add set_new_connection_scheduling_group_cb() to http_server, allowing callers to provide a callback that returns the scheduling group in which TLS handshakes should run. When the callback is configured and the listener uses TLS, the server performs an explicit early handshake (via tls::get_protocol_version) in the designated scheduling group before switching back to the default group for request processing. When no callback is set, behaviour is unchanged: the TLS handshake happens lazily inside conn->process(). The per-connection work is moved into a new do_process_connection() member coroutine (called from the non-coroutine try_with_gate lambda in do_accept_one) so that scheduling-group switching, the early handshake, and conn->process() can all be expressed with co_await. Includes a test that verifies, on a TLS connection, that the callback is invoked, the handshake runs in the configured scheduling group, and the request handler runs in the default scheduling group.
1 parent b782e35 commit 6fde5b6

3 files changed

Lines changed: 158 additions & 7 deletions

File tree

include/seastar/http/httpd.hh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#pragma once
2323

24+
#include <functional>
2425
#include <limits>
2526
#include <cctype>
2627
#include <vector>
@@ -37,6 +38,7 @@
3738
#include <seastar/http/routes.hh>
3839
#include <seastar/net/tls.hh>
3940
#include <seastar/core/shared_ptr.hh>
41+
#include <seastar/core/scheduling.hh>
4042

4143
namespace seastar {
4244

@@ -136,6 +138,11 @@ class http_server {
136138
bool _generate_date_header = true;
137139
gate _task_gate;
138140
std::optional<net::keepalive_params> _keepalive_params;
141+
// Returns scheduling group for TLS handshakes. Consulted only for TLS listeners.
142+
std::function<scheduling_group()> _new_connection_scheduling_group_cb;
143+
// Hook invoked after switching to the TLS handshake scheduling group, right before the handshake.
144+
// Aimed for testing if the scheduling group was really changed.
145+
std::function<void()> _on_tls_handshake_sg_switch;
139146
public:
140147
routes _routes;
141148
using connection = seastar::httpd::connection;
@@ -199,6 +206,8 @@ public:
199206
/// When set to false the periodic date-update timer is also stopped.
200207
void set_generate_date_header(bool b);
201208

209+
void set_new_connection_scheduling_group_cb(std::function<scheduling_group()> fn);
210+
202211
future<> listen(socket_address addr, server_credentials_ptr credentials);
203212
future<> listen(socket_address addr, listen_options lo, server_credentials_ptr credentials);
204213
future<> listen(socket_address addr, listen_options lo);
@@ -219,6 +228,7 @@ public:
219228
static sstring http_date();
220229
private:
221230
future<> do_accept_one(int which, bool with_tls);
231+
future<> do_process_connection(connected_socket conn_fd, socket_address remote_address, bool tls);
222232
boost::intrusive::list<connection> _connections;
223233
friend class seastar::httpd::connection;
224234
friend class http_server_tester;
@@ -229,6 +239,9 @@ public:
229239
static std::vector<server_socket>& listeners(http_server& server) {
230240
return server._listeners;
231241
}
242+
static void set_on_tls_handshake_sg_switch(http_server& server, std::function<void()> fn) {
243+
server._on_tls_handshake_sg_switch = std::move(fn);
244+
}
232245
};
233246

234247
/*

src/http/httpd.cc

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include <seastar/util/short_streams.hh>
4848
#include <seastar/util/log.hh>
4949
#include <seastar/util/string_utils.hh>
50+
#include <seastar/coroutine/switch_to.hh>
5051

5152

5253
using namespace std::chrono_literals;
@@ -401,6 +402,10 @@ void http_server::set_generate_date_header(bool b) {
401402
}
402403
}
403404

405+
void http_server::set_new_connection_scheduling_group_cb(std::function<scheduling_group()> fn) {
406+
_new_connection_scheduling_group_cb = std::move(fn);
407+
}
408+
404409
future<> http_server::listen(socket_address addr, listen_options lo,
405410
server_credentials_ptr listener_credentials) {
406411
if (listener_credentials) {
@@ -476,16 +481,46 @@ future<> http_server::do_accept_one(int which, bool tls) {
476481
ar.connection.set_keepalive(true);
477482
ar.connection.set_keepalive_parameters(_keepalive_params.value());
478483
}
479-
auto local_address = ar.connection.local_address();
480-
auto conn = std::make_unique<connection>(*this, std::move(ar.connection),
481-
std::move(ar.remote_address), std::move(local_address), tls);
482-
(void)try_with_gate(_task_gate, [conn = std::move(conn)]() mutable {
483-
return conn->process().handle_exception([conn = std::move(conn)] (std::exception_ptr ex) {
484-
hlogger.error("request error: {}", ex);
485-
});
484+
// The lambda passed to try_with_gate must not be a coroutine,
485+
// because try_with_gate invokes it but does not store it: a coroutine
486+
// lambda would be destroyed while its frame is still running.
487+
(void)try_with_gate(_task_gate,
488+
[this, conn_fd = std::move(ar.connection),
489+
remote_address = std::move(ar.remote_address), tls]() mutable {
490+
return do_process_connection(std::move(conn_fd), std::move(remote_address), tls);
486491
}).handle_exception_type([] (const gate_closed_exception& e) {});
487492
}
488493

494+
// Named member coroutine for per-connection processing, called from the
495+
// non-coroutine lambda in try_with_gate inside do_accept_one(). Parameters
496+
// are passed by value so they live safely in the coroutine frame.
497+
future<> http_server::do_process_connection(connected_socket conn_fd, socket_address remote_address, bool tls) {
498+
auto local_address = conn_fd.local_address();
499+
// When a scheduling group callback is configured, perform an early
500+
// TLS handshake in the designated scheduling group. Without the
501+
// callback, the handshake happens lazily inside conn->process().
502+
if (tls && _new_connection_scheduling_group_cb) {
503+
co_await coroutine::switch_to(_new_connection_scheduling_group_cb());
504+
if (_on_tls_handshake_sg_switch) {
505+
_on_tls_handshake_sg_switch();
506+
}
507+
try {
508+
co_await seastar::tls::get_protocol_version(conn_fd);
509+
} catch (...) {
510+
hlogger.debug("TLS handshake failed: {}", std::current_exception());
511+
co_return;
512+
}
513+
co_await coroutine::switch_to(default_scheduling_group());
514+
}
515+
auto conn = std::make_unique<connection>(*this, std::move(conn_fd),
516+
std::move(remote_address), std::move(local_address), tls);
517+
try {
518+
co_await conn->process();
519+
} catch (...) {
520+
hlogger.error("request error: {}", std::current_exception());
521+
}
522+
}
523+
489524
uint64_t http_server::total_connections() const {
490525
return _total_connections;
491526
}

tests/unit/httpd_test.cc

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <seastar/core/do_with.hh>
1818
#include <seastar/core/future.hh>
1919
#include <seastar/core/loop.hh>
20+
#include <seastar/core/scheduling.hh>
2021
#include <seastar/core/when_all.hh>
2122
#include <seastar/core/units.hh>
2223
#include <seastar/testing/test_case.hh>
@@ -2530,3 +2531,105 @@ SEASTAR_TEST_CASE(test_http_reply_formatting) {
25302531

25312532
return make_ready_future<>();
25322533
}
2534+
2535+
// Verify that when set_new_connection_scheduling_group_cb() is configured,
2536+
// the TLS handshake runs in the designated scheduling group and the
2537+
// request handler runs in the default scheduling group.
2538+
SEASTAR_TEST_CASE(test_new_connection_scheduling_group) {
2539+
return seastar::async([] {
2540+
auto sg = create_scheduling_group("test-httpd-sg", 100).get();
2541+
std::exception_ptr ex;
2542+
2543+
try {
2544+
bool callback_invoked = false;
2545+
scheduling_group observed_handler_sg = sg; // intentionally wrong initial value
2546+
scheduling_group observed_handshake_sg; // default SG; should become sg
2547+
2548+
static const char test_cert[] =
2549+
"-----BEGIN CERTIFICATE-----\n"
2550+
"MIIBzDCCAXOgAwIBAgIULUjD6YNpUtkaibrj8Z5pMg/bQlEwCgYIKoZIzj0EAwIw\n"
2551+
"FDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MDMxOTA4Mjg0NloYDzIxMjYwMjIz\n"
2552+
"MDgyODQ2WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjO\n"
2553+
"PQMBBwNCAATsDbUqgqgR3llGWbVnz3jcY16/ZUWZT4kaIneoRbjTlMgr/Z5cBM2H\n"
2554+
"ZaN7zY6yuLFPkf/yRNCki9Q565EqqIzRo4GgMIGdMB0GA1UdDgQWBBTDUmIk5BxS\n"
2555+
"nWiOWNucItd2PWCL3jBPBgNVHSMESDBGgBTDUmIk5BxSnWiOWNucItd2PWCL3qEY\n"
2556+
"pBYwFDESMBAGA1UEAwwJbG9jYWxob3N0ghQtSMPpg2lS2RqJuuPxnmkyD9tCUTAP\n"
2557+
"BgNVHRMBAf8EBTADAQH/MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATAKBggq\n"
2558+
"hkjOPQQDAgNHADBEAiBVsdCtlAEz0bqR73UViNXOKln5KCEon77KdjKHsDI1gAIg\n"
2559+
"da4opFU4dPGt3kKOe2UNTIxre0rdIuQmv4sidCTBfx4=\n"
2560+
"-----END CERTIFICATE-----\n";
2561+
static const char test_key[] =
2562+
"-----BEGIN PRIVATE KEY-----\n"
2563+
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPJLOJNZ40LFhA4h4\n"
2564+
"DD+iARBzBUVEHY18F04UNcm/FNihRANCAATsDbUqgqgR3llGWbVnz3jcY16/ZUWZ\n"
2565+
"T4kaIneoRbjTlMgr/Z5cBM2HZaN7zY6yuLFPkf/yRNCki9Q565EqqIzR\n"
2566+
"-----END PRIVATE KEY-----\n";
2567+
2568+
auto server_creds = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
2569+
server_creds->set_x509_key(
2570+
tls::blob(test_cert, sizeof(test_cert) - 1),
2571+
tls::blob(test_key, sizeof(test_key) - 1),
2572+
tls::x509_crt_format::PEM);
2573+
2574+
tls::credentials_builder client_builder;
2575+
client_builder.set_x509_trust(tls::blob(test_cert, sizeof(test_cert) - 1), tls::x509_crt_format::PEM);
2576+
auto client_creds = client_builder.build_certificate_credentials();
2577+
2578+
auto addr = socket_address(ipv4_addr("127.0.0.1", 0));
2579+
listen_options lo;
2580+
lo.reuse_address = true;
2581+
lo.set_fixed_cpu(this_shard_id());
2582+
2583+
http_server server("test");
2584+
server.set_new_connection_scheduling_group_cb([sg, &callback_invoked] {
2585+
callback_invoked = true;
2586+
return sg;
2587+
});
2588+
http_server_tester::set_on_tls_handshake_sg_switch(server, [&observed_handshake_sg] {
2589+
observed_handshake_sg = current_scheduling_group();
2590+
});
2591+
server._routes.put(GET, "/test", new function_handler([&observed_handler_sg](const_req req) {
2592+
observed_handler_sg = current_scheduling_group();
2593+
return "";
2594+
}, "txt"));
2595+
2596+
server.listen(addr, lo, server_creds).get();
2597+
auto actual_addr = http_server_tester::listeners(server)[0].local_address();
2598+
2599+
// Run the client in a separate fiber so the reactor can
2600+
// interleave client and server TLS handshake progress.
2601+
// The server's get_protocol_version() forces an early
2602+
// handshake that needs the client to send ClientHello
2603+
// concurrently.
2604+
future<> client = seastar::async([&] {
2605+
auto c_socket = tls::connect(client_creds, actual_addr,
2606+
tls::tls_options{.server_name = sstring("localhost")}).get();
2607+
auto input = c_socket.input();
2608+
auto output = c_socket.output();
2609+
auto close_in = deferred_close(input);
2610+
auto close_out = deferred_close(output);
2611+
2612+
output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
2613+
output.flush().get();
2614+
auto resp = input.read().get();
2615+
BOOST_REQUIRE_NE(resp.size(), 0u);
2616+
BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
2617+
});
2618+
2619+
client.get();
2620+
2621+
BOOST_REQUIRE(callback_invoked);
2622+
BOOST_REQUIRE(observed_handler_sg == default_scheduling_group());
2623+
BOOST_REQUIRE(observed_handshake_sg == sg);
2624+
2625+
server.stop().get();
2626+
} catch (...) {
2627+
ex = std::current_exception();
2628+
}
2629+
2630+
destroy_scheduling_group(sg).get();
2631+
if (ex) {
2632+
std::rethrow_exception(std::move(ex));
2633+
}
2634+
});
2635+
}

0 commit comments

Comments
 (0)