From c456596bd762650777dfa2118cca34021a529112 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 2 Oct 2026 17:28:41 +1000 Subject: [PATCH] [client] network: report detailed transport failures Report the exact MsQuic status and initialization stage when network transport construction or connection fails. Diagnose certificate, stream, protocol, and peer shutdown failures instead of returning only the generic transport error. --- client/transports/Network/connection.c | 114 ++++++++++++++++++++++--- client/transports/Network/network.c | 66 ++++++++++++-- 2 files changed, 161 insertions(+), 19 deletions(-) diff --git a/client/transports/Network/connection.c b/client/transports/Network/connection.c index 0767b0ba..6dba878b 100644 --- a/client/transports/Network/connection.c +++ b/client/transports/Network/connection.c @@ -2194,6 +2194,8 @@ static LG_TransportStatus networkConnectionOpenBulkStream( static void networkConnectionProtocolFailure(NetworkConnection * this) { + if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) + DEBUG_ERROR("Network protocol processing failed"); networkConnectionFail(this, LG_TRANSPORT_ERROR); LG_LOCK(this->lock); @@ -2346,6 +2348,8 @@ static void networkConnectionTrustDecision(void * opaque, bool trusted) QUIC_TLS_ALERT_CODE_BAD_CERTIFICATE); if (QUIC_FAILED(status)) { + DEBUG_ERROR("MsQuic certificate validation completion failed: 0x%08x", + (unsigned)status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); @@ -2358,13 +2362,19 @@ static QUIC_STATUS networkConnectionCertificate(NetworkConnection * this, const QUIC_BUFFER * certificate = (const QUIC_BUFFER *)event->PEER_CERTIFICATE_RECEIVED.Certificate; if (!certificate || !certificate->Buffer || !certificate->Length) + { + DEBUG_ERROR("Network server did not provide a usable certificate"); return QUIC_STATUS_BAD_CERTIFICATE; + } uint8_t fingerprint[NETWORK_FINGERPRINT_SIZE]; char fingerprintText[NETWORK_FINGERPRINT_TEXT_SIZE]; if (!networkFingerprintSHA256(certificate->Buffer, certificate->Length, fingerprint)) + { + DEBUG_ERROR("Failed to fingerprint the network server certificate"); return QUIC_STATUS_BAD_CERTIFICATE; + } networkFingerprintFormat(fingerprint, fingerprintText); switch (networkKnownHostsCheck( @@ -2445,6 +2455,8 @@ static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, QUIC_STREAM_OPEN_FLAG_NONE, networkStreamCallback, this, &stream); if (QUIC_FAILED(status)) { + DEBUG_ERROR("MsQuic control StreamOpen failed: 0x%08x", + (unsigned)status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); @@ -2459,6 +2471,8 @@ static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, QUIC_STREAM_START_FLAG_SHUTDOWN_ON_FAIL); if (QUIC_FAILED(status)) { + DEBUG_ERROR("MsQuic control StreamStart failed: 0x%08x", + (unsigned)status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); @@ -2484,7 +2498,8 @@ static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, { const QUIC_UINT62 error = event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode; - if (error && networkConnectionRecordPeerError(this, error)) + if (!atomic_load_explicit(&this->stopping, memory_order_acquire) && + networkConnectionRecordPeerError(this, error)) DEBUG_WARN("Network server closed the connection: error 0x%llx", (unsigned long long)error); networkConnectionFail(this, LG_TRANSPORT_DISCONNECTED); @@ -2569,9 +2584,17 @@ static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, switch (event->Type) { case QUIC_STREAM_EVENT_START_COMPLETE: - if (QUIC_FAILED(event->START_COMPLETE.Status) || - !networkConnectionSendHello(this)) + if (QUIC_FAILED(event->START_COMPLETE.Status)) { + DEBUG_ERROR("MsQuic control stream start failed: 0x%08x", + (unsigned)event->START_COMPLETE.Status); + networkConnectionFail(this, LG_TRANSPORT_ERROR); + this->api->ConnectionShutdown(this->connection, + QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); + } + else if (!networkConnectionSendHello(this)) + { + DEBUG_ERROR("Failed to send the network control handshake"); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); @@ -2582,6 +2605,7 @@ static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, if (!networkConnectionReceive( this, event->RECEIVE.Buffers, event->RECEIVE.BufferCount)) { + DEBUG_ERROR("Failed to process network control stream data"); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); @@ -2600,8 +2624,26 @@ static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, } case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: + if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) + DEBUG_ERROR("Network server closed the control stream"); + this->api->ConnectionShutdown(this->connection, + QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); + return QUIC_STATUS_SUCCESS; + case QUIC_STREAM_EVENT_PEER_SEND_ABORTED: + if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) + DEBUG_ERROR("Network server aborted the control send stream: " + "0x%llx", + (unsigned long long)event->PEER_SEND_ABORTED.ErrorCode); + this->api->ConnectionShutdown(this->connection, + QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); + return QUIC_STATUS_SUCCESS; + case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED: + if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) + DEBUG_ERROR("Network server aborted the control receive stream: " + "0x%llx", + (unsigned long long)event->PEER_RECEIVE_ABORTED.ErrorCode); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; @@ -2794,11 +2836,17 @@ bool networkConnectionCreate(NetworkConnection ** result, (!!params->trustPrompt != !!params->trustCancel) || params->capabilityCount > LG_NET_MAX_CAPABILITY_RECORDS || (params->capabilityCount && !params->capabilities)) + { + DEBUG_ERROR("Invalid network connection parameters"); return false; + } NetworkConnection * this = calloc(1, sizeof(*this)); if (!this) + { + DEBUG_ERROR("Failed to allocate the network connection"); return false; + } LG_LOCK_INIT(this->lock); LG_LOCK_INIT(this->stateCallbackLock); @@ -2812,11 +2860,19 @@ bool networkConnectionCreate(NetworkConnection ** result, if (!networkCapabilityOfferValid(capability) || capability->maxUnidirectionalStreams > NETWORK_MAX_PEER_STREAMS - offeredBulkStreams) + { + DEBUG_ERROR("Invalid network capability at index %u", + (unsigned)index); goto fail; + } const uint16_t serviceMask = UINT16_C(1) << capability->service; if (this->offeredServices & serviceMask) + { + DEBUG_ERROR("Duplicate network capability for service %u", + (unsigned)capability->service); goto fail; + } this->offeredCapabilities[capability->service] = *capability; this->offeredServices |= serviceMask; offeredBulkStreams += capability->maxUnidirectionalStreams; @@ -2828,7 +2884,10 @@ bool networkConnectionCreate(NetworkConnection ** result, this->endpoint = networkEndpoint(params->address, params->port); if (!this->address || (params->password && !this->password) || !this->endpoint) + { + DEBUG_ERROR("Failed to allocate the network connection endpoint"); goto fail; + } this->port = params->port; this->knownHosts = params->knownHosts; @@ -2846,26 +2905,41 @@ bool networkConnectionCreate(NetworkConnection ** result, if (!this->connectEvent || !this->closeEvent || !this->bulkReceiveEvent || !this->bulkReceiveIdle || !lgSignalEvent(this->bulkReceiveIdle)) + { + DEBUG_ERROR("Failed to initialize the network connection events"); goto fail; + } this->receiveData = malloc(NETWORK_MAX_CONTROL_BUFFER); this->receivePayload = malloc(NETWORK_MAX_CONTROL_PAYLOAD); if (!this->receiveData || !this->receivePayload || !lgNetStreamParserInit(&this->receiveParser, this->receiveData, NETWORK_MAX_CONTROL_BUFFER, NETWORK_MAX_CONTROL_PAYLOAD)) + { + DEBUG_ERROR("Failed to initialize the network receive buffers"); goto fail; + } - if (QUIC_FAILED(MsQuicOpen2(&this->api))) + QUIC_STATUS status = MsQuicOpen2(&this->api); + if (QUIC_FAILED(status)) + { + DEBUG_ERROR("MsQuicOpen2 failed: 0x%08x", (unsigned)status); goto fail; + } const QUIC_REGISTRATION_CONFIG registration = { .AppName = "looking-glass-client", .ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY, }; - if (QUIC_FAILED(this->api->RegistrationOpen( - ®istration, &this->registration))) + status = this->api->RegistrationOpen( + ®istration, &this->registration); + if (QUIC_FAILED(status)) + { + DEBUG_ERROR("MsQuic RegistrationOpen failed: 0x%08x", + (unsigned)status); goto fail; + } const QUIC_BUFFER alpn = { @@ -2902,10 +2976,15 @@ bool networkConnectionCreate(NetworkConnection ** result, .DatagramReceiveEnabled = true, .StreamRecvWindowUnidiDefault = NETWORK_STREAM_RECV_WINDOW, }; - if (QUIC_FAILED(this->api->ConfigurationOpen(this->registration, - &alpn, 1, &settings, sizeof(settings), NULL, - &this->configuration))) + status = this->api->ConfigurationOpen(this->registration, + &alpn, 1, &settings, sizeof(settings), NULL, + &this->configuration); + if (QUIC_FAILED(status)) + { + DEBUG_ERROR("MsQuic ConfigurationOpen failed: 0x%08x", + (unsigned)status); goto fail; + } /* PKI chain validation is disabled because identity is enforced by the * certificate callback and the persistent TOFU store above. */ @@ -2917,13 +2996,21 @@ bool networkConnectionCreate(NetworkConnection ** result, QUIC_CREDENTIAL_FLAG_INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAG_USE_PORTABLE_CERTIFICATES, }; - if (QUIC_FAILED(this->api->ConfigurationLoadCredential( - this->configuration, &credential))) + status = this->api->ConfigurationLoadCredential( + this->configuration, &credential); + if (QUIC_FAILED(status)) + { + DEBUG_ERROR("MsQuic ConfigurationLoadCredential failed: 0x%08x", + (unsigned)status); goto fail; + } if (!lgCreateThread("networkReceive", networkBulkReceiveThread, this, &this->bulkReceiveThread)) + { + DEBUG_ERROR("Failed to create the network receive worker"); goto fail; + } *result = this; return true; @@ -2988,7 +3075,10 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, void * cancelledOpaque) { if (!this || !session || this->connection) + { + DEBUG_ERROR("Invalid network connection state for connect"); return LG_TRANSPORT_ERROR; + } if (cancelled && cancelled(cancelledOpaque)) return LG_TRANSPORT_DISCONNECTED; @@ -3043,6 +3133,8 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, networkConnectionCallback, this, &this->connection); if (QUIC_FAILED(status)) { + DEBUG_ERROR("MsQuic ConnectionOpen failed for %s: 0x%08x", + this->endpoint, (unsigned)status); LG_LOCK(this->lock); this->connecting = false; this->connectStatus = LG_TRANSPORT_ERROR; diff --git a/client/transports/Network/network.c b/client/transports/Network/network.c index 42a925d4..7206deda 100644 --- a/client/transports/Network/network.c +++ b/client/transports/Network/network.c @@ -487,11 +487,17 @@ static void networkSendReady(void * opaque, LGNetService service, static bool networkCreate(LG_Transport ** result) { if (!result) + { + DEBUG_ERROR("Network transport creation requires an output pointer"); return false; + } LG_Transport * this = calloc(1, sizeof(*this)); if (!this) + { + DEBUG_ERROR("Failed to allocate the network transport"); return false; + } LG_LOCK_INIT(this->callbackLock); LG_LOCK_INIT(this->stateLock); @@ -502,17 +508,31 @@ static bool networkCreate(LG_Transport ** result) this->videoStatus.pointer.epoch = 1; this->videoStatus.pointer.reason = LG_TRANSPORT_DISCONNECTED; if (!networkKnownHostsInit(&this->knownHosts)) + { + DEBUG_ERROR("Failed to initialize the network known-hosts store"); goto fail; + } this->promptIdle = lgCreateEvent(false, 0); - if (!this->promptIdle || !lgSignalEvent(this->promptIdle)) + if (!this->promptIdle) + { + DEBUG_ERROR("Failed to create the network trust-prompt event"); goto fail; + } + if (!lgSignalEvent(this->promptIdle)) + { + DEBUG_ERROR("Failed to initialize the network trust-prompt event"); + goto fail; + } const char * address = option_get_string("network", "address"); const uint16_t port = (uint16_t)option_get_int("network", "port"); char endpoint[NETWORK_ENDPOINT_TEXT_SIZE]; if (!address) + { + DEBUG_ERROR("The network server address is not configured"); goto fail; + } snprintf(endpoint, sizeof(endpoint), "[%s]:%u", address, port); if (option_get_bool("network", "forgetCertificate") && !networkKnownHostsForget(&this->knownHosts, endpoint)) @@ -538,15 +558,45 @@ static bool networkCreate(LG_Transport ** result) .opaque = this, }; if (!networkConnectionCreate(&this->connection, ¶ms)) + { + DEBUG_ERROR("Failed to create the network connection"); goto fail; - if (!networkAudioCreate(&this->audio, this->connection) || - !networkClipboardCreate(&this->clipboard, this->connection) || - !networkControlCreate(&this->control, this->connection) || - !networkCursorCreate(&this->cursor, this->connection) || - !networkInputCreate(&this->input, this->connection) || - !networkRecoveryCreate(&this->recovery, this->connection) || - !networkVideoCreate(&this->video, this->connection)) + } + if (!networkAudioCreate(&this->audio, this->connection)) + { + DEBUG_ERROR("Failed to create the network audio service"); goto fail; + } + if (!networkClipboardCreate(&this->clipboard, this->connection)) + { + DEBUG_ERROR("Failed to create the network clipboard service"); + goto fail; + } + if (!networkControlCreate(&this->control, this->connection)) + { + DEBUG_ERROR("Failed to create the network control service"); + goto fail; + } + if (!networkCursorCreate(&this->cursor, this->connection)) + { + DEBUG_ERROR("Failed to create the network cursor service"); + goto fail; + } + if (!networkInputCreate(&this->input, this->connection)) + { + DEBUG_ERROR("Failed to create the network input service"); + goto fail; + } + if (!networkRecoveryCreate(&this->recovery, this->connection)) + { + DEBUG_ERROR("Failed to create the network recovery service"); + goto fail; + } + if (!networkVideoCreate(&this->video, this->connection)) + { + DEBUG_ERROR("Failed to create the network video service"); + goto fail; + } networkVideoSetStatusListener(this->video, networkVideoStatus, this); *result = this;