@@ -56,6 +56,7 @@ constexpr auto INTERFACE_MONITOR_RECONNECT_RETRY_DELAY =
5656 std::chrono::seconds{5 };
5757constexpr std::size_t kResolverStreamChunkBytes =
5858 static_cast <std::size_t >(16 ) * 1024U ;
59+ constexpr std::size_t kMaxConcurrentRoutingTests = 2 ;
5960
6061void send_all (int fd, const char *data, std::size_t size) {
6162 std::size_t written = 0 ;
@@ -220,6 +221,7 @@ Daemon::~Daemon() {
220221 resolver_hook_executor_.shutdown ();
221222 resolver_stream_executor_.shutdown ();
222223 resolver_io_executor_.shutdown ();
224+ routing_test_executor_.shutdown ();
223225 blocking_executor_.shutdown ();
224226
225227 if (control_fd_ >= 0 ) {
@@ -324,6 +326,22 @@ void Daemon::remove_ipc_control_socket() noexcept {
324326 }
325327}
326328
329+ bool Daemon::try_begin_routing_test () {
330+ std::size_t current = routing_tests_inflight_.load (std::memory_order_acquire);
331+ while (current < kMaxConcurrentRoutingTests ) {
332+ if (routing_tests_inflight_.compare_exchange_weak (
333+ current, current + 1 , std::memory_order_acq_rel,
334+ std::memory_order_acquire)) {
335+ return true ;
336+ }
337+ }
338+ return false ;
339+ }
340+
341+ void Daemon::finish_routing_test () {
342+ routing_tests_inflight_.fetch_sub (1 , std::memory_order_acq_rel);
343+ }
344+
327345void Daemon::handle_ipc_control_socket () {
328346 while (true ) {
329347 const int client = accept4 (ipc_control_fd_, nullptr , nullptr , SOCK_CLOEXEC );
@@ -340,7 +358,7 @@ void Daemon::handle_ipc_control_socket() {
340358 sizeof (timeout));
341359 nlohmann::json request = nlohmann::json::object ();
342360 nlohmann::json response;
343- bool resolver_stream_dispatched = false ;
361+ bool client_dispatched = false ;
344362 try {
345363 ucred peer{};
346364 socklen_t peer_length = sizeof (peer);
@@ -400,34 +418,78 @@ void Daemon::handle_ipc_control_socket() {
400418 const std::string target = request.value (" target" , " " );
401419 if (target.empty ())
402420 throw ipc::ControlProtocolError (" test-routing requires a target" );
421+ const Config active_config = config_store_.active_config ();
403422 const auto runtime_snapshot = runtime_state_store_.snapshot ();
404- const auto result =
405- compute_test_routing (config_store_.active_config (),
406- list_service_.cache_manager (), target,
407- &runtime_snapshot.firewall_state .get_rules ());
408- nlohmann::json entries = nlohmann::json::array ();
409- for (const auto &entry : result.entries ) {
410- nlohmann::json entry_json = {
411- {" ip" , entry.ip },
412- {" expected_outbound" , entry.expected_outbound },
413- {" actual_outbound" , entry.actual_outbound },
414- {" ok" , entry.ok }};
415- if (entry.list_match .has_value ()) {
416- entry_json[" list_match" ] = {
417- {" list_name" , entry.list_match ->list_name },
418- {" via" , entry.list_match ->via }};
419- }
420- entries.push_back (std::move (entry_json));
423+ const auto realized_rules = runtime_snapshot.firewall_state .get_rules ();
424+ const auto request_snapshot = request;
425+ if (!try_begin_routing_test ()) {
426+ response = ipc::make_error_response (
427+ request, " busy" , " too many routing tests are already running" );
428+ const std::string frame = ipc::encode_message (response);
429+ (void )send (client, frame.data (), frame.size (), MSG_NOSIGNAL );
430+ close (client);
431+ continue ;
421432 }
422- response = {{" protocol_version" , ipc::kControlProtocolVersion },
423- {" request_id" , request.at (" request_id" )},
433+ const bool queued = routing_test_executor_.try_post (
434+ " ipc-test-routing" ,
435+ [this , client, active_config, realized_rules, request_snapshot,
436+ target] {
437+ nlohmann::json worker_response;
438+ try {
439+ const auto result = compute_test_routing (
440+ active_config, list_service_.cache_manager (), target,
441+ &realized_rules);
442+ nlohmann::json entries = nlohmann::json::array ();
443+ for (const auto &entry : result.entries ) {
444+ nlohmann::json entry_json = {
445+ {" ip" , entry.ip },
446+ {" expected_outbound" , entry.expected_outbound },
447+ {" actual_outbound" , entry.actual_outbound },
448+ {" ok" , entry.ok }};
449+ if (entry.list_match .has_value ()) {
450+ entry_json[" list_match" ] = {
451+ {" list_name" , entry.list_match ->list_name },
452+ {" via" , entry.list_match ->via }};
453+ }
454+ entries.push_back (std::move (entry_json));
455+ }
456+ worker_response = {
457+ {" protocol_version" , ipc::kControlProtocolVersion },
458+ {" request_id" , request_snapshot.at (" request_id" )},
424459 {" ok" , !result.dns_error .has_value ()},
425460 {" result" ,
426461 {{" target" , result.target },
427462 {" resolved_ips" , result.resolved_ips },
428463 {" entries" , std::move (entries)},
429464 {" warnings" , result.warnings },
430465 {" dns_error" , result.dns_error }}}};
466+ } catch (const std::exception &error) {
467+ worker_response = ipc::make_error_response (
468+ request_snapshot, " daemon_error" , error.what ());
469+ } catch (...) {
470+ worker_response = ipc::make_error_response (
471+ request_snapshot, " daemon_error" ,
472+ " routing test failed with an unknown error" );
473+ }
474+
475+ finish_routing_test ();
476+
477+ try {
478+ const std::string frame = ipc::encode_message (worker_response);
479+ send_all (client, frame.data (), frame.size ());
480+ } catch (const std::exception &error) {
481+ Logger::instance ().warn (
482+ " test-routing control response failed: {}" , error.what ());
483+ }
484+ close (client);
485+ });
486+ if (queued) {
487+ client_dispatched = true ;
488+ continue ;
489+ }
490+ finish_routing_test ();
491+ response = ipc::make_error_response (
492+ request, " busy" , " routing test executor queue is full" );
431493 } else if (operation == " generate-resolver-config" ) {
432494 const RuntimeState runtime_state = runtime_state_machine_.state ();
433495 // The DNS configuration is a daemon-owned desired-state
@@ -527,7 +589,7 @@ void Daemon::handle_ipc_control_socket() {
527589 }
528590 });
529591 if (queued) {
530- resolver_stream_dispatched = true ;
592+ client_dispatched = true ;
531593 continue ;
532594 }
533595 response = ipc::make_error_response (
@@ -629,7 +691,7 @@ void Daemon::handle_ipc_control_socket() {
629691 response =
630692 ipc::make_error_response (request, " protocol_error" , error.what ());
631693 }
632- if (!resolver_stream_dispatched ) {
694+ if (!client_dispatched ) {
633695 const std::string frame = ipc::encode_message (response);
634696 (void )send (client, frame.data (), frame.size (), MSG_NOSIGNAL );
635697 close (client);
@@ -1376,6 +1438,7 @@ void Daemon::run() {
13761438 resolver_hook_executor_.shutdown ();
13771439 resolver_stream_executor_.shutdown ();
13781440 resolver_io_executor_.shutdown ();
1441+ routing_test_executor_.shutdown ();
13791442 blocking_executor_.shutdown ();
13801443
13811444#ifdef WITH_API
0 commit comments