reality: TLS-камуфляж для STCP (ClientHello/ServerHello + релей на реальный сайт)

This commit is contained in:
evgeny committed 2026-09-10 01:11:26 +03:00
1 parent 0f308fdbd7
commit edb3bb36e1
23 files changed
+1614 -28

No files matched your search

+80 -2
View File
@@ -52,7 +52,8 @@ typedef enum {
SECTION_NETWORK,
SECTION_GUI,
SECTION_NTP,
SECTION_LOG_UDP
SECTION_LOG_UDP,
SECTION_REALITY
} section_type_t;
static char* trim(char *str) {
@@ -698,7 +699,11 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
else srv->transport = 0; // default udp
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport", filename, line_num, key);
if (strcmp(key, "reality") == 0) {
srv->reality_enabled = atoi(value) ? 1 : 0;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport, reality", filename, line_num, key);
return -1;
}
@@ -808,6 +813,72 @@ static int parse_network(const char *key, const char *value, struct CFG_NETWORK
return -1;
}
static int parse_reality(const char *key, const char *value, struct reality_config *rc, const char *filename, int line_num) {
if (strcmp(key, "enabled") == 0) { rc->enabled = atoi(value) ? 1 : 0; return 0; }
if (strcmp(key, "server_name") == 0) { return assign_string(rc->server_name, sizeof(rc->server_name), value); }
if (strcmp(key, "dest") == 0) { return assign_string(rc->dest, sizeof(rc->dest), value); }
if (strcmp(key, "short_id") == 0) {
if (hex_to_binary(value, rc->short_id, REALITY_SHORT_ID_SIZE) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: reality short_id must be %d hex chars", filename, line_num, REALITY_SHORT_ID_SIZE * 2);
return -1;
}
return 0;
}
if (strcmp(key, "short_ids") == 0) {
rc->short_id_count = 0;
char buf[MAX_LINE_LEN];
assign_string(buf, sizeof(buf), value);
char *tok = strtok(buf, ",");
while (tok) {
char *t = trim(tok);
if (rc->short_id_count >= REALITY_MAX_SHORT_IDS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: too many reality short_ids (max %d)", filename, line_num, REALITY_MAX_SHORT_IDS);
return -1;
}
if (hex_to_binary(t, rc->short_ids[rc->short_id_count], REALITY_SHORT_ID_SIZE) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: bad reality short_id '%s'", filename, line_num, t);
return -1;
}
rc->short_id_count++;
tok = strtok(NULL, ",");
}
return 0;
}
if (strcmp(key, "public_key") == 0) {
if (hex_to_binary(value, rc->public_key, REALITY_AUTH_KEY_SIZE) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: reality public_key must be %d hex chars", filename, line_num, REALITY_AUTH_KEY_SIZE * 2);
return -1;
}
rc->has_public_key = 1;
return 0;
}
if (strcmp(key, "private_key") == 0) {
if (hex_to_binary(value, rc->private_key, REALITY_AUTH_KEY_SIZE) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: reality private_key must be %d hex chars", filename, line_num, REALITY_AUTH_KEY_SIZE * 2);
return -1;
}
rc->has_private_key = 1;
return 0;
}
if (strcmp(key, "version") == 0) {
int x = 0, y = 0, z = 0;
if (sscanf(value, "%d.%d.%d", &x, &y, &z) != 3 || x < 0 || x > 255 || y < 0 || y > 255 || z < 0 || z > 255) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: bad reality version '%s' (expected x.y.z)", filename, line_num, value);
return -1;
}
rc->version[0] = (uint8_t)x; rc->version[1] = (uint8_t)y; rc->version[2] = (uint8_t)z;
return 0;
}
if (strcmp(key, "time_window") == 0) { rc->time_window_sec = atoi(value); return 0; }
if (strcmp(key, "fingerprint") == 0) {
if (strcmp(value, "chrome") == 0) { rc->fingerprint = REALITY_FP_CHROME; return 0; }
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: unknown reality fingerprint '%s' (valid: chrome)", filename, line_num, value);
return -1;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown reality option '%s'. Valid: enabled, server_name, dest, short_id, short_ids, public_key, private_key, version, time_window, fingerprint", filename, line_num, key);
return -1;
}
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
if (line[0] != '[') return SECTION_UNKNOWN;
@@ -835,6 +906,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "ntp") == 0) return SECTION_NTP;
if (strcasecmp(section, "gui") == 0) return SECTION_GUI;
if (strcasecmp(section, "log_udp") == 0) return SECTION_LOG_UDP;
if (strcasecmp(section, "reality") == 0) return SECTION_REALITY;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@@ -887,6 +959,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.chatserver_allowed_groups[0] = '\0';
cfg->global.chatserver_storage_total_size = 0;
cfg->global.chatserver_storage_unit_size = 0;
reality_config_set_defaults(&cfg->global.reality);
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
@@ -1098,6 +1171,9 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
break;
case SECTION_GUI:
break;
case SECTION_REALITY:
parse_reality(key, value, &cfg->global.reality, filename, line_num);
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break;
@@ -1187,6 +1263,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
cfg->global.chatserver_allowed_groups[0] = '\0';
cfg->global.chatserver_storage_total_size = 0;
cfg->global.chatserver_storage_unit_size = 0;
reality_config_set_defaults(&cfg->global.reality);
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
@@ -1247,6 +1324,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
case SECTION_NTP: if (strcmp(key, "enabled") == 0) cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "server") == 0) { struct CFG_NTP_SERVER *ns = u_calloc(1, sizeof(struct CFG_NTP_SERVER)); if (ns) { strncpy(ns->name, value, sizeof(ns->name) - 1); ns->name[sizeof(ns->name) - 1] = '\0'; ns->next = cfg->global.ntp_servers; cfg->global.ntp_servers = ns; cfg->global.ntp_server_count++; } } else if (strcmp(key, "interval") == 0) { cfg->global.ntp_resync_interval = atoi(value); if (cfg->global.ntp_resync_interval < 60) cfg->global.ntp_resync_interval = 60; } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'", filename, line_num, key); break;
case SECTION_CHATSERVER: cfg->global.chatserver_enabled = 1; cfg->global.db_sync_enabled = 1; if (strcmp(key, "db_path") == 0) assign_string(cfg->global.db_path, sizeof(cfg->global.db_path), value); else if (parse_chatserver(key, value, &cfg->global, filename, line_num) < 0 && chat_setting_set(key, value) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown chatserver option '%s'. Valid: db_path, join_password, allowed_groups, storage_total_size, storage_unit_size, storage_autoload, opus_codec_preset, compressor_enabled, compressor_max_gain_db, compressor_rise_rate, media_download_max_peers", filename, line_num, key); } break;
case SECTION_GUI: break;
case SECTION_REALITY: parse_reality(key, value, &cfg->global.reality, filename, line_num); break;
case SECTION_LOG_UDP:
if (strcmp(key, "ip") == 0) strncpy(cfg->global.log_udp_ip, value, sizeof(cfg->global.log_udp_ip) - 1);
else if (strcmp(key, "port") == 0) cfg->global.log_udp_port = atoi(value);