/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "connection.h" #include "fingerprint.h" #include "common/debug.h" #include "common/event.h" #include "common/locking.h" #include #if !defined(QUIC_API_VERSION_2) #error "The network transport requires MsQuic API version 2" #endif #include #include #include #include #define NETWORK_CONNECT_POLL_MS 50U #define NETWORK_HANDSHAKE_TIMEOUT_MS 5000U #define NETWORK_IDLE_TIMEOUT_MS 10000U #define NETWORK_KEEPALIVE_INTERVAL_MS 1000U #define NETWORK_INITIAL_RTT_MS 1U #define NETWORK_MAX_PEER_STREAMS 16U #define NETWORK_MAX_HEADER_SIZE 4096U #define NETWORK_MAX_CONTROL_PAYLOAD (1024U * 1024U) #define NETWORK_MAX_CONTROL_BUFFER \ (2U * (NETWORK_MAX_HEADER_SIZE + NETWORK_MAX_CONTROL_PAYLOAD)) #define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U #define NETWORK_HELLO_REQUEST_ID UINT64_C(1) struct NetworkSend { QUIC_BUFFER buffer; uint8_t data[]; }; struct NetworkConnection { const QUIC_API_TABLE * api; HQUIC registration; HQUIC configuration; HQUIC connection; HQUIC controlStream; LGEvent * connectEvent; LGEvent * closeEvent; LG_Lock lock; LG_Lock stateCallbackLock; atomic_bool stopping; atomic_bool sessionValid; char * address; uint16_t port; char * password; char * endpoint; NetworkKnownHosts * knownHosts; NetworkTrustPromptFn trustPrompt; NetworkTrustCancelFn trustCancel; NetworkPacketFn packet; NetworkConnectionStateFn state; void * opaque; bool certificatePending; char pendingFingerprint[NETWORK_FINGERPRINT_TEXT_SIZE]; LG_TransportStatus connectStatus; bool connecting; LG_TransportSession session; uint64_t sessionEpoch; uint64_t sendSequence; uint64_t receiveSequence; bool helloAcknowledged; bool authenticationComplete; bool capabilitiesComplete; bool connectFailed; uint32_t expectedCapabilities; uint32_t receivedCapabilities; uint16_t receivedServices; uint16_t reliableServices; uint32_t maxPayloadLength; uint32_t maxDatagramPayload; uint32_t serviceMaxPayload[LG_NET_SERVICE_USB + 1U]; uint32_t serviceMaxDatagram[LG_NET_SERVICE_USB + 1U]; uint8_t * receiveData; size_t receiveSize; size_t receiveCapacity; }; static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, void * opaque, QUIC_CONNECTION_EVENT * event); static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event); static QUIC_STATUS QUIC_API networkRejectedStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event); static char * networkDuplicate(const char * value) { if (!value) return NULL; const size_t length = strlen(value) + 1U; char * result = malloc(length); if (result) memcpy(result, value, length); return result; } static char * networkEndpoint(const char * address, uint16_t port) { const size_t length = strlen(address) + 10U; char * endpoint = malloc(length); if (!endpoint) return NULL; snprintf(endpoint, length, "[%s]:%u", address, port); return endpoint; } static uint16_t networkServiceVersion(LGNetService service) { switch (service) { case LG_NET_SERVICE_CORE: return LG_NET_CORE_VERSION_CURRENT; case LG_NET_SERVICE_RECOVERY: return LG_NET_RECOVERY_VERSION_CURRENT; case LG_NET_SERVICE_VIDEO: return LG_NET_VIDEO_VERSION_CURRENT; case LG_NET_SERVICE_CURSOR: return LG_NET_CURSOR_VERSION_CURRENT; case LG_NET_SERVICE_INPUT: return LG_NET_INPUT_VERSION_CURRENT; case LG_NET_SERVICE_AUDIO: return LG_NET_AUDIO_VERSION_CURRENT; case LG_NET_SERVICE_CLIPBOARD: return LG_NET_CLIPBOARD_VERSION_CURRENT; case LG_NET_SERVICE_FILE: return LG_NET_FILE_VERSION_CURRENT; case LG_NET_SERVICE_USB: return LG_NET_USB_VERSION_RESERVED; } return 0; } static uint16_t networkReadU16(const uint8_t * data) { return (uint16_t)data[0] | (uint16_t)((uint16_t)data[1] << 8); } static uint32_t networkReadU32(const uint8_t * data) { return (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); } static void networkConnectionSetState(NetworkConnection * this, bool connected, LG_TransportStatus reason) { LG_LOCK(this->stateCallbackLock); atomic_store_explicit( &this->sessionValid, connected, memory_order_release); if (this->state) this->state(this->opaque, connected, reason); LG_UNLOCK(this->stateCallbackLock); } static void networkConnectionFail(NetworkConnection * this, LG_TransportStatus status) { const bool wasStopping = atomic_exchange_explicit( &this->stopping, true, memory_order_acq_rel); LG_TransportStatus reason; LG_LOCK(this->lock); if (this->connecting && !this->connectFailed) { this->connectStatus = wasStopping ? LG_TRANSPORT_DISCONNECTED : status; this->connectFailed = true; } reason = this->connectStatus == LG_TRANSPORT_OK ? status : this->connectStatus; LG_UNLOCK(this->lock); networkConnectionSetState(this, false, reason); lgSignalEvent(this->connectEvent); } static bool networkConnectionSendPacket(NetworkConnection * this, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength, bool allowConnecting) { if (payloadLength > NETWORK_MAX_CONTROL_PAYLOAD || (payloadLength && !payload) || !lgNetServiceKnown(service) || !lgNetMessageKnown(service, messageType) || serviceVersion != networkServiceVersion(service) || (flags & (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) || atomic_load_explicit(&this->stopping, memory_order_acquire)) return false; const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; struct NetworkSend * send = malloc(sizeof(*send) + packetSize); if (!send) return false; LGNetEnvelope envelope; lgNetEnvelopeInit(&envelope, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); envelope.flags = flags | LG_NET_ENVELOPE_RELIABLE; envelope.payloadLength = payloadLength; envelope.componentEpoch = componentEpoch; envelope.requestID = requestID; LG_LOCK(this->lock); HQUIC stream = this->controlStream; const bool serviceReady = allowConnecting || service == LG_NET_SERVICE_CORE || ((this->receivedServices & (UINT16_C(1) << service)) && (this->reliableServices & (UINT16_C(1) << service))); const uint32_t maxPayloadLength = allowConnecting ? NETWORK_MAX_CONTROL_PAYLOAD : service == LG_NET_SERVICE_CORE ? this->maxPayloadLength : this->serviceMaxPayload[service]; if (!stream || (!allowConnecting && !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) || !serviceReady || payloadLength > maxPayloadLength || this->sendSequence == UINT64_MAX) { LG_UNLOCK(this->lock); free(send); return false; } envelope.sessionEpoch = this->sessionEpoch; envelope.sequence = ++this->sendSequence; if (!lgNetPacketEncode(send->data, packetSize, &envelope, payload)) { LG_UNLOCK(this->lock); free(send); return false; } send->buffer.Buffer = send->data; send->buffer.Length = (uint32_t)packetSize; const QUIC_STATUS status = this->api->StreamSend(stream, &send->buffer, 1, QUIC_SEND_FLAG_NONE, send); LG_UNLOCK(this->lock); if (QUIC_FAILED(status)) { free(send); return false; } return true; } static bool networkConnectionSendHello(NetworkConnection * this) { LGNetHello hello; lgNetHelloInit(&hello, LG_NET_ROLE_CLIENT); hello.maxPayloadLength = NETWORK_MAX_CONTROL_PAYLOAD; hello.maxDatagramPayload = NETWORK_MAX_DATAGRAM_PAYLOAD; if (this->password && *this->password) { DEBUG_ERROR("Network password authentication is not implemented yet"); return false; } uint8_t payload[LG_NET_HELLO_WIRE_SIZE]; if (!lgNetHelloEncode(payload, sizeof(payload), &hello)) return false; return networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_HELLO, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, payload, sizeof(payload), true); } static void networkConnectionReady(NetworkConnection * this) { LG_LOCK(this->stateCallbackLock); LG_LOCK(this->lock); if (!this->connecting || this->connectStatus == LG_TRANSPORT_OK || atomic_load_explicit(&this->stopping, memory_order_acquire)) { LG_UNLOCK(this->lock); LG_UNLOCK(this->stateCallbackLock); return; } this->connectStatus = LG_TRANSPORT_OK; atomic_store_explicit(&this->sessionValid, true, memory_order_release); LG_UNLOCK(this->lock); if (this->state) this->state(this->opaque, true, LG_TRANSPORT_OK); LG_UNLOCK(this->stateCallbackLock); lgSignalEvent(this->connectEvent); } static bool networkConnectionHandleCapability(NetworkConnection * this, const LGNetPacketView * packet) { LGNetCapability capability; if (lgNetCapabilityDecode(&capability, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) return false; LG_LOCK(this->lock); if (!this->helloAcknowledged || this->capabilitiesComplete || this->receivedCapabilities >= this->expectedCapabilities || capability.service <= LG_NET_SERVICE_CORE || capability.service > LG_NET_SERVICE_USB || !(capability.flags & LG_NET_CAPABILITY_SELECTED) || capability.versionMin != networkServiceVersion(capability.service) || capability.maxPayloadLength > this->maxPayloadLength || capability.maxDatagramPayload > this->maxDatagramPayload || (this->receivedServices & (UINT16_C(1) << capability.service))) { LG_UNLOCK(this->lock); return false; } this->receivedServices |= UINT16_C(1) << capability.service; if (capability.flags & LG_NET_CAPABILITY_RELIABLE) this->reliableServices |= UINT16_C(1) << capability.service; this->serviceMaxPayload[capability.service] = capability.maxPayloadLength; this->serviceMaxDatagram[capability.service] = capability.maxDatagramPayload; ++this->receivedCapabilities; LG_UNLOCK(this->lock); return true; } static bool networkConnectionHandleCore(NetworkConnection * this, const LGNetPacketView * packet) { const LGNetEnvelopeFlags responseFlags = LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL | LG_NET_ENVELOPE_RELIABLE; uint64_t sessionEpoch; LG_LOCK(this->lock); sessionEpoch = this->sessionEpoch; LG_UNLOCK(this->lock); switch (packet->envelope.messageType) { case LG_NET_CORE_MESSAGE_HELLO_ACK: { LGNetHelloAck acknowledgement; if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || lgNetHelloAckDecode(&acknowledgement, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK || acknowledgement.protocolVersion != LG_NET_PROTOCOL_VERSION_CURRENT || acknowledgement.authMode != LG_NET_AUTH_NONE || acknowledgement.maxPayloadLength > NETWORK_MAX_CONTROL_PAYLOAD || acknowledgement.maxDatagramPayload > NETWORK_MAX_DATAGRAM_PAYLOAD || packet->envelope.sessionEpoch != acknowledgement.sessionEpoch) return false; LG_LOCK(this->lock); if (this->helloAcknowledged) { LG_UNLOCK(this->lock); return false; } this->helloAcknowledged = true; this->authenticationComplete = true; this->expectedCapabilities = acknowledgement.capabilityCount; this->sessionEpoch = acknowledgement.sessionEpoch; this->maxPayloadLength = acknowledgement.maxPayloadLength; this->maxDatagramPayload = acknowledgement.maxDatagramPayload; LG_UNLOCK(this->lock); return true; } case LG_NET_CORE_MESSAGE_CAPABILITY: if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.sessionEpoch != sessionEpoch) return false; return networkConnectionHandleCapability(this, packet); case LG_NET_CORE_MESSAGE_CAPABILITIES_DONE: { if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.sessionEpoch != sessionEpoch || packet->payloadLength) return false; bool ready; LG_LOCK(this->lock); ready = !this->capabilitiesComplete && this->helloAcknowledged && this->authenticationComplete && this->receivedCapabilities == this->expectedCapabilities; this->capabilitiesComplete = ready; LG_UNLOCK(this->lock); if (ready) networkConnectionReady(this); return ready; } case LG_NET_CORE_MESSAGE_AUTH_CHALLENGE: DEBUG_ERROR("The server requested unsupported password authentication"); return false; case LG_NET_CORE_MESSAGE_AUTH_RESULT: return false; default: return false; } } static bool networkConnectionHandlePacket(NetworkConnection * this, const LGNetPacketView * packet, bool datagram) { if (packet->envelope.service == LG_NET_SERVICE_CORE) return networkConnectionHandleCore(this, packet); if (!atomic_load_explicit(&this->sessionValid, memory_order_acquire)) return false; LG_LOCK(this->lock); const bool sessionValid = packet->envelope.sessionEpoch == this->sessionEpoch && (this->receivedServices & (UINT16_C(1) << packet->envelope.service)) && (this->reliableServices & (UINT16_C(1) << packet->envelope.service)) && packet->payloadLength <= this->serviceMaxPayload[packet->envelope.service]; LG_UNLOCK(this->lock); if (!sessionValid) return false; if (this->packet) this->packet(this->opaque, packet, datagram); return true; } static bool networkConnectionValidateControl(NetworkConnection * this, const LGNetPacketView * packet) { uint32_t maxPayloadLength; LG_LOCK(this->lock); maxPayloadLength = this->maxPayloadLength; LG_UNLOCK(this->lock); return packet->envelope.headerSize <= NETWORK_MAX_HEADER_SIZE && packet->payloadLength <= maxPayloadLength && packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT && (packet->envelope.flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL)) == (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL) && !(packet->envelope.flags & (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) && lgNetServiceKnown(packet->envelope.service) && lgNetMessageKnown(packet->envelope.service, packet->envelope.messageType) && packet->envelope.serviceVersion == networkServiceVersion(packet->envelope.service); } static bool networkConnectionDecodeDatagram(NetworkConnection * this, const QUIC_BUFFER * buffer, LGNetPacketView * packet) { if (!buffer || !buffer->Buffer || !packet || lgNetPacketDecode(packet, buffer->Buffer, buffer->Length) != LG_NET_PARSE_OK || packet->wireSize != buffer->Length || packet->envelope.service == LG_NET_SERVICE_CORE || !lgNetServiceKnown(packet->envelope.service) || !lgNetMessageKnown(packet->envelope.service, packet->envelope.messageType) || packet->envelope.serviceVersion != networkServiceVersion(packet->envelope.service)) return false; uint64_t sessionEpoch; uint16_t receivedServices; uint32_t serviceMaxDatagram; LG_LOCK(this->lock); sessionEpoch = this->sessionEpoch; receivedServices = this->receivedServices; serviceMaxDatagram = this->serviceMaxDatagram[packet->envelope.service]; LG_UNLOCK(this->lock); return atomic_load_explicit( &this->sessionValid, memory_order_acquire) && (receivedServices & (UINT16_C(1) << packet->envelope.service)) && serviceMaxDatagram && packet->envelope.headerSize <= NETWORK_MAX_HEADER_SIZE && packet->payloadLength <= serviceMaxDatagram && packet->envelope.sessionEpoch == sessionEpoch && packet->envelope.sequence != 0 && packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT && (packet->envelope.flags & LG_NET_ENVELOPE_DATAGRAM) && !(packet->envelope.flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_EARLY_DATA)); } static bool networkConnectionReserveReceive(NetworkConnection * this, size_t additional) { if (this->receiveSize > NETWORK_MAX_CONTROL_BUFFER || additional > NETWORK_MAX_CONTROL_BUFFER - this->receiveSize) return false; const size_t required = this->receiveSize + additional; if (required <= this->receiveCapacity) return true; size_t capacity = this->receiveCapacity ? this->receiveCapacity : 4096U; while (capacity < required) { const size_t next = capacity * 2U; if (next < capacity || next > NETWORK_MAX_CONTROL_BUFFER) { capacity = NETWORK_MAX_CONTROL_BUFFER; break; } capacity = next; } uint8_t * data = realloc(this->receiveData, capacity); if (!data) return false; this->receiveData = data; this->receiveCapacity = capacity; return true; } static bool networkConnectionReceive(NetworkConnection * this, const QUIC_BUFFER * buffers, uint32_t count) { if (count && !buffers) return false; size_t additional = 0; for (uint32_t i = 0; i < count; ++i) { if (buffers[i].Length && !buffers[i].Buffer) return false; if (buffers[i].Length > SIZE_MAX - additional) return false; additional += buffers[i].Length; } if (!networkConnectionReserveReceive(this, additional)) return false; for (uint32_t i = 0; i < count; ++i) { if (!buffers[i].Length) continue; memcpy(this->receiveData + this->receiveSize, buffers[i].Buffer, buffers[i].Length); this->receiveSize += buffers[i].Length; } while (this->receiveSize >= LG_NET_ENVELOPE_WIRE_SIZE) { const uint16_t headerSize = networkReadU16(this->receiveData + 8U); const uint32_t payloadSize = networkReadU32(this->receiveData + 24U); if (headerSize < LG_NET_ENVELOPE_WIRE_SIZE || headerSize > NETWORK_MAX_HEADER_SIZE || payloadSize > NETWORK_MAX_CONTROL_PAYLOAD || payloadSize > NETWORK_MAX_CONTROL_BUFFER - headerSize) return false; const size_t packetSize = (size_t)headerSize + payloadSize; if (packetSize > this->receiveSize) break; LGNetPacketView packet; if (lgNetPacketDecode(&packet, this->receiveData, packetSize) != LG_NET_PARSE_OK) return false; LG_LOCK(this->lock); const bool sequenceValid = this->receiveSequence != UINT64_MAX && packet.envelope.sequence == this->receiveSequence + 1U; if (sequenceValid) this->receiveSequence = packet.envelope.sequence; LG_UNLOCK(this->lock); if (!sequenceValid || !networkConnectionValidateControl(this, &packet) || !networkConnectionHandlePacket(this, &packet, false)) return false; this->receiveSize -= packetSize; memmove(this->receiveData, this->receiveData + packetSize, this->receiveSize); } return true; } static void networkConnectionTrustDecision(void * opaque, bool trusted) { NetworkConnection * this = opaque; LG_LOCK(this->lock); if (!this->certificatePending || !this->connection) { LG_UNLOCK(this->lock); return; } trusted = trusted && !atomic_load_explicit( &this->stopping, memory_order_acquire); this->certificatePending = false; HQUIC connection = this->connection; char fingerprint[NETWORK_FINGERPRINT_TEXT_SIZE]; memcpy(fingerprint, this->pendingFingerprint, sizeof(fingerprint)); memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); LG_UNLOCK(this->lock); if (trusted) trusted = networkKnownHostsRemember( this->knownHosts, this->endpoint, fingerprint); const QUIC_STATUS status = this->api->ConnectionCertificateValidationComplete(connection, trusted, trusted ? QUIC_TLS_ALERT_CODE_SUCCESS : QUIC_TLS_ALERT_CODE_BAD_CERTIFICATE); if (QUIC_FAILED(status)) { networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } } static QUIC_STATUS networkConnectionCertificate(NetworkConnection * this, const QUIC_CONNECTION_EVENT * event) { const QUIC_BUFFER * certificate = (const QUIC_BUFFER *)event->PEER_CERTIFICATE_RECEIVED.Certificate; if (!certificate || !certificate->Buffer || !certificate->Length) return QUIC_STATUS_BAD_CERTIFICATE; uint8_t fingerprint[NETWORK_FINGERPRINT_SIZE]; char fingerprintText[NETWORK_FINGERPRINT_TEXT_SIZE]; if (!networkFingerprintSHA256(certificate->Buffer, certificate->Length, fingerprint)) return QUIC_STATUS_BAD_CERTIFICATE; networkFingerprintFormat(fingerprint, fingerprintText); switch (networkKnownHostsCheck( this->knownHosts, this->endpoint, fingerprintText)) { case NETWORK_KNOWN_HOST_MATCH: return QUIC_STATUS_SUCCESS; case NETWORK_KNOWN_HOST_CHANGED: DEBUG_ERROR("The certificate for %s changed; remove its entry from " "network_known_hosts before trusting a replacement", this->endpoint); return QUIC_STATUS_BAD_CERTIFICATE; case NETWORK_KNOWN_HOST_ERROR: DEBUG_ERROR("Failed to check the network server fingerprint"); return QUIC_STATUS_BAD_CERTIFICATE; case NETWORK_KNOWN_HOST_UNKNOWN: break; } if (!this->trustPrompt) return QUIC_STATUS_BAD_CERTIFICATE; LG_LOCK(this->lock); if (this->certificatePending || atomic_load_explicit(&this->stopping, memory_order_acquire)) { LG_UNLOCK(this->lock); return QUIC_STATUS_BAD_CERTIFICATE; } this->certificatePending = true; memcpy(this->pendingFingerprint, fingerprintText, sizeof(this->pendingFingerprint)); LG_UNLOCK(this->lock); if (!this->trustPrompt(this->opaque, this->endpoint, fingerprintText, networkConnectionTrustDecision, this)) { LG_LOCK(this->lock); const bool validationCompleted = !this->certificatePending; if (!validationCompleted) { this->certificatePending = false; memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); } LG_UNLOCK(this->lock); return validationCompleted ? QUIC_STATUS_PENDING : QUIC_STATUS_BAD_CERTIFICATE; } /* MsQuic marks validation pending before invoking this callback and permits * ConnectionCertificateValidationComplete from the callback. Always return * PENDING once the prompt owns the decision, including during cancellation. */ return QUIC_STATUS_PENDING; } static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, void * opaque, QUIC_CONNECTION_EVENT * event) { NetworkConnection * this = opaque; switch (event->Type) { case QUIC_CONNECTION_EVENT_CONNECTED: { if (atomic_load_explicit(&this->stopping, memory_order_acquire)) { this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; } HQUIC stream = NULL; QUIC_STATUS status = this->api->StreamOpen(handle, QUIC_STREAM_OPEN_FLAG_NONE, networkStreamCallback, this, &stream); if (QUIC_FAILED(status)) { networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; } LG_LOCK(this->lock); this->controlStream = stream; LG_UNLOCK(this->lock); status = this->api->StreamStart( stream, QUIC_STREAM_START_FLAG_IMMEDIATE | QUIC_STREAM_START_FLAG_SHUTDOWN_ON_FAIL); if (QUIC_FAILED(status)) { networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } return QUIC_STATUS_SUCCESS; } case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT: networkConnectionFail(this, LG_TRANSPORT_UNAVAILABLE); return QUIC_STATUS_SUCCESS; case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: networkConnectionFail(this, LG_TRANSPORT_DISCONNECTED); return QUIC_STATUS_SUCCESS; case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: networkConnectionSetState( this, false, LG_TRANSPORT_DISCONNECTED); lgSignalEvent(this->connectEvent); lgSignalEvent(this->closeEvent); return QUIC_STATUS_SUCCESS; case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: this->api->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, (void *)networkRejectedStreamCallback, this); this->api->StreamShutdown(event->PEER_STREAM_STARTED.Stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT | QUIC_STREAM_SHUTDOWN_FLAG_IMMEDIATE, 0); return QUIC_STATUS_SUCCESS; case QUIC_CONNECTION_EVENT_DATAGRAM_RECEIVED: { LGNetPacketView packet; if (networkConnectionDecodeDatagram( this, event->DATAGRAM_RECEIVED.Buffer, &packet) && this->packet) this->packet(this->opaque, &packet, true); return QUIC_STATUS_SUCCESS; } case QUIC_CONNECTION_EVENT_PEER_CERTIFICATE_RECEIVED: return networkConnectionCertificate(this, event); default: return QUIC_STATUS_SUCCESS; } } static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event) { NetworkConnection * this = opaque; switch (event->Type) { case QUIC_STREAM_EVENT_START_COMPLETE: if (QUIC_FAILED(event->START_COMPLETE.Status) || !networkConnectionSendHello(this)) { networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_RECEIVE: if (!networkConnectionReceive( this, event->RECEIVE.Buffers, event->RECEIVE.BufferCount)) { networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_SEND_COMPLETE: free(event->SEND_COMPLETE.ClientContext); return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: case QUIC_STREAM_EVENT_PEER_SEND_ABORTED: case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED: this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: LG_LOCK(this->lock); if (this->controlStream == handle) this->controlStream = NULL; LG_UNLOCK(this->lock); this->api->StreamClose(handle); return QUIC_STATUS_SUCCESS; default: return QUIC_STATUS_SUCCESS; } } static QUIC_STATUS QUIC_API networkRejectedStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event) { NetworkConnection * this = opaque; if (event->Type == QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE) this->api->StreamClose(handle); return QUIC_STATUS_SUCCESS; } bool networkConnectionCreate(NetworkConnection ** result, const NetworkConnectionParams * params) { if (!result || !params || !params->address || !params->knownHosts || (!!params->trustPrompt != !!params->trustCancel)) return false; NetworkConnection * this = calloc(1, sizeof(*this)); if (!this) return false; LG_LOCK_INIT(this->lock); LG_LOCK_INIT(this->stateCallbackLock); atomic_init(&this->stopping, false); atomic_init(&this->sessionValid, false); this->address = networkDuplicate(params->address); this->password = networkDuplicate(params->password); this->endpoint = networkEndpoint(params->address, params->port); if (!this->address || (params->password && !this->password) || !this->endpoint) goto fail; this->port = params->port; this->knownHosts = params->knownHosts; this->trustPrompt = params->trustPrompt; this->trustCancel = params->trustCancel; this->packet = params->packet; this->state = params->state; this->opaque = params->opaque; this->connectEvent = lgCreateEvent(false, 0); this->closeEvent = lgCreateEvent(false, 0); if (!this->connectEvent || !this->closeEvent) goto fail; if (QUIC_FAILED(MsQuicOpen2(&this->api))) 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))) goto fail; const QUIC_BUFFER alpn = { .Length = LG_NET_ALPN_LENGTH, .Buffer = (uint8_t *)LG_NET_ALPN, }; const QUIC_SETTINGS settings = { .IsSet = { .HandshakeIdleTimeoutMs = true, .IdleTimeoutMs = true, .InitialRttMs = true, .KeepAliveIntervalMs = true, .PeerBidiStreamCount = true, .PeerUnidiStreamCount = true, .DatagramReceiveEnabled = true, }, .HandshakeIdleTimeoutMs = NETWORK_HANDSHAKE_TIMEOUT_MS, .IdleTimeoutMs = NETWORK_IDLE_TIMEOUT_MS, .InitialRttMs = NETWORK_INITIAL_RTT_MS, .KeepAliveIntervalMs = NETWORK_KEEPALIVE_INTERVAL_MS, .PeerBidiStreamCount = NETWORK_MAX_PEER_STREAMS, .PeerUnidiStreamCount = NETWORK_MAX_PEER_STREAMS, .DatagramReceiveEnabled = true, }; if (QUIC_FAILED(this->api->ConfigurationOpen(this->registration, &alpn, 1, &settings, sizeof(settings), NULL, &this->configuration))) goto fail; /* PKI chain validation is disabled because identity is enforced by the * certificate callback and the persistent TOFU store above. */ const QUIC_CREDENTIAL_CONFIG credential = { .Type = QUIC_CREDENTIAL_TYPE_NONE, .Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION | QUIC_CREDENTIAL_FLAG_INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAG_USE_PORTABLE_CERTIFICATES, }; if (QUIC_FAILED(this->api->ConfigurationLoadCredential( this->configuration, &credential))) goto fail; *result = this; return true; fail: networkConnectionDestroy(&this); return false; } void networkConnectionDestroy(NetworkConnection ** connection) { if (!connection || !*connection) return; NetworkConnection * this = *connection; networkConnectionDisconnect(this); if (this->configuration) this->api->ConfigurationClose(this->configuration); if (this->registration) this->api->RegistrationClose(this->registration); if (this->api) MsQuicClose(this->api); if (this->closeEvent) lgFreeEvent(this->closeEvent); if (this->connectEvent) lgFreeEvent(this->connectEvent); LG_LOCK_FREE(this->stateCallbackLock); LG_LOCK_FREE(this->lock); free(this->receiveData); free(this->endpoint); free(this->password); free(this->address); free(this); *connection = NULL; } LG_TransportStatus networkConnectionConnect(NetworkConnection * this, LG_TransportSession * session, LG_TransportCancelledFn cancelled, void * cancelledOpaque) { if (!this || !session || this->connection) return LG_TRANSPORT_ERROR; if (this->password && *this->password) { DEBUG_ERROR("network:password requires a future password proof provider"); return LG_TRANSPORT_ERROR; } if (cancelled && cancelled(cancelledOpaque)) return LG_TRANSPORT_DISCONNECTED; atomic_store_explicit(&this->stopping, false, memory_order_release); atomic_store_explicit(&this->sessionValid, false, memory_order_release); lgResetEvent(this->connectEvent); lgResetEvent(this->closeEvent); LG_LOCK(this->lock); this->connectStatus = LG_TRANSPORT_UNAVAILABLE; this->connecting = true; this->sessionEpoch = 0; this->sendSequence = 0; this->receiveSequence = 0; this->helloAcknowledged = false; this->authenticationComplete = false; this->capabilitiesComplete = false; this->connectFailed = false; this->expectedCapabilities = 0; this->receivedCapabilities = 0; this->receivedServices = 0; this->reliableServices = 0; this->maxPayloadLength = NETWORK_MAX_CONTROL_PAYLOAD; this->maxDatagramPayload = 0; memset(this->serviceMaxPayload, 0, sizeof(this->serviceMaxPayload)); memset(this->serviceMaxDatagram, 0, sizeof(this->serviceMaxDatagram)); this->receiveSize = 0; memset(&this->session, 0, sizeof(this->session)); snprintf(this->session.version, sizeof(this->session.version), "LG network protocol %u", LG_NET_PROTOCOL_VERSION_CURRENT); snprintf(this->session.name, sizeof(this->session.name), "%s", this->endpoint); this->session.os = LG_TRANSPORT_OS_OTHER; LG_UNLOCK(this->lock); QUIC_STATUS status = this->api->ConnectionOpen(this->registration, networkConnectionCallback, this, &this->connection); if (QUIC_FAILED(status)) { LG_LOCK(this->lock); this->connecting = false; this->connectStatus = LG_TRANSPORT_ERROR; LG_UNLOCK(this->lock); this->connection = NULL; return LG_TRANSPORT_ERROR; } status = this->api->ConnectionStart(this->connection, this->configuration, QUIC_ADDRESS_FAMILY_UNSPEC, this->address, this->port); if (QUIC_FAILED(status)) { this->api->ConnectionClose(this->connection); this->connection = NULL; LG_LOCK(this->lock); this->connecting = false; this->connectStatus = LG_TRANSPORT_UNAVAILABLE; LG_UNLOCK(this->lock); return LG_TRANSPORT_UNAVAILABLE; } while (!lgWaitEvent(this->connectEvent, NETWORK_CONNECT_POLL_MS)) if (cancelled && cancelled(cancelledOpaque)) { networkConnectionCancel(this); break; } LG_LOCK(this->lock); const LG_TransportStatus connectStatus = this->connectStatus; this->connecting = false; if (connectStatus == LG_TRANSPORT_OK) *session = this->session; LG_UNLOCK(this->lock); if (connectStatus != LG_TRANSPORT_OK) networkConnectionDisconnect(this); return connectStatus; } void networkConnectionCancel(NetworkConnection * this) { if (!this) return; atomic_store_explicit(&this->stopping, true, memory_order_release); if (this->trustCancel) this->trustCancel(this->opaque); LG_LOCK(this->stateCallbackLock); LG_LOCK(this->lock); HQUIC connection = this->connection; const bool certificatePending = this->certificatePending && connection; atomic_store_explicit(&this->sessionValid, false, memory_order_release); this->certificatePending = false; memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); if (this->connecting) { this->connectStatus = LG_TRANSPORT_DISCONNECTED; this->connectFailed = true; } LG_UNLOCK(this->lock); LG_UNLOCK(this->stateCallbackLock); if (certificatePending) this->api->ConnectionCertificateValidationComplete(connection, false, QUIC_TLS_ALERT_CODE_BAD_CERTIFICATE); if (connection) this->api->ConnectionShutdown(connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); if (this->connectEvent) lgSignalEvent(this->connectEvent); } void networkConnectionDisconnect(NetworkConnection * this) { if (!this) return; networkConnectionCancel(this); LG_LOCK(this->lock); HQUIC connection = this->connection; LG_UNLOCK(this->lock); if (!connection) return; lgWaitEvent(this->closeEvent, TIMEOUT_INFINITE); LG_LOCK(this->stateCallbackLock); LG_LOCK(this->lock); const bool ownsConnection = this->connection == connection; if (ownsConnection) { this->connection = NULL; this->controlStream = NULL; this->certificatePending = false; memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); } LG_UNLOCK(this->lock); if (ownsConnection) { this->api->ConnectionClose(connection); atomic_store_explicit( &this->sessionValid, false, memory_order_release); if (this->state) this->state(this->opaque, false, LG_TRANSPORT_DISCONNECTED); } LG_UNLOCK(this->stateCallbackLock); } bool networkConnectionValid(const NetworkConnection * this) { return this && atomic_load_explicit( &this->sessionValid, memory_order_acquire); } LG_TransportStatus networkConnectionSend(NetworkConnection * this, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength) { if (!this || !networkConnectionValid(this)) return LG_TRANSPORT_DISCONNECTED; return networkConnectionSendPacket(this, service, messageType, serviceVersion, flags, componentEpoch, requestID, payload, payloadLength, false) ? LG_TRANSPORT_OK : LG_TRANSPORT_ERROR; }