ZaStoGram/TMessagesProj/jni/tgnet/ConnectionSocket.cpp
loop-uh b6e4301642
Some checks failed
ZaStoGram source guards / guards (push) Failing after 26s
Build three ZaStoGram APKs / build (x86, ZaStoGram-standalone-x86, X86, x86) (push) Failing after 2m24s
Build three ZaStoGram APKs / build (arm64-v8a, ZaStoGram-standalone-arm64-v8a, Arm64, arm64) (push) Failing after 2m26s
Build three ZaStoGram APKs / build (armeabi-v7a, ZaStoGram-standalone-armeabi-v7a, Armv7, armv7) (push) Failing after 2m26s
Писать стек нативного падения и не выдавать отказ TCP за блокировку TLS
- После нативного падения (SIGSEGV в звонке, logs (1) (6)) в лог шла
  только строка native_crash status=11. Теперь из tombstone, который
  Android 12+ хранит в ApplicationExitInfo, пишутся сигнал, адрес,
  abort message и стек упавшего потока: previous_native_crash_stack.
- wss_disconnect писал phase=wss_tls_handshake с момента открытия сокета,
  и сокет, у которого не поднялся даже TCP, читался как блокировка TLS
  (logs (1) (5)). Такой сокет теперь помечается phase=wss_tcp_connect.
2026-09-25 16:17:18 +03:00

