6969#include < openssl/ts.h>
7070#endif
7171
72+ #include < algorithm>
73+ #include < thread>
7274#include < utility>
7375#include < string>
7476#include < unistd.h>
@@ -1599,11 +1601,20 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSL
15991601 SSLMultiCertConfigLoader::CertLoadData data;
16001602
16011603 if (!this ->_prep_ssl_ctx (sslMultCertSettings, data, common_names, unique_names)) {
1602- lookup->is_valid = false ;
1604+ {
1605+ std::lock_guard<std::mutex> lock (_loader_mutex);
1606+ lookup->is_valid = false ;
1607+ }
16031608 return false ;
16041609 }
16051610
16061611 std::vector<SSLLoadingContext> ctxs = this ->init_server_ssl_ctx (data, sslMultCertSettings.get ());
1612+
1613+ // Serialize all mutations to the shared SSLCertLookup.
1614+ // The expensive work above (_prep_ssl_ctx + init_server_ssl_ctx) runs
1615+ // without the lock, allowing parallel cert loading across threads.
1616+ std::lock_guard<std::mutex> lock (_loader_mutex);
1617+
16071618 for (const auto &loadingctx : ctxs) {
16081619 if (!sslMultCertSettings ||
16091620 !this ->_store_single_ssl_ctx (lookup, sslMultCertSettings, shared_SSL_CTX{loadingctx.ctx , SSL_CTX_free}, loadingctx.ctx_type ,
@@ -1782,7 +1793,7 @@ SSLMultiCertConfigLoader::_store_single_ssl_ctx(SSLCertLookup *lookup, const sha
17821793}
17831794
17841795swoc::Errata
1785- SSLMultiCertConfigLoader::load (SSLCertLookup *lookup)
1796+ SSLMultiCertConfigLoader::load (SSLCertLookup *lookup, bool firstLoad )
17861797{
17871798 const SSLConfigParams *params = this ->_params ;
17881799
@@ -1806,10 +1817,69 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
18061817 }
18071818
18081819 swoc::Errata errata (ERRATA_NOTE);
1809- int item_num = 0 ;
18101820
1811- for (const auto &item : parse_result.value ) {
1821+ static constexpr int MAX_LOAD_THREADS = 256 ;
1822+
1823+ int num_threads = params->configLoadConcurrency ;
1824+ if (firstLoad) {
1825+ num_threads = std::clamp (static_cast <int >(std::thread::hardware_concurrency ()), 1 , MAX_LOAD_THREADS);
1826+ }
1827+ num_threads = std::min (num_threads, static_cast <int >(parse_result.value .size ()));
1828+
1829+ if (num_threads > 1 && parse_result.value .size () > 1 ) {
1830+ std::size_t bucket_size = parse_result.value .size () / num_threads;
1831+ std::size_t remainder = parse_result.value .size () % num_threads;
1832+ auto current = parse_result.value .cbegin ();
1833+
1834+ std::vector<std::thread> threads;
1835+ Note (" (%s) loading %zu certs with %d threads" , this ->_debug_tag (), parse_result.value .size (), num_threads);
1836+
1837+ for (int t = 0 ; t < num_threads; ++t) {
1838+ std::size_t this_bucket = bucket_size + (static_cast <std::size_t >(t) < remainder ? 1 : 0 );
1839+ auto end = current + this_bucket;
1840+ int base_index = static_cast <int >(std::distance (parse_result.value .cbegin (), current));
1841+ threads.emplace_back (&SSLMultiCertConfigLoader::_load_items, this , lookup, current, end, base_index, std::ref (errata));
1842+ current = end;
1843+ }
1844+
1845+ for (auto &th : threads) {
1846+ th.join ();
1847+ }
1848+
1849+ Note (" (%s) loaded %zu certs in %d threads" , this ->_debug_tag (), parse_result.value .size (), num_threads);
1850+ } else {
1851+ _load_items (lookup, parse_result.value .cbegin (), parse_result.value .cend (), 0 , errata);
1852+ Note (" (%s) loaded %zu certs (single-threaded)" , this ->_debug_tag (), parse_result.value .size ());
1853+ }
1854+
1855+ // We *must* have a default context even if it can't possibly work. The default context is used to
1856+ // bootstrap the SSL handshake so that we can subsequently do the SNI lookup to switch to the real
1857+ // context.
1858+ if (lookup->ssl_default == nullptr ) {
1859+ shared_SSLMultiCertConfigParams sslMultiCertSettings (new SSLMultiCertConfigParams);
1860+ sslMultiCertSettings->addr = ats_strdup (" *" );
1861+ if (!this ->_store_ssl_ctx (lookup, sslMultiCertSettings)) {
1862+ errata.note (ERRATA_ERROR, " failed set default context" );
1863+ }
1864+ }
1865+
1866+ return errata;
1867+ }
1868+
1869+ void
1870+ SSLMultiCertConfigLoader::_load_items (SSLCertLookup *lookup, config::SSLMultiCertConfig::const_iterator begin,
1871+ config::SSLMultiCertConfig::const_iterator end, int base_index, swoc::Errata &errata)
1872+ {
1873+ // Each thread needs its own elevated privileges since POSIX capabilities are per-thread
1874+ uint32_t elevate_setting = 0 ;
1875+ elevate_setting = RecGetRecordInt (" proxy.config.ssl.cert.load_elevated" ).value_or (0 );
1876+ ElevateAccess elevate_access (elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0 );
1877+
1878+ int item_num = base_index;
1879+ for (auto it = begin; it != end; ++it) {
18121880 item_num++;
1881+ const auto &item = *it;
1882+
18131883 shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
18141884
18151885 if (!item.ssl_cert_name .empty ()) {
@@ -1846,25 +1916,14 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
18461916 // There must be a certificate specified unless the tunnel action is set.
18471917 if (sslMultiCertSettings->cert || sslMultiCertSettings->opt == SSLCertContextOption::OPT_TUNNEL) {
18481918 if (!this ->_store_ssl_ctx (lookup, sslMultiCertSettings)) {
1919+ std::lock_guard<std::mutex> lock (_loader_mutex);
18491920 errata.note (ERRATA_ERROR, " Failed to load certificate at item {}" , item_num);
18501921 }
18511922 } else {
1923+ std::lock_guard<std::mutex> lock (_loader_mutex);
18521924 errata.note (ERRATA_WARN, " No ssl_cert_name specified and no tunnel action set at item {}" , item_num);
18531925 }
18541926 }
1855-
1856- // We *must* have a default context even if it can't possibly work. The default context is used to
1857- // bootstrap the SSL handshake so that we can subsequently do the SNI lookup to switch to the real
1858- // context.
1859- if (lookup->ssl_default == nullptr ) {
1860- shared_SSLMultiCertConfigParams sslMultiCertSettings (new SSLMultiCertConfigParams);
1861- sslMultiCertSettings->addr = ats_strdup (" *" );
1862- if (!this ->_store_ssl_ctx (lookup, sslMultiCertSettings)) {
1863- errata.note (ERRATA_ERROR, " failed set default context" );
1864- }
1865- }
1866-
1867- return errata;
18681927}
18691928
18701929// Release SSL_CTX and the associated data. This works for both
0 commit comments