From 576a05e7919077420d32c51b5d41d6fd94488fce Mon Sep 17 00:00:00 2001 From: kaetemi Date: Sat, 4 Mar 2023 14:31:07 +0800 Subject: [PATCH] Load QUIC certificate from file, ref ryzom/ryzomcore#628 --- nel/include/nel/net/login_server.h | 3 + nel/src/net/login_server.cpp | 10 + ryzom/client/src/quic_connection.cpp | 10 +- .../src/frontend_service/frontend_service.cpp | 8 +- .../src/frontend_service/quic_selfsign.cpp | 36 ++-- .../src/frontend_service/quic_transceiver.cpp | 194 ++++++++++++++---- .../src/frontend_service/quic_transceiver.h | 3 + 7 files changed, 202 insertions(+), 62 deletions(-) diff --git a/nel/include/nel/net/login_server.h b/nel/include/nel/net/login_server.h index 3968ff388c..875c8208eb 100644 --- a/nel/include/nel/net/login_server.h +++ b/nel/include/nel/net/login_server.h @@ -86,6 +86,9 @@ class CLoginServer { /// Call this method to retrieve the listen address static const std::string &getListenAddress(); + /// Call this method to retrieve the listen host + static const CInetHost &getListenHost(); + /// Return true if we are in 'dev' mode static bool acceptsInvalidCookie(); diff --git a/nel/src/net/login_server.cpp b/nel/src/net/login_server.cpp index ff7d992c48..75f0d1a417 100644 --- a/nel/src/net/login_server.cpp +++ b/nel/src/net/login_server.cpp @@ -50,6 +50,7 @@ static list PendingUsers; static CCallbackServer *Server = NULL; static string ListenAddr; +static CInetHost ListenHost; static bool AcceptInvalidCookie = false; @@ -293,11 +294,13 @@ void CLoginServer::setListenAddress(const string &la) // check that listen address is valid if (ListenAddr.empty()) { + ListenHost.clear(); nlerror("FATAL : listen address in invalid, it should be either set via ListenAddress variable or with -D argument"); nlstop; } nlinfo("LS: Listen Address that will be sent to the client is now '%s'", ListenAddr.c_str()); + ListenHost = CInetHost(ListenAddr); } uint32 CLoginServer::getNbPendingUsers() @@ -500,6 +503,12 @@ const std::string &CLoginServer::getListenAddress() return ListenAddr; } + +/// Call this method to retrieve the listen address +const CInetHost &CLoginServer::getListenHost() +{ + return ListenHost; +} bool CLoginServer::acceptsInvalidCookie() { return AcceptInvalidCookie; @@ -577,6 +586,7 @@ NLMISC_CATEGORISED_DYNVARIABLE(nel, string, LSListenAddress, "the listen address { ListenAddr = *pointer; } + ListenHost = CInetHost(ListenAddr); nlinfo ("LS: Listen Address that will be send to client is '%s'", ListenAddr.c_str()); } } diff --git a/ryzom/client/src/quic_connection.cpp b/ryzom/client/src/quic_connection.cpp index 48c8d3b11f..878c27fff6 100644 --- a/ryzom/client/src/quic_connection.cpp +++ b/ryzom/client/src/quic_connection.cpp @@ -210,8 +210,12 @@ void CQuicConnectionImpl::connect() return; } - static const CStringView protocolName = "ryzomcore4"; - static const QUIC_BUFFER alpn = { (uint32)protocolName.size(), (uint8 *)protocolName.data() }; + // Protocol feature levels, corresponds to Ryzom Core release versions, keep datagram-only support to give users and server owners the option to keep bandwidth restricted + // 4.1: Only datagram support (restricted bandwidth, same behaviour as UDP) + // 4.2?: Add up to 4 unidirectional streams from the server to client to send long impulses (keep bandwidth from client restricted) (DB_INIT, STRING, STRING_MANAGER, MODULE_GATEWAY) + // 4.3?: Add a single bidirectional stream opened by the client for the scenario editor gateway (more efficient MODULE_GATEWAY replacement) + static const CStringView protocolName41 = "ryzomcore/4.1"; + static const QUIC_BUFFER alpn = { (uint32)protocolName41.size(), (uint8 *)protocolName41.data() }; // Configuration, initialized in start, but destroyed on release only (may attempt more than once) QUIC_STATUS status = QUIC_STATUS_SUCCESS; @@ -510,7 +514,7 @@ _Function_class_(QUIC_CONNECTION_CALLBACK) break; } case QUIC_CONNECTION_EVENT_DATAGRAM_RECEIVED: - nldebug("Datagram received"); + // nldebug("Datagram received"); // YES PLEASE m->datagramReceived(ev->DATAGRAM_RECEIVED.Buffer->Buffer, ev->DATAGRAM_RECEIVED.Buffer->Length); status = QUIC_STATUS_SUCCESS; diff --git a/ryzom/server/src/frontend_service/frontend_service.cpp b/ryzom/server/src/frontend_service/frontend_service.cpp index 92a315c98e..0463fa760c 100644 --- a/ryzom/server/src/frontend_service/frontend_service.cpp +++ b/ryzom/server/src/frontend_service/frontend_service.cpp @@ -1231,8 +1231,8 @@ void CFrontEndService::init() CLoginServer::init( "", cbDisconnectClient ); // // Init front end listening port - CInetHost listenAddr(CLoginServer::getListenAddress()); - uint16 frontendPort = listenAddr.port(); + CInetHost listenHost = CLoginServer::getListenHost(); + uint16 frontendPort = listenHost.port(); if (frontendPort == 0) { @@ -1266,8 +1266,8 @@ void CFrontEndService::init() nlinfo( "Initializing receiving subsystem..." ); _ReceiveSub.init( frontendPort, lastAcceptableFrontendPort, _DgramLength, &_History, &_SendSub.clientIdCont() ); frontendPort = _ReceiveSub.dataSock()->localAddr().port(); - listenAddr.setPort( frontendPort ); - CLoginServer::setListenAddress(PublishFSHostAsIP.get() ? listenAddr.address().asIPString() : listenAddr.toString()); + listenHost.setPort( frontendPort ); + CLoginServer::setListenAddress(PublishFSHostAsIP.get() ? listenHost.address().asIPString() : listenHost.toString()); StalledMode = false; LastTickTime = 0; diff --git a/ryzom/server/src/frontend_service/quic_selfsign.cpp b/ryzom/server/src/frontend_service/quic_selfsign.cpp index fbeca98ee3..16d4881cda 100644 --- a/ryzom/server/src/frontend_service/quic_selfsign.cpp +++ b/ryzom/server/src/frontend_service/quic_selfsign.cpp @@ -40,20 +40,12 @@ #define CXPLAT_CERT_CREATION_EVENT_NAME L"RyzomCoreCertEvent" #define CXPLAT_CERT_CREATION_EVENT_WAIT 10000 -#define CXPLAT_CERTIFICATE_TEST_FRIENDLY_NAME L"RyzomCoreTestCert2" +#define CXPLAT_CERTIFICATE_TEST_FRIENDLY_NAME L"Ryzom Core 4 Server Development Certificate" #define CXPLAT_CERTIFICATE_TEST_CLIENT_FRIENDLY_NAME L"RyzomCoreTestClientCert" #define CXPLAT_KEY_CONTAINER_NAME L"RyzomCoreSelfSignKey2" #define CXPLAT_KEY_SIZE 2048 -#define CXPLAT_TEST_CERT_VALID_SERVER_FRIENDLY_NAME L"RyzomCoreTestServer" -#define CXPLAT_TEST_CERT_VALID_CLIENT_FRIENDLY_NAME L"RyzomCoreTestClient" -#define CXPLAT_TEST_CERT_EXPIRED_SERVER_FRIENDLY_NAME L"RyzomCoreTestExpiredServer" -#define CXPLAT_TEST_CERT_EXPIRED_CLIENT_FRIENDLY_NAME L"RyzomCoreTestExpiredClient" -#define CXPLAT_TEST_CERT_VALID_SERVER_SUBJECT_NAME "RyzomCoreTestServer" -#define CXPLAT_TEST_CERT_VALID_CLIENT_SUBJECT_NAME "RyzomCoreTestClient" -#define CXPLAT_TEST_CERT_EXPIRED_SERVER_SUBJECT_NAME "RyzomCoreTestExpiredServer" -#define CXPLAT_TEST_CERT_EXPIRED_CLIENT_SUBJECT_NAME "RyzomCoreTestExpiredClient" -#define CXPLAT_TEST_CERT_SELF_SIGNED_CLIENT_SUBJECT_NAME "RyzomCoreClient" +#define CXPLAT_TEST_CERT_SELF_SIGNED_CLIENT_SUBJECT_NAME L"RyzomCoreClient" #define CXPLAT_TEST_CERT_SELF_SIGNED_SERVER_SUBJECT_NAME "localhost" #define QuicTraceEvent(x, y, z, msg) nlwarning("%s", msg) @@ -266,7 +258,16 @@ CreateSubjAltNameExtension( _Out_ PCERT_EXTENSION CertExtension) { CERT_ALT_NAME_ENTRY AltName = { CERT_ALT_NAME_DNS_NAME }; - AltName.pwszDNSName = L"localhost"; + const NLNET::CInetHost &listenHost = NLNET::CLoginServer::getListenHost(); + std::string localHostName = listenHost.hostname(); + if (localHostName.empty() || NLNET::CIPv6Address(localHostName).isValid()) // IP address... + { + localHostName = NLNET::CInetHost::localHostName(); + if (NLNET::CIPv6Address(localHostName).isValid()) // IP address... + localHostName = CXPLAT_TEST_CERT_SELF_SIGNED_SERVER_SUBJECT_NAME; + } + std::wstring dnsName = NLMISC::utf8ToWide(localHostName); + AltName.pwszDNSName = (LPWSTR)dnsName.c_str(); CERT_ALT_NAME_INFO NameInfo; NameInfo.cAltEntry = 1; NameInfo.rgAltEntry = &AltName; @@ -819,7 +820,7 @@ void * CreateClientCertificate() { PCCERT_CONTEXT CertContext; - if (FAILED(CreateSelfSignedCertificate(L"CN=MsQuicClient", TRUE, &CertContext))) + if (FAILED(CreateSelfSignedCertificate(L"CN=" CXPLAT_TEST_CERT_SELF_SIGNED_CLIENT_SUBJECT_NAME, TRUE, &CertContext))) { return NULL; } @@ -831,7 +832,16 @@ void * CreateServerCertificate() { PCCERT_CONTEXT CertContext; - if (FAILED(CreateSelfSignedCertificate(L"CN=localhost", FALSE, &CertContext))) + const NLNET::CInetHost &listenHost = NLNET::CLoginServer::getListenHost(); + std::string localHostName = listenHost.hostname(); + if (localHostName.empty() || NLNET::CIPv6Address(localHostName).isValid()) // IP address... + { + localHostName = NLNET::CInetHost::localHostName(); + if (NLNET::CIPv6Address(localHostName).isValid()) // IP address... + localHostName = CXPLAT_TEST_CERT_SELF_SIGNED_SERVER_SUBJECT_NAME; + } + std::wstring subjectName = L"CN=" + NLMISC::utf8ToWide(localHostName); + if (FAILED(CreateSelfSignedCertificate((LPCWSTR)subjectName.c_str(), FALSE, &CertContext))) { return NULL; } diff --git a/ryzom/server/src/frontend_service/quic_transceiver.cpp b/ryzom/server/src/frontend_service/quic_transceiver.cpp index cd5ddb3ee8..45a410def7 100644 --- a/ryzom/server/src/frontend_service/quic_transceiver.cpp +++ b/ryzom/server/src/frontend_service/quic_transceiver.cpp @@ -20,6 +20,7 @@ #include "nel/misc/mutex.h" #include "nel/misc/buf_fifo.h" #include "nel/misc/string_view.h" +#include "nel/misc/variable.h" #include "config.h" #include "quic_selfsign.h" @@ -61,6 +62,13 @@ using namespace NLNET; namespace /* anonymous */ { +CVariable QuicConnection("fs", "QuicConnection", "", true, 0, true); +CVariable QuicCertificate("fs", "QuicCertificate", "", "", 0, true); +CVariable QuicPrivateKey("fs", "QuicPrivateKey", "", "", 0, true); +CVariable QuicLetsEncryptLive("fs", "QuicLetsEncryptLive", "", "/home/nevrax/letsencrypt/live", 0, true); + +CAtomicInt s_UserContextCount = CAtomicInt(TNotInitialized()); + // This really hammers fast class CAtomicFlagLock { @@ -176,21 +184,38 @@ CQuicTransceiver::~CQuicTransceiver() release(); } +bool CQuicTransceiver::isConfigEnabled() const +{ + return QuicConnection.get(); +} + void CQuicTransceiver::start(uint16 port) { stop(); + if (!QuicConnection.get()) + { + nlinfo("QUIC listener disabled"); + return; + } + if (!MsQuic) { nlwarning("QUIC API not available"); + return; } - static const CStringView protocolName = "ryzomcore4"; - static const QUIC_BUFFER alpn = { (uint32)protocolName.size(), (uint8 *)protocolName.data() }; + nldebug("Configure QUIC at port %i", (int)port); + // Protocol feature levels, corresponds to Ryzom Core release versions, keep datagram-only support to give users and server owners the option to keep bandwidth restricted + // 4.1: Only datagram support (restricted bandwidth, same behaviour as UDP) + // 4.2?: Add up to 4 unidirectional streams from the server to client to send long impulses (keep bandwidth from client restricted) (DB_INIT, STRING, STRING_MANAGER, MODULE_GATEWAY) + // 4.3?: Add a single bidirectional stream opened by the client for the scenario editor gateway (more efficient MODULE_GATEWAY replacement) + static const CStringView protocolName41 = "ryzomcore/4.1"; + static const QUIC_BUFFER alpn = { (uint32)protocolName41.size(), (uint8 *)protocolName41.data() }; // Configuration, initialized in start, but destroyed on release only (may attempt more than once) QUIC_STATUS status = QUIC_STATUS_SUCCESS; - if (!m->Configuration) + if (!m->Configuration) // Might need a configuration per supported version... or just the highest supported :) { QUIC_SETTINGS settings = { 0 }; settings.DatagramReceiveEnabled = TRUE; @@ -214,50 +239,108 @@ void CQuicTransceiver::start(uint16 port) return; } - // TODO: Certificate should depend on a configuration variable, if it's configured, at least for production + if (QuicCertificate.get().empty() || QuicPrivateKey.get().empty()) + { + // Attempt to auto configure certificate from well-known locations + const NLNET::CInetHost &listenHost = NLNET::CLoginServer::getListenHost(); + std::string localHostName = listenHost.hostname(); + if (localHostName.empty() || NLNET::CIPv6Address(localHostName).isValid()) // IP address... + localHostName = NLNET::CInetHost::localHostName(); + if (!localHostName.empty() && !NLNET::CIPv6Address(localHostName).isValid()) // If not empty and not an IP address + { + std::string fullchain = QuicLetsEncryptLive.get() + "/" + localHostName + "/fullchain.pem"; + std::string privkey = QuicLetsEncryptLive.get() + "/" + localHostName + "/privkey.pem"; + nldebug("Check if %s and %s exist", fullchain.c_str(), privkey.c_str()); + if (CFile::fileExists(fullchain) && CFile::fileExists(privkey)) + { + nldebug("They exist"); + QuicCertificate.set(fullchain); + QuicPrivateKey.set(privkey); + } + else + { + nldebug("They don't exist"); + } + } + } - // Programmatically create a self signed certificate, only valid in Windows - // This is very useful for development servers - uint8 certHash[20]; - void *schannelCert = FES_findOrCreateSelfSignedCertificate(certHash); // PCCERT_CONTEXT - if (schannelCert) + // Certificate depends on a configuration variable, if it's configured, at least for production + bool liveCert = false; + if (!QuicCertificate.get().empty() && !QuicPrivateKey.get().empty()) { // Server credentials + nlinfo("Using certificate %s with key %s", QuicCertificate.get().c_str(), QuicPrivateKey.get().c_str()); + std::string certificate = NLMISC::utf8ToMbcs(QuicCertificate.get()); + std::string privateKey = NLMISC::utf8ToMbcs(QuicPrivateKey.get()); QUIC_CREDENTIAL_CONFIG credConfig; + QUIC_CERTIFICATE_FILE certFile; memset(&credConfig, 0, sizeof(credConfig)); - credConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_CONTEXT; + credConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE; credConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE; - credConfig.CertificateContext = (QUIC_CERTIFICATE *)schannelCert; + credConfig.CertificateFile = &certFile; + certFile.CertificateFile = certificate.c_str(); + certFile.PrivateKeyFile = privateKey.c_str(); status = MsQuic->ConfigurationLoadCredential(m->Configuration, &credConfig); if (QUIC_FAILED(status)) { - nlwarning("MsQuic->ConfigurationLoadCredential failed with status 0x%x", status); - FES_freeSelfSignedCertificate((void *)schannelCert); - schannelCert = nullptr; + nlwarning("MsQuic->ConfigurationLoadCredential failed with status 0x%x, try a self-signed", status); + } + else + { + // TODO: Flag to reload somehow when the files change! + liveCert = true; } } else { -#ifdef NL_OS_WINDOWS - nlwarning("Failed to create self-signed certificate"); -#endif + nlwarning("No certificate configured for QUIC on hostname %s, try a self-signed", NLNET::CLoginServer::getListenHost().hostname().c_str()); } - if (schannelCert) - { - // Don't need the certificate anymore (I guess? Let's hope it's been copied by MsQuic!) - nlinfo("Self-signed certificate hash: %s", NLMISC::toHexa(certHash, 20).c_str()); - FES_freeSelfSignedCertificate((void *)schannelCert); - schannelCert = nullptr; - } - else + + if (!liveCert) { - // Either we could not create a self-signed certificate on Windows, or could not load it into the configuration - // Try with an OpenSSL certificate - // TODO - nlwarning("Self signed OpenSSL certificates are not yet supported, QUIC will not work. Specify a certificate in the configuration file."); - MsQuic->ConfigurationClose(m->Configuration); - m->Configuration = nullptr; - return; + // Programmatically create a self signed certificate, only valid in Windows + // This is very useful for development servers + uint8 certHash[20]; + void *certContext = FES_findOrCreateSelfSignedCertificate(certHash); // PCCERT_CONTEXT + if (certContext) + { + // Server credentials + QUIC_CREDENTIAL_CONFIG credConfig; + memset(&credConfig, 0, sizeof(credConfig)); + credConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_CONTEXT; + credConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE; + credConfig.CertificateContext = (QUIC_CERTIFICATE *)certContext; + status = MsQuic->ConfigurationLoadCredential(m->Configuration, &credConfig); + if (QUIC_FAILED(status)) + { + nlwarning("MsQuic->ConfigurationLoadCredential failed with status 0x%x", status); + FES_freeSelfSignedCertificate((void *)certContext); + certContext = nullptr; + } + } + else + { +#ifdef NL_OS_WINDOWS + nlwarning("Failed to create self-signed certificate"); +#endif + } + if (certContext) + { + // Don't need the certificate anymore (I guess? Let's hope it's been copied by MsQuic!) + nlinfo("Self-signed certificate hash: %s", NLMISC::toHexa(certHash, 20).c_str()); + FES_freeSelfSignedCertificate((void *)certContext); + certContext = nullptr; + } + else + { + // Either we could not create a self-signed certificate on Windows, or could not load it into the configuration + // Try with an OpenSSL certificate + // TODO + nlwarning("Self signed OpenSSL certificate generation is not yet supported, QUIC will not work. Specify a certificate in the configuration file"); + MsQuic->ConfigurationClose(m->Configuration); + m->Configuration = nullptr; + return; + } } } @@ -274,17 +357,24 @@ void CQuicTransceiver::start(uint16 port) } // Start listening - status = MsQuic->ListenerStart(m->Listener, &alpn, 1, &addr); + { + std::unique_lock lock(m->ListenerStopMutex); // Lock m->Listening + status = MsQuic->ListenerStart(m->Listener, &alpn, 1, &addr); + if (!QUIC_FAILED(status)) + { + // Ok + m->Listening.store(true, std::memory_order_relaxed); // Released by lock + nlinfo("Listening for QUIC connections on port %i", (int)port); + } + } + + // Failed if (QUIC_FAILED(status)) { stop(); nlwarning("MsQuic->ListenerStart failed with status 0x%x", status); return; } - - // Ok - m->Listening.store(true, std::memory_order_release); - nlinfo("Listening for QUIC connections on port %i", (int)port); } void CQuicTransceiver::stop() @@ -298,49 +388,62 @@ void CQuicTransceiver::stop() try { // Wait for stop + nldebug("Wait for QUIC listener stop"); std::unique_lock lock(m->ListenerStopMutex); - m->ListenerStopCondition.wait(lock, [this] { return !m->Listening; }); + m->ListenerStopCondition.wait(lock, [this] { return !m->Listening.load(std::memory_order_relaxed); /* Aqcuired by lock */ }); + nldebug("Stop wait OK"); } catch (const std::exception &e) { nlwarning("Exception while waiting for listener stop: %s", e.what()); } } + nldebug("Close listener"); MsQuic->ListenerClose(m->Listener); m->Listener = null; + nldebug("Listener closed"); } } void CQuicTransceiver::release() { // Close configuration + nldebug("Closing configuration"); if (m->Configuration) { MsQuic->ConfigurationClose(m->Configuration); m->Configuration = null; } + nldebug("Configuration closed"); // Close registration + nldebug("Closing registration"); if (m->Registration) { MsQuic->RegistrationClose(m->Registration); m->Registration = null; } + nldebug("Registration closed"); // Close library + nldebug("Closing library"); if (MsQuic) { MsQuicClose(MsQuic); MsQuic = null; } + nldebug("Library closed"); } CQuicUserContext::CQuicUserContext() { + nldebug("Create QUIC user context, total %i", (int)(s_UserContextCount++)); } CQuicUserContext::~CQuicUserContext() { + nldebug("Destroy QUIC user context, total %i", (int)(s_UserContextCount--)); + // This should never get called before the connection is shutdown, // since we increase the reference when the connection gets opened, // and decrease the reference when the connection is shutdown. @@ -373,14 +476,20 @@ _Function_class_(QUIC_LISTENER_CALLBACK) // They're in. MsQuic->SetCallbackHandler(ev->NEW_CONNECTION.Connection, (void *)CQuicTransceiverImpl::connectionCallback, (void *)user); status = MsQuic->ConnectionSetConfiguration(ev->NEW_CONNECTION.Connection, m->Configuration); - nlwarning("New QUIC connection"); + nldebug("New QUIC connection"); + if (QUIC_FAILED(status)) + { + nlwarning("MsQuic->ConnectionSetConfiguration failed with status 0x%x", status); + // Assuming we still get a QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE after this, which will decreaseRef the user context! + } break; } case QUIC_LISTENER_EVENT_STOP_COMPLETE: { - nlwarning("QUIC listener stopped"); + nldebug("QUIC listener stopped"); std::unique_lock lock(m->ListenerStopMutex); - m->Listening.store(false, std::memory_order_release); + m->Listening.store(false, std::memory_order_relaxed); // Released by lock m->ListenerStopCondition.notify_all(); + nldebug("QUIC listener stop notified"); status = QUIC_STATUS_SUCCESS; break; } @@ -437,7 +546,7 @@ _Function_class_(QUIC_CONNECTION_CALLBACK) break; } case QUIC_CONNECTION_EVENT_DATAGRAM_RECEIVED: - nldebug("Datagram received"); + // nldebug("Datagram received"); // YES PLEASE self->datagramReceived(user, ev->DATAGRAM_RECEIVED.Buffer->Buffer, ev->DATAGRAM_RECEIVED.Buffer->Length); status = QUIC_STATUS_SUCCESS; @@ -616,6 +725,7 @@ CQuicTransceiver::~CQuicTransceiver() void CQuicTransceiver::start(uint16 port) { + nlwarning("QUIC not supported, no listener started"); } void CQuicTransceiver::stop() diff --git a/ryzom/server/src/frontend_service/quic_transceiver.h b/ryzom/server/src/frontend_service/quic_transceiver.h index 7b1af8f1f5..0c610b159b 100644 --- a/ryzom/server/src/frontend_service/quic_transceiver.h +++ b/ryzom/server/src/frontend_service/quic_transceiver.h @@ -199,6 +199,9 @@ class CQuicTransceiver CQuicTransceiver(uint32 msgsize); ~CQuicTransceiver(); + /// Check if QUIC is enabled in the config + bool isConfigEnabled() const; + /// Start listening void start(uint16 port);