Files
LookingGlass/client/transports/Network/connection.c
2026-11-06 14:37:38 +11:00

1204 lines
38 KiB
C

/**
* 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 <msquic.h>
#if !defined(QUIC_API_VERSION_2)
#error "The network transport requires MsQuic API version 2"
#endif
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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(
&registration, &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;
}