Skip to content

Add SSL caching share for curl #6537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@
"sdpath",
"serializers",
"Seriot",
"Shringarputale",
"southafricanorth",
"southcentralus",
"southeastasia",
Expand All @@ -261,6 +262,7 @@
"stoll",
"stoull",
"STREQ",
"Sushrut",
"Sutou",
"testid",
"swedencentral",
Expand Down
8 changes: 8 additions & 0 deletions sdk/core/azure-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@

### Features Added

- [[#6535]](https://github.com/Azure/azure-sdk-for-cpp/issues/6535) Enable SSL caching for libcurl transport by default, which is backwards compatible behavior with older libcurl versions, so using the default settings won't result in transport error when using libcurl >= 8.12. The option is controlled by `CurlTransportOptions::EnableCurlSslCaching`, and is on by default. (A community contribution, courtesy of _[chewi](https://github.com/sushshring)_)

### Breaking Changes

### Bugs Fixed

### Other Changes

### Acknowledgments

Thank you to our developer community members who helped to make Azure Core better with their contributions to this release:

- Sushrut Shringarputale _([GitHub](https://github.com/sushshring))_

## 1.15.0 (2025-03-06)

### Features Added
Expand Down
5 changes: 5 additions & 0 deletions sdk/core/azure-core/inc/azure/core/http/curl_transport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ namespace Azure { namespace Core { namespace Http {
* @brief If set, integrates libcurl's internal tracing with Azure logging.
*/
bool EnableCurlTracing = false;

/**
* @brief If set, enables libcurl's internal SSL session caching.
*/
bool EnableCurlSslCaching = true;
};

/**
Expand Down
48 changes: 48 additions & 0 deletions sdk/core/azure-core/src/http/curl/curl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ inline bool SetLibcurlOption(
*outError = curl_easy_setopt(handle.get(), option, value);
return *outError == CURLE_OK;
}

inline bool SetLibcurlShareOption(
std::unique_ptr<Azure::Core::_detail::CURLSHWrapper> const& handle,
CURLSHoption option,
curl_lock_data value,
CURLSHcode* outError)
{
*outError = curl_share_setopt(handle->share_handle, option, value);
return *outError == CURLSHE_OK;
}

#if defined(_MSC_VER)
#pragma warning(pop)
#endif
Expand Down Expand Up @@ -2303,6 +2314,43 @@ CurlConnection::CurlConnection(
}
CURLcode result;

m_sslShareHandle = std::make_unique<Azure::Core::_detail::CURLSHWrapper>();
if (!m_sslShareHandle->share_handle)
{
throw Azure::Core::Http::TransportException(
_detail::DefaultFailedToGetNewConnectionTemplate + hostDisplayName + ". "
+ std::string("curl_share_init returned Null"));
}

if (!options.EnableCurlSslCaching)
{
// Disable SSL session ID caching
if (!SetLibcurlOption(m_handle, CURLOPT_SSL_SESSIONID_CACHE, 0L, &result))
{
throw Azure::Core::Http::TransportException(
_detail::DefaultFailedToGetNewConnectionTemplate + hostDisplayName + ". "
+ std::string(curl_easy_strerror(result)));
}
}
else
{
CURLSHcode shResult;
if (!SetLibcurlShareOption(
m_sslShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION, &shResult))
{
throw Azure::Core::Http::TransportException(
_detail::DefaultFailedToGetNewConnectionTemplate + hostDisplayName + ". "
+ std::string(curl_share_strerror(shResult)));
}

if (!SetLibcurlOption(m_handle, CURLOPT_SHARE, m_sslShareHandle.get(), &result))
{
throw Azure::Core::Http::TransportException(
_detail::DefaultFailedToGetNewConnectionTemplate + hostDisplayName + ". "
+ std::string(curl_easy_strerror(result)));
}
}

if (options.EnableCurlTracing)
{
if (!SetLibcurlOption(
Expand Down
28 changes: 28 additions & 0 deletions sdk/core/azure-core/src/http/curl/curl_connection_private.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,33 @@ namespace Azure { namespace Core {
{
using type = _internal::BasicUniqueHandle<CURL, curl_easy_cleanup>;
};

/**
*
* @brief Unique handle wrapper for CURLSH handles.
*
* @note Internally, CURL and CURLSH are declared as the same void type,
* so to avoid collisions we need this wrapper.
*/
struct CURLSHWrapper
{
CURLSH* share_handle;

CURLSHWrapper() : share_handle{curl_share_init()} {};

~CURLSHWrapper()
{
if (share_handle != nullptr)
{
curl_share_cleanup(share_handle);
}
}
};

/**
* @brief Unique handle for CURLSHWrapper handles
*/
using UniqueCURLSHHandle = std::unique_ptr<CURLSHWrapper>;
} // namespace _detail

namespace Http {
Expand Down Expand Up @@ -142,6 +169,7 @@ namespace Azure { namespace Core {
class CurlConnection final : public CurlNetworkConnection {
private:
Azure::Core::_internal::UniqueHandle<CURL> m_handle;
Azure::Core::_detail::UniqueCURLSHHandle m_sslShareHandle;
curl_socket_t m_curlSocket;
std::chrono::steady_clock::time_point m_lastUseTime;
std::string m_connectionKey;
Expand Down
37 changes: 37 additions & 0 deletions sdk/core/azure-core/test/ut/curl_options_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,41 @@ namespace Azure { namespace Core { namespace Test {
0);
}

TEST(CurlTransportOptions, disableSslCaching)
{
Azure::Core::Http::CurlTransportOptions curlOptions;
curlOptions.EnableCurlSslCaching = false;

auto transportAdapter = std::make_shared<Azure::Core::Http::CurlTransport>(curlOptions);
Azure::Core::Http::Policies::TransportOptions options;
options.Transport = transportAdapter;
auto transportPolicy
= std::make_unique<Azure::Core::Http::Policies::_internal::TransportPolicy>(options);

std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
policies.emplace_back(std::move(transportPolicy));
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);

Azure::Core::Url url(AzureSdkHttpbinServer::Get());
Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url);

std::unique_ptr<Azure::Core::Http::RawResponse> response;
EXPECT_NO_THROW(response = pipeline.Send(request, Azure::Core::Context{}));
if (response)
{
auto responseCode = response->GetStatusCode();
int expectedCode = 200;
EXPECT_PRED2(
[](int expected, int code) { return expected == code; },
expectedCode,
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
responseCode));
}

// Clean the connection from the pool *Windows fails to clean if we leave to be clean upon
// app-destruction
EXPECT_NO_THROW(Azure::Core::Http::_detail::CurlConnectionPool::g_curlConnectionPool
.ConnectionPoolIndex.clear());
}

}}} // namespace Azure::Core::Test
4 changes: 2 additions & 2 deletions sdk/core/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ extends:
CtestRegex: azure-core.|json-test
LiveTestCtestRegex: azure-core.|json-test
LiveTestTimeoutInMinutes: 90 # default is 60 min. We need a little longer on worst case for Win+jsonTests
LineCoverageTarget: 85
BranchCoverageTarget: 63
LineCoverageTarget: 84
BranchCoverageTarget: 62
PreTestSteps:
- pwsh: |
$(Build.SourcesDirectory)/sdk/core/azure-core-amqp/Test-Setup.ps1
Expand Down