Move MTProxy policy engine from tgnet/ to self-contained jni/mtproxy/ module. Consolidate reconnect hold computation into single MtProxyRetryAuthority owner (was split across Connection, ConnectionSocket, endpoint policy, and probe coordinator, causing hold misalignment bugs). Add MtProxyTerminalDiagnostic module with host tests for pre-I/O verdict preservation (fixes 02.07/30.06 clobber livelocks). Auto-generate phase classification (isPreIoTerminalVerdict, needsReconnectBackoff, isObservationFacadePhase, isLocalSchedulerTimeout) from single Python contract into both C++ and Java to eliminate parallel maintenance. Pass native hold (coordinator terminal hold + endpoint cooldown) via JNI to Java so reconnect timer and health store use THE clock, not re-derived ones. Optimize log flushing: batch debug lines, flush errors immediately. Add module boundary guard, host build system (MSVC), and unit tests for retry logic and terminal diagnostic derivation.
253 lines
11 KiB
C++
253 lines
11 KiB
C++
#include "MtProxyServerFlightParser.h"
|
|
|
|
#include "MtProxyOptions.h"
|
|
|
|
#include <cstring>
|
|
#include <openssl/hmac.h>
|
|
#include <vector>
|
|
|
|
static constexpr size_t MT_PROXY_TLS_DIGEST_SIZE = 32;
|
|
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 constexpr uint8_t MT_PROXY_TLS_RECORD_HANDSHAKE = 0x16;
|
|
|
|
static bool mtProxyTlsVersionLooksValid(const uint8_t *data, size_t size) {
|
|
return size >= 3 && data[1] == 0x03 && data[2] <= 0x04;
|
|
}
|
|
|
|
const char *mtProxyServerHelloParserName(int32_t parserMode) {
|
|
switch (normalizeMtProxyServerHelloParserOption(parserMode)) {
|
|
case MT_PROXY_SERVER_HELLO_PARSER_TLS_ALERT_EXACT_DESC:
|
|
return "tolerate_tls_alert_exact_desc";
|
|
case MT_PROXY_SERVER_HELLO_PARSER_FRAGMENTED_SERVER_HELLO:
|
|
return "tolerate_fragmented_server_hello";
|
|
case MT_PROXY_SERVER_HELLO_PARSER_CCS_TICKET_ORDERING:
|
|
return "tolerate_ccs_ticket_ordering";
|
|
case MT_PROXY_SERVER_HELLO_PARSER_EXTRA_RECORDS:
|
|
return "tolerate_extra_records_before_server_hello";
|
|
case MT_PROXY_SERVER_HELLO_PARSER_LENIENT_RECORD:
|
|
return "lenient_record_parser";
|
|
case MT_PROXY_SERVER_HELLO_PARSER_RESERVED:
|
|
return "reserved_hmac_parser";
|
|
case MT_PROXY_SERVER_HELLO_PARSER_STANDARD:
|
|
default:
|
|
return "standard_hmac_parser";
|
|
}
|
|
}
|
|
|
|
MtProxyServerFlightRecordInfo mtProxyServerFlightReadRecordInfo(const uint8_t *data, size_t size) {
|
|
MtProxyServerFlightRecordInfo result;
|
|
if (data == nullptr || size == 0) {
|
|
return result;
|
|
}
|
|
result.recordType = data[0];
|
|
if (size >= 2) {
|
|
result.tlsMajor = data[1];
|
|
}
|
|
if (size >= 3) {
|
|
result.tlsMinor = data[2];
|
|
}
|
|
if (size >= 5) {
|
|
result.recordLength = (int32_t) (((uint32_t) data[3] << 8) + data[4]);
|
|
}
|
|
if (mtProxyServerFlightLooksLikeTlsAlert(data, size)) {
|
|
result.alertLevel = data[5];
|
|
result.alertDescription = data[6];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool mtProxyServerFlightLooksLikeTlsAlert(const uint8_t *data, size_t size) {
|
|
if (data == nullptr || size < 5 || data[0] != MT_PROXY_TLS_RECORD_ALERT || !mtProxyTlsVersionLooksValid(data, size)) {
|
|
return false;
|
|
}
|
|
size_t recordLength = ((size_t) data[3] << 8) + data[4];
|
|
return recordLength == 2 && size >= 7;
|
|
}
|
|
|
|
bool mtProxyServerFlightHandshakeRecordNeedsMoreBytes(const uint8_t *data, size_t size) {
|
|
if (data == nullptr || size < 5 || data[0] != MT_PROXY_TLS_RECORD_HANDSHAKE || !mtProxyTlsVersionLooksValid(data, size)) {
|
|
return false;
|
|
}
|
|
size_t recordLength = ((size_t) data[3] << 8) + data[4];
|
|
return size < recordLength + 5;
|
|
}
|
|
|
|
static bool mtProxyVerifyServerHelloHmac(const std::string &secret, const uint8_t *clientRandom, const uint8_t *responseBytes, size_t responseSize) {
|
|
if (responseSize < 43 || responseSize > MT_PROXY_SERVER_FLIGHT_MAX_BYTES) {
|
|
return false;
|
|
}
|
|
std::vector<uint8_t> hmacInput(MT_PROXY_TLS_DIGEST_SIZE + responseSize);
|
|
memcpy(hmacInput.data(), clientRandom, MT_PROXY_TLS_DIGEST_SIZE);
|
|
memcpy(hmacInput.data() + MT_PROXY_TLS_DIGEST_SIZE, responseBytes, responseSize);
|
|
memset(hmacInput.data() + MT_PROXY_TLS_DIGEST_SIZE + 11, 0, MT_PROXY_TLS_DIGEST_SIZE);
|
|
|
|
uint8_t digest[MT_PROXY_TLS_DIGEST_SIZE];
|
|
uint32_t outLength = 0;
|
|
HMAC(EVP_sha256(), secret.data(), secret.size(), hmacInput.data(), hmacInput.size(), digest, &outLength);
|
|
return outLength == MT_PROXY_TLS_DIGEST_SIZE && memcmp(digest, responseBytes + 11, MT_PROXY_TLS_DIGEST_SIZE) == 0;
|
|
}
|
|
|
|
MtProxyServerFlightParseResult mtProxyParseServerHelloFlight(const std::string &secret, const uint8_t *clientRandom, const uint8_t *data, size_t size, int32_t parserMode) {
|
|
MtProxyServerFlightParseResult result;
|
|
if (data == nullptr || size < 5) {
|
|
result.waitMore = true;
|
|
result.reason = "server_hello_header_wait";
|
|
return result;
|
|
}
|
|
int32_t normalizedParser = normalizeMtProxyServerHelloParserOption(parserMode);
|
|
bool allowLeadingRecords = normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_LENIENT_RECORD
|
|
|| normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_EXTRA_RECORDS
|
|
|| normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_CCS_TICKET_ORDERING
|
|
|| normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_FRAGMENTED_SERVER_HELLO
|
|
|| normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_TLS_ALERT_EXACT_DESC;
|
|
size_t skippedBytes = 0;
|
|
if (allowLeadingRecords) {
|
|
while (skippedBytes + 5 <= size && data[skippedBytes] != MT_PROXY_TLS_RECORD_HANDSHAKE) {
|
|
uint8_t recordType = data[skippedBytes];
|
|
if (data[skippedBytes + 1] != 0x03) {
|
|
break;
|
|
}
|
|
size_t recordLen = ((size_t) data[skippedBytes + 3] << 8) + data[skippedBytes + 4];
|
|
size_t recordEnd = skippedBytes + 5 + recordLen;
|
|
if (recordEnd > MT_PROXY_SERVER_FLIGHT_MAX_BYTES) {
|
|
result.invalid = true;
|
|
result.candidateBytes = skippedBytes;
|
|
result.reason = "leading_record_len_invalid";
|
|
return result;
|
|
}
|
|
if (size < recordEnd) {
|
|
result.waitMore = true;
|
|
result.candidateBytes = recordEnd;
|
|
result.reason = "leading_record_wait";
|
|
return result;
|
|
}
|
|
if (recordType == MT_PROXY_TLS_RECORD_ALERT) {
|
|
result.invalid = true;
|
|
result.candidateBytes = recordEnd;
|
|
result.reason = normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_TLS_ALERT_EXACT_DESC
|
|
? "tls_alert_exact_desc"
|
|
: "tls_alert_before_server_hello";
|
|
return result;
|
|
}
|
|
bool skipRecord = recordType == MT_PROXY_TLS_RECORD_CHANGE_CIPHER_SPEC
|
|
|| (recordType == MT_PROXY_TLS_RECORD_APPLICATION_DATA
|
|
&& normalizedParser != MT_PROXY_SERVER_HELLO_PARSER_CCS_TICKET_ORDERING);
|
|
if (!skipRecord) {
|
|
break;
|
|
}
|
|
skippedBytes = recordEnd;
|
|
}
|
|
if (skippedBytes > 0) {
|
|
if (size - skippedBytes < 5) {
|
|
result.waitMore = true;
|
|
result.candidateBytes = skippedBytes + 5;
|
|
result.reason = "server_hello_header_wait";
|
|
return result;
|
|
}
|
|
data += skippedBytes;
|
|
size -= skippedBytes;
|
|
}
|
|
}
|
|
if (data[0] != MT_PROXY_TLS_RECORD_HANDSHAKE || data[1] != 0x03 || data[2] != 0x03) {
|
|
result.invalid = true;
|
|
result.candidateBytes = skippedBytes;
|
|
result.reason = normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_LENIENT_RECORD
|
|
? "lenient_hello1_mismatch"
|
|
: "hello1_mismatch";
|
|
return result;
|
|
}
|
|
size_t len1 = ((size_t) data[3] << 8) + data[4];
|
|
if (len1 > MT_PROXY_SERVER_FLIGHT_MAX_BYTES - 16) {
|
|
result.invalid = true;
|
|
result.reason = "len1_invalid";
|
|
return result;
|
|
}
|
|
size_t firstRecordEnd = len1 + 5;
|
|
if (size < firstRecordEnd) {
|
|
result.waitMore = true;
|
|
result.candidateBytes = skippedBytes + firstRecordEnd;
|
|
result.reason = normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_FRAGMENTED_SERVER_HELLO
|
|
? "fragmented_server_hello_wait"
|
|
: "server_hello_wait_first_record";
|
|
return result;
|
|
}
|
|
if (size < firstRecordEnd + 11) {
|
|
result.waitMore = true;
|
|
result.candidateBytes = skippedBytes + firstRecordEnd + 11;
|
|
result.reason = "server_hello_wait_standard_header";
|
|
return result;
|
|
}
|
|
static const uint8_t hello2[] = {0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 0x17, 0x03, 0x03};
|
|
if (std::memcmp(hello2, data + firstRecordEnd, sizeof(hello2)) != 0) {
|
|
result.invalid = true;
|
|
result.candidateBytes = skippedBytes + firstRecordEnd;
|
|
result.reason = normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_CCS_TICKET_ORDERING
|
|
? "ccs_ticket_ordering_mismatch"
|
|
: "hello2_mismatch";
|
|
return result;
|
|
}
|
|
size_t len2 = ((size_t) data[firstRecordEnd + 9] << 8) + data[firstRecordEnd + 10];
|
|
if (len2 > MT_PROXY_SERVER_FLIGHT_MAX_BYTES - len1 - 5 - 11) {
|
|
result.invalid = true;
|
|
result.reason = "len2_invalid";
|
|
return result;
|
|
}
|
|
size_t candidateBytes = len2 + len1 + 5 + 11;
|
|
result.candidateBytes = skippedBytes + candidateBytes;
|
|
if (size < candidateBytes) {
|
|
result.waitMore = true;
|
|
result.reason = normalizedParser == MT_PROXY_SERVER_HELLO_PARSER_FRAGMENTED_SERVER_HELLO
|
|
? "fragmented_server_hello_data_wait"
|
|
: "server_hello_wait_standard_data";
|
|
return result;
|
|
}
|
|
size_t appFlightBytes = len2;
|
|
while (candidateBytes <= size) {
|
|
if (mtProxyVerifyServerHelloHmac(secret, clientRandom, data, candidateBytes)) {
|
|
result.matched = true;
|
|
result.matchedBytes = skippedBytes + candidateBytes;
|
|
result.appFlightBytes = appFlightBytes;
|
|
result.candidateBytes = skippedBytes + candidateBytes;
|
|
result.reason = skippedBytes > 0 ? "lenient_hmac_ok" : "standard_hmac_ok";
|
|
return result;
|
|
}
|
|
if (candidateBytes == size) {
|
|
break;
|
|
}
|
|
if (size - candidateBytes < 5) {
|
|
result.waitMore = true;
|
|
result.candidateBytes = skippedBytes + candidateBytes;
|
|
result.reason = "server_hello_tail_header_wait";
|
|
return result;
|
|
}
|
|
static const uint8_t appHeader[] = {0x17, 0x03, 0x03};
|
|
if (std::memcmp(appHeader, data + candidateBytes, sizeof(appHeader)) != 0) {
|
|
result.invalid = true;
|
|
result.candidateBytes = skippedBytes + candidateBytes;
|
|
result.reason = "server_hello_tail_mismatch";
|
|
return result;
|
|
}
|
|
size_t nextLen = ((size_t) data[candidateBytes + 3] << 8) + data[candidateBytes + 4];
|
|
if (nextLen > MT_PROXY_SERVER_FLIGHT_MAX_BYTES - candidateBytes - 5) {
|
|
result.invalid = true;
|
|
result.candidateBytes = skippedBytes + candidateBytes;
|
|
result.reason = "server_hello_tail_len_invalid";
|
|
return result;
|
|
}
|
|
if (size < candidateBytes + 5 + nextLen) {
|
|
result.waitMore = true;
|
|
result.candidateBytes = skippedBytes + candidateBytes;
|
|
result.reason = "server_hello_tail_data_wait";
|
|
return result;
|
|
}
|
|
candidateBytes += 5 + nextLen;
|
|
appFlightBytes += 5 + nextLen;
|
|
result.candidateBytes = candidateBytes;
|
|
}
|
|
result.reason = MtProxyPhase::ServerHelloHmacMismatch;
|
|
result.candidateBytes = skippedBytes + candidateBytes;
|
|
result.appFlightBytes = appFlightBytes;
|
|
return result;
|
|
}
|