修复: 升级框架并完善报告导出

- 升级 Drogon 和 Trantor,修复畸形请求导致的连接计数泄漏\n- 增加第三方框架版本校验与自动重建\n- 完善完整报告导出和接口文档
This commit is contained in:
cloud
2026-08-10 09:50:09 +08:00
parent 99ed321d24
commit 0e28826073
82 changed files with 3095 additions and 566 deletions
Vendored Executable → Regular
+12 -7
View File
@@ -5,8 +5,17 @@ option(BUILD_DOC "Build Doxygen documentation" OFF)
option(BUILD_C-ARES "Build C-ARES" ON)
option(BUILD_TESTING "Build tests" OFF)
option(BUILD_SHARED_LIBS "Build trantor as a shared lib" OFF)
option(TRANTOR_USE_TLS
"TLS provider for trantor. Valid options are 'openssl', 'botan' or '' (let the build scripr decide)" ""
set(TRANTOR_USE_TLS
""
CACHE STRING "TLS provider for trantor. Valid options are 'openssl', 'botan', 'none' or '' (auto-detect)"
)
set_property(
CACHE TRANTOR_USE_TLS
PROPERTY STRINGS
""
openssl
botan
none
)
option(USE_SPDLOG "Allow using the spdlog logging library" OFF)
@@ -14,7 +23,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/)
set(TRANTOR_MAJOR_VERSION 1)
set(TRANTOR_MINOR_VERSION 5)
set(TRANTOR_PATCH_VERSION 26)
set(TRANTOR_PATCH_VERSION 28)
set(TRANTOR_VERSION ${TRANTOR_MAJOR_VERSION}.${TRANTOR_MINOR_VERSION}.${TRANTOR_PATCH_VERSION})
include(GNUInstallDirs)
@@ -158,10 +167,6 @@ else(WIN32)
set(TRANTOR_SOURCES ${TRANTOR_SOURCES} trantor/net/inner/FileBufferNodeUnix.cc)
endif(WIN32)
# Somehow the default value of TRANTOR_USE_TLS is OFF
if(TRANTOR_USE_TLS STREQUAL OFF)
set(TRANTOR_USE_TLS "")
endif()
set(VALID_TLS_PROVIDERS "openssl" "botan" "none")
list(
FIND
+29 -1
View File
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
## [1.5.28] - 2026-05-06
### Fixed
- Avoid abort on closeWrite shutdown failure.
## [1.5.27] - 2026-05-06
### Changed
- Add automatic SSL.
- Add getter for `TcpConnection::closeCallback_`.
- Remove spurious executable permissions from non-script sources.
### Fixed
- Fix `TRANTOR_USE_TLS` cache setting in CMake.
- Fix TLS implementation quirks.
- Fix server-side mTLS client certificate hostname validation.
## [1.5.26] - 2026-01-26
### Changed
@@ -742,7 +766,11 @@ All notable changes to this project will be documented in this file.
## [1.0.0-rc1] - 2019-06-11
[Unreleased]: https://github.com/an-tao/trantor/compare/v1.5.26...HEAD
[Unreleased]: https://github.com/an-tao/trantor/compare/v1.5.28...HEAD
[1.5.28]: https://github.com/an-tao/trantor/compare/v1.5.27...v1.5.28
[1.5.27]: https://github.com/an-tao/trantor/compare/v1.5.26...v1.5.27
[1.5.26]: https://github.com/an-tao/trantor/compare/v1.5.25...v1.5.26
Vendored Executable → Regular
View File
@@ -351,6 +351,10 @@ class TRANTOR_EXPORT TcpConnection
{
closeCallback_ = std::move(cb);
}
CloseCallback getCloseCallback() const
{
return closeCallback_;
}
void setSSLErrorCallback(const SSLErrorCallback &cb)
{
sslErrorCallback_ = cb;
@@ -367,6 +371,8 @@ class TRANTOR_EXPORT TcpConnection
size_t timeout,
const std::shared_ptr<TimingWheel> &timingWheel) = 0;
virtual void forwardToTLSBuffer(MsgBuffer *buffer) = 0;
protected:
// callbacks
RecvMessageCallback recvMsgCallback_;
View File
@@ -203,6 +203,12 @@ class TcpConnectionImpl : public TcpConnection,
timingWheel->insertEntry(timeout, entry);
}
void forwardToTLSBuffer(MsgBuffer *buffer) override
{
if (tlsProviderPtr_)
tlsProviderPtr_->recvData(buffer);
}
private:
/// Internal use only.
@@ -474,8 +474,6 @@ SSLContextPtr trantor::newSSLContext(const TLSPolicy &policy, bool server)
ctx->certStore =
std::make_shared<Botan::Flatfile_Certificate_Store>(
policy.getCaPath());
if (server)
ctx->requireClientCert = true;
}
else if (policy.getUseSystemCertStore())
{
@@ -484,6 +482,8 @@ SSLContextPtr trantor::newSSLContext(const TLSPolicy &policy, bool server)
ctx->certStore = systemCertStore;
}
}
if (server && policy.getValidate() && !policy.getCaPath().empty())
ctx->requireClientCert = true;
if (policy.getUseOldTLS())
LOG_WARN << "SSLPloicy have set useOldTLS to true. BUt Botan does not "
@@ -8,12 +8,10 @@
#include <openssl/bio.h>
#include <openssl/x509v3.h>
#include <fstream>
#include <memory>
#include <mutex>
#include <list>
#include <unordered_map>
#include <array>
#include <limits>
#include "callbacks.h"
@@ -70,62 +68,6 @@ inline bool loadWindowsSystemCert(X509_STORE *store)
}
#endif
inline bool verifyCommonName(X509 *cert, const std::string &hostname)
{
X509_NAME *subjectName = X509_get_subject_name(cert);
if (subjectName != nullptr)
{
std::array<char, BUFSIZ> name;
auto length = X509_NAME_get_text_by_NID(subjectName,
NID_commonName,
name.data(),
(int)name.size());
if (length == -1)
return false;
return utils::verifySslName(std::string(name.begin(),
name.begin() + length),
hostname);
}
return false;
}
inline bool verifyAltName(X509 *cert, const std::string &hostname)
{
bool good = false;
auto altNames = static_cast<const struct stack_st_GENERAL_NAME *>(
X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
if (altNames)
{
int numNames = sk_GENERAL_NAME_num(altNames);
for (int i = 0; i < numNames && !good; i++)
{
auto val = sk_GENERAL_NAME_value(altNames, i);
if (val->type != GEN_DNS)
{
LOG_WARN << "Name using IP addresses are not supported. Open "
"an issue if you need that feature";
continue;
}
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
#else
auto name = (const char *)ASN1_STRING_data(val->d.ia5);
#endif
auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
good = utils::verifySslName(std::string(name, name + name_len),
hostname);
}
}
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)altNames);
return good;
}
static bool validatePeerCertificate(SSL *ssl,
X509 *cert,
const std::string &hostname,
@@ -136,12 +78,16 @@ static bool validatePeerCertificate(SSL *ssl,
assert(cert != nullptr);
LOG_TRACE << "Validating peer certificate";
if (isServer)
if (!isServer)
{
bool domainIsValid =
verifyCommonName(cert, hostname) || verifyAltName(cert, hostname);
if (!domainIsValid)
const int rc =
X509_check_host(cert, hostname.data(), hostname.size(), 0, nullptr);
if (rc != 1)
{
LOG_TRACE << "Peer certificate does not match hostname: "
<< hostname;
return false;
}
}
auto result = SSL_get_verify_result(ssl);
@@ -423,16 +369,20 @@ class SessionManager
#endif
}
// Returns a session with an additional reference held by the caller.
// Caller must SSL_SESSION_free() when done. Required because the entry
// in sessionMap_ may be evicted/replaced/expired by another thread the
// moment we release the mutex, so the SessionManager's reference is not
// a stable ownership root for the returned pointer.
SSL_SESSION *get(const std::string &hostname, InetAddress peerAddr)
{
std::lock_guard<std::mutex> lock(mutex_);
auto key = toKey(hostname, peerAddr);
auto it = sessionMap_.find(key);
if (it != sessionMap_.end())
{
return it->second->session;
}
return nullptr;
auto it = sessionMap_.find(toKey(hostname, peerAddr));
if (it == sessionMap_.end())
return nullptr;
SSL_SESSION *s = it->second->session;
SSL_SESSION_up_ref(s);
return s;
}
void removeExcessSession()
@@ -529,7 +479,9 @@ struct OpenSSLProvider : public TLSProvider, public NonCopyable
conn_->peerAddr());
if (cachedSession)
{
// SSL_set_session takes its own reference; release ours.
SSL_set_session(ssl_, cachedSession);
SSL_SESSION_free(cachedSession);
}
SSL_set_connect_state(ssl_);
}
@@ -671,7 +623,10 @@ struct OpenSSLProvider : public TLSProvider, public NonCopyable
cert,
policyPtr_->getHostname(),
policyPtr_->getAllowBrokenChain(),
contextPtr_->isServer);
!contextPtr_
->isServer); // From the server's point of view,
// the client certificate is verified
// and vice versa
if (!valid)
{
LOG_TRACE
@@ -0,0 +1,54 @@
#include <trantor/net/TcpClient.h>
#include <trantor/utils/Logger.h>
#include <trantor/net/EventLoopThread.h>
#include <string>
#include <iostream>
#include <atomic>
using namespace trantor;
#define USE_IPV6 0
int main()
{
trantor::Logger::setLogLevel(trantor::Logger::kDebug);
LOG_DEBUG << "TcpClient class test!";
EventLoop loop;
#if USE_IPV6
InetAddress serverAddr("::1", 8888, true);
#else
InetAddress serverAddr("127.0.0.1", 8888);
#endif
std::shared_ptr<trantor::TcpClient> client[10];
std::atomic_int connCount;
connCount = 1;
for (int i = 0; i < 1; ++i)
{
client[i] = std::make_shared<trantor::TcpClient>(&loop,
serverAddr,
"tcpclienttest");
auto policy = TLSPolicy::defaultClientPolicy();
policy->setValidate(false);
client[i]->enableSSL(std::move(policy));
client[i]->setConnectionCallback(
[i, &loop, &connCount](const TcpConnectionPtr &conn) {
if (conn->connected())
{
LOG_DEBUG << i << " connected";
conn->send("Hello");
}
else
{
LOG_DEBUG << i << " disconnected";
--connCount;
if (connCount == 0)
loop.quit();
}
});
client[i]->setMessageCallback(
[](const TcpConnectionPtr &conn, MsgBuffer *buf) {
auto msg = std::string(buf->peek(), buf->readableBytes());
LOG_INFO << msg;
buf->retrieveAll();
});
client[i]->connect();
}
loop.loop();
}
@@ -0,0 +1,63 @@
#include <trantor/net/TcpServer.h>
#include <trantor/utils/Logger.h>
#include <trantor/net/EventLoopThread.h>
#include <string>
#include <iostream>
using namespace trantor;
#define USE_IPV6 0
bool has_ssl(MsgBuffer *buffer)
{
if (buffer->readableBytes() < 3)
return false;
const char *data = buffer->peek();
unsigned char byte1 = static_cast<unsigned char>(data[0]);
unsigned char byte2 = static_cast<unsigned char>(data[1]);
unsigned char byte3 = static_cast<unsigned char>(data[2]);
return (byte1 == 0x16) && (byte2 == 0x03) && (byte3 == 0x01);
}
int main()
{
LOG_DEBUG << "test start";
Logger::setLogLevel(Logger::kDebug);
EventLoopThread loopThread;
loopThread.run();
#if USE_IPV6
InetAddress addr(8888, true, true);
#else
InetAddress addr(8888);
#endif
TcpServer server(loopThread.getLoop(), addr, "test");
// auto ctx = newSSLServerContext("server.pem", "server.pem", {});
LOG_INFO << "start";
server.setRecvMessageCallback(
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
if (has_ssl(buffer))
{
LOG_DEBUG << "SSL data received";
auto policy =
TLSPolicy::defaultServerPolicy("server.crt", "server.key");
connectionPtr->startEncryption(policy, true);
connectionPtr->forwardToTLSBuffer(buffer);
return;
}
LOG_DEBUG << std::string{buffer->peek(), buffer->readableBytes()};
connectionPtr->send(*buffer);
buffer->retrieveAll();
connectionPtr->shutdown();
});
server.setConnectionCallback([](const TcpConnectionPtr &connPtr) {
if (connPtr->connected())
{
LOG_DEBUG << "New connection";
}
else if (connPtr->disconnected())
{
LOG_DEBUG << "connection disconnected";
}
});
server.setIoLoopNum(3);
server.start();
loopThread.wait();
}
@@ -23,6 +23,8 @@ add_executable(logger_macro_test LoggerMacroTest.cc)
add_executable(delayed_ssl_server_test DelayedSSLServerTest.cc)
add_executable(delayed_ssl_client_test DelayedSSLClientTest.cc)
add_executable(tcp_asyncstream_server_test TcpAsyncStreamServerTest.cc)
add_executable(automatic_ssl_server_test AutomaticSSLServerTest.cc)
add_executable(automatic_ssl_client_test AutomaticSSLClientTest.cc)
set(targets_list
ssl_server_test
ssl_client_test
@@ -49,6 +51,8 @@ set(targets_list
delayed_ssl_server_test
delayed_ssl_client_test
tcp_asyncstream_server_test
automatic_ssl_server_test
automatic_ssl_client_test
)
if(HAVE_SPDLOG)
View File
@@ -4,7 +4,6 @@ add_executable(inetaddress_unittest InetAddressUnittest.cc)
add_executable(date_unittest DateUnittest.cc)
add_executable(split_string_unittest splitStringUnittest.cc)
add_executable(string_encoding_unittest stringEncodingUnittest.cc)
add_executable(ssl_name_verify_unittest sslNameVerifyUnittest.cc)
add_executable(hash_unittest HashUnittest.cc)
set(UNITTEST_TARGETS
msgbuffer_unittest
@@ -12,7 +11,6 @@ set(UNITTEST_TARGETS
date_unittest
split_string_unittest
string_encoding_unittest
ssl_name_verify_unittest
hash_unittest
)
set_property(TARGET ${UNITTEST_TARGETS} PROPERTY CXX_STANDARD 14)
@@ -1,50 +0,0 @@
#include <trantor/utils/Utilities.h>
#include <gtest/gtest.h>
#include <iostream>
using namespace trantor;
using namespace trantor::utils;
TEST(sslNameCheck, baseCases)
{
EXPECT_EQ(verifySslName("example.com", "example.com"), true);
EXPECT_EQ(verifySslName("example.com", "example.org"), false);
EXPECT_EQ(verifySslName("example.com", "www.example.com"), false);
}
TEST(sslNameCheck, rfc6125Examples)
{
EXPECT_EQ(verifySslName("*.example.com", "foo.example.com"), true);
EXPECT_EQ(verifySslName("*.example.com", "foo.bar.example.com"), false);
EXPECT_EQ(verifySslName("*.example.com", "example.com"), false);
EXPECT_EQ(verifySslName("*bar.example.com", "foobar.example.com"), true);
EXPECT_EQ(verifySslName("baz*.example.com", "baz1.example.com"), true);
EXPECT_EQ(verifySslName("b*z.example.com", "buzz.example.com"), true);
}
TEST(sslNameCheck, rfcCounterExamples)
{
EXPECT_EQ(verifySslName("buz*.example.com", "buaz.example.com"), false);
EXPECT_EQ(verifySslName("*bar.example.com", "aaasdasbaz.example.com"),
false);
EXPECT_EQ(verifySslName("b*z.example.com", "baaaaaa.example.com"), false);
}
TEST(sslNameCheck, wildExamples)
{
EXPECT_EQ(verifySslName("datatracker.ietf.org", "datatracker.ietf.org"),
true);
EXPECT_EQ(verifySslName("*.nsysu.edu.tw", "nsysu.edu.tw"), false);
EXPECT_EQ(verifySslName("nsysu.edu.tw", "nsysu.edu.tw"), true);
}
TEST(sslNameCheck, edgeCase)
{
EXPECT_EQ(verifySslName(".example.com", "example.com"), false);
EXPECT_EQ(verifySslName("example.com.", "example.com."), true);
}
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
View File
@@ -228,120 +228,6 @@ std::string fromWidePath(const std::wstring &wstrPath)
return toUtf8(srcPath);
}
bool verifySslName(const std::string &certName, const std::string &hostname)
{
if (certName.find('*') == std::string::npos)
{
return certName == hostname;
}
size_t firstDot = certName.find('.');
size_t hostFirstDot = hostname.find('.');
size_t pos, len, hostPos, hostLen;
if (firstDot != std::string::npos)
{
pos = firstDot + 1;
}
else
{
firstDot = pos = certName.size();
}
len = certName.size() - pos;
if (hostFirstDot != std::string::npos)
{
hostPos = hostFirstDot + 1;
}
else
{
hostFirstDot = hostPos = hostname.size();
}
hostLen = hostname.size() - hostPos;
// *. in the beginning of the cert name
if (certName.compare(0, firstDot, "*") == 0)
{
return certName.compare(pos, len, hostname, hostPos, hostLen) == 0;
}
// * in the left most. but other chars in the right
else if (certName[0] == '*')
{
// compare if `hostname` ends with `certName` but without the leftmost
// should be fine as domain names can't be that long
intmax_t hostnameIdx = hostname.size() - 1;
intmax_t certNameIdx = certName.size() - 1;
while (hostnameIdx >= 0 && certNameIdx != 0)
{
if (hostname[hostnameIdx] != certName[certNameIdx])
{
return false;
}
hostnameIdx--;
certNameIdx--;
}
if (certNameIdx != 0)
{
return false;
}
return true;
}
// * in the right of the first dot
else if (firstDot != 0 && certName[firstDot - 1] == '*')
{
if (certName.compare(pos, len, hostname, hostPos, hostLen) != 0)
{
return false;
}
for (size_t i = 0;
i < hostFirstDot && i < firstDot && certName[i] != '*';
i++)
{
if (hostname[i] != certName[i])
{
return false;
}
}
return true;
}
// else there's a * in the middle
else
{
if (certName.compare(pos, len, hostname, hostPos, hostLen) != 0)
{
return false;
}
for (size_t i = 0;
i < hostFirstDot && i < firstDot && certName[i] != '*';
i++)
{
if (hostname[i] != certName[i])
{
return false;
}
}
intmax_t hostnameIdx = hostFirstDot - 1;
intmax_t certNameIdx = firstDot - 1;
while (hostnameIdx >= 0 && certNameIdx >= 0 &&
certName[certNameIdx] != '*')
{
if (hostname[hostnameIdx] != certName[certNameIdx])
{
return false;
}
hostnameIdx--;
certNameIdx--;
}
return true;
}
assert(false && "This line should not be reached in verifySslName");
// should not reach
return certName == hostname;
}
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
@@ -171,15 +171,6 @@ inline std::string fromNativePath(const std::wstring &strPath)
return fromWidePath(strPath);
}
/**
* @brief Check if the name supplied by the SSL Cert matches a FQDN
* @param certName The name supplied by the SSL Cert
* @param hostName The FQDN to match
*
* @return true if matches. false otherwise
*/
bool verifySslName(const std::string &certName, const std::string &hostName);
/**
* @brief Returns the TLS backend used by trantor. Could be "None", "OpenSSL" or
* "Botan"