ZaStoGram/TMessagesProj/jni/mtproxy/MtProxyAdaptivePolicy.cpp
2026-08-05 19:18:49 +03:00

678 lines
30 KiB
C++

/*
* This is the source code of tgnet library v. 1.1
* It is licensed under GNU GPL v. 2 or later.
*/
#include "MtProxyAdaptivePolicy.h"
#include "MtProxyClientHelloPolicy.h"
#include "MtProxyServerFlightParser.h"
#include <map>
#include <openssl/rand.h>
#include <pthread.h>
#include <vector>
struct MtProxyTlsAutoProfileState {
int32_t profileIndex = -1;
uint32_t failures = 0;
};
static pthread_mutex_t mtProxyTlsAutoProfilesMutex = PTHREAD_MUTEX_INITIALIZER;
static std::map<std::string, MtProxyTlsAutoProfileState> tlsAutoRotateProfiles;
static constexpr int32_t MT_PROXY_ALTERNATE_PROFILE_COUNT = 4;
static uint64_t tlsAutoRotateSalt() {
static uint64_t salt = 0;
if (salt == 0) {
RAND_bytes((uint8_t *) &salt, sizeof(salt));
if (salt == 0) {
salt = 0x9e3779b97f4a7c15ULL;
}
}
return salt;
}
static uint64_t profileHash(uint64_t hash, const std::string &value) {
for (char c : value) {
hash ^= (uint8_t) c;
hash *= 0x100000001b3ULL;
}
return hash;
}
static int32_t autoRotatePoolProfile(int32_t index) {
static const int32_t profiles[] = {
MT_PROXY_TLS_PROFILE_YANDEX,
MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID,
MT_PROXY_TLS_PROFILE_FIREFOX,
MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP,
};
int32_t normalizedIndex = index % MT_PROXY_ALTERNATE_PROFILE_COUNT;
if (normalizedIndex < 0) {
normalizedIndex += MT_PROXY_ALTERNATE_PROFILE_COUNT;
}
return profiles[normalizedIndex];
}
static int32_t autoRotateInitialIndex(const std::string &key) {
uint64_t hash = 0xcbf29ce484222325ULL ^ tlsAutoRotateSalt();
hash = profileHash(hash, key);
return (int32_t) (hash % MT_PROXY_ALTERNATE_PROFILE_COUNT);
}
static const char *adaptiveTlsProfileName(int32_t profile) {
switch (normalizeMtProxyTlsProfileOption(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";
}
}
bool MtProxyAdaptivePolicy::profileUsesGrease(int32_t profile) {
switch (normalizeMtProxyTlsProfileOption(profile)) {
case MT_PROXY_TLS_PROFILE_CHROME_MODERN:
case MT_PROXY_TLS_PROFILE_FIREFOX:
case MT_PROXY_TLS_PROFILE_ANDROID_CHROME:
case MT_PROXY_TLS_PROFILE_YANDEX:
case MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP:
return true;
default:
return false;
}
}
static bool profileUsesModernExtensions(int32_t profile) {
switch (normalizeMtProxyTlsProfileOption(profile)) {
case MT_PROXY_TLS_PROFILE_CHROME_MODERN:
case MT_PROXY_TLS_PROFILE_FIREFOX:
case MT_PROXY_TLS_PROFILE_ANDROID_CHROME:
case MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID:
case MT_PROXY_TLS_PROFILE_YANDEX:
return true;
default:
return false;
}
}
static int32_t alternateCompatibilityTlsProfile(int32_t alternateProfileIndex) {
static const int32_t profiles[] = {
MT_PROXY_TLS_PROFILE_YANDEX,
MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID,
MT_PROXY_TLS_PROFILE_FIREFOX,
MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP,
};
int32_t normalizedIndex = alternateProfileIndex % MT_PROXY_ALTERNATE_PROFILE_COUNT;
if (normalizedIndex < 0) {
normalizedIndex += MT_PROXY_ALTERNATE_PROFILE_COUNT;
}
return profiles[normalizedIndex];
}
static bool serverHelloParserVariantAllowed(MtProxyRecoveryAction action) {
return action.kind == MtProxyRecoveryActionKind::AdvanceParserAllowed;
}
uint32_t MtProxyAdaptivePolicy::sniVariantMask(int32_t variant) {
if (variant < 0 || variant >= SNI_VARIANT_COUNT) {
return 0;
}
return 1U << (uint32_t) variant;
}
const char *MtProxyAdaptivePolicy::clientHelloFamilyName(int32_t family) {
switch (family) {
case CLIENT_HELLO_CHROME_MODERN_SOFT_FRAGMENT:
return "yandex_soft_fragment";
case CLIENT_HELLO_CHROME_MODERN_NO_FRAGMENT:
return "tdesktop_yandex_no_fragment";
case CLIENT_HELLO_ANDROID_CHROME_NO_FRAGMENT:
return "firefox_android_no_fragment";
case CLIENT_HELLO_FIREFOX_ANDROID_NO_FRAGMENT:
return "firefox_no_fragment";
case CLIENT_HELLO_LEGACY_NO_GREASE_NO_MODERN_EXTENSIONS:
return "legacy_no_grease_no_modern_extensions";
case CLIENT_HELLO_LEGACY_TLS12_MINIMAL:
return "android_okhttp_no_fragment";
default:
return "tdesktop_yandex_no_fragment";
}
}
const char *MtProxyAdaptivePolicy::sniVariantName(int32_t variant) {
switch (variant) {
case SNI_ORIGINAL:
return "original_sni";
case SNI_SANITIZED:
return "sanitized_sni";
case SNI_LOWERCASE_ASCII:
return "lowercase_ascii_sni";
case SNI_NO_TRAILING_DOT:
return "no_trailing_dot_sni";
case SNI_PUNYCODE:
return "punycode_sni";
case SNI_OPTIONAL_NO_SNI:
return "optional_no_sni";
default:
return "original_sni";
}
}
const char *MtProxyAdaptivePolicy::parserVariantName(int32_t parserVariant) {
switch (parserVariant) {
case PARSER_STANDARD_HMAC:
return "standard_hmac_parser";
case PARSER_LENIENT_RECORD:
return "lenient_record_parser";
case PARSER_TOLERATE_EXTRA_RECORDS_BEFORE_SERVER_HELLO:
return "tolerate_extra_records_before_server_hello";
case PARSER_TOLERATE_CCS_TICKET_ORDERING:
return "tolerate_ccs_ticket_ordering";
case PARSER_TOLERATE_FRAGMENTED_SERVER_HELLO:
return "tolerate_fragmented_server_hello";
case PARSER_TOLERATE_TLS_ALERT_EXACT_DESC:
return "tolerate_tls_alert_exact_desc";
default:
return "standard_hmac_parser";
}
}
const char *MtProxyAdaptivePolicy::classicVariantName(int32_t classicVariant) {
switch (classicVariant) {
case CLASSIC_STANDARD_INTERMEDIATE:
return "standard_intermediate";
case CLASSIC_RANDOMIZED_INTERMEDIATE:
return "randomized_intermediate";
case CLASSIC_ABRIDGED_FALLBACK:
return "abridged_fallback";
case CLASSIC_INTERMEDIATE_FALLBACK:
return "intermediate_fallback";
case CLASSIC_NONE:
default:
return "none";
}
}
static int32_t parserModeForVariant(int32_t parserVariant) {
switch (parserVariant) {
case MtProxyAdaptivePolicy::PARSER_LENIENT_RECORD:
return MT_PROXY_SERVER_HELLO_PARSER_LENIENT_RECORD;
case MtProxyAdaptivePolicy::PARSER_TOLERATE_EXTRA_RECORDS_BEFORE_SERVER_HELLO:
return MT_PROXY_SERVER_HELLO_PARSER_EXTRA_RECORDS;
case MtProxyAdaptivePolicy::PARSER_TOLERATE_CCS_TICKET_ORDERING:
return MT_PROXY_SERVER_HELLO_PARSER_CCS_TICKET_ORDERING;
case MtProxyAdaptivePolicy::PARSER_TOLERATE_FRAGMENTED_SERVER_HELLO:
return MT_PROXY_SERVER_HELLO_PARSER_FRAGMENTED_SERVER_HELLO;
case MtProxyAdaptivePolicy::PARSER_TOLERATE_TLS_ALERT_EXACT_DESC:
return MT_PROXY_SERVER_HELLO_PARSER_TLS_ALERT_EXACT_DESC;
case MtProxyAdaptivePolicy::PARSER_STANDARD_HMAC:
default:
return MT_PROXY_SERVER_HELLO_PARSER_STANDARD;
}
}
static bool sniVariantAllowed(uint32_t mask, int32_t variant) {
return (mask & MtProxyAdaptivePolicy::sniVariantMask(variant)) != 0;
}
static std::vector<MtProxyAdaptivePolicy::RecipeCursor> buildRecipeCursorLadder(uint32_t allowedSniVariants, bool classicFallbackAllowed, MtProxyRecoveryAction action) {
std::vector<MtProxyAdaptivePolicy::RecipeCursor> ladder;
if (allowedSniVariants == 0) {
allowedSniVariants = MtProxyAdaptivePolicy::sniVariantMask(MtProxyAdaptivePolicy::SNI_ORIGINAL);
}
int32_t parserLimit = serverHelloParserVariantAllowed(action)
? MtProxyAdaptivePolicy::PARSER_VARIANT_COUNT
: MtProxyAdaptivePolicy::PARSER_STANDARD_HMAC + 1;
for (int32_t sniVariant = MtProxyAdaptivePolicy::SNI_ORIGINAL; sniVariant <= MtProxyAdaptivePolicy::SNI_PUNYCODE; sniVariant++) {
if (!sniVariantAllowed(allowedSniVariants, sniVariant)) {
continue;
}
for (int32_t family = 0; family < MtProxyAdaptivePolicy::CLIENT_HELLO_FAMILY_COUNT; family++) {
for (int32_t parser = 0; parser < parserLimit; parser++) {
MtProxyAdaptivePolicy::RecipeCursor cursor;
cursor.family = family;
cursor.sniVariant = sniVariant;
cursor.parserVariant = parser;
cursor.classicVariant = MtProxyAdaptivePolicy::CLASSIC_NONE;
ladder.push_back(cursor);
}
}
}
if (sniVariantAllowed(allowedSniVariants, MtProxyAdaptivePolicy::SNI_OPTIONAL_NO_SNI)) {
for (int32_t family = 0; family < MtProxyAdaptivePolicy::CLIENT_HELLO_FAMILY_COUNT; family++) {
for (int32_t parser = 0; parser < parserLimit; parser++) {
MtProxyAdaptivePolicy::RecipeCursor cursor;
cursor.family = family;
cursor.sniVariant = MtProxyAdaptivePolicy::SNI_OPTIONAL_NO_SNI;
cursor.parserVariant = parser;
cursor.classicVariant = MtProxyAdaptivePolicy::CLASSIC_NONE;
ladder.push_back(cursor);
}
}
}
if (classicFallbackAllowed) {
for (int32_t classicVariant = MtProxyAdaptivePolicy::CLASSIC_STANDARD_INTERMEDIATE; classicVariant < MtProxyAdaptivePolicy::CLASSIC_VARIANT_COUNT; classicVariant++) {
MtProxyAdaptivePolicy::RecipeCursor cursor;
cursor.classicVariant = classicVariant;
cursor.sniVariant = MtProxyAdaptivePolicy::SNI_OPTIONAL_NO_SNI;
ladder.push_back(cursor);
}
}
return ladder;
}
MtProxyAdaptivePolicy::RecipeCursor MtProxyAdaptivePolicy::initialCursor(uint32_t allowedSniVariants) {
MtProxyRecoveryAction action;
action.kind = MtProxyRecoveryActionKind::AdvanceClientHelloOnly;
std::vector<RecipeCursor> ladder = buildRecipeCursorLadder(allowedSniVariants, false, action);
for (const RecipeCursor &candidate : ladder) {
if (candidate.family == CLIENT_HELLO_CHROME_MODERN_NO_FRAGMENT) {
return candidate;
}
}
if (!ladder.empty()) {
return ladder.front();
}
RecipeCursor cursor;
cursor.sniVariant = SNI_ORIGINAL;
return cursor;
}
bool MtProxyAdaptivePolicy::nextCursorForRecovery(RecipeCursor *cursor, MtProxyRecoveryAction action, uint32_t allowedSniVariants, bool classicFallbackAllowed) {
if (cursor == nullptr || !mtProxyRecoveryActionAdvancesRecipe(action)) {
return false;
}
std::vector<RecipeCursor> ladder = buildRecipeCursorLadder(allowedSniVariants, classicFallbackAllowed, action);
if (ladder.empty()) {
return false;
}
for (size_t i = 0; i < ladder.size(); i++) {
const RecipeCursor &candidate = ladder[i];
if (candidate.family == cursor->family
&& candidate.sniVariant == cursor->sniVariant
&& candidate.parserVariant == cursor->parserVariant
&& candidate.classicVariant == cursor->classicVariant) {
if (i + 1 >= ladder.size()) {
return false;
}
uint32_t nextGeneration = cursor->generation + 1;
*cursor = ladder[i + 1];
cursor->generation = nextGeneration;
return true;
}
}
*cursor = ladder.front();
cursor->generation++;
return true;
}
bool MtProxyAdaptivePolicy::nextCursor(RecipeCursor *cursor, const std::string &diagnostic, uint32_t allowedSniVariants, bool classicFallbackAllowed) {
MtProxyRecoveryAction action = mtProxyRecoveryActionForPhase(diagnostic, 0);
return nextCursorForRecovery(cursor, action, allowedSniVariants, classicFallbackAllowed);
}
static int32_t greaseProbeTlsProfile(int32_t configuredProfile, int32_t effectiveProfile) {
configuredProfile = mtProxyEffectiveWireTlsProfile(configuredProfile);
effectiveProfile = mtProxyEffectiveWireTlsProfile(effectiveProfile);
if (MtProxyAdaptivePolicy::profileUsesGrease(configuredProfile)) {
return configuredProfile;
}
if (MtProxyAdaptivePolicy::profileUsesGrease(effectiveProfile)) {
return effectiveProfile;
}
return mtProxyDefaultTlsProfile();
}
static std::string sniForVariant(const MtProxyAdaptivePolicy::RecipeInput &input, int32_t variant) {
switch (variant) {
case MtProxyAdaptivePolicy::SNI_ORIGINAL:
return input.originalSni.empty() ? input.sni : input.originalSni;
case MtProxyAdaptivePolicy::SNI_SANITIZED:
return input.sanitizedSni.empty() ? input.sni : input.sanitizedSni;
case MtProxyAdaptivePolicy::SNI_LOWERCASE_ASCII:
return input.lowercaseAsciiSni.empty() ? input.sni : input.lowercaseAsciiSni;
case MtProxyAdaptivePolicy::SNI_NO_TRAILING_DOT:
return input.noTrailingDotSni.empty() ? input.sni : input.noTrailingDotSni;
case MtProxyAdaptivePolicy::SNI_PUNYCODE:
return input.punycodeSni.empty() ? input.sni : input.punycodeSni;
case MtProxyAdaptivePolicy::SNI_OPTIONAL_NO_SNI:
return "";
default:
return input.sni;
}
}
MtProxyAdaptivePolicy::CompatibilityRecipe MtProxyAdaptivePolicy::recipeForCursor(const RecipeInput &input, const RecipeCursor &cursor) {
CompatibilityRecipe recipe;
recipe.cursor = cursor;
recipe.familyName = clientHelloFamilyName(cursor.family);
recipe.sniVariantName = sniVariantName(cursor.sniVariant);
recipe.parserVariantName = parserVariantName(cursor.parserVariant);
recipe.classicVariantName = classicVariantName(cursor.classicVariant);
recipe.clientHelloSni = sniForVariant(input, cursor.sniVariant);
recipe.experimentalNoSni = cursor.sniVariant == SNI_OPTIONAL_NO_SNI;
recipe.connectionPatternMode = normalizeMtProxyConnectionPatternOption(input.connectionPatternMode);
recipe.recordSizingMode = normalizeMtProxyRecordSizingOption(input.recordSizingMode);
recipe.timingMode = normalizeMtProxyTimingOption(input.timingMode);
recipe.startupCoverMode = normalizeMtProxyStartupCoverOption(input.startupCoverMode);
recipe.serverHelloParserMode = parserModeForVariant(cursor.parserVariant);
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
recipe.effectiveTlsProfile = mtProxyDefaultTlsProfile();
if (cursor.classicVariant != CLASSIC_NONE) {
recipe.transportMode = "classic_obfuscated";
recipe.effectiveTlsProfile = normalizeMtProxyTlsProfileOption(input.effectiveTlsProfile);
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
recipe.serverHelloParserMode = MT_PROXY_SERVER_HELLO_PARSER_STANDARD;
recipe.tlsProfile = adaptiveTlsProfileName(recipe.effectiveTlsProfile);
recipe.useGrease = false;
recipe.useModernExtensions = false;
return recipe;
}
recipe.transportMode = input.fakeTls ? "faketls_ee" : "classic_obfuscated";
switch (cursor.family) {
case CLIENT_HELLO_CHROME_MODERN_SOFT_FRAGMENT:
recipe.effectiveTlsProfile = MT_PROXY_TLS_PROFILE_YANDEX;
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_SOFT;
break;
case CLIENT_HELLO_CHROME_MODERN_NO_FRAGMENT:
recipe.effectiveTlsProfile = MT_PROXY_TLS_PROFILE_YANDEX;
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
break;
case CLIENT_HELLO_ANDROID_CHROME_NO_FRAGMENT:
recipe.effectiveTlsProfile = MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID;
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
break;
case CLIENT_HELLO_FIREFOX_ANDROID_NO_FRAGMENT:
recipe.effectiveTlsProfile = MT_PROXY_TLS_PROFILE_FIREFOX;
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
break;
case CLIENT_HELLO_LEGACY_NO_GREASE_NO_MODERN_EXTENSIONS:
recipe.effectiveTlsProfile = MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE;
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
break;
case CLIENT_HELLO_LEGACY_TLS12_MINIMAL:
recipe.effectiveTlsProfile = MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP;
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
break;
default:
recipe.effectiveTlsProfile = mtProxyDefaultTlsProfile();
recipe.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
break;
}
if (input.probeGrease || input.greaseSupported) {
recipe.effectiveTlsProfile = greaseProbeTlsProfile(input.configuredTlsProfile, recipe.effectiveTlsProfile);
}
recipe.effectiveTlsProfile = mtProxyEffectiveWireTlsProfile(recipe.effectiveTlsProfile);
recipe.tlsProfile = adaptiveTlsProfileName(recipe.effectiveTlsProfile);
recipe.fragmentClientHello = recipe.clientHelloFragmentation != MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
recipe.useGrease = profileUsesGrease(recipe.effectiveTlsProfile);
recipe.useModernExtensions = profileUsesModernExtensions(recipe.effectiveTlsProfile);
return recipe;
}
MtProxyAdaptivePolicy::RecipeResult MtProxyAdaptivePolicy::applyRecipe(const RecipeInput &input) {
if (input.useRecipeCursor) {
CompatibilityRecipe compatibilityRecipe = recipeForCursor(input, input.cursor);
RecipeResult result;
result.recipeLevel = input.recipeLevel;
result.clientHelloFragmentation = compatibilityRecipe.clientHelloFragmentation;
result.effectiveTlsProfile = compatibilityRecipe.effectiveTlsProfile;
result.serverHelloParserMode = compatibilityRecipe.serverHelloParserMode;
result.clientHelloSni = compatibilityRecipe.clientHelloSni;
result.connectionPatternMode = compatibilityRecipe.connectionPatternMode;
result.recordSizingMode = compatibilityRecipe.recordSizingMode;
result.timingMode = compatibilityRecipe.timingMode;
result.startupCoverMode = compatibilityRecipe.startupCoverMode;
result.changed = result.clientHelloFragmentation != normalizeMtProxyClientHelloFragmentationOption(input.clientHelloFragmentation)
|| result.effectiveTlsProfile != normalizeMtProxyTlsProfileOption(input.effectiveTlsProfile)
|| result.serverHelloParserMode != normalizeMtProxyServerHelloParserOption(input.serverHelloParserMode)
|| result.clientHelloSni != input.sni
|| result.connectionPatternMode != normalizeMtProxyConnectionPatternOption(input.connectionPatternMode)
|| result.recordSizingMode != normalizeMtProxyRecordSizingOption(input.recordSizingMode)
|| result.timingMode != normalizeMtProxyTimingOption(input.timingMode)
|| result.startupCoverMode != normalizeMtProxyStartupCoverOption(input.startupCoverMode);
return result;
}
RecipeResult result;
result.recipeLevel = input.recipeLevel;
result.clientHelloFragmentation = normalizeMtProxyClientHelloFragmentationOption(input.clientHelloFragmentation);
result.effectiveTlsProfile = normalizeMtProxyTlsProfileOption(input.effectiveTlsProfile);
result.serverHelloParserMode = normalizeMtProxyServerHelloParserOption(input.serverHelloParserMode);
result.connectionPatternMode = normalizeMtProxyConnectionPatternOption(input.connectionPatternMode);
result.recordSizingMode = normalizeMtProxyRecordSizingOption(input.recordSizingMode);
result.timingMode = normalizeMtProxyTimingOption(input.timingMode);
result.startupCoverMode = normalizeMtProxyStartupCoverOption(input.startupCoverMode);
bool useGreaseProfile = input.probeGrease || (input.greaseSupported && input.recipeLevel <= 1);
if (useGreaseProfile) {
int32_t previousProfile = result.effectiveTlsProfile;
result.effectiveTlsProfile = greaseProbeTlsProfile(input.configuredTlsProfile, result.effectiveTlsProfile);
if (result.effectiveTlsProfile != previousProfile) {
result.changed = true;
}
} else if (input.fakeTls && input.recipeLevel == 2) {
int32_t previousProfile = result.effectiveTlsProfile;
result.effectiveTlsProfile = MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE;
if (result.effectiveTlsProfile != previousProfile) {
result.changed = true;
}
} else if (input.fakeTls && input.recipeLevel >= 3) {
int32_t previousProfile = result.effectiveTlsProfile;
result.effectiveTlsProfile = alternateCompatibilityTlsProfile(input.alternateProfileIndex);
if (result.effectiveTlsProfile != previousProfile) {
result.changed = true;
}
}
if (!input.fakeTls || input.endpointKey.empty() || input.recipeLevel <= 0) {
return result;
}
if (input.lastDiagnostic == "post_handshake_no_appdata") {
if (result.recordSizingMode == MT_PROXY_RECORD_SIZING_OFF) {
result.recordSizingMode = MT_PROXY_RECORD_SIZING_CONSERVATIVE;
result.changed = true;
}
if (result.timingMode == MT_PROXY_TIMING_OFF) {
result.timingMode = MT_PROXY_TIMING_GENTLE;
result.changed = true;
}
if (result.startupCoverMode == MT_PROXY_STARTUP_COVER_OFF) {
result.startupCoverMode = MT_PROXY_STARTUP_COVER_SOFT;
result.changed = true;
}
if (input.recipeLevel >= 2 && result.connectionPatternMode != MT_PROXY_CONNECTION_PATTERN_STRICT) {
if (result.connectionPatternMode == MT_PROXY_CONNECTION_PATTERN_OFF
|| result.connectionPatternMode == MT_PROXY_CONNECTION_PATTERN_SOFT
|| result.connectionPatternMode == MT_PROXY_CONNECTION_PATTERN_BROWSER) {
result.connectionPatternMode = MT_PROXY_CONNECTION_PATTERN_QUIET;
result.changed = true;
}
}
return result;
}
if (input.recipeLevel >= 1 && result.clientHelloFragmentation != MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF) {
result.clientHelloFragmentation = MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
result.changed = true;
}
if (serverHelloParserVariantAllowed(mtProxyRecoveryActionForPhase(input.lastDiagnostic, 0))
&& input.recipeLevel >= 4
&& result.serverHelloParserMode != MT_PROXY_SERVER_HELLO_PARSER_RESERVED) {
result.serverHelloParserMode = MT_PROXY_SERVER_HELLO_PARSER_RESERVED;
result.changed = true;
}
if (input.recipeLevel >= 4 && result.connectionPatternMode != MT_PROXY_CONNECTION_PATTERN_STRICT) {
if (result.connectionPatternMode == MT_PROXY_CONNECTION_PATTERN_OFF
|| result.connectionPatternMode == MT_PROXY_CONNECTION_PATTERN_SOFT
|| result.connectionPatternMode == MT_PROXY_CONNECTION_PATTERN_BROWSER) {
result.connectionPatternMode = MT_PROXY_CONNECTION_PATTERN_QUIET;
result.changed = true;
}
}
return result;
}
MtProxyAdaptivePolicy::MtProxyRecipe MtProxyAdaptivePolicy::recipeForResult(const RecipeInput &input, const RecipeResult &result) {
MtProxyRecipe recipe;
recipe.transportMode = input.fakeTls ? "faketls_ee" : "classic_obfuscated";
recipe.tlsProfile = adaptiveTlsProfileName(result.effectiveTlsProfile);
recipe.fragmentClientHello = result.clientHelloFragmentation != MT_PROXY_CLIENT_HELLO_FRAGMENTATION_OFF;
recipe.useGrease = MtProxyAdaptivePolicy::profileUsesGrease(result.effectiveTlsProfile);
recipe.useModernExtensions = profileUsesModernExtensions(result.effectiveTlsProfile);
recipe.serverHelloParser = mtProxyServerHelloParserName(result.serverHelloParserMode);
recipe.sni = result.clientHelloSni.empty() ? input.sni : result.clientHelloSni;
return recipe;
}
MtProxyAdaptivePolicy::MtProxyRecipe MtProxyAdaptivePolicy::recipeForCompatibilityRecipe(const CompatibilityRecipe &recipe) {
MtProxyRecipe result;
result.transportMode = recipe.transportMode;
result.tlsProfile = recipe.tlsProfile;
result.fragmentClientHello = recipe.fragmentClientHello;
result.useGrease = recipe.useGrease;
result.useModernExtensions = recipe.useModernExtensions;
result.serverHelloParser = recipe.parserVariantName;
result.sni = recipe.clientHelloSni;
return result;
}
std::string MtProxyAdaptivePolicy::recipeId(const CompatibilityRecipe &recipe) {
return recipe.transportMode
+ "+" + recipe.familyName
+ "+" + recipe.sniVariantName
+ "+" + recipe.parserVariantName
+ "+classic=" + recipe.classicVariantName
+ "+" + recipe.tlsProfile
+ "+" + (recipe.fragmentClientHello ? "soft_fragment" : "no_fragment")
+ "+" + (recipe.useGrease ? "grease" : "no_grease")
+ "+" + (recipe.useModernExtensions ? "modern_extensions" : "no_modern_extensions")
+ (recipe.experimentalNoSni ? "+experimental_no_sni=1" : "+experimental_no_sni=0")
+ "+sni=" + (recipe.clientHelloSni.empty() ? "none" : recipe.clientHelloSni);
}
std::string MtProxyAdaptivePolicy::recipeId(const MtProxyRecipe &recipe) {
return recipe.transportMode
+ "+" + recipe.tlsProfile
+ "+" + (recipe.fragmentClientHello ? "soft_fragment" : "no_fragment")
+ "+" + (recipe.useGrease ? "grease" : "no_grease")
+ "+" + (recipe.useModernExtensions ? "modern_extensions" : "no_modern_extensions")
+ "+" + recipe.serverHelloParser
+ "+sni=" + (recipe.sni.empty() ? "none" : recipe.sni);
}
int32_t MtProxyAdaptivePolicy::resolveEffectiveTlsProfile(int32_t profile, const std::string &key) {
profile = normalizeMtProxyTlsProfileOption(profile);
if (profile != MT_PROXY_TLS_PROFILE_AUTO_ROTATE) {
return mtProxyEffectiveWireTlsProfile(profile);
}
pthread_mutex_lock(&mtProxyTlsAutoProfilesMutex);
MtProxyTlsAutoProfileState &state = tlsAutoRotateProfiles[key];
if (state.profileIndex < 0) {
state.profileIndex = autoRotateInitialIndex(key);
}
int32_t result = mtProxyEffectiveWireTlsProfile(autoRotatePoolProfile(state.profileIndex));
pthread_mutex_unlock(&mtProxyTlsAutoProfilesMutex);
return result;
}
MtProxyAdaptivePolicy::RotateResult MtProxyAdaptivePolicy::rotateTlsProfileOnFailureIfNeeded(const std::string &key, const std::string &diagnostic, int32_t previousProfile) {
RotateResult result;
result.previousProfile = normalizeMtProxyTlsProfileOption(previousProfile);
if (key.empty() || !failureNeedsRecipe(diagnostic)) {
return result;
}
pthread_mutex_lock(&mtProxyTlsAutoProfilesMutex);
MtProxyTlsAutoProfileState &state = tlsAutoRotateProfiles[key];
if (state.profileIndex < 0) {
state.profileIndex = autoRotateInitialIndex(key);
}
state.profileIndex = (state.profileIndex + 1) % MT_PROXY_ALTERNATE_PROFILE_COUNT;
state.failures++;
result.failures = state.failures;
result.nextProfile = mtProxyEffectiveWireTlsProfile(autoRotatePoolProfile(state.profileIndex));
pthread_mutex_unlock(&mtProxyTlsAutoProfilesMutex);
result.rotated = true;
return result;
}
bool MtProxyAdaptivePolicy::failureNeedsRecipe(const std::string &diagnostic) {
if (diagnostic == "tcp_not_connected"
|| diagnostic == "tcp_connection_refused"
|| diagnostic == "tcp_connect_timeout") {
return false; // ClientHello was not sent, so JA4 did not cause this failure.
}
bool recipePhase = diagnostic == "true_client_hello_timeout"
|| diagnostic == "faketls_server_hello_wait_timeout"
|| diagnostic == "server_closed_after_client_hello"
|| diagnostic == "client_hello_sent_no_server_hello"
|| diagnostic == "tls_alert_after_client_hello"
|| diagnostic == "short_tls_response_after_client_hello"
|| diagnostic == "unrecognized_response_after_client_hello"
|| diagnostic == "unrecognized_tls_response_after_client_hello"
|| diagnostic == "server_hello_hmac_mismatch";
if (!recipePhase) {
return false;
}
return mtProxyRecoveryActionAdvancesRecipe(mtProxyRecoveryActionForPhase(diagnostic, 0));
}
int32_t MtProxyAdaptivePolicy::compatibilityTlsProfile(int32_t configuredProfile, int32_t effectiveProfile, int32_t recipeLevel) {
(void) configuredProfile;
effectiveProfile = normalizeMtProxyTlsProfileOption(effectiveProfile);
if (recipeLevel <= 1) {
return effectiveProfile;
}
if (recipeLevel == 2) {
if (effectiveProfile == MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE) {
return MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID;
}
return MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE;
}
if (recipeLevel == 3) {
return alternateCompatibilityTlsProfile(0);
}
return alternateCompatibilityTlsProfile(MT_PROXY_ALTERNATE_PROFILE_COUNT - 1);
}
int32_t MtProxyAdaptivePolicy::adaptiveTlsProfile(int32_t configuredProfile, int32_t effectiveProfile) {
configuredProfile = normalizeMtProxyTlsProfileOption(configuredProfile);
if (configuredProfile != MT_PROXY_TLS_PROFILE_AUTO && configuredProfile != MT_PROXY_TLS_PROFILE_AUTO_ROTATE) {
return mtProxyEffectiveWireTlsProfile(effectiveProfile);
}
switch (normalizeMtProxyTlsProfileOption(effectiveProfile)) {
case MT_PROXY_TLS_PROFILE_YANDEX:
return MT_PROXY_TLS_PROFILE_YANDEX;
case MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID:
return MT_PROXY_TLS_PROFILE_FIREFOX_ANDROID;
case MT_PROXY_TLS_PROFILE_FIREFOX:
return MT_PROXY_TLS_PROFILE_FIREFOX;
case MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP:
return MT_PROXY_TLS_PROFILE_ANDROID_OKHTTP;
case MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE:
return MT_PROXY_TLS_PROFILE_LEGACY_NO_GREASE;
case MT_PROXY_TLS_PROFILE_CHROME_MODERN:
case MT_PROXY_TLS_PROFILE_ANDROID_CHROME:
return mtProxyDefaultTlsProfile();
default:
return mtProxyDefaultTlsProfile();
}
}