5638 lines
289 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This is the source code of tgnet library v. 1.1
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2015-2018.
*/
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <cerrno>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <memory.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <map>
#include <mutex>
#include <utility>
#include <vector>
#include <openssl/bn.h>
#include "ByteStream.h"
#include "ConnectionSocket.h"
#include "FileLog.h"
#include "Defines.h"
#include "ConnectionsManager.h"
#include "EventObject.h"
#include "NativeByteBuffer.h"
#include "Timer.h"
#include "BuffersStorage.h"
#include "Connection.h"
#include "mtproxy/MtProxyAdaptivePolicy.h"
#include "mtproxy/MtProxyClientHelloPolicy.h"
#include "mtproxy/MtProxyDataPathShaper.h"
#include "mtproxy/MtProxyEndpointPolicy.h"
#include "mtproxy/MtProxyFailureEvidence.h"
#include "mtproxy/MtProxyHandshakePlan.h"
#include "mtproxy/MtProxyHandshakeScheduler.h"
#include "mtproxy/MtProxyPhaseContract.h"
#include "mtproxy/MtProxyProbeCoordinator.h"
#include "mtproxy/MtProxyRecoveryPolicy.h"
#include "mtproxy/MtProxyRetryAuthority.h"
#include "mtproxy/MtProxySecretDomain.h"
#include "mtproxy/MtProxyServerFlightParser.h"
#include "mtproxy/MtProxySocketPublisher.h"
#include "mtproxy/MtProxyTerminalDiagnostic.h"
#include <random>
#include <pthread.h>
#define outgoingByteStream stateMachine.socket.outgoingByteStream
#define eventMask stateMachine.socket.eventMask
#define socketAddress stateMachine.socket.address
#define socketAddress6 stateMachine.socket.address6
#define socketFd stateMachine.socket.fd
#define epollRegistered stateMachine.epoll.registered
#define currentTransportState stateMachine.diagnostics.lifecycle
#define timeout stateMachine.socket.timeout
#define onConnectedSent stateMachine.notification.connectedSent
#define socketCloseNotified stateMachine.notification.closeNotified
#define proxyCloseDiagnosticSuppressed stateMachine.notification.closeDiagnosticSuppressed
#define socketDeadForWrites stateMachine.notification.deadForWrites
#define lastEventTime stateMachine.socket.lastEventTime
#define eventObject stateMachine.socket.eventObject
#define currentNetworkType stateMachine.socket.currentNetworkType
#define isIpv6 stateMachine.socket.isIpv6
#define currentAddress stateMachine.socket.currentAddress
#define currentPort stateMachine.socket.currentPort
#define currentSocksUsername stateMachine.socket.currentSocksUsername
#define currentSocksPassword stateMachine.socket.currentSocksPassword
#define waitingForHostResolve stateMachine.hostResolve.waitingHost
#define adjustWriteOpAfterResolve stateMachine.hostResolve.adjustWriteAfterResolve
#define currentSecret stateMachine.diagnostics.currentSecret
#define currentSecretDomain stateMachine.diagnostics.currentSecretDomain
#define currentOriginalSecretDomain stateMachine.diagnostics.currentOriginalSecretDomain
#define currentSanitizedSecretDomain stateMachine.diagnostics.currentSanitizedSecretDomain
#define currentLowercaseSecretDomain stateMachine.diagnostics.currentLowercaseSecretDomain
#define currentNoTrailingDotSecretDomain stateMachine.diagnostics.currentNoTrailingDotSecretDomain
#define currentPunycodeSecretDomain stateMachine.diagnostics.currentPunycodeSecretDomain
#define currentClientHelloSni stateMachine.diagnostics.currentClientHelloSni
#define currentSecretKind stateMachine.diagnostics.currentSecretKind
#define currentSecretIsFakeTls stateMachine.diagnostics.currentSecretIsFakeTls
#define currentAllowedSniVariants stateMachine.diagnostics.currentAllowedSniVariants
#define currentRecipeFamily stateMachine.diagnostics.currentRecipeFamily
#define currentRecipeSniVariant stateMachine.diagnostics.currentRecipeSniVariant
#define currentRecipeParserVariant stateMachine.diagnostics.currentRecipeParserVariant
#define currentRecipeClassicVariant stateMachine.diagnostics.currentRecipeClassicVariant
#define currentProxyTlsProfile stateMachine.diagnostics.currentProxyTlsProfile
#define currentEffectiveProxyTlsProfile stateMachine.diagnostics.currentEffectiveProxyTlsProfile
#define currentClientHelloFragmentation stateMachine.diagnostics.currentClientHelloFragmentation
#define currentServerHelloParserMode stateMachine.diagnostics.currentServerHelloParserMode
#define currentConnectionPatternMode stateMachine.diagnostics.currentConnectionPatternMode
#define currentRecordSizingMode stateMachine.diagnostics.currentRecordSizingMode
#define currentTimingMode stateMachine.diagnostics.currentTimingMode
#define currentStartupCoverMode stateMachine.diagnostics.currentStartupCoverMode
#define startupCoverStartTime stateMachine.fakeTls.startupCoverStartTime
#define startupCoverFrameCount stateMachine.fakeTls.startupCoverFrameCount
#define startupCoverStartedLogged stateMachine.fakeTls.startupCoverStartedLogged
#define startupCoverEndedLogged stateMachine.fakeTls.startupCoverEndedLogged
#define adjustWriteOpAfterPreTcpGate stateMachine.pendingWrite.adjustWriteAfterPreTcpGate
#define currentProxyTlsProfileKey stateMachine.diagnostics.currentProxyTlsProfileKey
#define currentMtProxyEndpointKey stateMachine.diagnostics.currentMtProxyEndpointKey
#define currentMtProxyRecipeCacheKey stateMachine.diagnostics.currentMtProxyRecipeCacheKey
#define currentMtProxyProbeKey stateMachine.diagnostics.currentMtProxyProbeKey
#define currentMtProxyNetworkEndpointKey stateMachine.diagnostics.currentMtProxyNetworkEndpointKey
#define currentMtProxyDnsCacheKey stateMachine.diagnostics.currentMtProxyDnsCacheKey
#define currentMtProxyAdmissionKey stateMachine.diagnostics.currentMtProxyAdmissionKey
#define proxyCheckDiagnostic stateMachine.diagnostics.proxyCheckDiagnostic
#define startupTimeline stateMachine.diagnostics.startupTimeline
#define tlsHashMismatch stateMachine.fakeTls.tlsHashMismatch
#define serverHelloHmacMismatchObserved stateMachine.fakeTls.serverHelloHmacMismatchObserved
#define serverHelloHmacMismatchTime stateMachine.fakeTls.serverHelloHmacMismatchTime
#define tlsBufferSized stateMachine.fakeTls.tlsBufferSized
#define tlsBufferRecordType stateMachine.fakeTls.tlsBufferRecordType
#define tlsBuffer stateMachine.fakeTls.tlsBuffer
#define tempBuffer stateMachine.pendingWrite.tempBuffer
#define pendingClientHello stateMachine.fakeTls.pendingClientHello
#define pendingTlsFrame stateMachine.fakeTls.pendingTlsFrame
#define bytesRead stateMachine.pendingWrite.bytesRead
#define pendingClientHelloSize stateMachine.fakeTls.pendingClientHelloSize
#define pendingClientHelloOffset stateMachine.fakeTls.pendingClientHelloOffset
#define pendingClientHelloFragmentTarget stateMachine.fakeTls.pendingClientHelloFragmentTarget
#define pendingClientHelloFragmentIndex stateMachine.fakeTls.pendingClientHelloFragmentIndex
#define pendingClientHelloFragmentCount stateMachine.fakeTls.pendingClientHelloFragmentCount
#define pendingClientHelloNextWriteTime stateMachine.fakeTls.pendingClientHelloNextWriteTime
#define pendingTlsFrameSize stateMachine.fakeTls.pendingTlsFrameSize
#define pendingTlsFrameOffset stateMachine.fakeTls.pendingTlsFrameOffset
#define pendingTlsPayloadSize stateMachine.fakeTls.pendingTlsPayloadSize
#define nextTlsFrameWriteTime stateMachine.fakeTls.nextTlsFrameWriteTime
#define tlsState stateMachine.fakeTls.tlsState
#define mtproxyFirstTlsFrameSentLogged stateMachine.fakeTls.mtproxyFirstTlsFrameSentLogged
#define mtproxyFirstTlsDataReceivedLogged stateMachine.fakeTls.mtproxyFirstTlsDataReceivedLogged
#define mtproxyFirstPlainDataSentLogged stateMachine.fakeTls.mtproxyFirstPlainDataSentLogged
#define mtproxyFirstPlainDataReceivedLogged stateMachine.fakeTls.mtproxyFirstPlainDataReceivedLogged
#define firstTransportPacketSent stateMachine.fakeTls.firstTransportPacketSent
#define firstTransportPacketReceived stateMachine.fakeTls.firstTransportPacketReceived
#define dataPathProven stateMachine.fakeTls.dataPathProven
#define mtproxyFirstTlsFrameSentTime stateMachine.fakeTls.mtproxyFirstTlsFrameSentTime
#define mtproxyFirstPlainDataSentTime stateMachine.fakeTls.mtproxyFirstPlainDataSentTime
#define mtproxyFirstDataReceivedTime stateMachine.fakeTls.mtproxyFirstDataReceivedTime
#define mtproxyTlsFrameCompletedCount stateMachine.fakeTls.mtproxyTlsFrameCompletedCount
#define currentTransportWss stateMachine.wss.active
#define currentDatacenterId stateMachine.wss.datacenterId
#define currentMediaConnection stateMachine.wss.mediaConnection
#define currentWssRoute stateMachine.wss.route
#define currentWssTransport stateMachine.wss.transport
#define outgoingWssPacketSizes stateMachine.wss.outgoingPacketSizes
#define wssFirstFrameSentTime stateMachine.wss.firstFrameSentTime
#define wssOpenTime stateMachine.wss.openTime
#define wssTunnelRotating stateMachine.wss.tunnelRotating
#define proxyAuthState stateMachine.socks.proxyAuthState
#define proxyHandshakeAdmissionTimer stateMachine.admission.timer
#define proxyHandshakeAdmissionQueued stateMachine.admission.queued
#define proxyHandshakeAdmissionQueuePublished stateMachine.admission.queuePublished
#define proxyHandshakeAdmissionActive stateMachine.admission.active
#define proxyHandshakeAdmissionReady stateMachine.admission.ready
#define proxyEndpointBackoffReady stateMachine.endpointGate.backoffReady
#define proxyEndpointTcpConnectActive stateMachine.endpointGate.tcpConnectActive
#define proxyEndpointTcpConnectReady stateMachine.endpointGate.tcpConnectReady
#define proxyEndpointTcpConnectGatePublished stateMachine.endpointGate.tcpConnectGatePublished
#define proxyEndpointDnsCoalesceReady stateMachine.endpointGate.dnsCoalesceReady
#define proxyHandshakeAdmissionIpv6 stateMachine.admission.ipv6
#define mtproxySocketConnectedLogged stateMachine.notification.mtproxySocketConnectedLogged
#define proxyHandshakeAdmissionGeneration stateMachine.admission.generation
#define proxyHandshakeAdmissionTimerGeneration stateMachine.admission.timerGeneration
#define proxyHandshakeAdmissionPriority stateMachine.admission.priority
#define proxyHandshakeAdmissionTimerMode stateMachine.admission.timerMode
#define proxyHandshakeAdmissionStartTime stateMachine.admission.startTime
#define proxyHandshakeClientHelloSentTime stateMachine.admission.clientHelloSentTime
#define proxyHandshakeAdmissionKey stateMachine.admission.key
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_ADMISSION = 1;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_FREEZE = 2;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_SERVER_HELLO = 3;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_CLIENT_HELLO_FRAGMENT = 4;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_TLS_FRAME = 5;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_HOST_RESOLVE = 6;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_ENDPOINT_BACKOFF = 7;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_DNS_COALESCE = 8;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_TCP_CONNECT_GATE = 9;
static constexpr int32_t MT_PROXY_HANDSHAKE_TIMER_PROBE_WAIT = 10;
static constexpr int64_t MT_PROXY_HANDSHAKE_FREEZE_TIMEOUT_MS = 4500;
static constexpr int64_t MT_PROXY_SERVER_HELLO_HMAC_WAIT_MS = 900;
// Единый порог для транспортов с собственным рукопожатием (MTProxy, WSS):
// сколько ждать ответа на первые байты приложения, прежде чем счесть
// соединение чёрной дырой — рукопожатие прошло, а данные глотает миддлбокс.
static constexpr int64_t TRANSPORT_APPDATA_NO_RESPONSE_TIMEOUT_MS = 5500;
static constexpr int64_t MT_PROXY_PLAIN_NO_RESPONSE_TIMEOUT_MS = TRANSPORT_APPDATA_NO_RESPONSE_TIMEOUT_MS;
static constexpr int64_t MT_PROXY_TLS_APPDATA_NO_RESPONSE_TIMEOUT_MS = TRANSPORT_APPDATA_NO_RESPONSE_TIMEOUT_MS;
static constexpr int64_t WSS_APPDATA_NO_RESPONSE_TIMEOUT_MS = TRANSPORT_APPDATA_NO_RESPONSE_TIMEOUT_MS;
// На медиа-соединении первым уходит запрос куска файла, а не рукопожатие: для
// «холодного» файла сервер честно думает дольше 5,5 с. Короткий сторож здесь
// ложно объявлял живой kwsN-1 чёрной дырой и уводил медиа в обход.
static constexpr int64_t WSS_MEDIA_APPDATA_NO_RESPONSE_TIMEOUT_MS = 20000;
// Зависшее TLS-рукопожатие иначе держит загрузку файла до её 25–40 с таймаута.
static constexpr int64_t WSS_HANDSHAKE_TIMEOUT_MS = 8000;
// Провайдер глотает SYN целого потока, повторы по нему бесполезны: новый сокет проходит.
static constexpr int64_t WSS_TCP_CONNECT_TIMEOUT_MS = 2500;
// A throttled network freezes each TCP connection to Cloudflare after about
// 16 KB downstream. A tunnel connection is replaced once it has received this
// much, so that one more answer (downloads over the tunnel ask for 8 KB parts)
// still fits under the freeze.
static constexpr uint64_t WSS_TUNNEL_ROTATE_BYTES = 6 * 1024;
// Every tunnel connection lives for one part, so a line per rotation would
// flood the log; they are summed up and reported every 32 or once a minute.
static void noteWssTunnelRotated(uint64_t received) {
static std::mutex mutex;
static uint32_t count = 0;
static uint64_t bytes = 0;
static int64_t since = 0;
uint32_t reportCount;
uint64_t reportBytes;
{
std::lock_guard<std::mutex> lock(mutex);
const int64_t now = ConnectionsManager::getInstance(0).getCurrentTimeMonotonicMillis();
if (count == 0) {
since = now;
}
count++;
bytes += received;
if (count < 32 && now - since < 60 * 1000) {
return;
}
reportCount = count;
reportBytes = bytes;
count = 0;
bytes = 0;
}
if (LOGS_ENABLED) DEBUG_D("wss_tunnel_rotated sockets=%u rx=%llu", reportCount, (unsigned long long) reportBytes);
}
static constexpr int64_t MT_PROXY_EARLY_APPDATA_DROP_MS = 2 * 60 * 1000;
// WEB proxy receive-wait reasons by WebProxyFlow.REASON_* value; the numbers
// cross JNI and must stay in sync with the Java constants.
static const char *const kWebProxyReceiveReasonNames[] = {
"unknown",
"stream_closed",
"carrier_down",
"max_wait",
"carrier_stalled",
"request_queued",
"reply_queued",
"reply_pending",
"reply_missing",
"reply_timeout",
};
// A carrier verdict never parks a connection for longer than this before
// asking again, whatever it suggested.
static constexpr int64_t WEB_PROXY_MAX_RECHECK_MS = 8000;
static const char *webProxyReceiveReasonName(int32_t reason) {
const int32_t count = (int32_t) (sizeof(kWebProxyReceiveReasonNames) / sizeof(kWebProxyReceiveReasonNames[0]));
return reason > 0 && reason < count ? kWebProxyReceiveReasonNames[reason] : kWebProxyReceiveReasonNames[0];
}
static const char *webProxyStreamClassName(int32_t streamClass) {
switch (streamClass) {
case WEB_PROXY_STREAM_CLASS_DOWNLOAD:
return "download";
case WEB_PROXY_STREAM_CLASS_UPLOAD:
return "upload";
default:
return "interactive";
}
}
static int socketUnsentBytes(int fd) {
int value = 0;
return (fd >= 0 && ioctl(fd, SIOCOUTQ, &value) == 0) ? value : 0;
}
static bool transportAppDataUnanswered(int64_t now, bool firstDataSent, bool noReplyYet, int64_t firstDataSentTime, int64_t timeoutMs) {
return firstDataSent && noReplyYet && firstDataSentTime > 0 && now - firstDataSentTime > timeoutMs;
}
static constexpr bool MT_PROXY_HANDSHAKE_CLOSE_ON_FREEZE_ENABLED = true;
// Outgoing MTProto bytes are pulled from outgoingByteStream and emitted one
// packet per WebSocket frame. Pulling pauses while the socket already holds
// this much serialized output.
static constexpr size_t WSS_MAX_BUFFERED_OUTPUT_BYTES = 256 * 1024;
static constexpr uint8_t MT_PROXY_TLS_RECORD_CHANGE_CIPHER_SPEC = 0x14;
static constexpr uint8_t MT_PROXY_TLS_RECORD_ALERT = 0x15;
static constexpr uint8_t MT_PROXY_TLS_RECORD_APPLICATION_DATA = 0x17;
static std::string mtProxyHexPreview(const uint8_t *data, size_t size) {
static const char hex[] = "0123456789abcdef";
const size_t limit = std::min<size_t>(size, 32);
std::string result;
result.reserve(limit * 2);
for (size_t i = 0; i < limit; i++) {
uint8_t value = data[i];
result.push_back(hex[value >> 4]);
result.push_back(hex[value & 0x0f]);
}
return result;
}
static const char *mtProxyDisconnectReasonName(int32_t reason) {
switch (reason) {
case 0:
return "drop";
case 1:
return "socket_error_or_eof";
case 2:
return "timeout_waiting_connect_or_pending_requests";
default:
return "unknown";
}
}
static const char *mtProxySocketErrorName(int32_t error) {
if (error == 0) {
return "none";
}
if (error < 0) {
return "internal";
}
return strerror(error);
}
// Crypto-secure RNG for FakeTLS profile variance and runtime-gated data shaping.
static uint32_t secureRandomUint32() {
uint32_t v;
RAND_bytes((uint8_t *) &v, sizeof(v));
return v;
}
// Unbiased uniform value in [0, bound) via rejection sampling (avoids modulo bias).
static uint32_t secureRandomBounded(uint32_t bound) {
if (bound <= 1) {
return 0;
}
uint32_t threshold = (uint32_t) (-bound) % bound; // == 2^32 mod bound
uint32_t v;
do {
v = secureRandomUint32();
} while (v < threshold);
return v % bound;
}
static int32_t normalizeMtProxyConnectionPatternMode(int32_t mode) {
if (mode >= MT_PROXY_CONNECTION_PATTERN_OFF && mode <= MT_PROXY_CONNECTION_PATTERN_BROWSER) {
return mode;
}
return MT_PROXY_CONNECTION_PATTERN_OFF;
}
static const char *mtProxyConnectionPatternModeName(int32_t mode) {
switch (normalizeMtProxyConnectionPatternMode(mode)) {
case MT_PROXY_CONNECTION_PATTERN_SOFT:
return "soft";
case MT_PROXY_CONNECTION_PATTERN_BROWSER:
return "browser";
case MT_PROXY_CONNECTION_PATTERN_QUIET:
return "quiet";
case MT_PROXY_CONNECTION_PATTERN_STRICT:
return "strict";
case MT_PROXY_CONNECTION_PATTERN_OFF:
default:
return "off";
}
}
#ifndef EPOLLRDHUP
#define EPOLLRDHUP 0x2000
#endif
#define MAX_GREASE 8
static BIGNUM *get_y2(BIGNUM *x, const BIGNUM *mod, BN_CTX *big_num_context) {
// returns y^2 = x^3 + 486662 * x^2 + x
BIGNUM *y = BN_dup(x);
assert(y != NULL);
BIGNUM *coef = BN_new();
BN_set_word(coef, 486662);
BN_mod_add(y, y, coef, mod, big_num_context);
BN_mod_mul(y, y, x, mod, big_num_context);
BN_one(coef);
BN_mod_add(y, y, coef, mod, big_num_context);
BN_mod_mul(y, y, x, mod, big_num_context);
BN_clear_free(coef);
return y;
}
static BIGNUM *get_double_x(BIGNUM *x, const BIGNUM *mod, BN_CTX *big_num_context) {
// returns x_2 =(x^2 - 1)^2/(4*y^2)
BIGNUM *denominator = get_y2(x, mod, big_num_context);
assert(denominator != NULL);
BIGNUM *coef = BN_new();
BN_set_word(coef, 4);
BN_mod_mul(denominator, denominator, coef, mod, big_num_context);
BIGNUM *numerator = BN_new();
assert(numerator != NULL);
BN_mod_mul(numerator, x, x, mod, big_num_context);
BN_one(coef);
BN_mod_sub(numerator, numerator, coef, mod, big_num_context);
BN_mod_mul(numerator, numerator, numerator, mod, big_num_context);
BN_mod_inverse(denominator, denominator, mod, big_num_context);
BN_mod_mul(numerator, numerator, denominator, mod, big_num_context);
BN_clear_free(coef);
BN_clear_free(denominator);
return numerator;
}
static void generate_key_ml_kem_768(unsigned char *key) {
constexpr uint32_t Q = 3329;
constexpr int N = 384;
std::vector<uint32_t> values(N * 2);
RAND_bytes(reinterpret_cast<unsigned char*>(values.data()),values.size() * sizeof(uint32_t));
for (int i = 0; i < N; ++i) {
uint32_t a = values[i * 2] % Q;
uint32_t b = values[i * 2 + 1] % Q;
key[i * 3 + 0] = static_cast<unsigned char>(a & 0xFFu);
key[i * 3 + 1] = static_cast<unsigned char>((a >> 8) | ((b & 0x0Fu) << 4));
key[i * 3 + 2] = static_cast<unsigned char>(b >> 4);
}
RAND_bytes(key + 1152, 32);
}
static void generate_public_key(unsigned char *key) {
BIGNUM *mod = NULL;
BN_hex2bn(&mod, "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
BIGNUM *pow = NULL;
BN_hex2bn(&pow, "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6");
BN_CTX *big_num_context = BN_CTX_new();
assert(big_num_context != NULL);
BIGNUM *x = BN_new();
while (1) {
RAND_bytes(key, 32);
key[31] &= 127;
BN_bin2bn(key, 32, x);
assert(x != NULL);
BN_mod_mul(x, x, x, mod, big_num_context);
BIGNUM *y = get_y2(x, mod, big_num_context);
BIGNUM *r = BN_new();
BN_mod_exp(r, y, pow, mod, big_num_context);
BN_clear_free(y);
if (BN_is_one(r)) {
BN_clear_free(r);
break;
}
BN_clear_free(r);
}
int i;
for (i = 0; i < 3; i++) {
BIGNUM *x2 = get_double_x(x, mod, big_num_context);
BN_clear_free(x);
x = x2;
}
int num_size = BN_num_bytes(x);
assert(num_size <= 32);
memset(key, '\0', 32 - num_size);
BN_bn2bin(x, key + (32 - num_size));
for (i = 0; i < 16; i++) {
unsigned char t = key[i];
key[i] = key[31 - i];
key[31 - i] = t;
}
BN_clear_free(x);
BN_CTX_free(big_num_context);
BN_clear_free(pow);
BN_clear_free(mod);
}
class TlsHello {
public:
TlsHello() {
RAND_bytes(grease, MAX_GREASE);
for (int a = 0; a < MAX_GREASE; a++) {
grease[a] = (uint8_t) ((grease[a] & 0xf0) + 0x0A);
}
for (size_t i = 1; i < MAX_GREASE; i += 2) {
if (grease[i] == grease[i - 1]) {
grease[i] ^= 0x10;
}
}
}
struct Op {
enum class Type {
String, Random, K, M, P, E, Zero, Domain, Grease, BeginScope, EndScope, Permutation
};
Type type;
size_t length;
int seed;
std::string data;
std::vector<std::vector<Op>> entities;
static Op string(const char str[], size_t len) {
Op res;
res.type = Type::String;
res.data = std::string(str, len);
return res;
}
static Op random(size_t length) {
Op res;
res.type = Type::Random;
res.length = length;
return res;
}
static Op K() {
Op res;
res.type = Type::K;
res.length = 32;
return res;
}
static Op E() {
Op res;
res.type = Type::E;
return res;
}
static Op M() {
Op res;
res.type = Type::M;
return res;
}
static Op P() {
Op res;
res.type = Type::P;
return res;
}
static Op zero(size_t length) {
Op res;
res.type = Type::Zero;
res.length = length;
return res;
}
static Op domain() {
Op res;
res.type = Type::Domain;
return res;
}
static Op grease(int seed) {
Op res;
res.type = Type::Grease;
res.seed = seed;
return res;
}
static Op begin_scope() {
Op res;
res.type = Type::BeginScope;
return res;
}
static Op end_scope() {
Op res;
res.type = Type::EndScope;
return res;
}
static Op permutation(std::vector<std::vector<Op>> entities) {
Op res;
res.type = Type::Permutation;
res.entities = std::move(entities);
return res;
}
};
static TlsHello getFirefoxDefault() {
TlsHello res;
res.ops = {
Op::string("\x16\x03\x01", 3),
Op::begin_scope(),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x03\x03", 2),
Op::zero(32),
Op::string("\x20", 1),
Op::random(32),
Op::string("\x00\x22", 2),
Op::grease(0),
Op::string("\x13\x01\x13\x03\x13\x02\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xc0\x2c\xc0\x30\xc0\x0a\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35", 32),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x00\x00", 2),
Op::begin_scope(),
Op::begin_scope(),
Op::string("\x00", 1),
Op::begin_scope(),
Op::domain(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope(),
Op::string("\x00\x17\x00\x00", 4),
Op::string("\xff\x01\x00\x01\x00", 5),
Op::string("\x00\x0a\x00\x10\x00\x0e", 6),
Op::grease(2),
Op::string("\x00\x1d\x00\x17\x00\x18\x00\x19\x01\x00\x01\x01", 12),
Op::string("\x00\x0b\x00\x02\x01\x00", 6),
Op::string("\x00\x23\x00\x00", 4),
Op::string("\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31", 18),
Op::string("\x00\x05\x00\x05\x01\x00\x00\x00\x00", 9),
Op::string("\x00\x22\x00\x0a\x00\x08\x04\x03\x05\x03\x06\x03\x02\x03", 14),
Op::string("\x00\x33\x05\x2f\x05\x2d", 6),
Op::string("\x11\xec\x04\xc0", 4),
Op::M(),
Op::K(),
Op::string("\x00\x1d\x00\x20", 4),
Op::K(),
Op::string("\x00\x17\x00\x41", 4),
Op::random(65),
Op::string("\x00\x2b\x00\x07\x06", 5),
Op::grease(4),
Op::string("\x03\x04\x03\x03", 4),
Op::string("\x00\x0d\x00\x18\x00\x16\x04\x03\x05\x03\x06\x03\x08\x04\x08\x05\x08\x06\x04\x01\x05\x01\x06\x01\x02\x03\x02\x01", 28),
Op::string("\x00\x2d\x00\x02\x01\x01", 6),
Op::string("\x00\x1c\x00\x02\x40\x01", 6),
Op::string("\x00\x1b\x00\x07\x06\x00\x01\x00\x02\x00\x03", 11),
Op::string("\xfe\x0d\x01\x19", 4),
Op::string("\x00\x00\x01\x00\x01", 5),
Op::random(1),
Op::string("\x00\x20", 2),
Op::K(),
Op::string("\x00\xef", 2),
Op::random(239),
Op::P(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
};
return res;
}
static TlsHello getDefault() {
TlsHello res;
res.ops = {
Op::string("\x16\x03\x01", 3),
Op::begin_scope(),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x03\x03", 2),
Op::zero(32),
Op::string("\x20", 1),
Op::random(32),
Op::string("\x00\x20", 2),
Op::grease(0),
Op::string("\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00", 32),
Op::begin_scope(),
Op::grease(2),
Op::string("\x00\x00", 2),
Op::permutation({
{
Op::string("\x00\x00", 2),
Op::begin_scope(),
Op::begin_scope(),
Op::string("\x00", 1),
Op::begin_scope(),
Op::domain(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
},
{ Op::string("\x00\x05\x00\x05\x01\x00\x00\x00\x00",9) },
{
Op::string("\x00\x0a\x00\x0c\x00\x0a", 6),
Op::grease(4),
Op::string("\x11\xec\x00\x1d\x00\x17\x00\x18", 8)
},
{ Op::string("\x00\x0b\x00\x02\x01\x00", 6) },
{ Op::string("\x00\x0d\x00\x18\x00\x16\x09\x04\x09\x05\x09\x06\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01",28) },
{ Op::string("\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31", 18) },
{ Op::string("\x00\x12\x00\x00", 4) },
{ Op::string("\x00\x17\x00\x00", 4) },
{ Op::string("\x00\x1b\x00\x03\x02\x00\x02", 7) },
{ Op::string("\x00\x23\x00\x00", 4) },
{
Op::string("\x00\x2b\x00\x07\x06", 5),
Op::grease(6),
Op::string("\x03\x04\x03\x03", 4)
},
{ Op::string("\x00\x2d\x00\x02\x01\x01", 6) },
{
Op::string("\x00\x33\x04\xef\x04\xed", 6),
Op::grease(4),
Op::string("\x00\x01\x00\x11\xec\x04\xc0", 7),
Op::M(),
Op::K(),
Op::string("\x00\x1d\x00\x20", 4),
Op::K(),
},
{ Op::string("\x44\xcd\x00\x05\x00\x03\x02\x68\x32", 9) },
{
Op::string("\xfe\x0d", 2),
Op::begin_scope(),
Op::string("\x00\x00\x01\x00\x01", 5),
Op::random(1),
Op::string("\x00\x20", 2),
Op::K(),
Op::begin_scope(),
Op::E(),
Op::end_scope(),
Op::end_scope()
},
{ Op::string("\xff\x01\x00\x01\x00", 5) }
}),
Op::grease(3),
Op::string("\x00\x01\x00", 3),
Op::P(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
};
return res;
}
static TlsHello getAndroidChromeDefault() {
return getDefault();
}
static TlsHello getChromeModernDefault() {
TlsHello res;
res.ops = {
Op::string("\x16\x03\x01", 3),
Op::begin_scope(),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x03\x03", 2),
Op::zero(32),
Op::string("\x20", 1),
Op::random(32),
Op::string("\x00\x20", 2),
Op::grease(0),
Op::string("\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00", 32),
Op::begin_scope(),
Op::grease(2),
Op::string("\x00\x00", 2),
Op::permutation({
{
Op::string("\x00\x00", 2),
Op::begin_scope(),
Op::begin_scope(),
Op::string("\x00", 1),
Op::begin_scope(),
Op::domain(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
},
{ Op::string("\x00\x05\x00\x05\x01\x00\x00\x00\x00",9) },
{
Op::string("\x00\x0a\x00\x0c\x00\x0a", 6),
Op::grease(4),
Op::string("\x11\xec\x00\x1d\x00\x17\x00\x18", 8)
},
{ Op::string("\x00\x0b\x00\x02\x00\x00", 6) },
{ Op::string("\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01",22) },
{ Op::string("\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31", 18) },
{ Op::string("\x00\x12\x00\x00", 4) },
{ Op::string("\x00\x17\x00\x00", 4) },
{ Op::string("\x00\x1b\x00\x03\x02\x00\x02", 7) },
{ Op::string("\x00\x23\x00\x00", 4) },
{
Op::string("\x00\x2b\x00\x07\x06", 5),
Op::grease(6),
Op::string("\x03\x04\x03\x03", 4)
},
{ Op::string("\x00\x2d\x00\x02\x01\x01", 6) },
{
Op::string("\x00\x33\x04\xef\x04\xed", 6),
Op::grease(4),
Op::string("\x00\x01\x00\x11\xec\x04\xc0", 7),
Op::M(),
Op::K(),
Op::string("\x00\x1d\x00\x20", 4),
Op::K(),
},
{ Op::string("\x44\x69\x00\x05\x00\x03\x02\x68\x32", 9) },
{
Op::string("\xfe\x0d", 2),
Op::begin_scope(),
Op::string("\x00\x00\x01\x00\x01", 5),
Op::random(1),
Op::string("\x00\x20", 2),
Op::K(),
Op::begin_scope(),
Op::E(),
Op::end_scope(),
Op::end_scope()
},
{ Op::string("\xff\x01\x00\x01\x00", 5) }
}),
Op::grease(3),
Op::string("\x00\x01\x00", 3),
Op::P(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
};
return res;
}
static TlsHello getLegacyNoGreaseDefault() {
TlsHello res;
res.ops = {
Op::string("\x16\x03\x01", 3),
Op::begin_scope(),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x03\x03", 2),
Op::zero(32),
Op::string("\x20", 1),
Op::random(32),
Op::string("\x00\x1a", 2),
Op::string("\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xc0\x2c\xc0\x30\x00\x9c\x00\x9d\x00\x2f\x00\x35", 26),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x00\x00", 2),
Op::begin_scope(),
Op::begin_scope(),
Op::string("\x00", 1),
Op::begin_scope(),
Op::domain(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope(),
Op::string("\x00\x17\x00\x00", 4),
Op::string("\xff\x01\x00\x01\x00", 5),
Op::string("\x00\x0a\x00\x08\x00\x06\x00\x1d\x00\x17\x00\x18", 12),
Op::string("\x00\x0b\x00\x02\x01\x00", 6),
Op::string("\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31", 18),
Op::string("\x00\x0d\x00\x14\x00\x12\x04\x03\x05\x03\x06\x03\x08\x04\x08\x05\x08\x06\x04\x01\x05\x01\x02\x03", 24),
Op::string("\x00\x2b\x00\x05\x04\x03\x04\x03\x03", 9),
Op::string("\x00\x2d\x00\x02\x01\x01", 6),
Op::string("\x00\x33\x00\x26\x00\x24\x00\x1d\x00\x20", 10),
Op::K(),
Op::P(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
};
return res;
}
static TlsHello getFirefoxAndroidDefault() {
TlsHello res;
res.ops = {
Op::string("\x16\x03\x01", 3),
Op::begin_scope(),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x03\x03", 2),
Op::zero(32),
Op::string("\x20", 1),
Op::random(32),
Op::string("\x00\x22", 2),
Op::string("\x13\x01\x13\x03\x13\x02\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xc0\x2c\xc0\x30\xc0\x0a\xc0\x09\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35", 34),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x00\x00", 2),
Op::begin_scope(),
Op::begin_scope(),
Op::string("\x00", 1),
Op::begin_scope(),
Op::domain(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope(),
Op::string("\x00\x17\x00\x00", 4),
Op::string("\xff\x01\x00\x01\x00", 5),
Op::string("\x00\x0a\x00\x10\x00\x0e\x11\xec\x00\x1d\x00\x17\x00\x18\x00\x19\x01\x00\x01\x01", 20),
Op::string("\x00\x0b\x00\x02\x01\x00", 6),
Op::string("\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31", 18),
Op::string("\x00\x05\x00\x05\x01\x00\x00\x00\x00", 9),
Op::string("\x00\x22\x00\x0a\x00\x08\x04\x03\x05\x03\x06\x03\x02\x03", 14),
Op::string("\x00\x33\x05\x2f\x05\x2d", 6),
Op::string("\x11\xec\x04\xc0", 4),
Op::M(),
Op::K(),
Op::string("\x00\x1d\x00\x20", 4),
Op::K(),
Op::string("\x00\x17\x00\x41", 4),
Op::random(65),
Op::string("\x00\x2b\x00\x05\x04\x03\x04\x03\x03", 9),
Op::string("\x00\x0d\x00\x18\x00\x16\x04\x03\x05\x03\x06\x03\x08\x04\x08\x05\x08\x06\x04\x01\x05\x01\x06\x01\x02\x03\x02\x01", 28),
Op::string("\x00\x2d\x00\x02\x01\x01", 6),
Op::string("\x00\x1c\x00\x02\x40\x01", 6),
Op::string("\x00\x1b\x00\x07\x06\x00\x01\x00\x02\x00\x03", 11),
Op::string("\xfe\x0d\x01\xb9", 4),
Op::string("\x00\x00\x01\x00\x01", 5),
Op::random(1),
Op::string("\x00\x20", 2),
Op::K(),
Op::string("\x01\x8f", 2),
Op::random(399),
Op::P(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
};
return res;
}
static TlsHello getAndroidOkHttpDefault() {
TlsHello res;
res.ops = {
Op::string("\x16\x03\x01", 3),
Op::begin_scope(),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x03\x03", 2),
Op::zero(32),
Op::string("\x20", 1),
Op::random(32),
Op::string("\x00\x20", 2),
Op::grease(0),
Op::string("\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00", 32),
Op::begin_scope(),
Op::grease(2),
Op::string("\x00\x00", 2),
Op::string("\x00\x00", 2),
Op::begin_scope(),
Op::begin_scope(),
Op::string("\x00", 1),
Op::begin_scope(),
Op::domain(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope(),
Op::string("\x00\x0a\x00\x0a\x00\x08", 6),
Op::grease(4),
Op::string("\x00\x1d\x00\x17\x00\x18", 6),
Op::string("\x00\x0b\x00\x02\x01\x00", 6),
Op::string("\x00\x0d\x00\x0e\x00\x0c\x04\x03\x05\x03\x04\x01\x05\x01\x02\x01\x02\x03", 18),
Op::string("\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31", 18),
Op::string("\x00\x2b\x00\x07\x06", 5),
Op::grease(6),
Op::string("\x03\x04\x03\x03", 4),
Op::string("\x00\x2d\x00\x02\x01\x01", 6),
Op::string("\x00\x33\x00\x26\x00\x24\x00\x1d\x00\x20", 10),
Op::K(),
Op::grease(3),
Op::string("\x00\x01\x00", 3),
Op::P(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
};
return res;
}
static TlsHello getYandexDefault() {
TlsHello res;
res.ops = {
Op::string("\x16\x03\x01", 3),
Op::begin_scope(),
Op::string("\x01\x00", 2),
Op::begin_scope(),
Op::string("\x03\x03", 2),
Op::zero(32),
Op::string("\x20", 1),
Op::random(32),
Op::string("\x00\x20", 2),
Op::grease(0),
Op::string("\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00", 32),
Op::begin_scope(),
Op::grease(2),
Op::string("\x00\x00", 2),
Op::string("\x00\x17\x00\x00", 4),
Op::string("\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01", 22),
Op::string("\x00\x00", 2),
Op::begin_scope(),
Op::begin_scope(),
Op::string("\x00", 1),
Op::begin_scope(),
Op::domain(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope(),
Op::string("\x00\x0b\x00\x02\x01\x00", 6),
Op::string("\x00\x2d\x00\x02\x01\x01", 6),
Op::string("\x00\x1b\x00\x03\x02\x00\x02", 7),
Op::string("\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31", 18),
Op::string("\xff\x01\x00\x01\x00", 5),
Op::string("\x00\x23\x00\x00", 4),
Op::string("\x00\x2b\x00\x07\x06", 5),
Op::grease(6),
Op::string("\x03\x04\x03\x03", 4),
Op::string("\x00\x12\x00\x00", 4),
Op::string("\x00\x05\x00\x05\x01\x00\x00\x00\x00", 9),
Op::string("\x44\xcd\x00\x05\x00\x03\x02\x68\x32", 9),
Op::string("\x00\x0a\x00\x0c\x00\x0a", 6),
Op::grease(4),
Op::string("\x11\xec\x00\x1d\x00\x17\x00\x18", 8),
Op::string("\xfe\x0d", 2),
Op::begin_scope(),
Op::string("\x00\x00\x01\x00\x01", 5),
Op::random(1),
Op::string("\x00\x20", 2),
Op::K(),
Op::begin_scope(),
Op::E(),
Op::end_scope(),
Op::end_scope(),
Op::string("\x00\x33\x04\xef\x04\xed", 6),
Op::grease(4),
Op::string("\x00\x01\x00\x11\xec\x04\xc0", 7),
Op::M(),
Op::K(),
Op::string("\x00\x1d\x00\x20", 4),
Op::K(),
Op::grease(3),
// Empty on purpose. A byte-perfect Yandex Browser capture
// carries one zero byte here and was refused where this
// deliberately imperfect tdesktop shape passed 12/12.
Op::string("\x00\x00", 2),
Op::P(),
Op::end_scope(),
Op::end_scope(),
Op::end_scope()
};
return res;
}
uint32_t writeToBuffer(uint8_t *data) {
uint32_t offset = 0;
for (auto op : ops) {
writeOp(op, data, offset);
}
return offset;
}
void setDomain(std::string value) {
domain = std::move(value);
}
private:
std::vector<Op> ops;
uint8_t grease[MAX_GREASE];
std::vector<size_t> scopeOffset;
std::string domain;
void writeOp(const TlsHello::Op &op, uint8_t *data, uint32_t &offset) {
using Type = TlsHello::Op::Type;
switch (op.type) {
case Type::String:
memcpy(data + offset, op.data.data(), op.data.size());
offset += op.data.size();
break;
case Type::Random:
RAND_bytes(data + offset, (size_t) op.length);
offset += op.length;
break;
case Type::K:
generate_public_key(data + offset);
offset += op.length;
break;
case Type::M:
generate_key_ml_kem_768(data + offset);
offset += 1184;
break;
case Type::Zero:
std::memset(data + offset, 0, op.length);
offset += op.length;
break;
case Type::Domain: {
size_t size = domain.size();
if (size > 253) {
size = 253;
}
memcpy(data + offset, domain.data(), size);
offset += size;
break;
}
case Type::Grease: {
data[offset] = grease[op.seed];
data[offset + 1] = grease[op.seed];
offset += 2;
break;
}
case Type::BeginScope:
scopeOffset.push_back(offset);
offset += 2;
break;
case Type::EndScope: {
auto begin_offset = scopeOffset.back();
scopeOffset.pop_back();
size_t size = offset - begin_offset - 2;
data[begin_offset] = static_cast<uint8_t>((size >> 8) & 0xff);
data[begin_offset + 1] = static_cast<uint8_t>(size & 0xff);
break;
}
case Type::E: {
size_t r = secureRandomBounded(4);
size_t length = (r == 0 ? 144 :
(r == 1 ? 176 :
(r == 2 ? 208 : 240)));
RAND_bytes(data + offset, (size_t) length);
offset += length;
break;
}
case Type::P: {
// The reference relay does not even enter FakeTLS parsing for
// a record shorter than 0x0200 bytes (517 including the TLS
// header). Bring small templates to that floor exactly; large
// post-quantum templates already satisfy it and stay untouched.
uint32_t length = offset;
if (length < MT_PROXY_CANONICAL_CLIENT_HELLO_BYTES) {
uint32_t extensionHeader = 4;
uint32_t zeros = length + extensionHeader
< MT_PROXY_CANONICAL_CLIENT_HELLO_BYTES
? (uint32_t) MT_PROXY_CANONICAL_CLIENT_HELLO_BYTES
- extensionHeader - length
: 0;
writeOp(Op::string("\x00\x15", 2), data, offset);
writeOp(Op::begin_scope(), data, offset);
writeOp(Op::zero(zeros), data, offset);
writeOp(Op::end_scope(), data, offset);
}
break;
}
case Type::Permutation: {
std::vector<std::vector<Op>> list = {};
for (const auto &part : op.entities) {
list.push_back(part);
}
size_t size = list.size();
for (int i = 0; i < size - 1; i++) {
int j = i + (int) secureRandomBounded((uint32_t) (size - i));
if (i != j) {
std::swap(list[i], list[j]);
}
}
for (const auto &part : list) {
for (const auto &op_local: part) {
writeOp(op_local, data, offset);
}
}
break;
}
}
}
};
static int32_t normalizeMtProxyTlsProfile(int32_t profile) {
if (profile == MT_PROXY_TLS_PROFILE_AUTO || profile == MT_PROXY_TLS_PROFILE_AUTO_ROTATE) {
return profile;
}
if (profile >= MT_PROXY_TLS_PROFILE_FIREFOX && profile <= MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE) {
return profile;
}
return MT_PROXY_TLS_PROFILE_ANDROID_CHROME;
}
static int32_t normalizeMtProxyClientHelloFragmentation(int32_t mode) {
return mode == MT_PROXY_CLIENT_HELLO_FRAGMENTATION_SOFT ? MT_PROXY_CLIENT_HELLO_FRAGMENTATION_SOFT : MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
}
static int32_t normalizeMtProxyRecordSizingMode(int32_t mode) {
if (mode >= MT_PROXY_RECORD_SIZING_OFF && mode <= MT_PROXY_RECORD_SIZING_VARIED) {
return mode;
}
return MT_PROXY_RECORD_SIZING_OFF;
}
static int32_t normalizeMtProxyTimingMode(int32_t mode) {
if (mode >= MT_PROXY_TIMING_OFF && mode <= MT_PROXY_TIMING_BALANCED) {
return mode;
}
return MT_PROXY_TIMING_OFF;
}
static int32_t normalizeMtProxyStartupCoverMode(int32_t mode) {
if (mode >= MT_PROXY_STARTUP_COVER_OFF && mode <= MT_PROXY_STARTUP_COVER_STRICT) {
return mode;
}
return MT_PROXY_STARTUP_COVER_OFF;
}
static const char *mtProxyTlsProfileName(int32_t profile) {
switch (normalizeMtProxyTlsProfile(profile)) {
case MT_PROXY_TLS_PROFILE_AUTO:
return "auto";
case MT_PROXY_TLS_PROFILE_FIREFOX:
return "firefox";
case MT_PROXY_TLS_PROFILE_ANDROID_CHROME:
return "android_chrome";
case MT_PROXY_TLS_PROFILE_YANDEX:
return "yandex";
case MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID:
return "firefox_android";
case MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP:
return "android_okhttp";
case MT_PROXY_TLS_PROFILE_AUTO_ROTATE:
return "auto_rotate";
case MT_PROXY_TLS_PROFILE_CHROME_MODERN:
return "chrome_modern";
case MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE:
return "legacy_no_grease_no_4469_no_modern_extensions";
default:
return "android_chrome";
}
}
static std::string mtProxySecretHashForRecipeKey(const std::string &secret) {
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256((const uint8_t *) secret.data(), secret.size(), digest);
char hex[17];
for (int i = 0; i < 8; i++) {
std::snprintf(hex + i * 2, 3, "%02x", digest[i]);
}
hex[16] = '\0';
return hex;
}
static std::string mtProxyRecipeCacheKeyFor(const std::string &host, uint16_t port, const std::string &secret, const std::string &sni) {
std::string key = MtProxyEndpointPolicy::networkEndpointKeyFor(host, port);
key += ":secret_hash=";
key += mtProxySecretHashForRecipeKey(secret);
if (!sni.empty()) {
key += ":";
key += sni;
}
return key;
}
static uint64_t mtProxyFailureResponseSignature(size_t responseBytes, const ByteArray *buffer) {
if (responseBytes == 0 || buffer == nullptr) {
return 0;
}
size_t sampleBytes = responseBytes < 128 ? responseBytes : 128;
uint64_t hash = 1469598103934665603ULL;
uint64_t responseLength = (uint64_t) responseBytes;
for (int shift = 0; shift < 64; shift += 8) {
hash ^= (uint8_t) ((responseLength >> shift) & 0xffU);
hash *= 1099511628211ULL;
}
for (size_t i = 0; i < sampleBytes; i++) {
hash ^= buffer->bytes[i];
hash *= 1099511628211ULL;
}
return hash == 0 ? 1 : hash;
}
static TlsHello selectMtProxyTlsHello(int32_t profile) {
switch (normalizeMtProxyTlsProfile(profile)) {
case MT_PROXY_TLS_PROFILE_FIREFOX:
return TlsHello::getFirefoxDefault();
case MT_PROXY_TLS_PROFILE_YANDEX:
return TlsHello::getYandexDefault();
case MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID:
return TlsHello::getFirefoxAndroidDefault();
case MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP:
return TlsHello::getAndroidOkHttpDefault();
case MT_PROXY_TLS_PROFILE_CHROME_MODERN:
return TlsHello::getChromeModernDefault();
case MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE:
return TlsHello::getLegacyNoGreaseDefault();
case MT_PROXY_TLS_PROFILE_ANDROID_CHROME:
default:
return TlsHello::getAndroidChromeDefault();
}
}
static bool isGreaseValue(uint16_t value) {
uint8_t high = (uint8_t) ((value >> 8) & 0xff);
uint8_t low = (uint8_t) (value & 0xff);
return high == low && (low & 0x0f) == 0x0a;
}
static uint16_t readBigEndian16(const uint8_t *data) {
return (uint16_t) (((uint16_t) data[0] << 8) | data[1]);
}
static void appendHex16(std::string &target, uint16_t value) {
char buffer[8];
std::snprintf(buffer, sizeof(buffer), "%04x", value);
if (!target.empty()) {
target += ",";
}
target += buffer;
}
static void appendGreaseValue(std::string &target, uint16_t value) {
if (isGreaseValue(value)) {
appendHex16(target, value);
}
}
static void logClientHelloFingerprint(const void *socket, const char *profileName, const uint8_t *data, uint32_t size) {
if (!LOGS_ENABLED) {
return;
}
std::string greaseValues;
std::string greaseExtensions;
bool has4469 = false;
bool has44cd = false;
auto logParsed = [&]() {
if (greaseValues.empty()) {
greaseValues = "-";
}
if (greaseExtensions.empty()) {
greaseExtensions = "-";
}
DEBUG_D("connection(%p) mtproxy_startup client_hello_fingerprint profile=%s size=%u grease_values=%s grease_exts=%s has_4469=%d has_44cd=%d", socket, profileName != nullptr ? profileName : "unknown", size, greaseValues.c_str(), greaseExtensions.c_str(), has4469 ? 1 : 0, has44cd ? 1 : 0);
};
auto logInvalid = [&](const char *reason) {
DEBUG_D("connection(%p) mtproxy_startup client_hello_fingerprint profile=%s size=%u parse=0 reason=%s grease_values=- grease_exts=- has_4469=0 has_44cd=0", socket, profileName != nullptr ? profileName : "unknown", size, reason != nullptr ? reason : "unknown");
};
if (data == nullptr || size < 100 || data[0] != 0x16 || data[5] != 0x01) {
logInvalid("prefix");
return;
}
uint32_t pos = 11 + 32;
if (pos >= size) {
logInvalid("random");
return;
}
uint32_t sessionLength = data[pos++];
if (pos + sessionLength + 2 > size) {
logInvalid("session");
return;
}
pos += sessionLength;
uint32_t cipherSuitesLength = readBigEndian16(data + pos);
pos += 2;
if (cipherSuitesLength < 2 || (cipherSuitesLength % 2) != 0 || pos + cipherSuitesLength > size) {
logInvalid("ciphers");
return;
}
uint32_t cipherSuitesEnd = pos + cipherSuitesLength;
while (pos + 1 < cipherSuitesEnd) {
appendGreaseValue(greaseValues, readBigEndian16(data + pos));
pos += 2;
}
if (pos >= size) {
logInvalid("compression");
return;
}
uint32_t compressionLength = data[pos++];
if (pos + compressionLength + 2 > size) {
logInvalid("compression_len");
return;
}
pos += compressionLength;
uint32_t extensionsLength = readBigEndian16(data + pos);
pos += 2;
if (pos + extensionsLength > size) {
logInvalid("extensions");
return;
}
uint32_t extensionsEnd = pos + extensionsLength;
while (pos + 4 <= extensionsEnd) {
uint16_t extensionType = readBigEndian16(data + pos);
uint32_t extensionLength = readBigEndian16(data + pos + 2);
pos += 4;
if (pos + extensionLength > extensionsEnd) {
logInvalid("extension_len");
return;
}
uint32_t extensionData = pos;
uint32_t extensionEnd = pos + extensionLength;
if (extensionType == 0x4469) {
has4469 = true;
} else if (extensionType == 0x44cd) {
has44cd = true;
}
if (isGreaseValue(extensionType)) {
appendHex16(greaseExtensions, extensionType);
appendGreaseValue(greaseValues, extensionType);
} else if (extensionType == 0x000a && extensionLength >= 2) {
uint32_t listLength = readBigEndian16(data + extensionData);
uint32_t listPos = extensionData + 2;
uint32_t listEnd = listPos + listLength;
if (listEnd <= extensionEnd) {
while (listPos + 1 < listEnd) {
appendGreaseValue(greaseValues, readBigEndian16(data + listPos));
listPos += 2;
}
}
} else if (extensionType == 0x002b && extensionLength >= 1) {
uint32_t listLength = data[extensionData];
uint32_t listPos = extensionData + 1;
uint32_t listEnd = listPos + listLength;
if (listEnd <= extensionEnd) {
while (listPos + 1 < listEnd) {
appendGreaseValue(greaseValues, readBigEndian16(data + listPos));
listPos += 2;
}
}
} else if (extensionType == 0x0033 && extensionLength >= 2) {
uint32_t listLength = readBigEndian16(data + extensionData);
uint32_t listPos = extensionData + 2;
uint32_t listEnd = listPos + listLength;
if (listEnd <= extensionEnd) {
while (listPos + 3 < listEnd) {
uint16_t group = readBigEndian16(data + listPos);
uint32_t keyLength = readBigEndian16(data + listPos + 2);
appendGreaseValue(greaseValues, group);
listPos += 4;
if (listPos + keyLength > listEnd) {
break;
}
listPos += keyLength;
}
}
}
pos = extensionEnd;
}
logParsed();
}
static bool validateServerCompatibleHello(const uint8_t *data, uint32_t size, const std::string &domain, const char *profileName) {
MtProxyClientHelloContractIssue issue = mtProxyCheckClientHelloContract(
data,
size,
domain);
if (issue == MtProxyClientHelloContractIssue::None) {
return true;
}
if (LOGS_ENABLED) {
DEBUG_E(
"mtproxy_startup profile %s breaks relay contract issue=%s size=%u",
profileName != nullptr ? profileName : "unknown",
mtProxyClientHelloContractIssueName(issue),
size);
}
return false;
}
ConnectionSocket::ConnectionSocket(int32_t instance) {
instanceNum = instance;
outgoingByteStream = new ByteStream();
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
eventObject = new EventObject(this, EventObjectTypeConnection);
}
ConnectionSocket::~ConnectionSocket() {
if (LOGS_ENABLED && mtProxyProbeLease.active()) DEBUG_D("connection(%p) mtproxy_startup probe_owner_release key=%s token=%llu", this, mtProxyProbeLease.key().c_str(), (unsigned long long) mtProxyProbeLease.ownerToken());
mtProxyProbeLease.release();
cancelProxyHandshakeAdmission();
clearPendingClientHello();
clearPendingTlsFrame();
if (proxyHandshakeAdmissionTimer != nullptr) {
delete proxyHandshakeAdmissionTimer;
proxyHandshakeAdmissionTimer = nullptr;
}
if (outgoingByteStream != nullptr) {
delete outgoingByteStream;
outgoingByteStream = nullptr;
}
if (eventObject != nullptr) {
delete eventObject;
eventObject = nullptr;
}
if (tempBuffer != nullptr) {
delete tempBuffer;
tempBuffer = nullptr;
}
if (tlsBuffer != nullptr) {
tlsBuffer->reuse();
tlsBuffer = nullptr;
}
}
bool ConnectionSocket::scheduleProxyHandshakeAdmissionIfNeeded(bool ipv6, int32_t timerMode) {
if (proxyAuthState < 10 || socketFd < 0 || isCurrentWebProxyBridge()) {
return false;
}
int32_t connectionPatternMode = normalizeMtProxyConnectionPatternMode(currentConnectionPatternMode);
if (!mtProxyHandshakeSchedulerUsesAdmission(connectionPatternMode)) {
if (proxyHandshakeAdmissionKey.empty()) {
proxyHandshakeAdmissionKey = currentMtProxyAdmissionKey;
}
if (proxyHandshakeAdmissionKey.empty()) {
return false;
}
setProxyHandshakeAdmissionState(0, 0, 1, 0, "admission_disabled");
proxyHandshakeAdmissionIpv6 = ipv6;
proxyHandshakeAdmissionStartTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
proxyHandshakeClientHelloSentTime = 0;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_disabled admission_mode=%s connection_pattern=%s key=%s priority=%d", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), proxyHandshakeAdmissionKey.c_str(), proxyHandshakeAdmissionPriority);
return false;
}
if (proxyHandshakeAdmissionPriority == MT_PROXY_HANDSHAKE_PRIORITY_BYPASS) {
return false;
}
if (proxyHandshakeAdmissionReady) {
setProxyHandshakeAdmissionState(-1, -1, -1, 0, "admission_ready_consumed");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "admission_ready_consumed");
return false;
}
if (proxyHandshakeAdmissionKey.empty()) {
proxyHandshakeAdmissionKey = currentMtProxyAdmissionKey;
}
if (proxyHandshakeAdmissionKey.empty()) {
return false;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyHandshakeAdmissionRequest request;
request.socket = this;
request.key = proxyHandshakeAdmissionKey;
request.generation = proxyHandshakeAdmissionGeneration;
request.requestClass = mtProxyRequestClassForPriority(proxyHandshakeAdmissionPriority);
request.priority = proxyHandshakeAdmissionPriority;
request.timerMode = timerMode;
request.connectionPatternMode = connectionPatternMode;
request.ipv6 = ipv6;
request.queueAlreadyPublished = proxyHandshakeAdmissionQueuePublished;
request.now = now;
MtProxyHandshakeAdmissionDecision decision = mtProxyHandshakeSchedulerAdmit(request);
if (decision.queued) {
if (decision.publishQueue) {
setProxyHandshakeAdmissionState(-1, 1, -1, -1, "admission_queue_publish");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_queue admission_mode=%s connection_pattern=%s key=%s request_class=%s priority=%d active=%d limit=%d global_active=%d global_limit=%d queued=%d cooldown_ms=%ld retry=%u", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), proxyHandshakeAdmissionKey.c_str(), mtProxyRequestClassName(request.requestClass), proxyHandshakeAdmissionPriority, decision.endpointActive, decision.endpointLimit, decision.globalActive, decision.globalLimit, decision.queuedCount, (long) decision.cooldownRemainingMs, decision.delayMs);
publishProxyConnectionStage("admission_queue");
} else if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup admission_queue_wait admission_mode=%s connection_pattern=%s key=%s request_class=%s priority=%d active=%d limit=%d global_active=%d global_limit=%d queued=%d cooldown_ms=%ld retry=%u", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), proxyHandshakeAdmissionKey.c_str(), mtProxyRequestClassName(request.requestClass), proxyHandshakeAdmissionPriority, decision.endpointActive, decision.endpointLimit, decision.globalActive, decision.globalLimit, decision.queuedCount, (long) decision.cooldownRemainingMs, decision.delayMs);
}
} else if (decision.granted) {
setProxyHandshakeAdmissionState(0, 0, 1, -1, "admission_grant");
proxyHandshakeAdmissionStartTime = now;
proxyHandshakeClientHelloSentTime = 0;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_grant admission_mode=%s connection_pattern=%s key=%s request_class=%s priority=%d active=%d limit=%d global_active=%d global_limit=%d delay=%u successes=%d", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), proxyHandshakeAdmissionKey.c_str(), mtProxyRequestClassName(request.requestClass), proxyHandshakeAdmissionPriority, decision.endpointActive, decision.endpointLimit, decision.globalActive, decision.globalLimit, decision.delayMs, decision.recentSuccesses);
}
if (decision.queued) {
setProxyHandshakeAdmissionState(1, -1, -1, -1, "admission_queue");
setTransportState(TransportState::WaitingGate, "admission_queue");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::AdmissionQueue, now + decision.delayMs, "admission_queue");
scheduleProxyHandshakeAdmissionTimer(decision.delayMs, timerMode, ipv6);
return true;
}
if (decision.delayMs == 0) {
if (timerMode != MT_PROXY_HANDSHAKE_TIMER_ADMISSION) {
setProxyHandshakeAdmissionState(-1, -1, -1, 1, "admission_zero_delay_ready");
}
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "admission_zero_delay_ready");
return false;
}
setProxyHandshakeAdmissionState(-1, -1, -1, 1, "admission_delay");
setTransportState(TransportState::WaitingGate, "admission_delay");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::AdmissionQueue, now + decision.delayMs, "admission_delay");
scheduleProxyHandshakeAdmissionTimer(decision.delayMs, timerMode, ipv6);
return true;
}
void ConnectionSocket::scheduleProxyHandshakeAdmissionTimer(uint32_t delay, int32_t mode, bool ipv6) {
if (proxyHandshakeAdmissionTimer == nullptr) {
proxyHandshakeAdmissionTimer = new Timer(instanceNum, [this] {
if (proxyHandshakeAdmissionTimer != nullptr) {
proxyHandshakeAdmissionTimer->stop();
}
uint32_t timerGeneration = proxyHandshakeAdmissionTimerGeneration;
int32_t mode = proxyHandshakeAdmissionTimerMode;
if (timerGeneration != proxyHandshakeAdmissionGeneration || mode == 0) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_timer_ignored generation=%u current_generation=%u mode=%d", this, timerGeneration, proxyHandshakeAdmissionGeneration, mode);
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_PROBE_WAIT) {
proxyHandshakeAdmissionTimerMode = 0;
bool delayedIpv6 = proxyHandshakeAdmissionIpv6;
mtProxyProbeWaitTimerFire(delayedIpv6);
return;
}
bool preTcpTimer = mode == MT_PROXY_HANDSHAKE_TIMER_ADMISSION
|| mode == MT_PROXY_HANDSHAKE_TIMER_HOST_RESOLVE
|| mode == MT_PROXY_HANDSHAKE_TIMER_ENDPOINT_BACKOFF
|| mode == MT_PROXY_HANDSHAKE_TIMER_DNS_COALESCE
|| mode == MT_PROXY_HANDSHAKE_TIMER_TCP_CONNECT_GATE;
if (preTcpTimer && !canRunMtProxyPreTcpTimer(mtProxyStartupTimerKindForMode(mode), timerGeneration)) {
proxyHandshakeAdmissionTimerMode = 0;
return;
}
proxyHandshakeAdmissionTimerMode = 0;
if (socketFd < 0) {
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_HOST_RESOLVE) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_host_resolve_timer_fire generation=%u ready=%d queued=%d active=%d", this, proxyHandshakeAdmissionGeneration, proxyHandshakeAdmissionReady ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0);
requestPendingHostResolve();
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_ENDPOINT_BACKOFF) {
bool delayedIpv6 = proxyHandshakeAdmissionIpv6;
setProxyEndpointBackoffReady(true, "endpoint_backoff_timer_fire");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "endpoint_backoff_timer_fire");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup endpoint_backoff_timer_fire generation=%u", this, proxyHandshakeAdmissionGeneration);
if (!waitingForHostResolve.empty()) {
requestPendingHostResolve();
return;
}
openConnectionInternal(delayedIpv6);
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_DNS_COALESCE) {
bool delayedIpv6 = proxyHandshakeAdmissionIpv6;
setProxyEndpointDnsCoalesceReady(true, "dns_coalesce_timer_fire");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "dns_coalesce_timer_fire");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup dns_coalesce_timer_fire generation=%u", this, proxyHandshakeAdmissionGeneration);
if (!waitingForHostResolve.empty()) {
bool cachedIpv6 = delayedIpv6;
std::string host = waitingForHostResolve;
bool blockedZeroAddress = false;
if (mtProxyEndpointUseCachedHostAddress(host, &cachedIpv6, &blockedZeroAddress)) {
setWaitingForHostResolve("", "dns_coalesce_cached_address");
setProxyEndpointDnsCoalesceReady(false, "dns_coalesce_cached_address");
openConnectionInternal(cachedIpv6);
return;
}
if (blockedZeroAddress) {
return;
}
setProxyEndpointDnsCoalesceReady(false, "dns_coalesce_request_pending");
requestPendingHostResolve();
return;
}
setProxyEndpointDnsCoalesceReady(false, "dns_coalesce_timer_fire");
openConnectionInternal(delayedIpv6);
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_TCP_CONNECT_GATE) {
bool delayedIpv6 = proxyHandshakeAdmissionIpv6;
setProxyEndpointTcpConnectGateState(-1, 1, -1, "tcp_connect_gate_timer_fire");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "tcp_connect_gate_timer_fire");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup tcp_connect_gate_timer_fire generation=%u", this, proxyHandshakeAdmissionGeneration);
openConnectionInternal(delayedIpv6);
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_FREEZE) {
markProxyHandshakeFreezeIfNeeded();
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_SERVER_HELLO) {
markProxyServerHelloHmacTimeoutIfNeeded();
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_CLIENT_HELLO_FRAGMENT) {
adjustWriteOp();
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_TLS_FRAME) {
adjustWriteOp();
return;
}
if (mode == MT_PROXY_HANDSHAKE_TIMER_ADMISSION) {
bool delayedIpv6 = proxyHandshakeAdmissionIpv6;
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "admission_timer_fire");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_timer_fire generation=%u ready=%d queued=%d active=%d", this, proxyHandshakeAdmissionGeneration, proxyHandshakeAdmissionReady ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0);
openConnectionInternal(delayedIpv6);
}
});
}
proxyHandshakeAdmissionTimer->stop();
proxyHandshakeAdmissionIpv6 = ipv6;
proxyHandshakeAdmissionTimerMode = mode;
proxyHandshakeAdmissionTimerGeneration = proxyHandshakeAdmissionGeneration;
proxyHandshakeAdmissionTimer->setTimeout(delay, false);
proxyHandshakeAdmissionTimer->start();
}
void ConnectionSocket::grantProxyHandshakeAdmission(bool ipv6, uint32_t generation, uint32_t delay, int32_t timerMode, const char *reason) {
if (generation != proxyHandshakeAdmissionGeneration || socketFd < 0 || proxyAuthState < 10) {
return;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
setProxyHandshakeAdmissionState(0, 0, 1, 1, "admission_grant_queued");
proxyHandshakeAdmissionStartTime = now;
proxyHandshakeClientHelloSentTime = 0;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_grant_queued admission_mode=%s connection_pattern=%s reason=%s key=%s priority=%d delay=%u", this, mtProxyConnectionPatternModeName(currentConnectionPatternMode), mtProxyConnectionPatternModeName(currentConnectionPatternMode), reason, proxyHandshakeAdmissionKey.c_str(), proxyHandshakeAdmissionPriority, delay);
setTransportState(TransportState::WaitingGate, "admission_grant_queued");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::AdmissionQueue, now + delay, "admission_grant_queued");
scheduleProxyHandshakeAdmissionTimer(delay, timerMode, ipv6);
}
void ConnectionSocket::cancelProxyHandshakeAdmission() {
if (proxyHandshakeAdmissionActive) {
releaseProxyHandshakeAdmission(false, "cancel");
}
mtProxyHandshakeSchedulerCancel(this);
setProxyHandshakeAdmissionState(0, 0, 0, 0, "cancelProxyHandshakeAdmission");
setProxyEndpointBackoffReady(false, "cancelProxyHandshakeAdmission");
setProxyEndpointTcpConnectGateState(-1, 0, 0, "cancelProxyHandshakeAdmission");
setProxyEndpointDnsCoalesceReady(false, "cancelProxyHandshakeAdmission");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "cancelProxyHandshakeAdmission");
proxyHandshakeAdmissionIpv6 = false;
proxyHandshakeAdmissionTimerMode = 0;
proxyHandshakeAdmissionTimerGeneration = proxyHandshakeAdmissionGeneration;
proxyHandshakeAdmissionGeneration++;
proxyHandshakeAdmissionStartTime = 0;
proxyHandshakeClientHelloSentTime = 0;
proxyHandshakeAdmissionKey.clear();
if (proxyHandshakeAdmissionTimer != nullptr) {
proxyHandshakeAdmissionTimer->stop();
}
}
void ConnectionSocket::releaseProxyHandshakeAdmission(bool succeeded, const char *reason) {
checkProxyHandshakeAdmissionRelease(succeeded, reason);
if (proxyHandshakeAdmissionKey.empty()) {
setProxyHandshakeAdmissionState(0, 0, 0, 0, "admission_release_empty_key");
return;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
int32_t connectionPatternMode = normalizeMtProxyConnectionPatternMode(currentConnectionPatternMode);
bool hadAdmission = proxyHandshakeAdmissionActive || proxyHandshakeAdmissionQueued;
bool wasActive = proxyHandshakeAdmissionActive;
bool isNeutralSchedulerWaitRelease = reason != nullptr && strcmp(reason, "tcp_connect_gate_wait") == 0;
bool isLifecycleHandshakeAbort = reason != nullptr && strcmp(reason, "background_handshake_aborted") == 0;
bool isCancelRelease = reason != nullptr && strcmp(reason, "cancel") == 0;
int64_t clientHelloElapsed = proxyHandshakeClientHelloSentTime > 0 ? now - proxyHandshakeClientHelloSentTime : 0;
bool shouldApplyFreezeCooldown = wasActive && proxyHandshakeClientHelloSentTime > 0 && clientHelloElapsed >= MT_PROXY_HANDSHAKE_FREEZE_TIMEOUT_MS && !isLifecycleHandshakeAbort;
bool shouldApplyTcpFailureCooldown = wasActive && !isNeutralSchedulerWaitRelease && startupTimeline.tcpConnectAttemptStarted() && proxyHandshakeClientHelloSentTime <= 0 && !isCancelRelease;
bool suppressQueuedGrant = !succeeded && wasActive && !isNeutralSchedulerWaitRelease && !isLifecycleHandshakeAbort && proxyHandshakeClientHelloSentTime > 0 && !isCancelRelease;
std::string admissionKey = proxyHandshakeAdmissionKey;
MtProxyHandshakeReleaseRequest releaseRequest;
releaseRequest.socket = this;
releaseRequest.key = admissionKey;
releaseRequest.hadAdmission = hadAdmission;
releaseRequest.wasActive = wasActive;
releaseRequest.succeeded = succeeded;
releaseRequest.neutralSchedulerWaitRelease = isNeutralSchedulerWaitRelease;
releaseRequest.lifecycleHandshakeAbort = isLifecycleHandshakeAbort;
releaseRequest.suppressQueuedGrant = suppressQueuedGrant;
releaseRequest.shouldApplyTcpFailureCooldown = shouldApplyTcpFailureCooldown;
releaseRequest.shouldApplyFreezeCooldown = shouldApplyFreezeCooldown;
releaseRequest.connectionPatternMode = connectionPatternMode;
releaseRequest.now = now;
MtProxyHandshakeReleaseDecision releaseDecision = mtProxyHandshakeSchedulerRelease(releaseRequest);
if (releaseDecision.ignored && LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup admission_release_ignored admission_mode=%s connection_pattern=%s reason=%s success=%d key=%s", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), reason, succeeded ? 1 : 0, admissionKey.c_str());
}
if (LOGS_ENABLED) {
switch (releaseDecision.cooldownKind) {
case MtProxyHandshakeCooldownKind::TcpFailure:
DEBUG_D("connection(%p) mtproxy_startup admission_tcp_failure_cooldown admission_mode=%s connection_pattern=%s reason=%s key=%s penalty=%d cooldown_ms=%ld", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), reason, admissionKey.c_str(), releaseDecision.cooldownPenalty, (long) releaseDecision.cooldownRemainingMs);
break;
case MtProxyHandshakeCooldownKind::Freeze:
DEBUG_D("connection(%p) mtproxy_startup admission_freeze_cooldown admission_mode=%s connection_pattern=%s reason=%s key=%s penalty=%d cooldown_ms=%ld", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), reason, admissionKey.c_str(), releaseDecision.cooldownPenalty, (long) releaseDecision.cooldownRemainingMs);
break;
case MtProxyHandshakeCooldownKind::Failure:
DEBUG_D("connection(%p) mtproxy_startup admission_failure_cooldown admission_mode=%s connection_pattern=%s reason=%s key=%s penalty=%d cooldown_ms=%ld", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), reason, admissionKey.c_str(), releaseDecision.cooldownPenalty, (long) releaseDecision.cooldownRemainingMs);
break;
case MtProxyHandshakeCooldownKind::FreezeObserved:
DEBUG_D("connection(%p) mtproxy_startup admission_freeze_observed admission_mode=%s connection_pattern=%s reason=%s key=%s cooldown=disabled", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), reason, admissionKey.c_str());
break;
case MtProxyHandshakeCooldownKind::None:
default:
break;
}
}
if (releaseDecision.publishHoldPhase && LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup admission_hold_after_client_hello_failure admission_mode=%s connection_pattern=%s reason=%s key=%s queued=%d cooldown_ms=%ld", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), reason, admissionKey.c_str(), releaseDecision.queuedCount, (long) releaseDecision.cooldownRemainingMs);
}
if (releaseDecision.hasNextRequest && LOGS_ENABLED) {
DEBUG_D("mtproxy_startup admission_dequeue_global admission_mode=%s connection_pattern=%s key=%s request_class=%s active=%d queued=%d global_active=%d global_limit=%d priority=%d", mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), releaseDecision.nextRequest.key.c_str(), mtProxyRequestClassName(releaseDecision.nextRequest.requestClass), releaseDecision.nextRequest.endpointActive, releaseDecision.nextRequest.endpointQueued, releaseDecision.nextRequest.globalActive, releaseDecision.nextRequest.globalLimit, releaseDecision.nextRequest.priority);
}
if (releaseDecision.publishHoldPhase) {
publishProxyConnectionStage("admission_hold_after_client_hello_failure");
}
setProxyHandshakeAdmissionState(0, 0, 0, 0, "admission_release");
proxyHandshakeAdmissionStartTime = 0;
proxyHandshakeClientHelloSentTime = 0;
proxyHandshakeAdmissionTimerMode = 0;
proxyHandshakeAdmissionTimerGeneration = proxyHandshakeAdmissionGeneration;
proxyHandshakeAdmissionKey.clear();
if (proxyHandshakeAdmissionTimer != nullptr) {
proxyHandshakeAdmissionTimer->stop();
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup admission_release admission_mode=%s connection_pattern=%s reason=%s success=%d key=%s next=%d global_active=%d global_limit=%d", this, mtProxyConnectionPatternModeName(connectionPatternMode), mtProxyConnectionPatternModeName(connectionPatternMode), reason, succeeded ? 1 : 0, admissionKey.c_str(), releaseDecision.hasNextRequest ? 1 : 0, releaseDecision.globalActive, releaseDecision.globalLimit);
if (releaseDecision.hasNextRequest && releaseDecision.nextRequest.socket != nullptr) {
releaseDecision.nextRequest.socket->grantProxyHandshakeAdmission(releaseDecision.nextRequest.ipv6, releaseDecision.nextRequest.generation, releaseDecision.nextRequest.delayMs, releaseDecision.nextRequest.timerMode, reason);
}
}
bool ConnectionSocket::scheduleMtProxyEndpointCircuitBreakerIfNeeded(bool ipv6) {
if (!isCurrentMtProxyConnection() || (currentMtProxyEndpointKey.empty() && currentMtProxyNetworkEndpointKey.empty())) {
return false;
}
if (isCurrentWebProxyBridge()) {
// The WEB bridge is a loopback socket into the WebView carrier: no
// remote relay to spare and no DPI to hide from, so an endpoint
// cooldown would only delay recovery of the carrier's streams.
return false;
}
int32_t connectionPatternMode = normalizeMtProxyConnectionPatternMode(currentConnectionPatternMode);
if (proxyEndpointBackoffReady) {
setProxyEndpointBackoffReady(false, "endpoint_backoff_ready_consumed");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "endpoint_backoff_ready_consumed");
return false;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyEndpointPolicy::MtProxyEndpointContext context;
context.endpointKey = currentMtProxyEndpointKey;
context.recipeCacheKey = currentMtProxyRecipeCacheKey;
context.networkEndpointKey = currentMtProxyNetworkEndpointKey;
context.connectionPatternMode = connectionPatternMode;
context.priority = proxyHandshakeAdmissionPriority;
MtProxyEndpointPolicy::CooldownResult cooldownResult = MtProxyEndpointPolicy::readCooldown(context, now);
if (!cooldownResult.active) {
return false;
}
MtProxyRetry::EndpointCooldownWaitInput cooldownWaitInput;
cooldownWaitInput.now = now;
cooldownWaitInput.cooldownUntil = cooldownResult.cooldownUntil;
cooldownWaitInput.cooldownRemainingMs = cooldownResult.remainingMs;
cooldownWaitInput.priority = proxyHandshakeAdmissionPriority;
cooldownWaitInput.connectionPatternMode = connectionPatternMode;
uint32_t delay = MtProxyRetry::endpointCooldownWaitMs(cooldownWaitInput);
publishProxyConnectionStage("endpoint_cooldown");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup endpoint_cooldown key=%s connection_pattern=%s priority=%d delay=%u cooldown_ms=%ld", this, cooldownResult.key.c_str(), mtProxyConnectionPatternModeName(connectionPatternMode), proxyHandshakeAdmissionPriority, delay, (long) cooldownResult.remainingMs);
setTransportState(TransportState::WaitingGate, "endpoint_cooldown");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::EndpointCooldown, now + delay, "endpoint_cooldown");
scheduleProxyHandshakeAdmissionTimer(delay, MT_PROXY_HANDSHAKE_TIMER_ENDPOINT_BACKOFF, ipv6);
return true;
}
bool ConnectionSocket::mtProxyProbeBeginOrJoin(bool ipv6) {
if (!isCurrentMtProxyConnection() || !currentSecretIsFakeTls || currentMtProxyProbeKey.empty()) {
return false;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyProbeCoordinator::ProbeKey probeKey;
probeKey.key = currentMtProxyProbeKey;
probeKey.endpointKey = currentMtProxyEndpointKey;
probeKey.networkEndpointKey = currentMtProxyNetworkEndpointKey;
probeKey.allowedSniVariants = currentAllowedSniVariants;
probeKey.configGeneration = proxyConfigGeneration;
MtProxyProbeCoordinator::Decision probeDecision = MtProxyProbeCoordinator::beginOrJoin(probeKey, mtProxyProbeLease.ownerToken(), now);
if (probeDecision.kind == MtProxyProbeCoordinator::DecisionKind::ProfilesExhaustedBackoff) {
MtProxyEndpointRecorder::recordProfilesExhaustedBackoff(
mtProxyEndpointProbeBackoffContext(probeDecision.waitMs, probeDecision.generation, ""),
mtProxyEndpointRecorderCallbacks());
closeSocket(1, -1);
return true;
}
if (probeDecision.kind == MtProxyProbeCoordinator::DecisionKind::HandshakeBudgetBackoff) {
MtProxyEndpointRecorder::recordHandshakeBudgetBackoff(
mtProxyEndpointProbeBackoffContext(probeDecision.waitMs, probeDecision.generation, probeDecision.terminalPhase),
mtProxyEndpointRecorderCallbacks());
closeSocket(1, -1);
return true;
}
if (probeDecision.kind == MtProxyProbeCoordinator::DecisionKind::JoinExisting) {
proxyCheckDiagnostic = "mtproxy_probe_wait";
publishProxyConnectionStage(proxyCheckDiagnostic.c_str());
setTransportState(TransportState::WaitingGate, "mtproxy_probe_wait");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::ProbeWait, now + probeDecision.waitMs, "mtproxy_probe_wait");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup probe_join key=%s endpoint=%s owner_generation=%u wait_ms=%u", this, currentMtProxyProbeKey.c_str(), currentMtProxyEndpointKey.c_str(), probeDecision.generation, probeDecision.waitMs);
scheduleProxyHandshakeAdmissionTimer(probeDecision.waitMs, MT_PROXY_HANDSHAKE_TIMER_PROBE_WAIT, ipv6);
return true;
}
if (probeDecision.kind == MtProxyProbeCoordinator::DecisionKind::UseWorkingRecipe) {
currentRecipeFamily = probeDecision.workingCursor.family;
currentRecipeSniVariant = probeDecision.workingCursor.sniVariant;
currentRecipeParserVariant = probeDecision.workingCursor.parserVariant;
currentRecipeClassicVariant = probeDecision.workingCursor.classicVariant;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup probe_working_recipe key=%s endpoint=%s owner_generation=%u working_recipe=%s family=%s sni_variant=%s parser_variant=%s classic_variant=%s", this, currentMtProxyProbeKey.c_str(), currentMtProxyEndpointKey.c_str(), probeDecision.generation, MtProxyAdaptivePolicy::recipeId(probeDecision.workingRecipe).c_str(), MtProxyAdaptivePolicy::clientHelloFamilyName(probeDecision.workingCursor.family), MtProxyAdaptivePolicy::sniVariantName(probeDecision.workingCursor.sniVariant), MtProxyAdaptivePolicy::parserVariantName(probeDecision.workingCursor.parserVariant), MtProxyAdaptivePolicy::classicVariantName(probeDecision.workingCursor.classicVariant));
} else {
if (LOGS_ENABLED && mtProxyProbeLease.active()) DEBUG_D("connection(%p) mtproxy_startup probe_owner_release key=%s token=%llu", this, mtProxyProbeLease.key().c_str(), (unsigned long long) mtProxyProbeLease.ownerToken());
mtProxyProbeLease.acquire(probeKey, probeDecision.ownerToken);
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup probe_start key=%s endpoint=%s generation=%u token=%llu", this, currentMtProxyProbeKey.c_str(), currentMtProxyEndpointKey.c_str(), probeDecision.generation, (unsigned long long) probeDecision.ownerToken);
}
return false;
}
void ConnectionSocket::mtProxyProbeWaitTimerFire(bool ipv6) {
if (!isCurrentMtProxyConnection() || currentMtProxyProbeKey.empty()) {
return;
}
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "probe_wait_timer_fire");
setTransportState(TransportState::Prepared, "probe_wait_timer_fire");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup probe_wait_timer_fire key=%s endpoint=%s", this, currentMtProxyProbeKey.c_str(), currentMtProxyEndpointKey.c_str());
openConnection(currentAddress, currentPort, "", ipv6, currentNetworkType, currentDatacenterId, currentMediaConnection);
}
bool ConnectionSocket::scheduleMtProxyEndpointTcpConnectGateIfNeeded(bool ipv6) {
if (!isCurrentMtProxyConnection() || currentMtProxyNetworkEndpointKey.empty()) {
return false;
}
if (isCurrentWebProxyBridge()) {
// Loopback bridge connects are cheap logical streams on one browser
// carrier; serializing them per endpoint only slows connection setup.
return false;
}
if (proxyEndpointTcpConnectActive) {
return false;
}
bool wasReady = proxyEndpointTcpConnectReady;
setProxyEndpointTcpConnectGateState(-1, 0, -1, "tcp_connect_gate_consume_ready");
MtProxyEndpointPolicy::TcpConnectGateResult gateResult = MtProxyEndpointPolicy::beginTcpConnect(currentMtProxyNetworkEndpointKey, wasReady);
if (!gateResult.shouldDelay) {
setProxyEndpointTcpConnectGateState(1, -1, -1, "tcp_connect_gate_grant");
}
if (!gateResult.shouldDelay) {
setProxyEndpointTcpConnectGateState(-1, -1, 0, "tcp_connect_gate_grant");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "tcp_connect_gate_grant");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup tcp_connect_gate_grant key=%s active=%d ready=%d", this, currentMtProxyNetworkEndpointKey.c_str(), gateResult.activeTcpConnects, wasReady ? 1 : 0);
return false;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
uint32_t delay = gateResult.delayMs;
if (!proxyEndpointTcpConnectGatePublished) {
setProxyEndpointTcpConnectGateState(-1, -1, 1, "tcp_connect_gate");
publishProxyConnectionStage("tcp_connect_gate");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup tcp_connect_gate key=%s active=%d delay=%u ready=%d", this, currentMtProxyNetworkEndpointKey.c_str(), gateResult.activeTcpConnects, delay, wasReady ? 1 : 0);
} else if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup tcp_connect_gate_wait key=%s active=%d delay=%u ready=%d", this, currentMtProxyNetworkEndpointKey.c_str(), gateResult.activeTcpConnects, delay, wasReady ? 1 : 0);
}
setTransportState(TransportState::WaitingGate, "tcp_connect_gate");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::TcpConnectGate, now + delay, "tcp_connect_gate");
if (proxyHandshakeAdmissionActive) {
releaseProxyHandshakeAdmission(false, "tcp_connect_gate_wait");
}
scheduleProxyHandshakeAdmissionTimer(delay, MT_PROXY_HANDSHAKE_TIMER_TCP_CONNECT_GATE, ipv6);
return true;
}
void ConnectionSocket::releaseMtProxyEndpointTcpConnect(const char *reason) {
if (!proxyEndpointTcpConnectActive || currentMtProxyNetworkEndpointKey.empty()) {
return;
}
int32_t activeTcpConnects = MtProxyEndpointPolicy::releaseTcpConnect(currentMtProxyNetworkEndpointKey);
setProxyEndpointTcpConnectGateState(0, -1, 0, "tcp_connect_gate_release");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup tcp_connect_gate_release key=%s active=%d reason=%s", this, currentMtProxyNetworkEndpointKey.c_str(), activeTcpConnects, reason != nullptr ? reason : "unknown");
}
bool ConnectionSocket::scheduleMtProxyDnsCoalesceIfNeeded(bool ipv6) {
if (!isCurrentMtProxyConnection() || currentMtProxyDnsCacheKey.empty() || isCurrentWebProxyBridge()) {
return false;
}
if (proxyEndpointDnsCoalesceReady) {
setProxyEndpointDnsCoalesceReady(false, "dns_coalesce_ready_consumed");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "dns_coalesce_ready_consumed");
return false;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyEndpointPolicy::DnsCoalesceResult coalesceResult = MtProxyEndpointPolicy::beginDnsCoalesce(currentMtProxyDnsCacheKey, now);
if (!coalesceResult.shouldDelay) {
return false;
}
uint32_t delay = coalesceResult.delayMs;
publishProxyConnectionStage("dns_coalesce_wait");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup dns_coalesce_wait dns_key=%s delay=%u", this, currentMtProxyDnsCacheKey.c_str(), delay);
setTransportState(TransportState::WaitingGate, "dns_coalesce_wait");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::DnsCoalesceWait, now + delay, "dns_coalesce_wait");
scheduleProxyHandshakeAdmissionTimer(delay, MT_PROXY_HANDSHAKE_TIMER_DNS_COALESCE, ipv6);
return true;
}
MtProxyEndpointRecorder::Callbacks ConnectionSocket::mtProxyEndpointRecorderCallbacks() {
MtProxyEndpointRecorder::Callbacks callbacks;
callbacks.publishObservation = [this](const MtProxySocketObservation &observation) {
publishMtProxySocketObservation(observation);
};
callbacks.setProxyCheckDiagnostic = [this](const std::string &diagnostic) {
proxyCheckDiagnostic = diagnostic;
};
callbacks.setSuggestedReconnectHold = [this](uint32_t holdMs) {
proxySuggestedReconnectHoldMs = holdMs;
};
callbacks.logInvariant = [this](const char *action, const char *reason) {
logTransportInvariant(action, reason);
};
callbacks.logDebug = [](const std::string &message) {
if (LOGS_ENABLED) {
DEBUG_D("%s", message.c_str());
}
};
return callbacks;
}
MtProxyEndpointRecorder::FailureContext ConnectionSocket::mtProxyEndpointFailureContext(const char *diagnostic, const char *reason) {
MtProxyEndpointRecorder::FailureContext context;
context.socketTag = this;
context.endpointKey = currentMtProxyEndpointKey;
context.recipeCacheKey = currentMtProxyRecipeCacheKey;
context.networkEndpointKey = currentMtProxyNetworkEndpointKey;
context.probeKey = currentMtProxyProbeKey;
context.fakeTls = currentSecretIsFakeTls;
context.allowedSniVariants = currentAllowedSniVariants;
context.activationGeneration = proxyActivationGeneration;
context.configGeneration = proxyConfigGeneration;
context.ownerToken = mtProxyProbeLease.ownerToken();
context.now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
context.recipe = currentMtProxyCompatibilityRecipe();
context.recipeId = currentMtProxyRecipeId();
context.recipeUsesGrease = currentMtProxyRecipeUsesGrease();
context.recipeIsGreaseProbe = currentMtProxyRecipeIsGreaseProbe();
context.classicFallbackAllowed = mtProxyClassicFallbackAllowed();
context.diagnostic = diagnostic == nullptr ? "" : diagnostic;
context.reason = reason == nullptr ? "unknown" : reason;
context.responseBytes = bytesRead;
context.responseSignature = mtProxyFailureResponseSignature(context.responseBytes, tempBuffer);
context.connectionPatternMode = normalizeMtProxyConnectionPatternMode(currentConnectionPatternMode);
context.connectionPatternName = mtProxyConnectionPatternModeName(context.connectionPatternMode);
context.priority = proxyHandshakeAdmissionPriority;
context.cursor.family = currentRecipeFamily;
context.cursor.sniVariant = currentRecipeSniVariant;
context.cursor.parserVariant = currentRecipeParserVariant;
context.cursor.classicVariant = currentRecipeClassicVariant;
context.recipeInput = currentMtProxyRecipeInput();
return context;
}
MtProxyEndpointRecorder::SuccessContext ConnectionSocket::mtProxyEndpointSuccessContext(const char *reason) {
MtProxyEndpointRecorder::SuccessContext context;
context.socketTag = this;
context.endpointKey = currentMtProxyEndpointKey;
context.recipeCacheKey = currentMtProxyRecipeCacheKey;
context.networkEndpointKey = currentMtProxyNetworkEndpointKey;
context.probeKey = currentMtProxyProbeKey;
context.fakeTls = currentSecretIsFakeTls;
context.allowedSniVariants = currentAllowedSniVariants;
context.activationGeneration = proxyActivationGeneration;
context.configGeneration = proxyConfigGeneration;
context.ownerToken = mtProxyProbeLease.ownerToken();
context.now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
context.recipe = currentMtProxyCompatibilityRecipe();
context.recipeId = currentMtProxyRecipeId();
context.recipeUsesGrease = currentMtProxyRecipeUsesGrease();
context.recipeIsGreaseProbe = currentMtProxyRecipeIsGreaseProbe();
context.classicFallbackAllowed = mtProxyClassicFallbackAllowed();
context.reason = reason == nullptr ? "unknown" : reason;
return context;
}
MtProxyEndpointRecorder::ProbeBackoffContext ConnectionSocket::mtProxyEndpointProbeBackoffContext(uint32_t holdMs, uint32_t generation, const std::string &terminalPhase) {
MtProxyEndpointRecorder::ProbeBackoffContext context;
context.socketTag = this;
context.endpointKey = currentMtProxyEndpointKey;
context.recipeCacheKey = currentMtProxyRecipeCacheKey;
context.networkEndpointKey = currentMtProxyNetworkEndpointKey;
context.probeKey = currentMtProxyProbeKey;
context.fakeTls = currentSecretIsFakeTls;
context.allowedSniVariants = currentAllowedSniVariants;
context.activationGeneration = proxyActivationGeneration;
context.configGeneration = proxyConfigGeneration;
context.ownerToken = mtProxyProbeLease.ownerToken();
context.now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
context.recipe = currentMtProxyCompatibilityRecipe();
context.recipeId = currentMtProxyRecipeId();
context.recipeUsesGrease = currentMtProxyRecipeUsesGrease();
context.recipeIsGreaseProbe = currentMtProxyRecipeIsGreaseProbe();
context.classicFallbackAllowed = mtProxyClassicFallbackAllowed();
context.holdMs = holdMs;
context.generation = generation;
context.terminalPhase = terminalPhase;
return context;
}
void ConnectionSocket::closeMtProxyDnsBlockedZeroAddress(const std::string &host, const std::string &ip, const char *reason) {
setWaitingForHostResolve("", "dns_blocked_zero_address");
setMtProxyDnsResolveAttemptStarted(false, "dns_blocked_zero_address");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "dns_blocked_zero_address");
setProxyEndpointDnsCoalesceReady(false, "dns_blocked_zero_address");
proxyCheckDiagnostic = MtProxyPhase::DnsBlockedZeroAddress;
MtProxySocketObservation observation;
observation.phase = MtProxyPhase::DnsBlockedZeroAddress;
observation.reason = reason != nullptr ? reason : "unknown";
publishMtProxySocketObservation(observation);
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup dns_blocked_zero_address host=%s ip=%s reason=%s", this, host.c_str(), ip.c_str(), reason != nullptr ? reason : "unknown");
closeSocket(1, -1);
}
bool ConnectionSocket::mtProxyEndpointUseCachedHostAddress(const std::string &host, bool *ipv6, bool *blockedZeroAddress) {
if (blockedZeroAddress != nullptr) {
*blockedZeroAddress = false;
}
if (!isCurrentMtProxyConnection() || currentMtProxyDnsCacheKey.empty()) {
return false;
}
std::string cachedIpv4;
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyEndpointPolicy::useCachedHostAddress(currentMtProxyDnsCacheKey, now, &cachedIpv4);
if (mtProxyIsBlockedZeroAddress(cachedIpv4)) {
if (blockedZeroAddress != nullptr) {
*blockedZeroAddress = true;
}
closeMtProxyDnsBlockedZeroAddress(host, cachedIpv4, "dns_cache_hit");
return false;
}
if (cachedIpv4.empty() || inet_pton(AF_INET, cachedIpv4.c_str(), &socketAddress.sin_addr.s_addr) != 1) {
return false;
}
if (ipv6 != nullptr) {
*ipv6 = false;
}
publishProxyConnectionStage("dns_cache_hit");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup dns_cache_hit dns_key=%s host=%s ip=%s", this, currentMtProxyDnsCacheKey.c_str(), host.c_str(), cachedIpv4.c_str());
return true;
}
void ConnectionSocket::mtProxyEndpointStoreResolvedAddress(const std::string &host, const std::string &ip) {
if (!isCurrentMtProxyConnection() || currentMtProxyDnsCacheKey.empty() || ip.empty()) {
return;
}
if (mtProxyIsBlockedZeroAddress(ip)) {
closeMtProxyDnsBlockedZeroAddress(host, ip, "dns_cache_store");
return;
}
struct in_addr parsedAddress;
if (inet_pton(AF_INET, ip.c_str(), &parsedAddress.s_addr) != 1) {
return;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyEndpointPolicy::storeResolvedAddress(currentMtProxyDnsCacheKey, ip, now);
publishProxyConnectionStage("dns_cache_store");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup dns_cache_store dns_key=%s host=%s ip=%s", this, currentMtProxyDnsCacheKey.c_str(), host.c_str(), ip.c_str());
}
MtProxyAdaptivePolicy::RecipeInput ConnectionSocket::currentMtProxyRecipeInput() {
MtProxyAdaptivePolicy::RecipeInput input;
input.fakeTls = currentSecretIsFakeTls;
input.endpointKey = currentMtProxyRecipeCacheKey.empty() ? currentMtProxyEndpointKey : currentMtProxyRecipeCacheKey;
input.sni = currentClientHelloSni.empty() ? currentSecretDomain : currentClientHelloSni;
input.originalSni = currentOriginalSecretDomain;
input.sanitizedSni = currentSanitizedSecretDomain.empty() ? currentSecretDomain : currentSanitizedSecretDomain;
input.lowercaseAsciiSni = currentLowercaseSecretDomain.empty() ? currentSecretDomain : currentLowercaseSecretDomain;
input.noTrailingDotSni = currentNoTrailingDotSecretDomain.empty() ? currentSecretDomain : currentNoTrailingDotSecretDomain;
input.punycodeSni = currentPunycodeSecretDomain;
input.allowedSniVariants = currentAllowedSniVariants;
input.useRecipeCursor = true;
input.cursor.family = currentRecipeFamily;
input.cursor.sniVariant = currentRecipeSniVariant;
input.cursor.parserVariant = currentRecipeParserVariant;
input.cursor.classicVariant = currentRecipeClassicVariant;
input.forceNoGrease = !MtProxyAdaptivePolicy::profileUsesGrease(currentEffectiveProxyTlsProfile);
input.probeGrease = false;
input.greaseSupported = false;
input.recipeLevel = 0;
input.alternateProfileIndex = 0;
input.clientHelloFragmentation = currentClientHelloFragmentation;
input.configuredTlsProfile = currentProxyTlsProfile;
input.effectiveTlsProfile = currentEffectiveProxyTlsProfile;
input.serverHelloParserMode = currentServerHelloParserMode;
input.connectionPatternMode = currentConnectionPatternMode;
input.recordSizingMode = currentRecordSizingMode;
input.timingMode = currentTimingMode;
input.startupCoverMode = currentStartupCoverMode;
return input;
}
MtProxyAdaptivePolicy::CompatibilityRecipe ConnectionSocket::currentMtProxyCompatibilityRecipe() {
if (!currentSecretIsFakeTls || currentMtProxyRecipeCacheKey.empty()) {
return MtProxyAdaptivePolicy::CompatibilityRecipe();
}
MtProxyAdaptivePolicy::RecipeInput input = currentMtProxyRecipeInput();
input.endpointKey = currentMtProxyRecipeCacheKey;
input.lastDiagnostic = MtProxyProbeCoordinator::lastRecipeDiagnosticForProbe(currentMtProxyProbeKey);
MtProxyProbeCoordinator::GreaseProbeResult greaseProbe = MtProxyProbeCoordinator::readGreaseProbeState(currentMtProxyProbeKey);
input.forceNoGrease = !greaseProbe.useGrease;
input.probeGrease = greaseProbe.probe;
input.greaseSupported = greaseProbe.supported;
return MtProxyAdaptivePolicy::recipeForCursor(input, input.cursor);
}
std::string ConnectionSocket::currentMtProxyRecipeId() {
if (!currentSecretIsFakeTls || currentMtProxyRecipeCacheKey.empty()) {
return "";
}
return MtProxyAdaptivePolicy::recipeId(currentMtProxyCompatibilityRecipe());
}
std::string ConnectionSocket::mtProxyRecipeIdForCursor(const MtProxyAdaptivePolicy::RecipeCursor &cursor) {
if (!currentSecretIsFakeTls || currentMtProxyRecipeCacheKey.empty()) {
return "";
}
MtProxyAdaptivePolicy::RecipeInput input = currentMtProxyRecipeInput();
input.endpointKey = currentMtProxyRecipeCacheKey;
input.cursor = cursor;
input.lastDiagnostic = MtProxyProbeCoordinator::lastRecipeDiagnosticForProbe(currentMtProxyProbeKey);
MtProxyProbeCoordinator::GreaseProbeResult greaseProbe = MtProxyProbeCoordinator::readGreaseProbeState(currentMtProxyProbeKey);
input.forceNoGrease = !greaseProbe.useGrease;
input.probeGrease = greaseProbe.probe;
input.greaseSupported = greaseProbe.supported;
return MtProxyAdaptivePolicy::recipeId(MtProxyAdaptivePolicy::recipeForCursor(input, cursor));
}
bool ConnectionSocket::currentMtProxyRecipeUsesGrease() {
return currentSecretIsFakeTls && MtProxyAdaptivePolicy::profileUsesGrease(currentEffectiveProxyTlsProfile);
}
bool ConnectionSocket::currentMtProxyRecipeIsGreaseProbe() {
if (!currentMtProxyRecipeUsesGrease() || currentMtProxyRecipeCacheKey.empty()) {
return false;
}
MtProxyProbeCoordinator::GreaseProbeResult greaseProbe = MtProxyProbeCoordinator::readGreaseProbeState(currentMtProxyProbeKey);
return greaseProbe.probe;
}
bool ConnectionSocket::mtProxyClassicFallbackAllowed() {
// ee secrets are SNI/HMAC-bound FakeTLS recipes; do not synthesize a classic secret from them.
return !currentSecretIsFakeTls
&& currentSecretKind != nullptr
&& (strcmp(currentSecretKind, "legacy") == 0 || strcmp(currentSecretKind, "dd") == 0);
}
void ConnectionSocket::applyMtProxyPhaseAdaptiveRecipe() {
if (!currentSecretIsFakeTls || currentMtProxyRecipeCacheKey.empty()) {
return;
}
MtProxyAdaptivePolicy::RecipeCursor cursor = MtProxyProbeCoordinator::recipeCursorForProbe(currentMtProxyProbeKey);
MtProxyProbeCoordinator::GreaseProbeResult greaseProbe = MtProxyProbeCoordinator::readGreaseProbeState(currentMtProxyProbeKey);
MtProxyAdaptivePolicy::RecipeInput input = currentMtProxyRecipeInput();
input.endpointKey = currentMtProxyRecipeCacheKey;
input.sni = currentSecretDomain;
input.cursor = cursor;
input.forceNoGrease = !greaseProbe.useGrease;
input.probeGrease = greaseProbe.probe;
input.greaseSupported = greaseProbe.supported;
input.lastDiagnostic = MtProxyProbeCoordinator::lastRecipeDiagnosticForProbe(currentMtProxyProbeKey);
MtProxyHandshakePlan plan = mtProxyBuildHandshakePlan(input, cursor, currentMtProxyEndpointKey, currentMtProxyProbeKey);
currentRecipeFamily = plan.cursor.family;
currentRecipeSniVariant = plan.cursor.sniVariant;
currentRecipeParserVariant = plan.cursor.parserVariant;
currentRecipeClassicVariant = plan.cursor.classicVariant;
currentClientHelloFragmentation = plan.clientHelloFragmentation;
currentEffectiveProxyTlsProfile = plan.tlsProfile;
currentServerHelloParserMode = plan.serverHelloParserMode;
currentClientHelloSni = plan.clientHelloSni;
currentConnectionPatternMode = plan.recipe.connectionPatternMode;
currentRecordSizingMode = plan.recordSizingMode;
currentTimingMode = plan.timingMode;
currentStartupCoverMode = plan.startupCoverMode;
publishProxyConnectionStage("phase_adaptive_recipe");
const MtProxyAdaptivePolicy::CompatibilityRecipe &descriptor = plan.recipe;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup phase_adaptive_recipe key=%s recipe_key=%s family=%s sni_variant=%s parser_variant=%s classic_variant=%s last=%s client_hello_sni=%s fragment=%d effective_profile=%s connection_pattern=%s record_sizing=%d timing=%d startup_cover=%d recipe=%s recipe_id=%s server_hello_parser=%s grease_probe=%d grease_supported=%d grease_rejected=%d experimental_no_sni=%d cursor_generation=%u", this, currentMtProxyEndpointKey.c_str(), currentMtProxyRecipeCacheKey.c_str(), descriptor.familyName.c_str(), descriptor.sniVariantName.c_str(), descriptor.parserVariantName.c_str(), descriptor.classicVariantName.c_str(), input.lastDiagnostic.c_str(), currentClientHelloSni.empty() ? "none" : currentClientHelloSni.c_str(), currentClientHelloFragmentation, mtProxyTlsProfileName(currentEffectiveProxyTlsProfile), mtProxyConnectionPatternModeName(currentConnectionPatternMode), currentRecordSizingMode, currentTimingMode, currentStartupCoverMode, plan.recipeId.c_str(), plan.recipeId.c_str(), mtProxyServerHelloParserName(currentServerHelloParserMode), plan.greaseProbe ? 1 : 0, plan.greaseSupported ? 1 : 0, greaseProbe.rejected ? 1 : 0, descriptor.experimentalNoSni ? 1 : 0, plan.cursor.generation);
}
void ConnectionSocket::markProxyHandshakeClientHelloSent() {
if (!proxyHandshakeAdmissionActive || proxyHandshakeAdmissionKey.empty()) {
return;
}
proxyHandshakeClientHelloSentTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
scheduleProxyHandshakeAdmissionTimer((uint32_t) MT_PROXY_HANDSHAKE_FREEZE_TIMEOUT_MS, MT_PROXY_HANDSHAKE_TIMER_FREEZE, proxyHandshakeAdmissionIpv6);
}
bool ConnectionSocket::didPauseDuringProxyServerHelloWait(int64_t now) {
if (proxyHandshakeClientHelloSentTime == 0) {
return false;
}
int64_t pauseTime = ConnectionsManager::getInstance(instanceNum).lastMonotonicPauseTime;
return pauseTime != 0 && pauseTime >= proxyHandshakeClientHelloSentTime && pauseTime <= now;
}
void ConnectionSocket::markProxyHandshakeFreezeIfNeeded() {
if (!proxyHandshakeAdmissionActive || proxyHandshakeClientHelloSentTime == 0 || proxyAuthState != 11) {
return;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
int64_t elapsed = now - proxyHandshakeClientHelloSentTime;
if (elapsed >= MT_PROXY_HANDSHAKE_FREEZE_TIMEOUT_MS) {
bool pausedDuringHandshake = didPauseDuringProxyServerHelloWait(now);
int64_t pauseTime = ConnectionsManager::getInstance(instanceNum).lastMonotonicPauseTime;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup faketls_server_hello_wait_timeout connection_pattern=%s key=%s elapsed=%ld background_pause=%d pause_age_ms=%ld", this, mtProxyConnectionPatternModeName(currentConnectionPatternMode), proxyHandshakeAdmissionKey.c_str(), (long) elapsed, pausedDuringHandshake ? 1 : 0, pausedDuringHandshake ? (long) (now - pauseTime) : -1L);
releaseProxyHandshakeAdmission(false, pausedDuringHandshake ? "background_handshake_aborted" : "freeze_timeout");
if (MT_PROXY_HANDSHAKE_CLOSE_ON_FREEZE_ENABLED) {
if (pausedDuringHandshake) {
proxyCheckDiagnostic = "background_handshake_aborted";
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup background_handshake_aborted elapsed=%ld pause_age_ms=%ld", this, (long) elapsed, (long) (now - pauseTime));
} else if (serverHelloHmacMismatchObserved) {
proxyCheckDiagnostic = MtProxyPhase::ServerHelloHmacMismatch;
tlsHashMismatch = true;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup server_hello_hmac_timeout bytes=%zu", this, bytesRead);
} else if (bytesRead == 0) {
proxyCheckDiagnostic = MtProxyPhase::FaketlsServerHelloWaitTimeout;
} else {
logMtProxyTlsAfterClientHello(bytesRead);
proxyCheckDiagnostic = classifyMtProxyPostClientHelloResponse(bytesRead);
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup server_hello_timeout_close elapsed=%ld", this, (long) elapsed);
closeSocket(1, ETIMEDOUT);
}
}
}
void ConnectionSocket::markProxyServerHelloHmacTimeoutIfNeeded() {
if (!serverHelloHmacMismatchObserved || serverHelloHmacMismatchTime == 0 || proxyAuthState != 11) {
return;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
int64_t elapsed = now - serverHelloHmacMismatchTime;
if (elapsed >= MT_PROXY_SERVER_HELLO_HMAC_WAIT_MS) {
proxyCheckDiagnostic = MtProxyPhase::ServerHelloHmacMismatch;
tlsHashMismatch = true;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup server_hello_hmac_timeout bytes=%zu elapsed=%ld", this, bytesRead, (long) elapsed);
closeSocket(1, ETIMEDOUT);
}
}
const char *ConnectionSocket::getProxyCheckDiagnostic() {
return proxyCheckDiagnostic.empty() ? "unknown_fail" : proxyCheckDiagnostic.c_str();
}
bool ConnectionSocket::isProxyCloseDiagnosticSuppressed() {
return proxyCloseDiagnosticSuppressed;
}
uint32_t ConnectionSocket::consumeSuggestedReconnectHoldMs() {
uint32_t hold = proxySuggestedReconnectHoldMs;
proxySuggestedReconnectHoldMs = 0;
return hold;
}
bool ConnectionSocket::isClosingOrClosedForWrites() const {
return socketDeadForWrites
|| socketCloseNotified
|| currentTransportState == TransportState::Closing;
}
const char *ConnectionSocket::transportStateName(TransportState state) {
return stateMachine.lifecycleName(state);
}
bool ConnectionSocket::isAllowedTransportTransition(TransportState previous, TransportState next) {
return stateMachine.isAllowedTransition(previous, next);
}
void ConnectionSocket::setTransportState(TransportState next, const char *reason) {
if (currentTransportState == next) {
return;
}
const char *previous = transportStateName(currentTransportState);
if (!isAllowedTransportTransition(currentTransportState, next)) {
logTransportInvariant("setTransportState", "invalid_transition");
}
stateMachine.setLifecycle(next);
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport state_change from=%s to=%s reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, previous, transportStateName(next), reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
const char *ConnectionSocket::proxyAuthStateName(uint8_t state) {
switch (state) {
case 0:
return "ready";
case 1:
return "socks_method_send";
case 2:
return "socks_method_wait";
case 3:
return "socks_auth_send";
case 4:
return "socks_auth_wait";
case 5:
return "socks_connect_send";
case 6:
return "socks_connect_wait";
case 10:
return "faketls_prepare";
case 11:
return "faketls_client_hello";
default:
return "unknown";
}
}
bool ConnectionSocket::isAllowedProxyAuthTransition(uint8_t previous, uint8_t next) {
if (previous == next) {
return true;
}
if (next == 0) {
return true;
}
struct ProxyAuthTransitionRule {
uint8_t previous;
uint8_t next;
};
static const ProxyAuthTransitionRule allowedTransitions[] = {
{0, 1},
{0, 10},
{1, 2},
{2, 3},
{2, 5},
{3, 4},
{4, 5},
{5, 6},
{10, 11},
};
for (const ProxyAuthTransitionRule &rule : allowedTransitions) {
if (rule.previous == previous && rule.next == next) {
return true;
}
}
return false;
}
void ConnectionSocket::setProxyAuthState(uint8_t next, const char *reason) {
if (proxyAuthState == next) {
return;
}
const char *previous = proxyAuthStateName(proxyAuthState);
if (!isAllowedProxyAuthTransition(proxyAuthState, next)) {
logTransportInvariant("setProxyAuthState", "invalid_transition");
}
stateMachine.setProxyAuthState(next);
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport proxy_state_change from=%s to=%s reason=%s proxy_state_from=%s proxy_state_to=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, previous, proxyAuthStateName(next), reason != nullptr ? reason : "unknown", previous, proxyAuthStateName(next), transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
const char *ConnectionSocket::tlsStateName(int8_t state) {
switch (state) {
case 0:
return "plain_or_inactive";
case 1:
return "faketls_ready";
case 2:
return "faketls_appdata";
default:
return "unknown";
}
}
bool ConnectionSocket::isAllowedTlsStateTransition(int8_t previous, int8_t next) {
if (previous == next) {
return true;
}
if (next == 0) {
return true;
}
struct TlsStateTransitionRule {
int8_t previous;
int8_t next;
};
static const TlsStateTransitionRule allowedTransitions[] = {
{0, 1},
{1, 2},
};
for (const TlsStateTransitionRule &rule : allowedTransitions) {
if (rule.previous == previous && rule.next == next) {
return true;
}
}
return false;
}
void ConnectionSocket::setTlsState(int8_t next, const char *reason) {
if (tlsState == next) {
return;
}
const char *previous = tlsStateName(tlsState);
if (!isAllowedTlsStateTransition(tlsState, next)) {
logTransportInvariant("setTlsState", "invalid_transition");
}
stateMachine.setTlsState(next);
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport tls_state_change from=%s to=%s reason=%s tls_state_from=%s tls_state_to=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, previous, tlsStateName(next), reason != nullptr ? reason : "unknown", previous, tlsStateName(next), transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::logTransportSnapshot(const char *event, const char *reason) {
if (isCurrentDirectConnection()) {
return;
}
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport snapshot event=%s reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, event != nullptr ? event : "unknown", reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::logTransportInvariant(const char *action, const char *reason) {
if (isCurrentDirectConnection()) {
return;
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport transport_invariant action=%s reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, action != nullptr ? action : "unknown", reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
const ConnectionSocket::TransportActionRule *ConnectionSocket::findTransportActionRule(const char *action) {
return stateMachine.findActionRule(action);
}
bool ConnectionSocket::isTransportStateAllowedForAction(const char *action) {
return findTransportActionRule(action) != nullptr;
}
bool ConnectionSocket::checkTransportActionRequirements(const char *action) {
// A disabled proxy means stock tgnet semantics. Direct sockets still use
// the syscall wrapper, but none of the MTProxy lifecycle policy can veto
// their socket, epoll, notification, receive, or write operations.
if (isCurrentDirectConnection()) {
return true;
}
const TransportActionRule *rule = findTransportActionRule(action);
if (rule == nullptr) {
logTransportInvariant(action, "invalid_state");
return false;
}
if (rule->socketPolicy == TransportSocketPolicy::LiveEpoll && !canUseLiveEpollSocket(action)) {
return false;
}
if (rule->socketPolicy == TransportSocketPolicy::NoSocket) {
if (socketFd >= 0) {
logTransportInvariant(action, "socket_already_open");
return false;
}
if (epollRegistered) {
logTransportInvariant(action, "epoll_already_registered");
return false;
}
}
if (rule->socketPolicy == TransportSocketPolicy::OpenWithoutEpoll) {
if (socketFd < 0) {
logTransportInvariant(action, "socket_closed");
return false;
}
if (epollRegistered) {
logTransportInvariant(action, "epoll_already_registered");
return false;
}
}
if (rule->expectedProxyAuthState >= 0 && proxyAuthState != (uint8_t) rule->expectedProxyAuthState) {
logTransportInvariant(action, "invalid_proxy_state");
return false;
}
if (rule->expectedTlsState >= 0 && tlsState != rule->expectedTlsState) {
logTransportInvariant(action, "invalid_tls_state");
return false;
}
if (rule->requireWssTransport) {
if (!currentTransportWss) {
logTransportInvariant(action, "not_wss");
return false;
}
if (currentWssTransport == nullptr) {
logTransportInvariant(action, "missing_transport");
return false;
}
}
if (rule->requireWssReady && (currentWssTransport == nullptr || !currentWssTransport->isReady())) {
logTransportInvariant(action, "wss_not_ready");
return false;
}
return true;
}
void ConnectionSocket::setSocketFd(int fd, const char *reason) {
int previousOpen = socketFd >= 0 ? 1 : 0;
if (!stateMachine.setSocketFd(fd)) {
return;
}
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport socket_fd_state_change open=%d previous_open=%d reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, socketFd >= 0 ? 1 : 0, previousOpen, reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setEpollRegistered(bool registered, const char *reason) {
if (!stateMachine.setEpollRegistered(registered)) {
return;
}
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport epoll_registration_state_change registered=%d reason=%s epoll_registered=%d transport_state=%s admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, registered ? 1 : 0, reason != nullptr ? reason : "unknown", epollRegistered ? 1 : 0, transportStateName(currentTransportState), proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
bool ConnectionSocket::canCreateSocket(const char *action) {
return checkTransportActionRequirements(action);
}
bool ConnectionSocket::canUseLiveEpollSocket(const char *action) {
if (socketFd < 0) {
logTransportInvariant(action, "socket_closed");
return false;
}
if (!epollRegistered) {
logTransportInvariant(action, "epoll_not_registered");
return false;
}
return true;
}
bool ConnectionSocket::canModifyEpollWriteInterest(const char *action) {
return checkTransportActionRequirements(action);
}
bool ConnectionSocket::canSendPendingClientHello() {
return checkTransportActionRequirements("sendPendingClientHello");
}
bool ConnectionSocket::canSendPendingTlsFrame() {
return checkTransportActionRequirements("sendPendingTlsFrame");
}
bool ConnectionSocket::canSendSocksHandshakeFrame(const char *action, uint8_t expectedProxyAuthState) {
const TransportActionRule *rule = findTransportActionRule(action);
if (rule != nullptr && rule->expectedProxyAuthState >= 0 && rule->expectedProxyAuthState != expectedProxyAuthState) {
logTransportInvariant(action, "proxy_state_policy_mismatch");
return false;
}
return checkTransportActionRequirements(action);
}
bool ConnectionSocket::canSendPlainMtProtoPayload() {
return checkTransportActionRequirements("sendPlainMtProtoPayload");
}
bool ConnectionSocket::canStartTcpConnect() {
return checkTransportActionRequirements("connect");
}
bool ConnectionSocket::canRegisterEpollSocket() {
return checkTransportActionRequirements("epoll_ctl_add");
}
bool ConnectionSocket::canConfigureOpenSocket() {
return checkTransportActionRequirements("configure_socket");
}
bool ConnectionSocket::canCheckSocketError() {
return checkTransportActionRequirements("checkSocketError");
}
bool ConnectionSocket::canProcessEpollEvent() {
return checkTransportActionRequirements("onEvent");
}
void ConnectionSocket::checkCloseSocketAction(const char *action) {
checkTransportActionRequirements(action);
}
bool ConnectionSocket::canUnregisterEpollSocket() {
return checkTransportActionRequirements("epoll_ctl_del");
}
bool ConnectionSocket::canCloseNativeSocket() {
return checkTransportActionRequirements("close_native_socket");
}
void ConnectionSocket::setProxyHandshakeAdmissionState(int8_t queued, int8_t published, int8_t active, int8_t ready, const char *reason) {
bool nextQueued = queued >= 0 ? queued != 0 : proxyHandshakeAdmissionQueued;
bool nextPublished = published >= 0 ? published != 0 : proxyHandshakeAdmissionQueuePublished;
bool nextActive = active >= 0 ? active != 0 : proxyHandshakeAdmissionActive;
bool nextReady = ready >= 0 ? ready != 0 : proxyHandshakeAdmissionReady;
if (proxyHandshakeAdmissionQueued == nextQueued
&& proxyHandshakeAdmissionQueuePublished == nextPublished
&& proxyHandshakeAdmissionActive == nextActive
&& proxyHandshakeAdmissionReady == nextReady) {
return;
}
proxyHandshakeAdmissionQueued = nextQueued;
proxyHandshakeAdmissionQueuePublished = nextPublished;
proxyHandshakeAdmissionActive = nextActive;
proxyHandshakeAdmissionReady = nextReady;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport admission_state_change queued=%d published=%d active=%d ready=%d reason=%s admission_active=%d admission_queued=%d transport_state=%s epoll_registered=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, proxyHandshakeAdmissionQueued ? 1 : 0, proxyHandshakeAdmissionQueuePublished ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionReady ? 1 : 0, reason != nullptr ? reason : "unknown", proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::checkProxyHandshakeAdmissionRelease(bool succeeded, const char *reason) {
if (!proxyHandshakeAdmissionActive && !proxyHandshakeAdmissionQueued) {
return;
}
if (checkTransportActionRequirements("releaseProxyHandshakeAdmission")) {
return;
}
logTransportInvariant("releaseProxyHandshakeAdmission", succeeded ? "unexpected_success_state" : "unexpected_failure_state");
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport admission_release_invariant reason=%s success=%d transport_state=%s admission_active=%d admission_queued=%d", this, reason != nullptr ? reason : "unknown", succeeded ? 1 : 0, transportStateName(currentTransportState), proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0);
}
}
void ConnectionSocket::setProxyEndpointTcpConnectGateState(int8_t active, int8_t ready, int8_t published, const char *reason) {
bool nextActive = active >= 0 ? active != 0 : proxyEndpointTcpConnectActive;
bool nextReady = ready >= 0 ? ready != 0 : proxyEndpointTcpConnectReady;
bool nextPublished = published >= 0 ? published != 0 : proxyEndpointTcpConnectGatePublished;
if (proxyEndpointTcpConnectActive == nextActive
&& proxyEndpointTcpConnectReady == nextReady
&& proxyEndpointTcpConnectGatePublished == nextPublished) {
return;
}
proxyEndpointTcpConnectActive = nextActive;
proxyEndpointTcpConnectReady = nextReady;
proxyEndpointTcpConnectGatePublished = nextPublished;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport tcp_connect_gate_state_change active=%d ready=%d published=%d reason=%s tcp_gate_active=%d transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, proxyEndpointTcpConnectActive ? 1 : 0, proxyEndpointTcpConnectReady ? 1 : 0, proxyEndpointTcpConnectGatePublished ? 1 : 0, reason != nullptr ? reason : "unknown", proxyEndpointTcpConnectActive ? 1 : 0, transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setProxyEndpointBackoffReady(bool ready, const char *reason) {
if (proxyEndpointBackoffReady == ready) {
return;
}
proxyEndpointBackoffReady = ready;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport endpoint_backoff_ready_state_change ready=%d reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, proxyEndpointBackoffReady ? 1 : 0, reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setProxyEndpointDnsCoalesceReady(bool ready, const char *reason) {
if (proxyEndpointDnsCoalesceReady == ready) {
return;
}
proxyEndpointDnsCoalesceReady = ready;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport dns_coalesce_ready_state_change ready=%d reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, proxyEndpointDnsCoalesceReady ? 1 : 0, reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setAdjustWriteOpAfterResolve(bool pending, const char *reason) {
if (adjustWriteOpAfterResolve == pending) {
return;
}
adjustWriteOpAfterResolve = pending;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport write_after_resolve_state_change pending=%d reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, adjustWriteOpAfterResolve ? 1 : 0, reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setAdjustWriteOpAfterPreTcpGate(bool pending, const char *reason) {
if (adjustWriteOpAfterPreTcpGate == pending) {
return;
}
adjustWriteOpAfterPreTcpGate = pending;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport write_after_pre_tcp_gate_state_change pending=%d reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, adjustWriteOpAfterPreTcpGate ? 1 : 0, reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setMtProxyTcpConnectAttemptStarted(bool started, const char *reason) {
if (startupTimeline.tcpConnectAttemptStarted() == started) {
return;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (started) {
startupTimeline.beginTcpConnect(now, timeout);
lastEventTime = now;
} else {
startupTimeline.finishTcpConnect();
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport tcp_connect_attempt_started_state_change started=%d reason=%s tcp_start_ms=%lld tcp_deadline_ms=%lld transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, startupTimeline.tcpConnectAttemptStarted() ? 1 : 0, reason != nullptr ? reason : "unknown", (long long) startupTimeline.tcpConnectStartTimeMs(), (long long) startupTimeline.tcpConnectDeadlineMs(), transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setMtProxyDnsResolveAttemptStarted(bool started, const char *reason) {
if (startupTimeline.dnsResolveAttemptStarted() == started) {
return;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (started) {
startupTimeline.beginDnsResolve(now, timeout);
} else {
startupTimeline.finishDnsResolve();
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport dns_resolve_attempt_started_state_change started=%d reason=%s dns_start_ms=%lld dns_deadline_ms=%lld transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, startupTimeline.dnsResolveAttemptStarted() ? 1 : 0, reason != nullptr ? reason : "unknown", (long long) startupTimeline.dnsResolveStartTimeMs(), (long long) startupTimeline.dnsResolveDeadlineMs(), transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setMtProxyPreTcpWaitPhase(MtProxyStartupPhase phase, int64_t deadlineMs, const char *reason) {
MtProxyStartupPhase previousPhase = startupTimeline.phase();
int64_t previousDeadline = startupTimeline.localWaitDeadlineMs();
if (phase == MtProxyStartupPhase::None) {
startupTimeline.finishLocalWait();
} else {
startupTimeline.beginLocalWait(phase, deadlineMs);
}
if (previousPhase == startupTimeline.phase() && previousDeadline == startupTimeline.localWaitDeadlineMs()) {
return;
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport pre_tcp_wait_state_change phase=%s deadline_ms=%lld reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, startupTimeline.phaseName(), (long long) startupTimeline.localWaitDeadlineMs(), reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::finishMtProxyPreTcpWait(const char *reason) {
if (!isCurrentMtProxyConnection()) {
return;
}
startupTimeline.finishLocalWait();
proxyHandshakeAdmissionTimerMode = 0;
proxyHandshakeAdmissionTimerGeneration = proxyHandshakeAdmissionGeneration;
proxyHandshakeAdmissionGeneration++;
if (proxyHandshakeAdmissionTimer != nullptr) {
proxyHandshakeAdmissionTimer->stop();
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup pre_tcp_wait_finished reason=%s generation=%u transport_state=%s epoll_registered=%d tcp_started=%d", this, reason != nullptr ? reason : "unknown", proxyHandshakeAdmissionGeneration, transportStateName(currentTransportState), epollRegistered ? 1 : 0, startupTimeline.tcpConnectAttemptStarted() ? 1 : 0);
}
}
MtProxyStartupTimerKind ConnectionSocket::mtProxyStartupTimerKindForMode(int32_t mode) {
switch (mode) {
case MT_PROXY_HANDSHAKE_TIMER_ADMISSION:
return MtProxyStartupTimerKind::Admission;
case MT_PROXY_HANDSHAKE_TIMER_HOST_RESOLVE:
return MtProxyStartupTimerKind::HostResolveAdmission;
case MT_PROXY_HANDSHAKE_TIMER_ENDPOINT_BACKOFF:
return MtProxyStartupTimerKind::EndpointBackoff;
case MT_PROXY_HANDSHAKE_TIMER_DNS_COALESCE:
return MtProxyStartupTimerKind::DnsCoalesce;
case MT_PROXY_HANDSHAKE_TIMER_TCP_CONNECT_GATE:
return MtProxyStartupTimerKind::TcpConnectGate;
default:
return MtProxyStartupTimerKind::None;
}
}
bool ConnectionSocket::canRunMtProxyPreTcpTimer(MtProxyStartupTimerKind expectedKind, uint32_t timerGeneration) {
MtProxyStartupTimerDecision decision = startupTimeline.canRunPreTcpTimer(expectedKind,
timerGeneration,
proxyHandshakeAdmissionGeneration,
mtProxyStartupTimerKindForMode(proxyHandshakeAdmissionTimerMode),
socketFd >= 0,
currentTransportState == TransportState::WaitingGate,
epollRegistered);
if (decision.canRun) {
return true;
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup pre_tcp_timer_ignored reason=%s mode=%s timer_mode=%s generation=%u current_generation=%u transport_state=%s epoll_registered=%d dns_started=%d tcp_started=%d socket_fd=%d", this, decision.ignoreReason != nullptr ? decision.ignoreReason : "unknown", MtProxyStartupTimeline::timerKindName(expectedKind), MtProxyStartupTimeline::timerKindName(mtProxyStartupTimerKindForMode(proxyHandshakeAdmissionTimerMode)), timerGeneration, proxyHandshakeAdmissionGeneration, transportStateName(currentTransportState), epollRegistered ? 1 : 0, startupTimeline.dnsResolveAttemptStarted() ? 1 : 0, startupTimeline.tcpConnectAttemptStarted() ? 1 : 0, socketFd);
}
return false;
}
void ConnectionSocket::classifyMtProxyPreTcpTimeoutDiagnostic(const char *reason) {
if (!isCurrentMtProxyConnection()) {
return;
}
if (currentTransportState != TransportState::Prepared && currentTransportState != TransportState::WaitingGate && currentTransportState != TransportState::TcpConnecting) {
return;
}
std::string previousDiagnostic = proxyCheckDiagnostic;
const char *diagnostic = startupTimeline.terminalDiagnostic(mtproxySocketConnectedLogged);
if (diagnostic != nullptr && diagnostic[0] != '\0') {
proxyCheckDiagnostic = diagnostic;
}
if (previousDiagnostic == proxyCheckDiagnostic) {
return;
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup pre_tcp_timeout_diagnostic phase=%s reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, proxyCheckDiagnostic.c_str(), reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
std::string ConnectionSocket::deriveMtProxyTerminalDiagnostic(int32_t reason, int32_t error) {
if (!isCurrentMtProxyConnection() || reason == 0) {
return proxyCheckDiagnostic;
}
// Pre-I/O terminal verdicts (handshake_profiles_exhausted, the FakeTLS
// terminal budget phases, DNS/secret verdicts) must survive the close
// path; the decision itself lives in the module so it is host-compiled.
// See MtProxyPhase::deriveTerminalDiagnostic + isPreIoTerminalVerdict.
MtProxyPhase::TerminalDiagnosticInput terminalInput;
terminalInput.currentDiagnostic = proxyCheckDiagnostic.c_str();
terminalInput.timeline = &startupTimeline;
terminalInput.socketConnectedLogged = mtproxySocketConnectedLogged;
terminalInput.closeReason = reason;
terminalInput.socketError = error;
MtProxyPhase::TerminalDiagnosticResult terminalResult = MtProxyPhase::deriveTerminalDiagnostic(terminalInput);
if (terminalResult.timelineDerived) {
classifyMtProxyPreTcpTimeoutDiagnostic("derive_terminal");
}
return terminalResult.diagnostic;
}
void ConnectionSocket::setMtProxySocketConnectedLogged(bool logged, const char *reason) {
if (mtproxySocketConnectedLogged == logged) {
return;
}
mtproxySocketConnectedLogged = logged;
if (logged) {
startupTimeline.finishTcpConnect();
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport socket_connected_logged_state_change logged=%d reason=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, mtproxySocketConnectedLogged ? 1 : 0, reason != nullptr ? reason : "unknown", transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
bool ConnectionSocket::canStartHostResolve() {
if (waitingForHostResolve.empty()) {
logTransportInvariant("host_resolve_start", "no_pending_host");
return false;
}
return checkTransportActionRequirements("host_resolve_start");
}
void ConnectionSocket::checkHostResolveCallback(const std::string &host) {
if (waitingForHostResolve != host) {
return;
}
if (!checkTransportActionRequirements("host_resolve_callback")) {
logTransportInvariant("host_resolve_callback", "invalid_state");
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport host_resolve_callback_invariant host=%s transport_state=%s admission_active=%d admission_queued=%d waiting_resolve=%d", this, host.c_str(), transportStateName(currentTransportState), proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1);
}
}
}
void ConnectionSocket::setWaitingForHostResolve(const std::string &host, const char *reason) {
if (waitingForHostResolve == host) {
return;
}
waitingForHostResolve = host;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport host_resolve_state_change waiting=%d reason=%s waiting_resolve=%d host_len=%zu transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d proxy_state=%d tls_state=%d", this, waitingForHostResolve.empty() ? 0 : 1, reason != nullptr ? reason : "unknown", waitingForHostResolve.empty() ? 0 : 1, waitingForHostResolve.size(), transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, (int) proxyAuthState, (int) tlsState);
}
}
bool ConnectionSocket::canNotifyConnected(const char *action) {
if (!checkTransportActionRequirements(action)) {
return false;
}
if (onConnectedSent) {
logTransportInvariant(action, "already_notified");
return false;
}
return true;
}
void ConnectionSocket::setSocketCloseNotified(bool notified, const char *reason) {
if (socketCloseNotified == notified) {
return;
}
socketCloseNotified = notified;
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport socket_close_notify_state_change notified=%d reason=%s close_notified=%d transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, notified ? 1 : 0, reason != nullptr ? reason : "unknown", socketCloseNotified ? 1 : 0, transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
void ConnectionSocket::setConnectedNotified(bool sent, const char *reason) {
if (onConnectedSent == sent) {
return;
}
onConnectedSent = sent;
if (NETWORK_DEBUG_LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport connected_notify_state_change sent=%d reason=%s connected_notified=%d transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, sent ? 1 : 0, reason != nullptr ? reason : "unknown", onConnectedSent ? 1 : 0, transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
}
}
bool ConnectionSocket::canDeliverReceivedData(const char *action) {
return checkTransportActionRequirements(action);
}
bool ConnectionSocket::canSendWssFrame() {
return checkTransportActionRequirements("sendWssFrame");
}
bool ConnectionSocket::canQueueOutboundBuffer(const char *action) {
if (isCurrentDirectConnection()) {
return true;
}
if (isClosingOrClosedForWrites()) {
logTransportInvariant(action, "dead_for_writes");
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport write_suppressed_dead_connection action=%s phase=%s transport_state=%s close_notified=%d dead_for_writes=%d", this, action != nullptr ? action : "unknown", proxyCheckDiagnostic.c_str(), transportStateName(currentTransportState), socketCloseNotified ? 1 : 0, socketDeadForWrites ? 1 : 0);
}
return false;
}
return checkTransportActionRequirements(action);
}
bool ConnectionSocket::canSendRawSocketBytes(const char *action) {
return checkTransportActionRequirements(action);
}
bool ConnectionSocket::canReceiveRawSocketBytes() {
return checkTransportActionRequirements("raw_socket_recv");
}
void ConnectionSocket::markConnectionDeadForWrites(const char *reason) {
if (socketDeadForWrites) {
return;
}
socketDeadForWrites = true;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_transport dead_for_writes reason=%s phase=%s transport_state=%s close_notified=%d", this, reason != nullptr ? reason : "unknown", proxyCheckDiagnostic.c_str(), transportStateName(currentTransportState), socketCloseNotified ? 1 : 0);
}
}
void ConnectionSocket::clearPendingClientHello() {
if (pendingClientHello != nullptr) {
delete pendingClientHello;
pendingClientHello = nullptr;
}
pendingClientHelloSize = 0;
pendingClientHelloOffset = 0;
pendingClientHelloFragmentTarget = 0;
pendingClientHelloFragmentIndex = 0;
pendingClientHelloFragmentCount = 0;
pendingClientHelloNextWriteTime = 0;
}
bool ConnectionSocket::buildPendingClientHello(uint32_t size) {
if (pendingClientHello != nullptr || size == 0) {
return false;
}
pendingClientHelloSize = size;
pendingClientHelloOffset = 0;
pendingClientHello = new ByteArray(size);
std::memcpy(pendingClientHello->bytes, tempBuffer->bytes, size);
if (currentClientHelloFragmentation == MT_PROXY_CLIENT_HELLO_FRAGMENTATION_SOFT && size >= 384) {
uint32_t maxFirst = std::min<uint32_t>(768, size - 96);
uint32_t minFirst = std::min<uint32_t>(224, maxFirst);
uint32_t range = maxFirst > minFirst ? maxFirst - minFirst + 1 : 1;
pendingClientHelloFragmentTarget = minFirst + secureRandomBounded(range);
pendingClientHelloFragmentCount = 2;
pendingClientHelloFragmentIndex = 0;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup client_hello_fragment_plan mode=soft total=%u first=%u fragments=%u", this, size, pendingClientHelloFragmentTarget, pendingClientHelloFragmentCount);
}
return true;
}
bool ConnectionSocket::sendPendingClientHelloFragment(uint32_t limit) {
if (!canSendRawSocketBytes("raw_client_hello_send")) {
return false;
}
while (pendingClientHello != nullptr && pendingClientHelloOffset < limit) {
ssize_t sentLength = stateMachine.sendBytes(pendingClientHello->bytes + pendingClientHelloOffset, limit - pendingClientHelloOffset, 0);
if (sentLength < 0) {
int err = errno;
if (err == EINTR) {
continue;
}
if (err == EAGAIN || err == EWOULDBLOCK) {
adjustWriteOp();
return true;
}
if (LOGS_ENABLED) DEBUG_E("connection(%p) ClientHello pending send failed errno=%d", this, err);
closeSocket(1, -1);
return false;
}
if (sentLength == 0) {
adjustWriteOp();
return true;
}
pendingClientHelloOffset += (uint32_t) sentLength;
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (pendingClientHelloOffset < pendingClientHelloSize && LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup client_hello_send_progress bytes=%u expected=%u", this, pendingClientHelloOffset, pendingClientHelloSize);
}
}
return true;
}
bool ConnectionSocket::sendPendingClientHello() {
if (!canSendPendingClientHello()) {
return false;
}
while (pendingClientHello != nullptr && pendingClientHelloOffset < pendingClientHelloSize) {
if (pendingClientHelloFragmentTarget > pendingClientHelloOffset && pendingClientHelloFragmentTarget < pendingClientHelloSize) {
if (!sendPendingClientHelloFragment(pendingClientHelloFragmentTarget)) {
return false;
}
if (pendingClientHelloOffset < pendingClientHelloFragmentTarget) {
return true;
}
uint32_t delay = 35 + secureRandomBounded(66);
pendingClientHelloNextWriteTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis() + delay;
pendingClientHelloFragmentIndex++;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup client_hello_fragment mode=soft index=%u offset=%u total=%u next_delay=%u", this, pendingClientHelloFragmentIndex, pendingClientHelloOffset, pendingClientHelloSize, delay);
pendingClientHelloFragmentTarget = 0;
scheduleProxyHandshakeAdmissionTimer(delay, MT_PROXY_HANDSHAKE_TIMER_CLIENT_HELLO_FRAGMENT, proxyHandshakeAdmissionIpv6);
return true;
}
if (pendingClientHelloNextWriteTime > 0) {
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (now < pendingClientHelloNextWriteTime) {
return true;
}
pendingClientHelloNextWriteTime = 0;
}
if (!sendPendingClientHelloFragment(pendingClientHelloSize)) {
return false;
}
}
return true;
}
void ConnectionSocket::clearPendingTlsFrame() {
if (pendingTlsFrame != nullptr) {
delete pendingTlsFrame;
pendingTlsFrame = nullptr;
}
pendingTlsFrameSize = 0;
pendingTlsFrameOffset = 0;
pendingTlsPayloadSize = 0;
nextTlsFrameWriteTime = 0;
}
bool ConnectionSocket::scheduleMtProxyDataTimingIfNeeded() {
int32_t timingMode = effectiveMtProxyTimingMode();
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyDataTimingWaitInput input;
input.timingMode = timingMode;
input.hasPendingTlsFrame = pendingTlsFrame != nullptr;
input.nextWriteTime = nextTlsFrameWriteTime;
input.now = now;
MtProxyDataTimingWaitDecision waitDecision = mtProxyDataTimingWaitDecision(input);
if (waitDecision.clearScheduledTime) {
nextTlsFrameWriteTime = 0;
}
if (!waitDecision.shouldWait) {
return false;
}
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_data timing_delay mode=%d startup_cover=%d delay=%u", this, (int) timingMode, currentStartupCoverMode, waitDecision.delayMs);
}
scheduleProxyHandshakeAdmissionTimer(waitDecision.delayMs, MT_PROXY_HANDSHAKE_TIMER_TLS_FRAME, proxyHandshakeAdmissionIpv6);
return true;
}
void ConnectionSocket::startMtProxyStartupCover() {
MtProxyStartupCoverPolicy policy = mtProxyStartupCoverPolicy(currentSecretIsFakeTls, currentStartupCoverMode);
if (!policy.enabled) {
return;
}
startupCoverStartTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
startupCoverFrameCount = 0;
startupCoverStartedLogged = true;
startupCoverEndedLogged = false;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_data startup_cover_start mode=%d window_ms=%lld max_frames=%u", this, policy.mode, (long long) policy.windowMs, policy.maxFrames);
}
}
bool ConnectionSocket::mtProxyStartupCoverActive() {
MtProxyStartupCoverPolicy policy = mtProxyStartupCoverPolicy(currentSecretIsFakeTls, currentStartupCoverMode);
if (!policy.enabled || startupCoverStartTime == 0) {
return false;
}
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
MtProxyStartupCoverState state;
state.startTime = startupCoverStartTime;
state.frameCount = startupCoverFrameCount;
MtProxyStartupCoverEvaluation evaluation = mtProxyEvaluateStartupCover(policy, state, now);
if (evaluation.shouldEnd) {
if (!startupCoverEndedLogged && LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_data startup_cover_end mode=%d elapsed=%lld frames=%u", this, policy.mode, (long long) evaluation.elapsedMs, startupCoverFrameCount);
}
startupCoverEndedLogged = true;
startupCoverStartTime = 0;
return false;
}
return evaluation.active;
}
int32_t ConnectionSocket::effectiveMtProxyRecordSizingMode() {
return mtProxyEffectiveRecordSizingMode(currentRecordSizingMode, currentStartupCoverMode, mtProxyStartupCoverActive());
}
int32_t ConnectionSocket::effectiveMtProxyTimingMode() {
return mtProxyEffectiveTimingMode(currentTimingMode, currentStartupCoverMode, mtProxyStartupCoverActive());
}
bool ConnectionSocket::buildPendingTlsFrame(NativeByteBuffer *buffer, uint32_t remaining) {
if (pendingTlsFrame != nullptr || buffer == nullptr || remaining == 0) {
return false;
}
MtProxyRecordSizingInput sizingInput;
sizingInput.recordSizingMode = currentRecordSizingMode;
sizingInput.startupCoverMode = currentStartupCoverMode;
sizingInput.startupCoverActive = mtProxyStartupCoverActive();
sizingInput.firstTlsFrameSent = mtproxyFirstTlsFrameSentLogged;
sizingInput.remaining = remaining;
sizingInput.randomBounded = secureRandomBounded;
MtProxyRecordSizingDecision sizingDecision = nextMtProxyTlsRecordPayloadSize(sizingInput);
if (sizingDecision.effectiveRecordSizingMode != MT_PROXY_RECORD_SIZING_OFF && LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_data record_sizing mode=%d startup_cover=%d cap=%u remaining=%u first_sent=%d", this, (int) sizingDecision.effectiveRecordSizingMode, currentStartupCoverMode, sizingDecision.payloadSize, remaining, mtproxyFirstTlsFrameSentLogged ? 1 : 0);
}
if (remaining > sizingDecision.payloadSize) {
remaining = sizingDecision.payloadSize;
}
size_t headersSize = 0;
if (tlsState == 1) {
static std::string header1 = std::string("\x14\x03\x03\x00\x01\x01", 6);
std::memcpy(tempBuffer->bytes, header1.data(), header1.size());
headersSize += header1.size();
setTlsState(2, "first_tls_frame_header");
}
static std::string header2 = std::string("\x17\x03\x03", 3);
std::memcpy(tempBuffer->bytes + headersSize, header2.data(), header2.size());
headersSize += header2.size();
tempBuffer->bytes[headersSize] = static_cast<uint8_t>((remaining >> 8) & 0xff);
tempBuffer->bytes[headersSize + 1] = static_cast<uint8_t>(remaining & 0xff);
headersSize += 2;
std::memcpy(tempBuffer->bytes + headersSize, buffer->bytes(), remaining);
pendingTlsFrameSize = (uint32_t) headersSize + remaining;
pendingTlsFrameOffset = 0;
pendingTlsPayloadSize = remaining;
pendingTlsFrame = new ByteArray(pendingTlsFrameSize);
std::memcpy(pendingTlsFrame->bytes, tempBuffer->bytes, pendingTlsFrameSize);
return true;
}
bool ConnectionSocket::sendPendingTlsFrame() {
if (!canSendPendingTlsFrame()) {
return false;
}
if (!canSendRawSocketBytes("raw_tls_frame_send")) {
return false;
}
while (pendingTlsFrame != nullptr && pendingTlsFrameOffset < pendingTlsFrameSize) {
ssize_t sentLength = stateMachine.sendBytes(pendingTlsFrame->bytes + pendingTlsFrameOffset, pendingTlsFrameSize - pendingTlsFrameOffset, 0);
if (sentLength < 0) {
int err = errno;
if (err == EINTR) {
continue;
}
if (err == EAGAIN || err == EWOULDBLOCK) {
adjustWriteOp();
return true;
}
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS pending send failed errno=%d", this, err);
closeSocket(1, -1);
return false;
}
if (sentLength == 0) {
adjustWriteOp();
return true;
}
pendingTlsFrameOffset += (uint32_t) sentLength;
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (ConnectionsManager::getInstance(instanceNum).delegate != nullptr) {
ConnectionsManager::getInstance(instanceNum).delegate->onBytesSent((int32_t) sentLength, currentNetworkType, instanceNum);
}
}
if (pendingTlsFrame != nullptr) {
if (tlsState != 0 && !mtproxyFirstTlsFrameSentLogged) {
mtproxyFirstTlsFrameSentLogged = true;
firstTransportPacketSent = true;
mtproxyFirstTlsFrameSentTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
publishProxyConnectionStage("first_tls_app_sent");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup first_tls_app_sent payload=%u frame=%u", this, pendingTlsPayloadSize, pendingTlsFrameSize);
}
outgoingByteStream->discard(pendingTlsPayloadSize);
mtproxyTlsFrameCompletedCount++;
bool startupCoverActive = mtProxyStartupCoverActive();
if (LOGS_ENABLED && (mtproxyTlsFrameCompletedCount <= 8 || startupCoverActive)) {
DEBUG_D("connection(%p) mtproxy_data tls_frame_complete index=%u payload=%u frame=%u record_sizing=%d timing=%d startup_cover=%d more_data=%d", this, mtproxyTlsFrameCompletedCount, pendingTlsPayloadSize, pendingTlsFrameSize, (int) effectiveMtProxyRecordSizingMode(), (int) effectiveMtProxyTimingMode(), currentStartupCoverMode, outgoingByteStream->hasData() ? 1 : 0);
}
if (startupCoverActive) {
startupCoverFrameCount++;
mtProxyStartupCoverActive();
}
clearPendingTlsFrame();
MtProxyDataTimingInput timingInput;
timingInput.timingMode = currentTimingMode;
timingInput.startupCoverMode = currentStartupCoverMode;
timingInput.startupCoverActive = mtProxyStartupCoverActive();
timingInput.hasPendingTlsFrame = pendingTlsFrame != nullptr;
timingInput.hasOutgoingData = outgoingByteStream->hasData();
timingInput.randomBounded = secureRandomBounded;
MtProxyDataTimingDecision timingDecision = mtProxyDataTimingDecision(timingInput);
if (timingDecision.shouldDelay) {
nextTlsFrameWriteTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis() + timingDecision.delayMs;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_data timing_next mode=%d startup_cover=%d delay=%u", this, (int) timingDecision.effectiveTimingMode, currentStartupCoverMode, timingDecision.delayMs);
}
}
adjustWriteOp();
}
return true;
}
bool ConnectionSocket::resetTransportSocketForOpenConnection() {
bool hasOpenResources = socketFd >= 0
|| currentWssTransport != nullptr
|| epollRegistered
|| currentTransportState != TransportState::Idle
|| !waitingForHostResolve.empty()
|| adjustWriteOpAfterResolve
|| adjustWriteOpAfterPreTcpGate
|| startupTimeline.dnsResolveAttemptStarted()
|| startupTimeline.tcpConnectAttemptStarted()
|| startupTimeline.hasLocalWait()
|| proxyHandshakeAdmissionActive
|| proxyHandshakeAdmissionQueued
|| proxyHandshakeAdmissionQueuePublished
|| proxyHandshakeAdmissionReady
|| proxyEndpointTcpConnectActive
|| proxyEndpointTcpConnectReady
|| proxyEndpointTcpConnectGatePublished
|| proxyEndpointBackoffReady
|| proxyEndpointDnsCoalesceReady;
if (hasOpenResources) {
logTransportSnapshot("open_connection_reset", "openConnection_reset");
if (currentTransportState != TransportState::Closing) {
setTransportState(TransportState::Closing, "openConnection_reset");
}
if (epollRegistered) {
if (socketFd >= 0 && canUnregisterEpollSocket()) {
stateMachine.epollCtlDel(ConnectionsManager::getInstance(instanceNum).epolFd);
}
setEpollRegistered(false, "openConnection_reset_epoll_ctl_del");
}
if (currentWssTransport != nullptr) {
currentWssTransport->close();
setSocketFd(-1, "openConnection_reset_close_transport_socket");
} else if (socketFd >= 0) {
if (canCloseNativeSocket()) {
if (!stateMachine.closeNativeSocket("openConnection_reset_close_native_socket") && LOGS_ENABLED) {
DEBUG_E("connection(%p) unable to close stale socket during openConnection reset", this);
}
}
setSocketFd(-1, "openConnection_reset_close_native_socket");
}
}
currentWssTransport.reset();
currentTransportWss = false;
currentWssRoute = tgnet::wss::Route();
wssFirstFrameSentTime = 0;
wssOpenTime = 0;
setWaitingForHostResolve("", "openConnection_reset_cleanup");
setAdjustWriteOpAfterResolve(false, "openConnection_reset_cleanup");
setAdjustWriteOpAfterPreTcpGate(false, "openConnection_reset_cleanup");
setMtProxyTcpConnectAttemptStarted(false, "openConnection_reset_cleanup");
setMtProxyDnsResolveAttemptStarted(false, "openConnection_reset_cleanup");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "openConnection_reset_cleanup");
setProxyHandshakeAdmissionState(0, 0, 0, 0, "openConnection_reset_cleanup");
setProxyEndpointTcpConnectGateState(0, 0, 0, "openConnection_reset_cleanup");
setProxyEndpointBackoffReady(false, "openConnection_reset_cleanup");
setProxyEndpointDnsCoalesceReady(false, "openConnection_reset_cleanup");
setTransportState(TransportState::Idle, "openConnection_reset_cleanup");
bool resetClean = socketFd < 0
&& !epollRegistered
&& currentTransportState == TransportState::Idle
&& waitingForHostResolve.empty()
&& !adjustWriteOpAfterResolve
&& !adjustWriteOpAfterPreTcpGate
&& !startupTimeline.dnsResolveAttemptStarted()
&& !startupTimeline.tcpConnectAttemptStarted()
&& !startupTimeline.hasLocalWait()
&& !proxyHandshakeAdmissionActive
&& !proxyHandshakeAdmissionQueued
&& !proxyEndpointTcpConnectActive;
if (!resetClean) {
logTransportInvariant("openConnection", "reset_not_clean");
logTransportSnapshot("open_connection_reset_failed", "openConnection_reset_cleanup");
}
return resetClean;
}
void ConnectionSocket::openConnection(std::string address, uint16_t port, std::string secret, bool ipv6, int32_t networkType, int32_t datacenterId, bool mediaConnection) {
ConnectionsManager &manager = ConnectionsManager::getInstance(instanceNum);
proxyActivationGeneration = manager.getProxyActivationGeneration();
proxyConfigGeneration = manager.getProxyConfigGeneration();
proxyActivationOrigin = manager.getProxyActivationOrigin();
if (LOGS_ENABLED && mtProxyProbeLease.active()) DEBUG_D("connection(%p) mtproxy_startup probe_owner_release key=%s token=%llu", this, mtProxyProbeLease.key().c_str(), (unsigned long long) mtProxyProbeLease.ownerToken());
mtProxyProbeLease.release();
releaseMtProxyEndpointTcpConnect("openConnection_reset");
cancelProxyHandshakeAdmission();
if (!resetTransportSocketForOpenConnection()) {
proxyCheckDiagnostic = "connection_not_started";
if (LOGS_ENABLED) DEBUG_E("connection(%p) mtproxy_startup open_connection_reset_failed address=%s port=%u", this, address.c_str(), (unsigned int) port);
return;
}
clearPendingClientHello();
clearPendingTlsFrame();
currentNetworkType = networkType;
isIpv6 = ipv6;
setSocketCloseNotified(false, "openConnection");
proxyCloseDiagnosticSuppressed = false;
socketDeadForWrites = false;
setEpollRegistered(false, "openConnection");
setTransportState(TransportState::Prepared, "openConnection");
stateMachine.setTransportMode(TransportMode::None);
currentAddress = address;
currentPort = port;
currentSocksUsername.clear();
currentSocksPassword.clear();
setWaitingForHostResolve("", "openConnection");
setAdjustWriteOpAfterResolve(false, "openConnection");
setAdjustWriteOpAfterPreTcpGate(false, "openConnection");
setMtProxyTcpConnectAttemptStarted(false, "openConnection");
setMtProxyDnsResolveAttemptStarted(false, "openConnection");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "openConnection");
currentSecret = "";
currentSecretDomain = "";
currentOriginalSecretDomain = "";
currentSanitizedSecretDomain = "";
currentLowercaseSecretDomain = "";
currentNoTrailingDotSecretDomain = "";
currentPunycodeSecretDomain = "";
currentClientHelloSni = "";
currentSecretKind = "none";
currentSecretIsFakeTls = false;
currentAllowedSniVariants = 0;
currentRecipeFamily = MtProxyAdaptivePolicy::CLIENT_HELLO_CHROME_MODERN_NO_FRAGMENT;
currentRecipeSniVariant = MtProxyAdaptivePolicy::SNI_ORIGINAL;
currentRecipeParserVariant = MtProxyAdaptivePolicy::PARSER_STANDARD_HMAC;
currentRecipeClassicVariant = MtProxyAdaptivePolicy::CLASSIC_NONE;
currentTransportWss = false;
currentDatacenterId = datacenterId;
currentMediaConnection = mediaConnection;
currentWssTransport.reset();
currentWssRoute = tgnet::wss::Route();
wssFirstFrameSentTime = 0;
currentProxyTlsProfile = normalizeMtProxyTlsProfile(MT_PROXY_TLS_PROFILE_ANDROID_CHROME);
currentEffectiveProxyTlsProfile = currentProxyTlsProfile;
currentClientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
currentServerHelloParserMode = MT_PROXY_SERVER_HELLO_PARSER_STANDARD;
currentConnectionPatternMode = MT_PROXY_CONNECTION_PATTERN_OFF;
currentRecordSizingMode = MT_PROXY_RECORD_SIZING_OFF;
currentTimingMode = MT_PROXY_TIMING_OFF;
currentStartupCoverMode = MT_PROXY_STARTUP_COVER_OFF;
startupCoverStartTime = 0;
startupCoverFrameCount = 0;
startupCoverStartedLogged = false;
startupCoverEndedLogged = false;
nextTlsFrameWriteTime = 0;
currentProxyTlsProfileKey = "";
currentMtProxyEndpointKey = "";
currentMtProxyRecipeCacheKey = "";
currentMtProxyProbeKey = "";
currentMtProxyNetworkEndpointKey = "";
currentMtProxyDnsCacheKey = "";
currentMtProxyAdmissionKey = "";
proxyCheckDiagnostic = "connection_not_started";
proxySuggestedReconnectHoldMs = 0;
proxyHandshakeAdmissionKey = "";
setProxyHandshakeAdmissionState(-1, 0, -1, -1, "openConnection");
setProxyEndpointBackoffReady(false, "openConnection");
setProxyEndpointTcpConnectGateState(0, 0, 0, "openConnection");
setProxyEndpointDnsCoalesceReady(false, "openConnection");
setTlsState(0, "openConnection");
setMtProxySocketConnectedLogged(false, "openConnection");
mtproxyFirstTlsFrameSentLogged = false;
mtproxyFirstTlsDataReceivedLogged = false;
mtproxyFirstPlainDataSentLogged = false;
mtproxyFirstPlainDataReceivedLogged = false;
firstTransportPacketSent = false;
firstTransportPacketReceived = false;
dataPathProven = false;
mtproxyFirstTlsFrameSentTime = 0;
mtproxyFirstPlainDataSentTime = 0;
mtproxyFirstDataReceivedTime = 0;
mtproxyTlsFrameCompletedCount = 0;
ConnectionsManager::getInstance(instanceNum).attachConnection(this);
memset(&socketAddress, 0, sizeof(sockaddr_in));
memset(&socketAddress6, 0, sizeof(sockaddr_in6));
std::string *proxyAddress = &overrideProxyAddress;
std::string *proxySecret = &overrideProxySecret;
uint16_t proxyPort = overrideProxyPort;
MtProxyOptions proxyOptions = overrideMtProxyOptions;
if (proxyAddress->empty()) {
proxyAddress = &ConnectionsManager::getInstance(instanceNum).proxyAddress;
proxyPort = ConnectionsManager::getInstance(instanceNum).proxyPort;
proxySecret = &ConnectionsManager::getInstance(instanceNum).proxySecret;
proxyOptions = ConnectionsManager::getInstance(instanceNum).proxyMtProxyOptions;
}
stateMachine.endpointGate.webProxyBridge = proxyOptions.webBridge && !proxyAddress->empty() && !proxySecret->empty();
stateMachine.endpointGate.webProxyBridgePort = stateMachine.endpointGate.webProxyBridge ? proxyPort : 0;
stateMachine.endpointGate.webProxyLocalPort = 0;
stateMachine.endpointGate.webProxyWaitAnchor = 0;
stateMachine.endpointGate.webProxyRecheckAt = 0;
stateMachine.endpointGate.webProxyWaitReason = 0;
manager.transportConnectionOpened = true;
bool shouldUseWss = overrideProxyAddress.empty()
&& manager.wssEnabled
&& proxyAddress->empty();
tgnet::wss::Route selectedWssRoute;
shouldUseWss = shouldUseWss && tgnet::wss::OfficialRoute(
datacenterId,
mediaConnection,
manager.testBackend,
address,
&selectedWssRoute);
if (!shouldUseWss && manager.wssEnabled && overrideProxyAddress.empty() && proxyAddress->empty() && LOGS_ENABLED) {
DEBUG_D("connection(%p) wss_startup direct dc%d media=%d target=%s:%u reason=no_usable_wss_route", this, (int) datacenterId, mediaConnection ? 1 : 0, address.c_str(), (unsigned int) port);
}
if (shouldUseWss && manager.getIpStratagy() == USE_IPV6_ONLY
&& !selectedWssRoute.relayHostFallback.empty()) {
// У устройства нет IPv4 вообще, а зашитые адреса релеев — только IPv4.
// Начинать с них значит гарантированно терять попытку на каждом
// соединении: у пользователя с отключённым IPv4 из-за этого заметно
// медленнее грузились диалоги. Идём сразу по имени, DNS отдаст AAAA.
selectedWssRoute.viaFallback = true;
selectedWssRoute.connectHost = selectedWssRoute.relayHostFallback;
}
if (shouldUseWss) {
currentTransportWss = true;
stateMachine.setTransportMode(TransportMode::Wss);
currentSecretKind = "wss";
setProxyAuthState(0, "wss_setup");
currentWssRoute = selectedWssRoute;
currentWssTransport = tgnet::wss::CreateSocket(currentWssRoute);
const std::string &wssConnectHost = currentWssRoute.connectHost;
uint16_t wssConnectPort = currentWssRoute.relayPort;
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(wssConnectPort);
bool continueCheckAddress = false;
if (inet_pton(AF_INET, wssConnectHost.c_str(), &socketAddress.sin_addr.s_addr) == 1) {
// The relay owns the socket address. Never carry the address
// family of the ignored dcOption target into WSS setup.
ipv6 = false;
} else {
continueCheckAddress = true;
}
if (continueCheckAddress) {
if (inet_pton(AF_INET6, wssConnectHost.c_str(), &socketAddress6.sin6_addr.s6_addr) == 1) {
socketAddress6.sin6_family = AF_INET6;
socketAddress6.sin6_port = htons(wssConnectPort);
ipv6 = true;
continueCheckAddress = false;
}
}
if (continueCheckAddress) {
#ifdef USE_DELEGATE_HOST_RESOLVE
setWaitingForHostResolve(wssConnectHost, "wss_host_resolve_start");
ConnectionsManager &wssManager = ConnectionsManager::getInstance(instanceNum);
if (!canStartHostResolve() || wssManager.delegate == nullptr) {
proxyCheckDiagnostic = "connection_not_started";
currentWssTransport->timedOut();
closeSocket(1, -1);
return;
}
setTransportState(TransportState::WaitingGate, "wss_host_resolve_start");
wssManager.delegate->getHostByName(wssConnectHost, instanceNum, this);
return;
#else
struct hostent *he;
if ((he = gethostbyname(wssConnectHost.c_str())) == nullptr) {
proxyCheckDiagnostic = "host_resolve_failed";
if (LOGS_ENABLED) DEBUG_E("connection(%p) can't resolve WSS host %s address", this, wssConnectHost.c_str());
currentWssTransport->timedOut();
closeSocket(1, -1);
return;
}
struct in_addr **addr_list = (struct in_addr **) he->h_addr_list;
if (addr_list[0] != nullptr) {
socketAddress.sin_addr.s_addr = addr_list[0]->s_addr;
ipv6 = false;
} else {
proxyCheckDiagnostic = "host_resolve_failed";
currentWssTransport->timedOut();
closeSocket(1, -1);
return;
}
#endif
}
isIpv6 = ipv6;
} else if (!proxyAddress->empty()) {
currentSecretKind = proxySecret->empty() ? "socks" : mtProxySecretKindName(*proxySecret);
if (LOGS_ENABLED) DEBUG_D("connection(%p) connecting via proxy %s:%d secret[%d] secret_kind=%s", this, proxyAddress->c_str(), proxyPort, (int) proxySecret->size(), currentSecretKind);
uint32_t tempBuffLength;
if (proxySecret->empty()) {
stateMachine.setTransportMode(TransportMode::Socks5);
setProxyAuthState(1, "socks_proxy_setup");
tempBuffLength = 1024;
if (!overrideProxyAddress.empty()) {
currentSocksUsername = overrideProxyUser;
currentSocksPassword = overrideProxyPassword;
} else {
currentSocksUsername = ConnectionsManager::getInstance(instanceNum).proxyUser;
currentSocksPassword = ConnectionsManager::getInstance(instanceNum).proxyPassword;
}
} else if (proxySecret->size() > 17 && (*proxySecret)[0] == '\xee') {
stateMachine.setTransportMode(TransportMode::FakeTlsMtProxy);
setProxyAuthState(10, "faketls_proxy_setup");
currentSecret = proxySecret->substr(1, 16);
MtProxySecretDomainPlan domainPlan = buildMtProxySecretDomainPlan(proxySecret->substr(17));
currentSecretDomain = domainPlan.canonicalDomain;
currentOriginalSecretDomain = domainPlan.originalDomain;
currentSanitizedSecretDomain = domainPlan.sanitizedDomain;
currentLowercaseSecretDomain = domainPlan.lowercaseAsciiDomain;
currentNoTrailingDotSecretDomain = domainPlan.noTrailingDotDomain;
currentPunycodeSecretDomain = domainPlan.punycodeDomain;
currentClientHelloSni = currentSecretDomain;
currentAllowedSniVariants = domainPlan.allowedSniVariants;
currentSecretIsFakeTls = true;
currentMtProxyDnsCacheKey = MtProxyEndpointPolicy::dnsCacheKeyFor(*proxyAddress, proxyPort);
currentMtProxyNetworkEndpointKey = MtProxyEndpointPolicy::networkEndpointKeyFor(*proxyAddress, proxyPort);
currentMtProxyAdmissionKey = MtProxyEndpointPolicy::admissionKeyFor(*proxyAddress, proxyPort, currentSecretDomain);
currentProxyTlsProfile = normalizeMtProxyTlsProfile(proxyOptions.tlsProfile);
currentMtProxyEndpointKey = MtProxyEndpointPolicy::endpointKeyFor(*proxyAddress, proxyPort, currentSecretKind, currentSecretDomain);
currentMtProxyRecipeCacheKey = mtProxyRecipeCacheKeyFor(*proxyAddress, proxyPort, currentSecret, currentSecretDomain);
currentMtProxyProbeKey = currentMtProxyRecipeCacheKey;
currentProxyTlsProfileKey = currentMtProxyRecipeCacheKey;
if (domainPlan.terminalDiagnostic != nullptr) {
if (strcmp(domainPlan.terminalDiagnostic, MtProxyPhase::SecretParseInvalidDomainControlChar) == 0) {
proxyCheckDiagnostic = MtProxyPhase::SecretParseInvalidDomainControlChar;
} else {
proxyCheckDiagnostic = MtProxyPhase::SecretParseInvalidDomain;
}
if (LOGS_ENABLED) DEBUG_E("connection(%p) mtproxy_startup secret_domain_invalid phase=%s raw_len=%d sanitized=%s", this, proxyCheckDiagnostic.c_str(), (int) (proxySecret->size() - 17), currentSecretDomain.c_str());
closeSocket(1, -1);
return;
}
if (domainPlan.sanitized) {
publishSanitizedSecretDomainIfNeeded(proxySecret->size() - 17);
}
currentEffectiveProxyTlsProfile = MtProxyAdaptivePolicy::resolveEffectiveTlsProfile(currentProxyTlsProfile, currentProxyTlsProfileKey);
currentClientHelloFragmentation = normalizeMtProxyClientHelloFragmentation(proxyOptions.clientHelloFragmentation);
currentConnectionPatternMode = normalizeMtProxyConnectionPatternMode(proxyOptions.connectionPatternMode);
currentRecordSizingMode = normalizeMtProxyRecordSizingMode(proxyOptions.recordSizingMode);
currentTimingMode = normalizeMtProxyTimingMode(proxyOptions.timingMode);
currentStartupCoverMode = normalizeMtProxyStartupCoverMode(proxyOptions.startupCoverMode);
if (mtProxyProbeBeginOrJoin(ipv6)) {
return;
}
applyMtProxyPhaseAdaptiveRecipe();
proxyHandshakeAdmissionKey = currentMtProxyAdmissionKey;
tempBuffLength = 65 * 1024;
} else {
stateMachine.setTransportMode(TransportMode::PlainMtProxy);
setProxyAuthState(0, "plain_proxy_setup");
std::string plainSecretHash = "secret_hash=" + mtProxySecretHashForRecipeKey(*proxySecret);
currentMtProxyDnsCacheKey = MtProxyEndpointPolicy::dnsCacheKeyFor(*proxyAddress, proxyPort);
currentMtProxyNetworkEndpointKey = MtProxyEndpointPolicy::networkEndpointKeyFor(*proxyAddress, proxyPort);
currentMtProxyEndpointKey = MtProxyEndpointPolicy::endpointKeyFor(*proxyAddress, proxyPort, currentSecretKind, plainSecretHash);
currentMtProxyRecipeCacheKey = "";
currentMtProxyProbeKey = mtProxyRecipeCacheKeyFor(*proxyAddress, proxyPort, *proxySecret, "");
currentClientHelloFragmentation = normalizeMtProxyClientHelloFragmentation(proxyOptions.clientHelloFragmentation);
currentConnectionPatternMode = normalizeMtProxyConnectionPatternMode(proxyOptions.connectionPatternMode);
currentRecordSizingMode = normalizeMtProxyRecordSizingMode(proxyOptions.recordSizingMode);
currentTimingMode = normalizeMtProxyTimingMode(proxyOptions.timingMode);
currentStartupCoverMode = normalizeMtProxyStartupCoverMode(proxyOptions.startupCoverMode);
tempBuffLength = 0;
}
if (tempBuffLength > 0) {
if (tempBuffer == nullptr || tempBuffer->length < tempBuffLength) {
if (tempBuffer != nullptr) {
delete tempBuffer;
}
tempBuffer = new ByteArray(tempBuffLength);
}
}
if (!canCreateSocket("create_proxy_socket")) {
closeSocket(1, -1);
return;
}
int createdFd = stateMachine.createNativeSocket(AF_INET, SOCK_STREAM, 0);
setSocketFd(createdFd, "create_proxy_socket");
if (socketFd < 0) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) can't create proxy socket", this);
closeSocket(1, -1);
return;
}
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(proxyPort);
bool continueCheckAddress;
if (inet_pton(AF_INET, proxyAddress->c_str(), &socketAddress.sin_addr.s_addr) != 1) {
continueCheckAddress = true;
if (LOGS_ENABLED) DEBUG_D("connection(%p) not ipv4 address %s", this, proxyAddress->c_str());
} else {
ipv6 = false;
continueCheckAddress = false;
}
if (continueCheckAddress) {
if (inet_pton(AF_INET6, proxyAddress->c_str(), &socketAddress6.sin6_addr.s6_addr) != 1) {
continueCheckAddress = true;
if (LOGS_ENABLED) DEBUG_D("connection(%p) not ipv6 address %s", this, proxyAddress->c_str());
} else {
ipv6 = true;
continueCheckAddress = false;
}
if (continueCheckAddress) {
std::string sslipAddress;
if (MtProxyEndpointPolicy::extractSslipIpv4Address(*proxyAddress, &socketAddress.sin_addr, &sslipAddress)) {
ipv6 = false;
continueCheckAddress = false;
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup resolved_sslip host=%s address=%s", this, proxyAddress->c_str(), sslipAddress.c_str());
}
}
if (continueCheckAddress) {
bool blockedZeroAddress = false;
if (mtProxyEndpointUseCachedHostAddress(*proxyAddress, &ipv6, &blockedZeroAddress)) {
continueCheckAddress = false;
}
if (blockedZeroAddress) {
return;
}
}
if (continueCheckAddress) {
#ifdef USE_DELEGATE_HOST_RESOLVE
setWaitingForHostResolve(*proxyAddress, "host_resolve_pending");
if (isCurrentMtProxyConnection() && scheduleMtProxyEndpointCircuitBreakerIfNeeded(ipv6)) {
return;
}
if (proxyAuthState >= 10 && scheduleProxyHandshakeAdmissionIfNeeded(ipv6, MT_PROXY_HANDSHAKE_TIMER_HOST_RESOLVE)) {
return;
}
if (isCurrentMtProxyConnection() && scheduleMtProxyDnsCoalesceIfNeeded(ipv6)) {
return;
}
requestPendingHostResolve();
return;
#else
struct hostent *he;
if ((he = gethostbyname(proxyAddress->c_str())) == nullptr) {
proxyCheckDiagnostic = "host_resolve_failed";
if (LOGS_ENABLED) DEBUG_E("connection(%p) can't resolve host %s address", this, proxyAddress->c_str());
closeSocket(1, -1);
return;
}
struct in_addr **addr_list = (struct in_addr **) he->h_addr_list;
if (addr_list[0] != nullptr) {
socketAddress.sin_addr.s_addr = addr_list[0]->s_addr;
if (LOGS_ENABLED) DEBUG_D("connection(%p) resolved host %s address %x", this, proxyAddress->c_str(), addr_list[0]->s_addr);
ipv6 = false;
char resolvedIp[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, addr_list[0], resolvedIp, sizeof(resolvedIp)) != nullptr) {
mtProxyEndpointStoreResolvedAddress(*proxyAddress, resolvedIp);
}
} else {
proxyCheckDiagnostic = "host_resolve_failed";
if (LOGS_ENABLED) DEBUG_E("connection(%p) can't resolve host %s address", this, proxyAddress->c_str());
closeSocket(1, -1);
return;
}
#endif
}
}
} else {
stateMachine.setTransportMode(secret.empty() ? TransportMode::Direct : TransportMode::PlainMtProxy);
setProxyAuthState(0, "direct_setup");
uint32_t tempBuffLength;
currentSecretKind = mtProxySecretKindName(secret);
if (secret.size() > 17 && secret[0] == '\xee') {
stateMachine.setTransportMode(TransportMode::FakeTlsMtProxy);
setProxyAuthState(10, "faketls_direct_setup");
currentSecret = secret.substr(1, 16);
MtProxySecretDomainPlan domainPlan = buildMtProxySecretDomainPlan(secret.substr(17));
currentSecretDomain = domainPlan.canonicalDomain;
currentOriginalSecretDomain = domainPlan.originalDomain;
currentSanitizedSecretDomain = domainPlan.sanitizedDomain;
currentLowercaseSecretDomain = domainPlan.lowercaseAsciiDomain;
currentNoTrailingDotSecretDomain = domainPlan.noTrailingDotDomain;
currentPunycodeSecretDomain = domainPlan.punycodeDomain;
currentClientHelloSni = currentSecretDomain;
currentAllowedSniVariants = domainPlan.allowedSniVariants;
currentSecretIsFakeTls = true;
currentMtProxyNetworkEndpointKey = MtProxyEndpointPolicy::networkEndpointKeyFor(address, port);
currentMtProxyAdmissionKey = MtProxyEndpointPolicy::admissionKeyFor(address, port, currentSecretDomain);
const MtProxyOptions managerOptions = ConnectionsManager::getInstance(instanceNum).proxyMtProxyOptions;
currentProxyTlsProfile = normalizeMtProxyTlsProfile(managerOptions.tlsProfile);
currentMtProxyEndpointKey = MtProxyEndpointPolicy::endpointKeyFor(address, port, currentSecretKind, currentSecretDomain);
currentMtProxyRecipeCacheKey = mtProxyRecipeCacheKeyFor(address, port, currentSecret, currentSecretDomain);
currentMtProxyProbeKey = currentMtProxyRecipeCacheKey;
currentProxyTlsProfileKey = currentMtProxyRecipeCacheKey;
if (domainPlan.terminalDiagnostic != nullptr) {
if (strcmp(domainPlan.terminalDiagnostic, MtProxyPhase::SecretParseInvalidDomainControlChar) == 0) {
proxyCheckDiagnostic = MtProxyPhase::SecretParseInvalidDomainControlChar;
} else {
proxyCheckDiagnostic = MtProxyPhase::SecretParseInvalidDomain;
}
if (LOGS_ENABLED) DEBUG_E("connection(%p) mtproxy_startup secret_domain_invalid phase=%s raw_len=%d sanitized=%s", this, proxyCheckDiagnostic.c_str(), (int) (secret.size() - 17), currentSecretDomain.c_str());
closeSocket(1, -1);
return;
}
if (domainPlan.sanitized) {
publishSanitizedSecretDomainIfNeeded(secret.size() - 17);
}
currentEffectiveProxyTlsProfile = MtProxyAdaptivePolicy::resolveEffectiveTlsProfile(currentProxyTlsProfile, currentProxyTlsProfileKey);
currentClientHelloFragmentation = normalizeMtProxyClientHelloFragmentation(managerOptions.clientHelloFragmentation);
currentConnectionPatternMode = normalizeMtProxyConnectionPatternMode(managerOptions.connectionPatternMode);
currentRecordSizingMode = normalizeMtProxyRecordSizingMode(managerOptions.recordSizingMode);
currentTimingMode = normalizeMtProxyTimingMode(managerOptions.timingMode);
currentStartupCoverMode = normalizeMtProxyStartupCoverMode(managerOptions.startupCoverMode);
tempBuffLength = 65 * 1024;
} else {
setProxyAuthState(0, "plain_direct_setup");
if (!secret.empty()) {
std::string plainSecretHash = "secret_hash=" + mtProxySecretHashForRecipeKey(secret);
currentMtProxyNetworkEndpointKey = MtProxyEndpointPolicy::networkEndpointKeyFor(address, port);
currentMtProxyEndpointKey = MtProxyEndpointPolicy::endpointKeyFor(address, port, currentSecretKind, plainSecretHash);
currentMtProxyRecipeCacheKey = "";
currentMtProxyProbeKey = mtProxyRecipeCacheKeyFor(address, port, secret, "");
const MtProxyOptions managerOptions = ConnectionsManager::getInstance(instanceNum).proxyMtProxyOptions;
currentClientHelloFragmentation = normalizeMtProxyClientHelloFragmentation(managerOptions.clientHelloFragmentation);
currentConnectionPatternMode = normalizeMtProxyConnectionPatternMode(managerOptions.connectionPatternMode);
currentRecordSizingMode = normalizeMtProxyRecordSizingMode(managerOptions.recordSizingMode);
currentTimingMode = normalizeMtProxyTimingMode(managerOptions.timingMode);
currentStartupCoverMode = normalizeMtProxyStartupCoverMode(managerOptions.startupCoverMode);
}
tempBuffLength = 0;
}
if (ipv6) {
socketAddress6.sin6_family = AF_INET6;
socketAddress6.sin6_port = htons(port);
if (inet_pton(AF_INET6, address.c_str(), &socketAddress6.sin6_addr.s6_addr) != 1) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) bad ipv6 %s", this, address.c_str());
closeSocket(1, -1);
return;
}
} else {
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(port);
if (inet_pton(AF_INET, address.c_str(), &socketAddress.sin_addr.s_addr) != 1) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) bad ipv4 %s", this, address.c_str());
closeSocket(1, -1);
return;
}
}
if (currentSecretIsFakeTls) {
if (mtProxyProbeBeginOrJoin(ipv6)) {
return;
}
applyMtProxyPhaseAdaptiveRecipe();
proxyHandshakeAdmissionKey = currentMtProxyAdmissionKey;
}
if (tempBuffLength > 0) {
if (tempBuffer == nullptr || tempBuffer->length < tempBuffLength) {
if (tempBuffer != nullptr) {
delete tempBuffer;
}
tempBuffer = new ByteArray(tempBuffLength);
}
}
if (!canCreateSocket("create_direct_socket")) {
closeSocket(1, -1);
return;
}
int createdFd = stateMachine.createNativeSocket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0);
setSocketFd(createdFd, "create_direct_socket");
if (socketFd < 0) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) can't create socket", this);
closeSocket(1, -1);
return;
}
}
if (!isCurrentDirectConnection()) {
publishProxyConnectionStage("connect_start");
}
if (LOGS_ENABLED && !isCurrentDirectConnection()) {
if (currentTransportWss) {
DEBUG_D("connection(%p) wss_startup connect_start relay=%s:%u domain=%s path=%s target=%s:%u", this, currentWssRoute.connectHost.c_str(), (unsigned int) currentWssRoute.relayPort, currentWssRoute.domain.c_str(), currentWssRoute.path.c_str(), currentAddress.c_str(), (unsigned int) currentPort);
} else {
DEBUG_D("connection(%p) mtproxy_startup connect_start proxy_state=%d secret_kind=%s is_faketls=%d domain_len=%d profile=%s effective_profile=%s clienthello_fragment=%d server_hello_parser=%s connection_pattern=%s record_sizing=%d timing=%d startup_cover=%d address=%s port=%u", this, (int) proxyAuthState, currentSecretKind, currentSecretIsFakeTls ? 1 : 0, (int) currentSecretDomain.size(), mtProxyTlsProfileName(currentProxyTlsProfile), mtProxyTlsProfileName(currentEffectiveProxyTlsProfile), currentClientHelloFragmentation, mtProxyServerHelloParserName(currentServerHelloParserMode), mtProxyConnectionPatternModeName(currentConnectionPatternMode), currentRecordSizingMode, currentTimingMode, currentStartupCoverMode, currentAddress.c_str(), (unsigned int) currentPort);
}
}
openConnectionInternal(ipv6);
}
void ConnectionSocket::openConnectionInternal(bool ipv6) {
if (isCurrentTransportWss()) {
const char *createAction = ipv6 ? "create_wss_ipv6_socket" : "create_wss_socket";
if (!canCreateSocket(createAction) || currentWssTransport == nullptr) {
closeSocket(1, -1);
return;
}
std::string diagnostic;
const sockaddr *address = ipv6
? reinterpret_cast<const sockaddr *>(&socketAddress6)
: reinterpret_cast<const sockaddr *>(&socketAddress);
const socklen_t addressLength = ipv6 ? sizeof(socketAddress6) : sizeof(socketAddress);
// A spare from the pool has already done TCP, TLS and the upgrade; its
// first EPOLLOUT below goes straight to on_connected.
std::unique_ptr<tgnet::wss::Socket> pooled = ipv6
? nullptr
: ConnectionsManager::getInstance(instanceNum).takePooledWssSocket(currentWssRoute);
if (pooled != nullptr) {
currentWssTransport = std::move(pooled);
if (LOGS_ENABLED) DEBUG_D("connection(%p) wss_startup pool_hit domain=%s", this, currentWssRoute.domain.c_str());
} else if (!currentWssTransport->open(address, addressLength, &diagnostic)) {
proxyCheckDiagnostic = diagnostic.empty() ? "wss_tcp_connect_failed" : diagnostic;
if (LOGS_ENABLED) DEBUG_E("connection(%p) wss_startup open failed diagnostic=%s", this, proxyCheckDiagnostic.c_str());
closeSocket(1, -1);
return;
}
setSocketFd(currentWssTransport->fd(), createAction);
if (socketFd < 0) {
closeSocket(1, -1);
return;
}
setTransportState(TransportState::TcpConnecting, "wss_socket_connect_start");
// OpenSSL can switch between WANT_READ and WANT_WRITE while the TLS
// handshake is in flight. A level-triggered WSS fd makes each desired
// direction observable; edge-triggered epoll can lose the writable
// edge during that switch and leave the client in "Connecting" until
// the request timeout.
eventMask.events = EPOLLOUT | EPOLLIN | EPOLLRDHUP | EPOLLERR;
eventMask.data.ptr = eventObject;
if (!canRegisterEpollSocket()
|| !stateMachine.epollCtlAdd(ConnectionsManager::getInstance(instanceNum).epolFd)) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) epoll_ctl, adding WSS socket failed", this);
closeSocket(1, -1);
return;
}
setEpollRegistered(true, "wss_epoll_ctl_add");
setTransportState(TransportState::EpollRegistered, "wss_epoll_ctl_add");
wssOpenTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
proxyCheckDiagnostic = "wss_tls_handshake";
adjustWriteOp();
return;
}
if (isCurrentMtProxyConnection() && scheduleMtProxyEndpointCircuitBreakerIfNeeded(ipv6)) {
return;
}
if (proxyAuthState >= 10) {
if (scheduleProxyHandshakeAdmissionIfNeeded(ipv6, MT_PROXY_HANDSHAKE_TIMER_ADMISSION)) {
return;
}
}
int epolFd = ConnectionsManager::getInstance(instanceNum).epolFd;
if (!canConfigureOpenSocket()) {
closeSocket(1, -1);
return;
}
int yes = 1;
if (setsockopt(socketFd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(int))) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) set TCP_NODELAY failed", this);
}
#ifdef DEBUG_VERSION
int size = 4 * 1024 * 1024;
if (setsockopt(socketFd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int))) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) set SO_SNDBUF failed", this);
}
if (setsockopt(socketFd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int))) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) set SO_RCVBUF failed", this);
}
#endif
if (fcntl(socketFd, F_SETFL, O_NONBLOCK) == -1) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) set O_NONBLOCK failed", this);
closeSocket(1, -1);
return;
}
if (isCurrentMtProxyConnection() && scheduleMtProxyEndpointTcpConnectGateIfNeeded(ipv6)) {
return;
}
const bool directTransport = isCurrentDirectConnection();
if (!directTransport) {
finishMtProxyPreTcpWait("socket_connect_start");
publishProxyConnectionStage("socket_connect_start");
setMtProxyTcpConnectAttemptStarted(true, "socket_connect_start");
proxyCheckDiagnostic = MtProxyPhase::TcpNotConnected;
}
setTransportState(TransportState::TcpConnecting, "socket_connect_start");
if (LOGS_ENABLED && !directTransport) DEBUG_D("connection(%p) %s socket_connect_start ipv6=%d state=%d", this, currentTransportWss ? "wss_startup" : "mtproxy_startup", ipv6 ? 1 : 0, (int) proxyAuthState);
if (!canStartTcpConnect()) {
closeSocket(1, -1);
return;
}
if (!stateMachine.connectNativeSocket((ipv6 ? (sockaddr *) &socketAddress6 : (sockaddr *) &socketAddress), (socklen_t) (ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in)))) {
closeSocket(1, -1);
} else {
eventMask.events = EPOLLOUT | EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET;
eventMask.data.ptr = eventObject;
if (!canRegisterEpollSocket()) {
closeSocket(1, -1);
return;
}
if (!stateMachine.epollCtlAdd(epolFd)) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) epoll_ctl, adding socket failed", this);
closeSocket(1, -1);
} else {
setEpollRegistered(true, "epoll_ctl_add");
setTransportState(TransportState::EpollRegistered, "epoll_ctl_add");
announceWebProxyStream();
}
}
if (epollRegistered && (adjustWriteOpAfterResolve || adjustWriteOpAfterPreTcpGate)) {
if (adjustWriteOpAfterResolve) {
setAdjustWriteOpAfterResolve(false, "epoll_ctl_add");
}
if (adjustWriteOpAfterPreTcpGate) {
setAdjustWriteOpAfterPreTcpGate(false, "epoll_ctl_add");
}
adjustWriteOp();
}
}
int32_t ConnectionSocket::checkSocketError(int32_t *error) {
if (!canCheckSocketError()) {
return true;
}
int ret;
int code;
socklen_t len = sizeof(int);
ret = getsockopt(socketFd, SOL_SOCKET, SO_ERROR, &code, &len);
if (ret != 0 || code != 0) {
if (LOGS_ENABLED) DEBUG_E("socket error 0x%x code 0x%x", ret, code);
}
*error = code;
return (ret || code) != 0;
}
bool ConnectionSocket::isCurrentTransportWss() {
return currentTransportWss && currentWssTransport != nullptr;
}
bool ConnectionSocket::isCurrentWssTunnel() {
return isCurrentTransportWss() && currentWssRoute.tunnel;
}
bool ConnectionSocket::isCurrentMtProxyConnection() {
return currentSecretKind != nullptr
&& strcmp(currentSecretKind, "none") != 0
&& strcmp(currentSecretKind, "socks") != 0
&& strcmp(currentSecretKind, "wss") != 0;
}
bool ConnectionSocket::isCurrentDirectConnection() const {
return stateMachine.diagnostics.transportMode == TransportMode::Direct;
}
bool ConnectionSocket::isCurrentWebProxyBridge() const {
return stateMachine.endpointGate.webProxyBridge;
}
void ConnectionSocket::setWebProxyStreamClass(int32_t streamClass) {
webProxyStreamClass = streamClass;
}
// Tells the WEB bridge which class of stream this socket carries, keyed by the
// socket's local port (the remote port of the socket the bridge accepts), so
// its uplink scheduler can put chat requests ahead of file transfers. The
// port is bound by connect() already, before any byte reaches the bridge.
void ConnectionSocket::announceWebProxyStream() {
auto &gate = stateMachine.endpointGate;
gate.webProxyLocalPort = 0;
// Proxy checks dial the bridge with an override and keep plain tgnet
// timing: they measure the proxy, they do not share its load.
if (!isCurrentWebProxyBridge() || hasMtProxyOverride() || socketFd < 0 || gate.webProxyBridgePort == 0) {
return;
}
sockaddr_storage local;
socklen_t length = sizeof(local);
memset(&local, 0, sizeof(local));
if (getsockname(socketFd, (sockaddr *) &local, &length) != 0) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) web_proxy_stream_open getsockname failed errno=%d", this, errno);
return;
}
uint16_t localPort = 0;
if (local.ss_family == AF_INET) {
localPort = ntohs(((sockaddr_in *) &local)->sin_port);
} else if (local.ss_family == AF_INET6) {
localPort = ntohs(((sockaddr_in6 *) &local)->sin6_port);
}
if (localPort == 0) {
return;
}
gate.webProxyLocalPort = localPort;
ConnectionsManager &manager = ConnectionsManager::getInstance(instanceNum);
if (manager.delegate != nullptr) {
manager.delegate->onWebProxyStreamOpened(gate.webProxyBridgePort, localPort, webProxyStreamClass, instanceNum);
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) web_proxy_stream_open class=%s", this, webProxyStreamClassName(webProxyStreamClass));
}
// Asked when a WEB bridge connection saw no data for its receive timeout.
// Every such connection is one stream of a single shared carrier, so silence
// alone cannot tell a reply still queued behind a download from a dead
// stream: the carrier can (WebProxyFlow.decideReceiveWait). Returns true to
// keep waiting. A stalled carrier is recovered by the bridge once for all of
// its streams instead of by N independent timeouts.
bool ConnectionSocket::deferWebProxyReceiveTimeout(int64_t now) {
auto &gate = stateMachine.endpointGate;
if (!isCurrentWebProxyBridge() || gate.webProxyLocalPort == 0 || !onConnectedSent) {
return false;
}
ConnectionsManager &manager = ConnectionsManager::getInstance(instanceNum);
if (manager.delegate == nullptr) {
return false;
}
if (gate.webProxyWaitAnchor != lastEventTime) {
// Data arrived (or a new request started the timer) since the
// carrier was last asked: this is a new silence.
gate.webProxyWaitAnchor = lastEventTime;
gate.webProxyRecheckAt = 0;
gate.webProxyWaitReason = 0;
}
if (gate.webProxyRecheckAt > now) {
return true;
}
const int64_t verdict = manager.delegate->webProxyReceiveWait(gate.webProxyBridgePort, gate.webProxyLocalPort, gate.webProxyWaitAnchor, instanceNum);
if (verdict > 0) {
int64_t waitMs = verdict >> 4;
const int32_t reason = (int32_t) (verdict & 0xf);
if (waitMs < 1) {
waitMs = 1;
} else if (waitMs > WEB_PROXY_MAX_RECHECK_MS) {
waitMs = WEB_PROXY_MAX_RECHECK_MS;
}
gate.webProxyRecheckAt = now + waitMs;
if (LOGS_ENABLED && reason != gate.webProxyWaitReason) {
DEBUG_D("connection(%p) web_proxy_receive_wait verdict=wait reason=%s class=%s silence_ms=%lld recheck_ms=%lld", this, webProxyReceiveReasonName(reason), webProxyStreamClassName(webProxyStreamClass), (long long) (now - gate.webProxyWaitAnchor), (long long) waitMs);
}
gate.webProxyWaitReason = reason;
return true;
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) web_proxy_receive_wait verdict=fail reason=%s class=%s silence_ms=%lld", this, webProxyReceiveReasonName((int32_t) -verdict), webProxyStreamClassName(webProxyStreamClass), (long long) (now - gate.webProxyWaitAnchor));
gate.webProxyRecheckAt = 0;
gate.webProxyWaitReason = 0;
return false;
}
bool ConnectionSocket::hasMtProxyOverride() const {
return !overrideProxyAddress.empty() && !overrideProxySecret.empty();
}
bool ConnectionSocket::dispatchWssPayloads(std::vector<std::vector<uint8_t>> &payloads) {
for (auto &payload : payloads) {
if (payload.empty()) {
continue;
}
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (ConnectionsManager::getInstance(instanceNum).delegate != nullptr) {
ConnectionsManager::getInstance(instanceNum).delegate->onBytesReceived((int32_t) payload.size(), currentNetworkType, instanceNum);
}
NativeByteBuffer *wssBuffer = BuffersStorage::getInstance().getFreeBuffer((uint32_t) payload.size());
wssBuffer->writeBytes(payload.data(), (uint32_t) payload.size());
wssBuffer->rewind();
if (!canDeliverReceivedData("wss_payload_recv")) {
wssBuffer->reuse();
closeSocket(1, -1);
return false;
}
onReceivedData(wssBuffer);
if (isDisconnected()) {
return false;
}
}
return true;
}
bool ConnectionSocket::flushWssStream(std::string *diagnostic) {
if (!isCurrentTransportWss() || !currentWssTransport->isReady()) {
return true;
}
// Outgoing bytes are queued in the shared stream like on any TCP transport,
// but frames are cut back at the recorded packet boundaries: the relay
// parses only the first MTProto packet of a frame and drops the rest with
// no error at all. Coalescing msgs_ack with the next handshake step this
// way stalled key creation for a whole datacenter.
uint32_t flushedBytes = 0;
while (outgoingByteStream->hasData()
&& currentWssTransport->queuedOutputBytes() < WSS_MAX_BUFFERED_OUTPUT_BYTES) {
if (!canSendWssFrame()) {
return false;
}
NativeByteBuffer *buffer = ConnectionsManager::getInstance(instanceNum).networkBuffer;
buffer->clear();
outgoingByteStream->get(buffer);
buffer->flip();
const uint32_t available = buffer->remaining();
if (available == 0) {
break;
}
uint32_t offset = 0;
while (offset < available
&& !outgoingWssPacketSizes.empty()
&& currentWssTransport->queuedOutputBytes() < WSS_MAX_BUFFERED_OUTPUT_BYTES) {
// One MTProto packet per frame: the relay parses only the first
// packet of a frame and silently discards whatever follows it.
const uint32_t packetSize = outgoingWssPacketSizes.front();
if (packetSize == 0 || offset + packetSize > available) {
break;
}
if (!currentWssTransport->write(buffer->bytes() + offset, packetSize, diagnostic)) {
return false;
}
outgoingWssPacketSizes.pop_front();
offset += packetSize;
}
if (offset == 0) {
break;
}
outgoingByteStream->discard(offset);
flushedBytes += offset;
if (ConnectionsManager::getInstance(instanceNum).delegate != nullptr) {
ConnectionsManager::getInstance(instanceNum).delegate->onBytesSent(
(int32_t) offset, currentNetworkType, instanceNum);
}
}
if (flushedBytes > 0 && wssFirstFrameSentTime == 0) {
wssFirstFrameSentTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
}
if (flushedBytes == 0 && !currentWssTransport->wantsWrite()) {
return true;
}
std::vector<std::vector<uint8_t>> payloads;
const bool transportAlive = currentWssTransport->onEvent(EPOLLOUT, payloads, diagnostic);
if (!dispatchWssPayloads(payloads)) {
return false;
}
return transportAlive;
}
void ConnectionSocket::publishSanitizedSecretDomainIfNeeded(size_t rawDomainLength) {
if (!isCurrentMtProxyConnection() || currentMtProxyEndpointKey.empty()) {
return;
}
if (!MtProxyEndpointPolicy::recordSecretDomainSanitized(currentMtProxyEndpointKey)) {
return;
}
proxyCheckDiagnostic = "secret_domain_sanitized";
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup secret_domain_sanitized raw_len=%d sanitized=%s", this, (int) rawDomainLength, currentSecretDomain.c_str());
}
publishProxyConnectionStage("secret_domain_sanitized");
}
void ConnectionSocket::publishProxyConnectionStage(const char *diagnostic) {
if (!isCurrentMtProxyConnection() || !overrideProxyAddress.empty() || diagnostic == nullptr) {
return;
}
std::string endpointKey = currentMtProxyEndpointKey;
if (endpointKey.empty()) {
endpointKey = currentMtProxyNetworkEndpointKey;
}
if (endpointKey.empty() && !currentAddress.empty() && currentPort != 0) {
endpointKey = MtProxyEndpointPolicy::networkEndpointKeyFor(currentAddress, currentPort);
}
if (endpointKey.empty()) {
return;
}
// Raise the unified reconnect hold before the event leaves native: for
// cooldown-carrying failures the endpoint cooldown becomes part of the
// suggested hold, so (a) Connection's reconnect timer waits it out via
// consumeSuggestedReconnectHoldMs (retrying earlier is denied pre-TCP
// anyway) and (b) the Java layer receives THE hold with the event and
// never re-derives it from its own clock.
// The WEB loopback bridge has no endpoint cooldown, so it never carries a
// cooldown-derived reconnect hold either.
if (!isCurrentWebProxyBridge() && MtProxyEndpointPolicy::failureNeedsCooldown(diagnostic)) {
int64_t cooldownHoldMs = MtProxyEndpointPolicy::cooldownMs(
diagnostic,
normalizeMtProxyConnectionPatternMode(currentConnectionPatternMode),
proxyHandshakeAdmissionPriority);
if (cooldownHoldMs > (int64_t) proxySuggestedReconnectHoldMs) {
proxySuggestedReconnectHoldMs = (uint32_t) cooldownHoldMs;
}
}
ConnectionsManager &manager = ConnectionsManager::getInstance(instanceNum);
if (manager.delegate != nullptr) {
std::string origin = proxyConnectionStageOrigin();
if (origin == "active_socket" && !proxyActivationOrigin.empty()) {
origin = proxyActivationOrigin;
}
std::string socketRole = proxyConnectionStageSocketRole();
if (origin == "background_keepalive") {
socketRole = "background_keepalive";
} else if (origin == "startup_restore") {
socketRole = "startup_restore";
}
manager.delegate->onProxyConnectionStageChanged(instanceNum, diagnostic, endpointKey, currentMtProxyProbeKey, origin, socketRole, (int32_t) proxyActivationGeneration, (int32_t) proxySuggestedReconnectHoldMs);
}
}
void ConnectionSocket::publishMtProxySocketObservation(const MtProxySocketObservation &observation) {
MtProxySocketPublisherContext context;
context.endpointKey = currentMtProxyEndpointKey;
context.probeKey = currentMtProxyProbeKey;
context.networkEndpointKey = currentMtProxyNetworkEndpointKey;
MtProxySocketPublisherCallbacks callbacks;
callbacks.publishVisibleStage = [this](const MtProxySocketObservation &publishedObservation) {
publishProxyConnectionStage(publishedObservation.phase);
};
callbacks.recordEndpointFailure = [this](const MtProxySocketObservation &publishedObservation) {
MtProxyEndpointRecorder::recordFailure(mtProxyEndpointFailureContext(publishedObservation.phase, publishedObservation.reason), mtProxyEndpointRecorderCallbacks());
};
mtProxyPublishSocketObservation(observation, context, callbacks);
}
std::string ConnectionSocket::proxyConnectionStageOrigin() {
return "active_socket";
}
std::string ConnectionSocket::proxyConnectionStageSocketRole() {
return "control_main";
}
std::string ConnectionSocket::getDiagnosticSnapshot() {
ConnectionsManager &manager = ConnectionsManager::getInstance(instanceNum);
std::string transport;
std::string connectHost;
uint16_t connectPort = currentPort;
if (currentTransportWss) {
transport = "wss";
connectHost = currentWssRoute.connectHost;
connectPort = currentWssRoute.relayPort;
} else if (isCurrentMtProxyConnection()) {
transport = currentSecretIsFakeTls ? "mtproxy_faketls" : "mtproxy";
connectHost = !overrideProxyAddress.empty() ? overrideProxyAddress : manager.proxyAddress;
connectPort = !overrideProxyAddress.empty() ? overrideProxyPort : manager.proxyPort;
} else if (!overrideProxyAddress.empty() || !manager.proxyAddress.empty()) {
transport = "socks5";
connectHost = !overrideProxyAddress.empty() ? overrideProxyAddress : manager.proxyAddress;
connectPort = !overrideProxyAddress.empty() ? overrideProxyPort : manager.proxyPort;
} else {
transport = "direct_tcp";
connectHost = currentAddress;
}
std::string result;
result.reserve(512);
auto append = [&](const char *key, const std::string &value) {
if (!result.empty()) result += ' ';
result += key;
result += '=';
result += value.empty() ? "none" : value;
};
auto appendNumber = [&](const char *key, int64_t value) {
append(key, std::to_string(value));
};
append("role", proxyConnectionStageSocketRole());
appendNumber("dc", currentDatacenterId);
appendNumber("media", currentMediaConnection ? 1 : 0);
append("transport", transport);
append("state", transportStateName(currentTransportState));
append("connect", connectHost + ":" + std::to_string(connectPort));
append("target", currentAddress + ":" + std::to_string(currentPort));
appendNumber("ipv6", isIpv6 ? 1 : 0);
appendNumber("socket_open", socketFd >= 0 ? 1 : 0);
appendNumber("epoll", epollRegistered ? 1 : 0);
appendNumber("connected", onConnectedSent ? 1 : 0);
append("diagnostic", proxyCheckDiagnostic);
if (currentTransportWss) {
append("wss_url", "wss://" + currentWssRoute.domain + currentWssRoute.path);
append("wss_relay", currentWssRoute.connectHost + ":" + std::to_string(currentWssRoute.relayPort));
appendNumber("wss_fallback", currentWssRoute.viaFallback ? 1 : 0);
}
appendNumber("endpoint_selected", currentMtProxyEndpointKey.empty() ? 0 : 1);
appendNumber("probe_active", currentMtProxyProbeKey.empty() ? 0 : 1);
appendNumber("activation_generation", proxyActivationGeneration);
appendNumber("network_type", currentNetworkType);
return result;
}
bool ConnectionSocket::matchesMtProxyEndpointKey(const std::string &endpointKey) {
if (endpointKey.empty() || !isCurrentMtProxyConnection()) {
return false;
}
if (endpointKey == currentMtProxyEndpointKey || endpointKey == currentMtProxyNetworkEndpointKey) {
return true;
}
if (!currentMtProxyEndpointKey.empty()
&& currentMtProxyEndpointKey.size() > endpointKey.size()
&& currentMtProxyEndpointKey.compare(0, endpointKey.size(), endpointKey) == 0
&& currentMtProxyEndpointKey[endpointKey.size()] == ':') {
return true;
}
return !currentMtProxyNetworkEndpointKey.empty()
&& endpointKey.size() > currentMtProxyNetworkEndpointKey.size()
&& endpointKey.compare(0, currentMtProxyNetworkEndpointKey.size(), currentMtProxyNetworkEndpointKey) == 0
&& endpointKey[currentMtProxyNetworkEndpointKey.size()] == ':';
}
bool ConnectionSocket::matchesMtProxyProbeKey(const std::string &probeKey) {
return !probeKey.empty()
&& isCurrentMtProxyConnection()
&& !currentMtProxyProbeKey.empty()
&& probeKey == currentMtProxyProbeKey;
}
void ConnectionSocket::cancelMtProxyEndpointAttempt(const char *reason) {
if (!isCurrentMtProxyConnection()) {
return;
}
const char *safeReason = reason != nullptr && reason[0] != '\0' ? reason : "unknown";
proxyCheckDiagnostic = "ignored_cancelled_generation";
proxyCloseDiagnosticSuppressed = true;
suppressNextProxyCloseDiagnostic = true;
publishProxyConnectionStage("ignored_cancelled_generation");
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup endpoint_attempt_cancelled endpoint=%s probe=%s network_key=%s reason=%s", this, currentMtProxyEndpointKey.c_str(), currentMtProxyProbeKey.c_str(), currentMtProxyNetworkEndpointKey.c_str(), safeReason);
}
releaseMtProxyEndpointTcpConnect("endpoint_attempt_cancelled");
releaseProxyHandshakeAdmission(false, "endpoint_attempt_cancelled");
cancelProxyHandshakeAdmission();
closeSocket(1, ECANCELED);
}
void ConnectionSocket::markMtProxyFirstPlainDataSent(uint32_t bytes) {
if (!isCurrentMtProxyConnection() || currentSecretIsFakeTls || bytes == 0 || mtproxyFirstPlainDataSentLogged) {
return;
}
mtproxyFirstPlainDataSentLogged = true;
firstTransportPacketSent = true;
mtproxyFirstPlainDataSentTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
proxyCheckDiagnostic = "mtproxy_packet_sent_no_response";
publishProxyConnectionStage("first_mtproxy_packet_sent");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup first_mtproxy_packet_sent bytes=%u secret_kind=%s", this, bytes, currentSecretKind);
}
void ConnectionSocket::markMtProxyFirstPlainDataReceived(uint32_t bytes) {
if (!isCurrentMtProxyConnection() || currentSecretIsFakeTls || bytes == 0 || mtproxyFirstPlainDataReceivedLogged) {
return;
}
mtproxyFirstPlainDataReceivedLogged = true;
firstTransportPacketReceived = true;
dataPathProven = true;
mtproxyFirstPlainDataSentTime = 0;
mtproxyFirstDataReceivedTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
proxyCheckDiagnostic = "dropped_after_appdata";
MtProxySocketObservation observation;
observation.phase = MtProxyPhase::FirstMtproxyPacketRecv;
observation.reason = MtProxyPhase::FirstMtproxyPacketRecv;
publishMtProxySocketObservation(observation);
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup first_mtproxy_packet_recv bytes=%u secret_kind=%s", this, bytes, currentSecretKind);
MtProxyEndpointRecorder::recordDataPathSuccess(mtProxyEndpointSuccessContext("first_mtproxy_packet_recv"), mtProxyEndpointRecorderCallbacks());
}
void ConnectionSocket::rotateMtProxyTlsProfileOnFailureIfNeeded(int32_t reason, int32_t error) {
if (!currentSecretIsFakeTls || currentProxyTlsProfile != MT_PROXY_TLS_PROFILE_AUTO_ROTATE || currentProxyTlsProfileKey.empty()) {
return;
}
if (reason == 0 && error == 0) {
return;
}
MtProxyAdaptivePolicy::RotateResult rotation = MtProxyAdaptivePolicy::rotateTlsProfileOnFailureIfNeeded(currentProxyTlsProfileKey, proxyCheckDiagnostic, currentEffectiveProxyTlsProfile);
if (!rotation.rotated) {
return;
}
if (LOGS_ENABLED) DEBUG_D("mtproxy_startup profile_rotate key=%s phase=%s previous=%s next=%s failures=%u", currentProxyTlsProfileKey.c_str(), proxyCheckDiagnostic.c_str(), mtProxyTlsProfileName(rotation.previousProfile), mtProxyTlsProfileName(rotation.nextProfile), rotation.failures);
}
void ConnectionSocket::logMtProxyTlsAfterClientHello(size_t responseBytes) {
if (!LOGS_ENABLED || tempBuffer == nullptr || responseBytes == 0) {
return;
}
const uint8_t *data = tempBuffer->bytes;
std::string hex = mtProxyHexPreview(data, responseBytes);
MtProxyServerFlightRecordInfo recordInfo = mtProxyServerFlightReadRecordInfo(data, responseBytes);
DEBUG_D("connection(%p) mtproxy_startup mtproxy_tls_after_client_hello bytes=%zu hex=%s record_type=%d tls_version=0x%02x%02x record_len=%d alert_level=%d alert_description=%d", this, responseBytes, hex.c_str(), recordInfo.recordType, recordInfo.tlsMajor < 0 ? 0 : recordInfo.tlsMajor, recordInfo.tlsMinor < 0 ? 0 : recordInfo.tlsMinor, recordInfo.recordLength, recordInfo.alertLevel, recordInfo.alertDescription);
}
const char *ConnectionSocket::classifyMtProxyPostClientHelloResponse(size_t responseBytes) {
if (tempBuffer == nullptr || responseBytes == 0) {
return "true_client_hello_timeout";
}
const uint8_t *data = tempBuffer->bytes;
if (mtProxyServerFlightLooksLikeTlsAlert(data, responseBytes)) {
return "tls_alert_after_client_hello";
}
if (responseBytes < 5 || mtProxyServerFlightHandshakeRecordNeedsMoreBytes(data, responseBytes)) {
return "short_tls_response_after_client_hello";
}
return "unrecognized_response_after_client_hello";
}
void ConnectionSocket::closeMtProxyPostClientHelloResponse(const char *diagnostic, const char *reason, int32_t error) {
if (diagnostic == nullptr || diagnostic[0] == '\0') {
diagnostic = "unrecognized_response_after_client_hello";
}
proxyCheckDiagnostic = diagnostic;
if (LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup post_client_hello_response_failed phase=%s reason=%s bytes=%zu", this, proxyCheckDiagnostic.c_str(), reason != nullptr ? reason : "unknown", bytesRead);
}
closeSocket(1, error);
}
// closeSocket is an ordered pipeline; the step order below is load-bearing and
// machine-checked by Tools/check_mtproxy_transport_state.py. Key constraints:
// the diagnostic must be fully resolved (step 3) before anything is logged or
// published (steps 4-5); the endpoint failure must be recorded (step 5) while
// the probe lease/owner token is still live (released in step 6); and
// onDisconnected must be the very last statement (step 8) because the
// Connection layer reads the resolved diagnostic and the suggested reconnect
// hold from this object.
void ConnectionSocket::closeSocket(int32_t reason, int32_t error) {
// The pipeline below is structural: each step is a named method and the
// guard suite pins their order. Step semantics live on the methods.
// [close-step 1/8] Reentry guard: a socket is closed exactly once.
if (!closeStepReentryGuard(reason, error)) {
return;
}
// [close-step 2/8] Transport gate.
closeStepTransportGate();
// [close-step 3/8] Diagnostic resolution: the only inter-step data flow.
CloseDiagnosticResolution closeResolution = closeStepResolveDiagnostic(reason, error);
// [close-step 4/8] Observability.
closeStepLogDisconnect(reason, error, closeResolution);
// [close-step 5/8] Verdict publication (before the probe lease release).
closeStepPublishVerdict(reason, closeResolution);
// [close-step 6/8] Resource release.
closeStepReleaseResources();
// [close-step 7/8] OS teardown.
closeStepOsTeardown();
// [close-step 8/8] State reset, then notify the Connection layer last.
closeStepResetStateAndNotify(reason, error);
}
// [close-step 1/8] Reentry guard: a socket is closed exactly once. Returns
// false when the close was already performed.
bool ConnectionSocket::closeStepReentryGuard(int32_t reason, int32_t error) {
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (socketCloseNotified) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup close_ignored_already_closed reason=%s error=%s phase=%s transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d", this, mtProxyDisconnectReasonName(reason), mtProxySocketErrorName(error), proxyCheckDiagnostic.c_str(), transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState);
return false;
}
return true;
}
// [close-step 2/8] Transport gate: mark dead-for-writes and enter Closing
// before any diagnostic work so concurrent write paths stop immediately.
void ConnectionSocket::closeStepTransportGate() {
markConnectionDeadForWrites("closeSocket");
logTransportSnapshot("close_start", "closeSocket");
checkCloseSocketAction("closeSocket");
setTransportState(TransportState::Closing, "closeSocket");
setSocketCloseNotified(true, "closeSocket");
}
// [close-step 3/8] Diagnostic resolution: capture forced suppression,
// reclassify early app-data drops, derive the terminal diagnostic
// (pre-I/O terminal verdicts survive via MtProxyPhase::isPreIoTerminalVerdict),
// and decide shadow-by-fresh-success suppression. proxyCheckDiagnostic is
// final after this step; later steps only read the returned resolution.
ConnectionSocket::CloseDiagnosticResolution ConnectionSocket::closeStepResolveDiagnostic(int32_t reason, int32_t error) {
CloseDiagnosticResolution resolution;
bool forcedSuppressProxyCloseDiagnostic = suppressNextProxyCloseDiagnostic;
suppressNextProxyCloseDiagnostic = false;
proxyCloseDiagnosticSuppressed = forcedSuppressProxyCloseDiagnostic;
if (reason != 0
&& isCurrentMtProxyConnection()
&& proxyCheckDiagnostic == "dropped_after_appdata"
&& mtproxyFirstDataReceivedTime > 0
&& lastEventTime - mtproxyFirstDataReceivedTime <= MT_PROXY_EARLY_APPDATA_DROP_MS) {
proxyCheckDiagnostic = "dropped_early_after_appdata";
}
std::string terminalDiagnostic = deriveMtProxyTerminalDiagnostic(reason, error);
if (!forcedSuppressProxyCloseDiagnostic && reason != 0 && isCurrentMtProxyConnection()) {
proxyCheckDiagnostic = terminalDiagnostic;
}
bool suppressProxyCloseDiagnostic = false;
if (forcedSuppressProxyCloseDiagnostic) {
suppressProxyCloseDiagnostic = true;
}
bool shadowedSocketFailure = false;
int64_t shadowedSocketFailureHoldMs = 0;
if (!forcedSuppressProxyCloseDiagnostic && reason != 0 && isCurrentMtProxyConnection()) {
if (terminalDiagnostic == MtProxyPhase::PostHandshakeNoAppdata
|| terminalDiagnostic == "mtproxy_packet_sent_no_response") {
MtProxyEndpointPolicy::MtProxyEndpointContext context;
context.endpointKey = currentMtProxyEndpointKey;
context.recipeCacheKey = currentMtProxyRecipeCacheKey;
context.networkEndpointKey = "";
context.fakeTls = currentSecretIsFakeTls;
int64_t now = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
shadowedSocketFailureHoldMs = MtProxyEndpointPolicy::shadowFailureByFreshDataPathSuccess(context, terminalDiagnostic, now);
if (shadowedSocketFailureHoldMs > 0) {
suppressProxyCloseDiagnostic = true;
shadowedSocketFailure = true;
}
}
if (proxyCheckDiagnostic == MtProxyPhase::PostHandshakeNoAppdata
&& !mtproxyFirstTlsFrameSentLogged
&& !mtproxyFirstPlainDataSentLogged) {
suppressProxyCloseDiagnostic = true;
} else if (proxyCheckDiagnostic == "dropped_after_appdata"
&& (mtproxyFirstTlsDataReceivedLogged || mtproxyFirstPlainDataReceivedLogged)) {
suppressProxyCloseDiagnostic = true;
}
}
resolution.terminalDiagnostic = terminalDiagnostic;
resolution.suppress = suppressProxyCloseDiagnostic;
resolution.shadowedSocketFailure = shadowedSocketFailure;
resolution.shadowedHoldMs = shadowedSocketFailureHoldMs;
return resolution;
}
// [close-step 4/8] Observability: the mtproxy_disconnect snapshot and the
// suppressed/rotate branch log the RESOLVED diagnostic from step 3.
void ConnectionSocket::closeStepLogDisconnect(int32_t reason, int32_t error, const CloseDiagnosticResolution &resolution) {
if (isCurrentDirectConnection()) {
return;
}
if (currentTransportWss) {
if (wssTunnelRotating) {
wssTunnelRotating = false;
noteWssTunnelRotated(currentWssTransport != nullptr ? currentWssTransport->receivedBytes() : 0);
return;
}
if (LOGS_ENABLED) {
const std::string session = currentWssTransport != nullptr ? currentWssTransport->takeSessionSummary() : std::string();
// The diagnostic says "wss_tls_handshake" from the moment the socket
// is opened; a socket that never got TCP up was read as a TLS block
// (logs (1) (5): every relay "stuck in TLS" had no tcp_connected).
const char *phaseText = proxyCheckDiagnostic.c_str();
if (proxyCheckDiagnostic == "wss_tls_handshake" && (currentWssTransport == nullptr || currentWssTransport->handshakePhase() == tgnet::transport::HandshakePhase::None)) {
phaseText = "wss_tcp_connect";
}
DEBUG_D("connection(%p) wss_disconnect account%d dc%d media=%d reason=%d reason_text=%s error=%d error_text=%s phase=%s transport_state=%s epoll_registered=%d %s", this, (int) instanceNum, (int) currentDatacenterId, currentMediaConnection ? 1 : 0, reason, mtProxyDisconnectReasonName(reason), error, mtProxySocketErrorName(error), phaseText, transportStateName(currentTransportState), epollRegistered ? 1 : 0, session.c_str());
}
return;
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_disconnect reason=%d reason_text=%s error=%d error_text=%s secret_kind=%s is_faketls=%d is_wss=%d transport_state=%s epoll_registered=%d admission_active=%d admission_queued=%d tcp_gate_active=%d waiting_resolve=%d proxy_state=%d tls_state=%d bytes_read=%zu pending_hello=%u/%u pending=%u/%u first_tls_sent=%d first_tls_recv=%d first_plain_sent=%d first_plain_recv=%d tls_frames_completed=%u", this, reason, mtProxyDisconnectReasonName(reason), error, mtProxySocketErrorName(error), currentSecretKind, currentSecretIsFakeTls ? 1 : 0, currentTransportWss ? 1 : 0, transportStateName(currentTransportState), epollRegistered ? 1 : 0, proxyHandshakeAdmissionActive ? 1 : 0, proxyHandshakeAdmissionQueued ? 1 : 0, proxyEndpointTcpConnectActive ? 1 : 0, waitingForHostResolve.empty() ? 0 : 1, (int) proxyAuthState, (int) tlsState, bytesRead, pendingClientHelloOffset, pendingClientHelloSize, pendingTlsFrameOffset, pendingTlsFrameSize, mtproxyFirstTlsFrameSentLogged ? 1 : 0, mtproxyFirstTlsDataReceivedLogged ? 1 : 0, mtproxyFirstPlainDataSentLogged ? 1 : 0, mtproxyFirstPlainDataReceivedLogged ? 1 : 0, mtproxyTlsFrameCompletedCount);
if (resolution.suppress) {
proxyCloseDiagnosticSuppressed = true;
if (resolution.shadowedSocketFailure) {
publishProxyConnectionStage("shadowed_socket_failure");
const char *heldBy = currentSecretIsFakeTls ? "first_tls_app_recv" : "first_mtproxy_packet_recv";
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup shadowed_socket_failure phase=%s held_by=%s hold_ms=%ld endpoint=%s network_key=%s", this, proxyCheckDiagnostic.c_str(), heldBy, (long) resolution.shadowedHoldMs, currentMtProxyEndpointKey.c_str(), currentMtProxyNetworkEndpointKey.c_str());
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup close_diagnostic_suppressed phase=%s reason=%s first_tls_sent=%d first_tls_recv=%d first_plain_sent=%d first_plain_recv=%d", this, proxyCheckDiagnostic.c_str(), mtProxyDisconnectReasonName(reason), mtproxyFirstTlsFrameSentLogged ? 1 : 0, mtproxyFirstTlsDataReceivedLogged ? 1 : 0, mtproxyFirstPlainDataSentLogged ? 1 : 0, mtproxyFirstPlainDataReceivedLogged ? 1 : 0);
} else {
rotateMtProxyTlsProfileOnFailureIfNeeded(reason, error);
}
}
// [close-step 5/8] Verdict publication: release the TCP connect gate, then
// publish close_diagnostic and record the endpoint failure. This MUST run
// before the probe lease release in step 6 so the coordinator still sees a
// live owner token when the failure is recorded (HANG-7).
void ConnectionSocket::closeStepPublishVerdict(int32_t reason, const CloseDiagnosticResolution &resolution) {
releaseMtProxyEndpointTcpConnect("closeSocket");
if (!resolution.suppress && reason != 0 && isCurrentMtProxyConnection() && !resolution.terminalDiagnostic.empty()) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup close_diagnostic phase=%s", this, resolution.terminalDiagnostic.c_str());
if (mtProxySocketObservationIsHighRiskPhase(resolution.terminalDiagnostic.c_str())) {
MtProxySocketObservation observation;
observation.phase = resolution.terminalDiagnostic.c_str();
observation.reason = "closeSocket";
observation.recordEndpointFailure = true;
publishMtProxySocketObservation(observation);
} else {
publishProxyConnectionStage(resolution.terminalDiagnostic.c_str());
MtProxyEndpointRecorder::recordFailure(mtProxyEndpointFailureContext(resolution.terminalDiagnostic.c_str(), "closeSocket"), mtProxyEndpointRecorderCallbacks());
}
}
}
// [close-step 6/8] Resource release: probe lease (after the failure was
// recorded), admission slot, and manager detach.
void ConnectionSocket::closeStepReleaseResources() {
if (LOGS_ENABLED && mtProxyProbeLease.active()) DEBUG_D("connection(%p) mtproxy_startup probe_owner_release key=%s token=%llu", this, mtProxyProbeLease.key().c_str(), (unsigned long long) mtProxyProbeLease.ownerToken());
mtProxyProbeLease.release();
releaseProxyHandshakeAdmission(false, "closeSocket");
cancelProxyHandshakeAdmission();
ConnectionsManager::getInstance(instanceNum).detachConnection(this);
}
// [close-step 7/8] OS teardown: epoll deregistration and native socket close.
void ConnectionSocket::closeStepOsTeardown() {
if (socketFd >= 0) {
if (epollRegistered) {
if (canUnregisterEpollSocket()) {
stateMachine.epollCtlDel(ConnectionsManager::getInstance(instanceNum).epolFd);
}
setEpollRegistered(false, "epoll_ctl_del");
}
if (currentWssTransport != nullptr) {
currentWssTransport->close();
} else if (canCloseNativeSocket()) {
if (!stateMachine.closeNativeSocket("close_native_socket")) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) unable to close socket", this);
}
}
setSocketFd(-1, "close_native_socket");
}
}
// [close-step 8/8] State reset to Idle, then notify the Connection layer.
// onDisconnected must stay the LAST statement: it reads the resolved
// diagnostic and consumeSuggestedReconnectHoldMs() from this object.
void ConnectionSocket::closeStepResetStateAndNotify(int32_t reason, int32_t error) {
setWaitingForHostResolve("", "closeSocket_cleanup");
setMtProxyTcpConnectAttemptStarted(false, "closeSocket_cleanup");
setMtProxyDnsResolveAttemptStarted(false, "closeSocket_cleanup");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "closeSocket_cleanup");
setAdjustWriteOpAfterResolve(false, "closeSocket_cleanup");
currentTransportWss = false;
currentWssTransport.reset();
currentWssRoute = tgnet::wss::Route();
outgoingWssPacketSizes.clear();
wssFirstFrameSentTime = 0;
wssOpenTime = 0;
currentSocksUsername.clear();
currentSocksPassword.clear();
setProxyAuthState(0, "closeSocket_cleanup");
setTlsState(0, "closeSocket_cleanup");
setConnectedNotified(false, "closeSocket_cleanup");
setMtProxySocketConnectedLogged(false, "closeSocket_cleanup");
serverHelloHmacMismatchObserved = false;
serverHelloHmacMismatchTime = 0;
clearPendingClientHello();
clearPendingTlsFrame();
outgoingByteStream->clean();
if (tlsBuffer != nullptr) {
tlsBuffer->reuse();
tlsBuffer = nullptr;
}
tlsBufferRecordType = 0;
checkCloseSocketAction("closeSocket_cleanup");
setTransportState(TransportState::Idle, "closeSocket_cleanup");
onDisconnected(reason, error);
}
void ConnectionSocket::onEvent(uint32_t events) {
if (!canProcessEpollEvent()) {
return;
}
if (isCurrentTransportWss()) {
std::vector<std::vector<uint8_t>> payloads;
std::string diagnostic;
const bool transportAlive = currentWssTransport->onEvent(events, payloads, &diagnostic);
if (transportAlive && currentWssTransport->isReady() && !onConnectedSent) {
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
proxyCheckDiagnostic = MtProxyPhase::PostHandshakeNoAppdata;
setTransportState(TransportState::MtprotoReady, "wss_ready");
if (LOGS_ENABLED) DEBUG_D("connection(%p) wss_startup on_connected", this);
if (!canNotifyConnected("wss_ready")) {
return;
}
onConnected();
setConnectedNotified(true, "wss_ready");
}
// Deliver payloads before acting on a transport failure: the frames
// drained ahead of an EOF may carry the server's transport error code,
// and MTProto needs it to recover (e.g. regenerate an auth key on -404)
// instead of blindly reconnecting with the same state.
if (!dispatchWssPayloads(payloads)) {
return;
}
// Only between packets: cutting a larger answer in the middle would
// resend it and cut it again forever. A part too big for the tunnel
// freezes instead, and the file loader then asks for smaller parts.
if (transportAlive
&& currentWssRoute.tunnel
&& currentWssTransport->receivedBytes() >= WSS_TUNNEL_ROTATE_BYTES
&& !hasPartialIncomingPacket()) {
wssTunnelRotating = true;
closeSocket(0, 0);
return;
}
if (!transportAlive) {
proxyCheckDiagnostic = diagnostic.empty() ? "wss_transport_failed" : diagnostic;
if (LOGS_ENABLED) DEBUG_E("connection(%p) wss_startup failed diagnostic=%s", this, proxyCheckDiagnostic.c_str());
closeSocket(1, -1);
return;
}
if (currentWssTransport->isReady() && !flushWssStream(&diagnostic)) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) wss_startup write failed diagnostic=%s", this, diagnostic.c_str());
closeSocket(1, -1);
return;
}
adjustWriteOp();
return;
}
if (events & EPOLLIN) {
int32_t error;
if (checkSocketError(&error) != 0) {
closeSocket(1, error);
return;
} else {
if (!canReceiveRawSocketBytes()) {
closeSocket(1, -1);
return;
}
ssize_t readCount;
NativeByteBuffer *buffer = ConnectionsManager::getInstance(instanceNum).networkBuffer;
while (true) {
buffer->rewind();
readCount = stateMachine.recvBytes(buffer->bytes(), READ_BUFFER_SIZE, 0);
int err = errno;
// if (LOGS_ENABLED) DEBUG_D("connection(%p) recv resulted with %d, errno=%d", this, readCount, err);
if (readCount < 0) {
if (err == EINTR) {
continue;
}
if (err == EAGAIN || err == EWOULDBLOCK) {
break;
}
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) recv failed", this);
return;
}
if (readCount > 0) {
buffer->limit((uint32_t) readCount);
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (proxyAuthState == 11) {
if (pendingClientHello != nullptr && pendingClientHelloOffset < pendingClientHelloSize && LOGS_ENABLED) {
DEBUG_D("connection(%p) mtproxy_startup server_data_before_client_hello_complete pending_hello=%u/%u read=%d", this, pendingClientHelloOffset, pendingClientHelloSize, (int) readCount);
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) TLS received %d", this, (int) readCount);
bool firstPostClientHelloRead = bytesRead == 0;
size_t newBytesRead = bytesRead + readCount;
if (newBytesRead > MT_PROXY_SERVER_FLIGHT_MAX_BYTES) {
proxyCheckDiagnostic = "unrecognized_response_after_client_hello";
closeMtProxyPostClientHelloResponse(proxyCheckDiagnostic.c_str(), "server_hello_too_much_data", -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS client hello too much data", this);
return;
}
std::memcpy(tempBuffer->bytes + bytesRead, buffer->bytes(), (size_t) readCount);
if (firstPostClientHelloRead) {
logMtProxyTlsAfterClientHello(newBytesRead);
}
if (mtProxyServerFlightLooksLikeTlsAlert(tempBuffer->bytes, newBytesRead)
&& normalizeMtProxyServerHelloParserOption(currentServerHelloParserMode) != MT_PROXY_SERVER_HELLO_PARSER_TLS_ALERT_EXACT_DESC) {
bytesRead = newBytesRead;
closeMtProxyPostClientHelloResponse("tls_alert_after_client_hello", "tls_alert_record", -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS alert after ClientHello", this);
return;
}
if (newBytesRead < 5) {
bytesRead = newBytesRead;
return;
}
MtProxyServerFlightParseResult parseResult = mtProxyParseServerHelloFlight(currentSecret, tempBuffer->bytes + MT_PROXY_SERVER_FLIGHT_MAX_BYTES, tempBuffer->bytes, newBytesRead, currentServerHelloParserMode);
if (parseResult.invalid) {
tlsHashMismatch = true;
bytesRead = newBytesRead;
closeMtProxyPostClientHelloResponse(std::strcmp(parseResult.reason, "tls_alert_exact_desc") == 0 ? "tls_alert_after_client_hello" : "unrecognized_response_after_client_hello", parseResult.reason, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS server hello parse invalid parser=%s reason=%s candidate=%zu", this, mtProxyServerHelloParserName(currentServerHelloParserMode), parseResult.reason, parseResult.candidateBytes);
return;
}
if (parseResult.waitMore) {
if (LOGS_ENABLED && std::strcmp(parseResult.reason, "server_hello_tail_data_wait") == 0) {
DEBUG_D("connection(%p) TLS server hello wait for tail data parser=%s reason=%s bytes=%zu candidate=%zu", this, mtProxyServerHelloParserName(currentServerHelloParserMode), parseResult.reason, newBytesRead, parseResult.candidateBytes);
} else if (LOGS_ENABLED) {
DEBUG_D("connection(%p) TLS server hello wait parser=%s reason=%s bytes=%zu candidate=%zu", this, mtProxyServerHelloParserName(currentServerHelloParserMode), parseResult.reason, newBytesRead, parseResult.candidateBytes);
}
bytesRead = newBytesRead;
return;
}
if (!parseResult.matched) {
proxyCheckDiagnostic = MtProxyPhase::ServerHelloHmacMismatch;
serverHelloHmacMismatchObserved = true;
if (serverHelloHmacMismatchTime == 0) {
serverHelloHmacMismatchTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
scheduleProxyHandshakeAdmissionTimer((uint32_t) MT_PROXY_SERVER_HELLO_HMAC_WAIT_MS, MT_PROXY_HANDSHAKE_TIMER_SERVER_HELLO, proxyHandshakeAdmissionIpv6);
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) TLS server hello hmac wait parser=%s bytes=%zu candidate=%zu", this, mtProxyServerHelloParserName(currentServerHelloParserMode), newBytesRead, parseResult.candidateBytes);
bytesRead = newBytesRead;
return;
}
serverHelloHmacMismatchObserved = false;
serverHelloHmacMismatchTime = 0;
publishProxyConnectionStage("server_hello_hmac_ok");
MtProxyEndpointRecorder::recordHandshakeOk(mtProxyEndpointSuccessContext("server_hello_hmac_ok"), mtProxyEndpointRecorderCallbacks());
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup server_hello_hmac_ok parser=%s bytes=%zu flight=%zu extra=%zu", this, mtProxyServerHelloParserName(currentServerHelloParserMode), parseResult.matchedBytes, parseResult.appFlightBytes, newBytesRead - parseResult.matchedBytes);
releaseProxyHandshakeAdmission(true, "server_hello_hmac_ok");
proxyCheckDiagnostic = MtProxyPhase::PostHandshakeNoAppdata;
setTlsState(1, "server_hello_hmac_ok");
startMtProxyStartupCover();
setTransportState(TransportState::MtprotoReady, "server_hello_hmac_ok");
setProxyAuthState(0, "server_hello_hmac_ok");
bytesRead = 0;
adjustWriteOp();
} else if (proxyAuthState == 2) {
if (readCount == 2) {
uint8_t auth_method = buffer->bytes()[1];
if (auth_method == 0xff) {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) unsupported proxy auth method", this);
} else if (auth_method == 0x02) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) proxy auth required", this);
setProxyAuthState(3, "socks_auth_required");
} else if (auth_method == 0x00) {
setProxyAuthState(5, "socks_no_auth");
}
adjustWriteOp();
} else {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) invalid proxy response on state 2", this);
}
} else if (proxyAuthState == 4) {
if (readCount == 2) {
uint8_t auth_method = buffer->bytes()[1];
if (auth_method != 0x00) {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) auth invalid", this);
} else {
setProxyAuthState(5, "socks_auth_ok");
}
adjustWriteOp();
} else {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) invalid proxy response on state 4", this);
}
} else if (proxyAuthState == 6) {
if (readCount > 2) {
uint8_t status = buffer->bytes()[1];
if (status == 0x00) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) connected via proxy", this);
setProxyAuthState(0, "socks_connected");
adjustWriteOp();
} else {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) invalid proxy status on state 6, 0x%x", this, status);
}
} else {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) invalid proxy response on state 6", this);
}
} else if (proxyAuthState == 0) {
if (ConnectionsManager::getInstance(instanceNum).delegate != nullptr) {
ConnectionsManager::getInstance(instanceNum).delegate->onBytesReceived((int32_t) readCount, currentNetworkType, instanceNum);
}
if (tlsState != 0) {
while (buffer->hasRemaining()) {
size_t newBytesRead = buffer->remaining();
if (tlsBuffer != nullptr) {
newBytesRead += tlsBuffer->position();
if (tlsBufferSized) {
newBytesRead += 5;
}
}
if (newBytesRead >= 5) {
if (tlsBuffer == nullptr || !tlsBufferSized) {
uint32_t pos = buffer->position();
uint8_t offset = 0;
uint8_t header[5];
if (tlsBuffer != nullptr) {
offset = (uint8_t) tlsBuffer->position();
memcpy(header, tlsBuffer->bytes(), offset);
tlsBuffer->reuse();
tlsBuffer = nullptr;
}
memcpy(header + offset, buffer->bytes() + pos, (uint8_t) (5 - offset));
if (header[1] != 0x03 || header[2] != 0x03) {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS response version mismatch type=0x%02x version=0x%02x%02x", this, header[0], header[1], header[2]);
return;
}
uint32_t len1 = (header[3] << 8) + header[4];
if (header[0] == MT_PROXY_TLS_RECORD_APPLICATION_DATA && len1 == 0) {
buffer->position(pos + (5 - offset));
tlsBufferRecordType = 0;
if (LOGS_ENABLED) DEBUG_D("connection(%p) TLS response empty application data skipped", this);
continue;
}
if (header[0] == MT_PROXY_TLS_RECORD_CHANGE_CIPHER_SPEC && len1 != 1) {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS response CCS len invalid %u", this, len1);
return;
}
if (header[0] == MT_PROXY_TLS_RECORD_ALERT && len1 != 2) {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS response alert len invalid %u", this, len1);
return;
}
if (header[0] != MT_PROXY_TLS_RECORD_APPLICATION_DATA && header[0] != MT_PROXY_TLS_RECORD_CHANGE_CIPHER_SPEC && header[0] != MT_PROXY_TLS_RECORD_ALERT) {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS response record type mismatch 0x%02x", this, header[0]);
return;
}
if (len1 > 64 * 1024) {
closeSocket(1, -1);
if (LOGS_ENABLED) DEBUG_E("connection(%p) TLS response len1 invalid", this);
return;
} else {
tlsBuffer = BuffersStorage::getInstance().getFreeBuffer(len1);
tlsBufferRecordType = header[0];
tlsBufferSized = true;
buffer->position(pos + (5 - offset));
}
} else {
if (LOGS_ENABLED) DEBUG_D("connection(%p) TLS response new data %d", this, buffer->remaining());
}
buffer->limit(std::min(buffer->position() + tlsBuffer->remaining(), buffer->limit()));
tlsBuffer->writeBytes(buffer);
buffer->limit((uint32_t) readCount);
if (tlsBuffer->remaining() == 0) {
tlsBuffer->rewind();
if (tlsBufferRecordType == MT_PROXY_TLS_RECORD_APPLICATION_DATA) {
if (!mtproxyFirstTlsDataReceivedLogged) {
mtproxyFirstTlsDataReceivedLogged = true;
firstTransportPacketReceived = true;
dataPathProven = true;
mtproxyFirstTlsFrameSentTime = 0;
mtproxyFirstDataReceivedTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
proxyCheckDiagnostic = "dropped_after_appdata";
MtProxySocketObservation observation;
observation.phase = MtProxyPhase::FirstTlsAppRecv;
observation.reason = MtProxyPhase::FirstTlsAppRecv;
publishMtProxySocketObservation(observation);
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup first_tls_app_recv payload=%d", this, tlsBuffer->limit());
MtProxyEndpointRecorder::recordDataPathSuccess(mtProxyEndpointSuccessContext("first_tls_app_recv"), mtProxyEndpointRecorderCallbacks());
}
if (!canDeliverReceivedData("first_tls_app_recv")) {
closeSocket(1, -1);
return;
}
onReceivedData(tlsBuffer);
if (tlsBuffer == nullptr) {
return;
}
} else if (tlsBufferRecordType == MT_PROXY_TLS_RECORD_CHANGE_CIPHER_SPEC) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) TLS response ChangeCipherSpec skipped", this);
} else if (tlsBufferRecordType == MT_PROXY_TLS_RECORD_ALERT) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_disconnect tls_alert", this);
tlsBuffer->reuse();
tlsBuffer = nullptr;
tlsBufferRecordType = 0;
closeSocket(1, 0);
return;
}
tlsBuffer->reuse();
tlsBuffer = nullptr;
tlsBufferRecordType = 0;
} else {
if (LOGS_ENABLED) DEBUG_D("connection(%p) TLS response wait for more data, total size %d, left %d", this, tlsBuffer->limit(), tlsBuffer->remaining());
}
} else {
if (tlsBuffer == nullptr) {
tlsBuffer = BuffersStorage::getInstance().getFreeBuffer(4);
tlsBufferSized = false;
}
tlsBuffer->writeBytes(buffer);
if (LOGS_ENABLED) DEBUG_D("connection(%p) TLS response wait for more data, not enough bytes for header, total = %d", this, (int) tlsBuffer->position());
}
}
} else {
markMtProxyFirstPlainDataReceived((uint32_t) readCount);
if (!canDeliverReceivedData("first_mtproxy_packet_recv")) {
closeSocket(1, -1);
return;
}
onReceivedData(buffer);
}
}
} else if (readCount == 0) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup recv_eof proxy_state=%d tls_state=%d", this, (int) proxyAuthState, (int) tlsState);
if (proxyAuthState == 11 && proxyHandshakeClientHelloSentTime != 0 && bytesRead == 0) {
proxyCheckDiagnostic = "server_closed_after_client_hello";
} else if (proxyAuthState == 11 && proxyHandshakeClientHelloSentTime != 0 && bytesRead > 0) {
logMtProxyTlsAfterClientHello(bytesRead);
proxyCheckDiagnostic = classifyMtProxyPostClientHelloResponse(bytesRead);
}
closeSocket(1, 0);
return;
}
// if (readCount != READ_BUFFER_SIZE) {
// break;
// }
}
}
}
if (events & EPOLLOUT) {
int32_t error;
if (checkSocketError(&error) != 0) {
closeSocket(1, error);
return;
} else {
if (!mtproxySocketConnectedLogged && isCurrentMtProxyConnection()) {
setMtProxySocketConnectedLogged(true, "socket_connected");
proxyCheckDiagnostic = "tcp_connected_no_pong";
publishProxyConnectionStage("socket_connected");
mtProxyProbeLease.touch(ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis());
releaseMtProxyEndpointTcpConnect("socket_connected");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup socket_connected state=%d tls=%d secret_kind=%s", this, (int) proxyAuthState, (int) tlsState, currentSecretKind);
}
if (proxyAuthState != 0) {
if (proxyAuthState >= 10) {
if (proxyAuthState == 10) {
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
tlsHashMismatch = false;
serverHelloHmacMismatchObserved = false;
serverHelloHmacMismatchTime = 0;
setProxyAuthState(11, "client_hello_prepare");
setTransportState(TransportState::FaketlsHandshake, "client_hello_prepare");
const char *profileName = mtProxyTlsProfileName(currentEffectiveProxyTlsProfile);
TlsHello hello = selectMtProxyTlsHello(currentEffectiveProxyTlsProfile);
hello.setDomain(currentClientHelloSni);
uint32_t size = hello.writeToBuffer(tempBuffer->bytes);
if (!validateServerCompatibleHello(tempBuffer->bytes, size, currentSecretDomain, profileName)) {
closeSocket(1, -1);
return;
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup profile selected=%s id=%d mode=%s server_hello_parser=%s sni_variant=%s client_hello_sni=%s hello=%u", this, profileName, (int) normalizeMtProxyTlsProfile(currentEffectiveProxyTlsProfile), mtProxyTlsProfileName(currentProxyTlsProfile), mtProxyServerHelloParserName(currentServerHelloParserMode), MtProxyAdaptivePolicy::sniVariantName(currentRecipeSniVariant), currentClientHelloSni.empty() ? "none" : currentClientHelloSni.c_str(), size);
uint32_t outLength;
HMAC(EVP_sha256(), currentSecret.data(), currentSecret.size(), tempBuffer->bytes, size, tempBuffer->bytes + MT_PROXY_SERVER_FLIGHT_MAX_BYTES, &outLength);
int32_t currentTime = ConnectionsManager::getInstance(instanceNum).getCurrentTime();
int32_t old = ((int32_t *) (tempBuffer->bytes + MT_PROXY_SERVER_FLIGHT_MAX_BYTES + 28))[0];
((int32_t *) (tempBuffer->bytes + MT_PROXY_SERVER_FLIGHT_MAX_BYTES + 28))[0] = old ^ currentTime;
memcpy(tempBuffer->bytes + 11, tempBuffer->bytes + MT_PROXY_SERVER_FLIGHT_MAX_BYTES, 32);
logClientHelloFingerprint(this, profileName, tempBuffer->bytes, size);
bytesRead = 0;
if (!buildPendingClientHello(size)) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) ClientHello pending buffer build failed", this);
closeSocket(1, -1);
return;
}
}
if (proxyAuthState == 11 && pendingClientHello != nullptr) {
uint32_t size = pendingClientHelloSize;
if (!sendPendingClientHello()) {
return;
}
if (pendingClientHello != nullptr && pendingClientHelloOffset < pendingClientHelloSize) {
return;
}
clearPendingClientHello();
proxyCheckDiagnostic = MtProxyPhase::FaketlsServerHelloWaitTimeout;
publishProxyConnectionStage("client_hello_sent");
mtProxyProbeLease.touch(ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis());
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup client_hello_sent bytes=%u expected=%u domain_len=%d sni_variant=%s", this, size, size, (int) currentClientHelloSni.size(), MtProxyAdaptivePolicy::sniVariantName(currentRecipeSniVariant));
markProxyHandshakeClientHelloSent();
adjustWriteOp();
}
} else {
if (proxyAuthState == 1) {
if (!canSendSocksHandshakeFrame("socks_method_select", 1)) {
return;
}
if (!canSendRawSocketBytes("raw_socks_method_send")) {
return;
}
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
tempBuffer->bytes[0] = 0x05;
tempBuffer->bytes[1] = 0x02;
tempBuffer->bytes[2] = 0x00;
tempBuffer->bytes[3] = 0x02;
if (stateMachine.sendBytes(tempBuffer->bytes, 4, 0) < 0) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) send failed", this);
closeSocket(1, -1);
return;
}
setProxyAuthState(2, "socks_method_sent");
adjustWriteOp();
} else if (proxyAuthState == 3) {
if (!canSendSocksHandshakeFrame("socks_auth", 3)) {
return;
}
if (!canSendRawSocketBytes("raw_socks_auth_send")) {
return;
}
tempBuffer->bytes[0] = 0x01;
uint8_t len1 = (uint8_t) currentSocksUsername.length();
uint8_t len2 = (uint8_t) currentSocksPassword.length();
tempBuffer->bytes[1] = len1;
memcpy(tempBuffer->bytes + 2, currentSocksUsername.c_str(), len1);
tempBuffer->bytes[2 + len1] = len2;
memcpy(tempBuffer->bytes + 3 + len1, currentSocksPassword.c_str(), len2);
if (stateMachine.sendBytes(tempBuffer->bytes, 3 + len1 + len2, 0) < 0) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) send failed", this);
closeSocket(1, -1);
return;
}
setProxyAuthState(4, "socks_auth_sent");
adjustWriteOp();
} else if (proxyAuthState == 5) {
if (!canSendSocksHandshakeFrame("socks_connect", 5)) {
return;
}
if (!canSendRawSocketBytes("raw_socks_connect_send")) {
return;
}
tempBuffer->bytes[0] = 0x05;
tempBuffer->bytes[1] = 0x01;
tempBuffer->bytes[2] = 0x00;
tempBuffer->bytes[3] = (uint8_t) (isIpv6 ? 0x04 : 0x01);
uint16_t networkPort = ntohs(currentPort);
inet_pton(isIpv6 ? AF_INET6 : AF_INET, currentAddress.c_str(), tempBuffer->bytes + 4);
memcpy(tempBuffer->bytes + 4 + (isIpv6 ? 16 : 4), &networkPort, sizeof(uint16_t));
if (stateMachine.sendBytes(tempBuffer->bytes, 4 + (isIpv6 ? 16 : 4) + 2, 0) < 0) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) send failed", this);
closeSocket(1, -1);
return;
}
setProxyAuthState(6, "socks_connect_sent");
adjustWriteOp();
}
}
} else {
if (!onConnectedSent) {
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
setTransportState(TransportState::MtprotoReady, "on_connected");
publishProxyConnectionStage("on_connected");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup on_connected tls=%d", this, (int) tlsState);
if (!canNotifyConnected("on_connected")) {
return;
}
onConnected();
setConnectedNotified(true, "on_connected");
}
if (tlsState != 0 && pendingTlsFrame != nullptr) {
if (!sendPendingTlsFrame()) {
return;
}
if (pendingTlsFrame != nullptr) {
return;
}
}
if (tlsState != 0 && scheduleMtProxyDataTimingIfNeeded()) {
return;
}
NativeByteBuffer *buffer = ConnectionsManager::getInstance(instanceNum).networkBuffer;
buffer->clear();
outgoingByteStream->get(buffer);
buffer->flip();
uint32_t remaining = buffer->remaining();
if (remaining) {
ssize_t sentLength;
if (tlsState != 0) {
if (!buildPendingTlsFrame(buffer, remaining)) {
return;
}
if (!sendPendingTlsFrame()) {
return;
}
} else {
if (!canSendPlainMtProtoPayload()) {
return;
}
if (!canSendRawSocketBytes("raw_plain_mtproto_send")) {
return;
}
if ((sentLength = stateMachine.sendBytes(buffer->bytes(), remaining, 0)) < 0) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) send failed", this);
closeSocket(1, -1);
return;
} else {
if (ConnectionsManager::getInstance(instanceNum).delegate != nullptr) {
ConnectionsManager::getInstance(instanceNum).delegate->onBytesSent((int32_t) sentLength, currentNetworkType, instanceNum);
}
markMtProxyFirstPlainDataSent((uint32_t) sentLength);
outgoingByteStream->discard((uint32_t) sentLength);
adjustWriteOp();
}
}
}
}
}
}
if (events & EPOLLHUP) {
if (LOGS_ENABLED) DEBUG_E("socket event has EPOLLHUP");
closeSocket(1, -1);
return;
} else if (events & EPOLLRDHUP) {
if (LOGS_ENABLED) DEBUG_E("socket event has EPOLLRDHUP");
closeSocket(1, -1);
return;
}
if (events & EPOLLERR) {
int32_t error = -1;
checkSocketError(&error);
if (LOGS_ENABLED) DEBUG_E("connection(%p) epoll error code=%d", this, error);
closeSocket(1, error);
return;
}
}
void ConnectionSocket::writeBuffer(uint8_t *data, uint32_t size) {
if (!canQueueOutboundBuffer("writeBufferRaw")) {
return;
}
NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(size);
buffer->writeBytes(data, size);
outgoingByteStream->append(buffer);
queueAdjustWriteOpAfterOutboundAppend("writeBufferRaw");
}
void ConnectionSocket::writeBuffer(NativeByteBuffer *buffer) {
if (!canQueueOutboundBuffer("writeBuffer")) {
if (buffer != nullptr) {
buffer->reuse();
}
return;
}
outgoingByteStream->append(buffer);
queueAdjustWriteOpAfterOutboundAppend("writeBuffer");
}
void ConnectionSocket::noteWssPacketBoundary(uint32_t size) {
if (!isCurrentTransportWss() || size == 0) {
return;
}
outgoingWssPacketSizes.push_back(size);
}
void ConnectionSocket::queueAdjustWriteOpAfterOutboundAppend(const char *reason) {
if (!waitingForHostResolve.empty()) {
setAdjustWriteOpAfterResolve(true, reason);
return;
}
if (!epollRegistered || currentTransportState == TransportState::Prepared || currentTransportState == TransportState::WaitingGate || currentTransportState == TransportState::TcpConnecting) {
setAdjustWriteOpAfterPreTcpGate(true, reason);
return;
}
adjustWriteOp();
}
void ConnectionSocket::adjustWriteOp() {
if (!waitingForHostResolve.empty()) {
setAdjustWriteOpAfterResolve(true, "adjustWriteOp_waiting_resolve");
return;
}
if (!canModifyEpollWriteInterest("adjustWriteOp")) {
return;
}
eventMask.events = EPOLLIN | EPOLLRDHUP | EPOLLERR;
if (!isCurrentTransportWss()) {
eventMask.events |= EPOLLET;
}
bool hasPendingClientHello = pendingClientHello != nullptr && pendingClientHelloOffset < pendingClientHelloSize;
bool hasPendingTlsFrame = pendingTlsFrame != nullptr && pendingTlsFrameOffset < pendingTlsFrameSize;
if (isCurrentTransportWss()) {
const bool hasPendingWssWrite = currentWssTransport->wantsWrite();
const bool canWriteQueuedApplicationData = outgoingByteStream->hasData()
&& currentWssTransport->canWriteApplicationData();
if (hasPendingWssWrite || canWriteQueuedApplicationData) {
eventMask.events |= EPOLLOUT;
}
} else if ((proxyAuthState == 0 && (hasPendingTlsFrame || outgoingByteStream->hasData() || !onConnectedSent)) || proxyAuthState == 1 || proxyAuthState == 3 || proxyAuthState == 5 || proxyAuthState == 10 || (proxyAuthState == 11 && hasPendingClientHello)) {
eventMask.events |= EPOLLOUT;
}
eventMask.data.ptr = eventObject;
if (!stateMachine.epollCtlMod(ConnectionsManager::getInstance(instanceNum).epolFd)) {
if (LOGS_ENABLED) DEBUG_E("connection(%p) epoll_ctl, modify socket failed", this);
closeSocket(1, -1);
}
}
void ConnectionSocket::setTimeout(time_t time) {
timeout = time;
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (LOGS_ENABLED) DEBUG_D("connection(%p) set current timeout = %lld", this, (long long) timeout);
}
time_t ConnectionSocket::getTimeout() {
return timeout;
}
int32_t ConnectionSocket::getCurrentNetworkType() const {
return currentNetworkType;
}
int32_t ConnectionSocket::getDatacenterId() const {
return stateMachine.wss.datacenterId;
}
bool ConnectionSocket::checkTimeout(int64_t now) {
if (isCurrentDirectConnection()) {
if (timeout != 0 && (now - lastEventTime) > (int64_t) timeout * 1000) {
if (!onConnectedSent || hasPendingRequests()) {
closeSocket(2, 0);
return true;
}
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (LOGS_ENABLED) DEBUG_D("connection(%p) reset last event time, no requests", this);
}
return false;
}
if (isCurrentTransportWss()
&& !currentWssTransport->isReady()
&& wssOpenTime > 0) {
const bool tcpPending = currentWssTransport->handshakePhase() == tgnet::transport::HandshakePhase::None;
if (now - wssOpenTime > (tcpPending ? WSS_TCP_CONNECT_TIMEOUT_MS : WSS_HANDSHAKE_TIMEOUT_MS)) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) wss_startup wss_handshake_timeout elapsed=%lld tcp_pending=%d phase=%s", this, (long long) (now - wssOpenTime), tcpPending ? 1 : 0, proxyCheckDiagnostic.c_str());
currentWssTransport->timedOut();
closeSocket(2, 0);
return true;
}
}
if (isCurrentTransportWss()
&& currentWssTransport->isReady()
&& wssFirstFrameSentTime > 0
&& (webProxyStreamClass == WEB_PROXY_STREAM_CLASS_UPLOAD
|| outgoingByteStream->hasData()
|| currentWssTransport->queuedOutputBytes() > 0
|| socketUnsentBytes(currentWssTransport->fd()) > 0)) {
// WHY: кусок файла в 512 КБ на медленной отдаче или за VPN, который сам
// подтверждает TCP, уходит дольше сторожа, и отправка перезапускалась
// с нуля каждые 5,5 с, так и не завершившись; её держит таймаут соединения.
wssFirstFrameSentTime = now;
}
if (isCurrentTransportWss()
&& currentWssTransport->isReady()
&& transportAppDataUnanswered(now, wssFirstFrameSentTime > 0,
currentWssTransport->handshakePhase() != tgnet::transport::HandshakePhase::FirstDataReceived,
wssFirstFrameSentTime,
currentMediaConnection ? WSS_MEDIA_APPDATA_NO_RESPONSE_TIMEOUT_MS : WSS_APPDATA_NO_RESPONSE_TIMEOUT_MS)) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) wss_startup wss_appdata_no_response_timeout elapsed=%lld", this, (long long) (now - wssFirstFrameSentTime));
currentWssTransport->noteAppDataTimeout();
proxyCheckDiagnostic = "wss_appdata_no_response_timeout";
closeSocket(2, 0);
return true;
}
if (isCurrentMtProxyConnection()
&& currentSecretIsFakeTls
&& proxyCheckDiagnostic == MtProxyPhase::PostHandshakeNoAppdata
&& transportAppDataUnanswered(now, mtproxyFirstTlsFrameSentLogged,
!mtproxyFirstTlsDataReceivedLogged,
mtproxyFirstTlsFrameSentTime, MT_PROXY_TLS_APPDATA_NO_RESPONSE_TIMEOUT_MS)) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup mtproxy_tls_appdata_no_response_timeout elapsed=%lld", this, (long long) (now - mtproxyFirstTlsFrameSentTime));
MtProxySocketObservation observation;
observation.phase = MtProxyPhase::PostHandshakeNoAppdata;
observation.reason = "mtproxy_tls_appdata_no_response_timeout";
publishMtProxySocketObservation(observation);
closeSocket(2, 0);
return true;
}
// The WEB bridge is loopback into a shared carrier: a first reply queued
// behind other streams is not a DPI blackhole, so it is judged by the
// carrier in the receive timeout below instead of this 5.5 s probe.
if (isCurrentMtProxyConnection()
&& !currentSecretIsFakeTls
&& !isCurrentWebProxyBridge()
&& proxyCheckDiagnostic == "mtproxy_packet_sent_no_response"
&& transportAppDataUnanswered(now, mtproxyFirstPlainDataSentLogged,
!mtproxyFirstPlainDataReceivedLogged,
mtproxyFirstPlainDataSentTime, MT_PROXY_PLAIN_NO_RESPONSE_TIMEOUT_MS)) {
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup mtproxy_packet_no_response_timeout elapsed=%lld", this, (long long) (now - mtproxyFirstPlainDataSentTime));
publishProxyConnectionStage(proxyCheckDiagnostic.c_str());
closeSocket(2, 0);
return true;
}
if (isCurrentMtProxyConnection()) {
MtProxyStartupTimeoutDecision startupDecision = startupTimeline.timeoutDecision(now, mtproxySocketConnectedLogged);
if (startupDecision.active && !startupDecision.expired) {
return false;
}
if (startupDecision.expired) {
if (!onConnectedSent || hasPendingRequests()) {
proxyCheckDiagnostic = startupDecision.diagnostic != nullptr ? startupDecision.diagnostic : "connection_not_started";
if (LOGS_ENABLED) {
if (strcmp(proxyCheckDiagnostic.c_str(), MtProxyPhase::TcpConnectTimeout) == 0) {
DEBUG_D("connection(%p) mtproxy_startup tcp_connect_timeout elapsed_ms=%lld deadline_ms=%lld start_ms=%lld", this, (long long) startupDecision.elapsedMs, (long long) startupDecision.deadlineMs, (long long) startupDecision.startMs);
} else {
DEBUG_D("connection(%p) mtproxy_startup pre_tcp_timeout diagnostic=%s event=%s phase=%s elapsed_ms=%lld deadline_ms=%lld start_ms=%lld", this, proxyCheckDiagnostic.c_str(), startupDecision.event != nullptr ? startupDecision.event : "unknown", MtProxyStartupTimeline::phaseName(startupDecision.phase), (long long) startupDecision.elapsedMs, (long long) startupDecision.deadlineMs, (long long) startupDecision.startMs);
}
}
closeSocket(2, 0);
return true;
}
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (LOGS_ENABLED) DEBUG_D("connection(%p) reset last event time, no requests", this);
return false;
}
}
if (timeout != 0 && (now - lastEventTime) > (int64_t) timeout * 1000) {
if (!onConnectedSent || hasPendingRequests()) {
if (isCurrentWebProxyBridge() && deferWebProxyReceiveTimeout(now)) {
return false;
}
// A tunnel stuck in the middle of an answer larger than it can
// carry is not a broken tunnel: the file loader switches to small
// parts, and counting it would send the DC to direct TCP instead.
if (isCurrentTransportWss() && currentWssTransport != nullptr
&& !(currentWssRoute.tunnel && hasPartialIncomingPacket())) {
currentWssTransport->timedOut();
}
classifyMtProxyPreTcpTimeoutDiagnostic("checkTimeout");
closeSocket(2, 0);
return true;
} else {
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
if (LOGS_ENABLED) DEBUG_D("connection(%p) reset last event time, no requests", this);
}
}
return false;
}
bool ConnectionSocket::hasTlsHashMismatch() {
return tlsHashMismatch;
}
void ConnectionSocket::resetLastEventTime() {
lastEventTime = ConnectionsManager::getInstance(instanceNum).getCurrentTimeMonotonicMillis();
}
bool ConnectionSocket::isDisconnected() {
return socketFd < 0;
}
void ConnectionSocket::dropConnection() {
closeSocket(0, 0);
}
void ConnectionSocket::setMtProxyHandshakePriority(int32_t priority) {
if (priority < MT_PROXY_HANDSHAKE_PRIORITY_BYPASS || priority > MT_PROXY_HANDSHAKE_PRIORITY_PROXY_CHECK) {
priority = MT_PROXY_HANDSHAKE_PRIORITY_PROXY_CHECK;
}
proxyHandshakeAdmissionPriority = priority;
}
void ConnectionSocket::setOverrideProxy(std::string address, uint16_t port, std::string username, std::string password, std::string secret, const MtProxyOptions &options) {
overrideProxyAddress = address;
overrideProxyPort = port;
overrideProxyUser = username;
overrideProxyPassword = password;
overrideProxySecret = secret;
overrideMtProxyOptions = normalizeMtProxyOptions(options);
}
void ConnectionSocket::requestPendingHostResolve() {
#ifdef USE_DELEGATE_HOST_RESOLVE
if (waitingForHostResolve.empty()) {
return;
}
if (!canStartHostResolve()) {
closeSocket(1, -1);
return;
}
ConnectionsManager &manager = ConnectionsManager::getInstance(instanceNum);
if (manager.delegate == nullptr) {
bool cachedIpv6 = false;
bool blockedZeroAddress = false;
if (mtProxyEndpointUseCachedHostAddress(waitingForHostResolve, &cachedIpv6, &blockedZeroAddress)) {
setWaitingForHostResolve("", "host_resolve_cached_address");
openConnectionInternal(cachedIpv6);
return;
}
if (blockedZeroAddress) {
return;
}
proxyCheckDiagnostic = "connection_not_started";
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup host_resolve_not_started host=%s reason=no_delegate", this, waitingForHostResolve.c_str());
closeSocket(1, -1);
return;
}
std::string host = waitingForHostResolve;
if (manager.delegate->isHostResolveNegativeCached(host, instanceNum)) {
setWaitingForHostResolve("", "dns_negative_cache_hit");
setMtProxyDnsResolveAttemptStarted(false, "dns_negative_cache_hit");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "dns_negative_cache_hit");
proxyCheckDiagnostic = "dns_negative_cache_hit";
publishProxyConnectionStage(proxyCheckDiagnostic.c_str());
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup dns_negative_cache_hit host=%s key=%s", this, host.c_str(), proxyHandshakeAdmissionKey.c_str());
closeSocket(1, -1);
return;
}
publishProxyConnectionStage("host_resolve_start");
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup host_resolve_start admission_mode=%s connection_pattern=%s host=%s key=%s", this, mtProxyConnectionPatternModeName(currentConnectionPatternMode), mtProxyConnectionPatternModeName(currentConnectionPatternMode), host.c_str(), proxyHandshakeAdmissionKey.c_str());
setMtProxyDnsResolveAttemptStarted(true, "host_resolve_start");
setTransportState(TransportState::WaitingGate, "host_resolve_start");
manager.delegate->getHostByName(host, instanceNum, this);
#endif
}
void ConnectionSocket::onHostNameResolved(std::string host, std::string ip, bool ipv6) {
ConnectionsManager::getInstance(instanceNum).scheduleTask([&, host, ip, ipv6] {
checkHostResolveCallback(host);
if (waitingForHostResolve == host) {
setWaitingForHostResolve("", "host_resolve_callback");
setMtProxyDnsResolveAttemptStarted(false, "host_resolve_callback");
setMtProxyPreTcpWaitPhase(MtProxyStartupPhase::None, 0, "host_resolve_callback");
if (isCurrentTransportWss()) {
bool resolvedIpv6 = false;
if (ip.empty() || ip == "dns_negative_cache_hit" || ip == "dns_blocked_zero_address") {
proxyCheckDiagnostic = "host_resolve_failed";
currentWssTransport->timedOut();
closeSocket(1, -1);
return;
}
if (inet_pton(AF_INET, ip.c_str(), &socketAddress.sin_addr.s_addr) == 1) {
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(currentWssRoute.relayPort);
} else if (inet_pton(AF_INET6, ip.c_str(), &socketAddress6.sin6_addr.s6_addr) == 1) {
socketAddress6.sin6_family = AF_INET6;
socketAddress6.sin6_port = htons(currentWssRoute.relayPort);
resolvedIpv6 = true;
} else {
proxyCheckDiagnostic = "host_resolve_failed";
if (LOGS_ENABLED) DEBUG_E("connection(%p) can't resolve WSS host %s address via delegate", this, host.c_str());
currentWssTransport->timedOut();
closeSocket(1, -1);
return;
}
if (LOGS_ENABLED) DEBUG_D("connection(%p) resolved WSS host %s address %s via delegate", this, host.c_str(), ip.c_str());
openConnectionInternal(resolvedIpv6);
return;
}
if (ip == "dns_negative_cache_hit") {
proxyCheckDiagnostic = "dns_negative_cache_hit";
publishProxyConnectionStage(proxyCheckDiagnostic.c_str());
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup dns_negative_cache_hit host=%s callback=1", this, host.c_str());
closeSocket(1, -1);
return;
}
if (ip == "dns_blocked_zero_address" || mtProxyIsBlockedZeroAddress(ip)) {
closeMtProxyDnsBlockedZeroAddress(host, ip, "host_resolve_callback");
return;
}
if (ip.empty() || inet_pton(AF_INET, ip.c_str(), &socketAddress.sin_addr.s_addr) != 1) {
bool cachedIpv6 = ipv6;
bool blockedZeroAddress = false;
if (mtProxyEndpointUseCachedHostAddress(host, &cachedIpv6, &blockedZeroAddress)) {
openConnectionInternal(cachedIpv6);
return;
}
if (blockedZeroAddress) {
return;
}
proxyCheckDiagnostic = "host_resolve_failed";
publishProxyConnectionStage(proxyCheckDiagnostic.c_str());
if (LOGS_ENABLED) DEBUG_D("connection(%p) mtproxy_startup host_resolve_failed host=%s ip=%s", this, host.c_str(), ip.c_str());
if (LOGS_ENABLED) DEBUG_E("connection(%p) can't resolve host %s address via delegate", this, host.c_str());
closeSocket(1, -1);
return;
}
mtProxyEndpointStoreResolvedAddress(host, ip);
if (LOGS_ENABLED) DEBUG_D("connection(%p) resolved host %s address %s via delegate", this, host.c_str(), ip.c_str());
openConnectionInternal(ipv6);
}
});
}