Template
1
0
mirror of https://github.com/bol-van/zapret2.git synced 2026-03-14 06:13:09 +00:00

nfqws2: change retransmission detection scheme

This commit is contained in:
bol-van
2025-12-08 16:46:04 +03:00
parent 146ab847df
commit 2017889207
11 changed files with 74 additions and 53 deletions

View File

@@ -72,3 +72,5 @@ v0.6
* zapret-lib,zapret-antidpi: tls_mod_shim supports sni=%var subst
* blockcheck2: syndata tests
* nfqws2: reasm support negative overlaps. gaps are not supported.
* nfqws2,zapret-auto: changed retransmission detection scheme.

View File

@@ -88,15 +88,11 @@ function standard_failure_detector(desync, crec, options)
local trigger = false
if desync.outgoing then
if #desync.dis.payload>0 and options.retrans and (crec.retrans or 0)<options.retrans then
if not crec.uppos then crec.uppos=0 end
if desync.track.tcp.pos_orig<=crec.uppos then
if is_retransmission(desync) then
crec.retrans = crec.retrans and (crec.retrans+1) or 1
DLOG("standard_failure_detector: retransmission "..crec.retrans.."/"..options.retrans)
trigger = crec.retrans>=options.retrans
end
if desync.track.tcp.pos_orig>crec.uppos then
crec.uppos=desync.track.tcp.pos_orig
end
end
else
if options.seq_rst and bitand(desync.dis.tcp.th_flags, TH_RST)~=0 then
@@ -171,7 +167,6 @@ function circular(ctx, desync)
if not desync.dis.tcp then
DLOG_ERR("circular: this orchestrator is tcp only. use filters to avoid udp traffic.")
instance_cutoff(ctx)
return
end
if not desync.track then

View File

@@ -186,7 +186,7 @@ function desync_orchestrator_example(ctx, desync)
end
end
-- these function duplicate range check logic from C code
-- these functions duplicate range check logic from C code
-- mode must be n,d,b,s,x,a
-- pos is {mode,pos}
-- range is {from={mode,pos}, to={mode,pos}, upper_cutoff}
@@ -238,7 +238,9 @@ end
function pos_str(desync, pos)
return pos.mode..pos_get(desync, pos.mode)
end
function is_retransmission(desync)
return desync.track and desync.track.tcp and 0==bitand(u32add(desync.track.tcp.uppos_orig_prev, -desync.track.tcp.pos_orig), 0x80000000)
end
-- prepare standard rawsend options from desync
-- repeats - how many time send the packet
@@ -1328,4 +1330,3 @@ function ipfrag2(dis, ipfrag_options)
return {dis1,dis2}
end

View File

@@ -180,9 +180,17 @@ static void ConntrackFeedPacket(t_ctrack *t, bool bReverse, const struct tcphdr
mss = ntohs(tcp_find_mss(tcphdr));
if (bReverse)
{
t->pos.pos_orig = t->pos.seq_last = ntohl(tcphdr->th_ack);
t->pos.ack_last = ntohl(tcphdr->th_seq);
t->pos.pos_orig = t->pos.seq_last = ntohl(tcphdr->th_ack);
t->pos.pos_reply = t->pos.ack_last + len_payload;
if (t->pos.state == SYN)
t->pos.uppos_reply_prev = t->pos.uppos_reply = t->pos.pos_reply;
else
{
t->pos.uppos_reply_prev = t->pos.uppos_reply;
if (!((t->pos.pos_reply - t->pos.uppos_reply) & 0x80000000))
t->pos.uppos_reply = t->pos.pos_reply;
}
t->pos.winsize_reply = ntohs(tcphdr->th_win);
t->pos.winsize_reply_calc = t->pos.winsize_reply;
if (t->pos.scale_reply != SCALE_NONE) t->pos.winsize_reply_calc <<= t->pos.scale_reply;
@@ -194,6 +202,14 @@ static void ConntrackFeedPacket(t_ctrack *t, bool bReverse, const struct tcphdr
t->pos.seq_last = ntohl(tcphdr->th_seq);
t->pos.pos_orig = t->pos.seq_last + len_payload;
t->pos.pos_reply = t->pos.ack_last = ntohl(tcphdr->th_ack);
if (t->pos.state == SYN)
t->pos.uppos_orig_prev = t->pos.uppos_orig = t->pos.pos_orig;
else
{
t->pos.uppos_orig_prev = t->pos.uppos_orig;
if (!((t->pos.pos_orig - t->pos.uppos_orig) & 0x80000000))
t->pos.uppos_orig = t->pos.pos_orig;
}
t->pos.winsize_orig = ntohs(tcphdr->th_win);
t->pos.winsize_orig_calc = t->pos.winsize_orig;
if (t->pos.scale_orig != SCALE_NONE) t->pos.winsize_orig_calc <<= t->pos.scale_orig;

View File

@@ -61,8 +61,7 @@ typedef struct
bool dp_search_complete;
uint8_t req_retrans_counter; // number of request retransmissions
bool req_seq_present,req_seq_finalized,req_seq_abandoned;
uint32_t req_seq_start,req_seq_end; // sequence interval of the request (to track retransmissions)
bool retrans_detect_finalized;
uint8_t incoming_ttl;

View File

@@ -20,6 +20,8 @@ typedef struct
uint64_t pdcounter_orig, pdcounter_reply; // data packet counter (with payload)
uint64_t pbcounter_orig, pbcounter_reply; // transferred byte counter. includes retransmissions. it's not the same as relative seq.
uint32_t pos_orig, pos_reply; // TCP: seq_last+payload, ack_last+payload UDP: sum of all seen payload lenghts including current
uint32_t uppos_orig, uppos_reply; // max seen position. useful to detect retransmissions
uint32_t uppos_orig_prev, uppos_reply_prev; // previous max seen position. useful to detect retransmissions
uint32_t seq_last, ack_last; // TCP: last seen seq and ack UDP: sum of all seen payload lenghts NOT including current
// tcp only state, not used in udp

View File

@@ -240,6 +240,11 @@ static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const ch
}
}
static bool is_retransmission(const t_ctrack *ctrack)
{
return !((ctrack->pos.uppos_orig_prev - ctrack->pos.pos_orig) & 0x80000000);
}
// return true if retrans trigger fires
static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int threshold, const char *client_ip_port, t_l7proto l7proto)
{
@@ -247,24 +252,28 @@ static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int thresho
{
if (l4proto == IPPROTO_TCP)
{
if (!ctrack->req_seq_finalized || ctrack->req_seq_abandoned)
if (ctrack->retrans_detect_finalized)
return false;
if (!seq_within(ctrack->pos.seq_last, ctrack->req_seq_start, ctrack->req_seq_end))
if (!seq_within(ctrack->pos.seq_last, ctrack->pos.seq0, ctrack->pos.seq0 + ctrack->dp->hostlist_auto_retrans_maxseq))
{
DLOG("req retrans : tcp seq %u not within the req range %u-%u. stop tracking.\n", ctrack->pos.seq_last, ctrack->req_seq_start, ctrack->req_seq_end);
ctrack->retrans_detect_finalized = true;
DLOG("retrans : tcp seq %u not within the req range %u-%u. stop tracking.\n", ctrack->pos.seq_last, ctrack->pos.seq0, ctrack->pos.seq0 + ctrack->dp->hostlist_auto_retrans_maxseq);
ctrack_stop_retrans_counter(ctrack);
auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, l7proto);
return false;
}
if (!is_retransmission(ctrack))
return false;
}
ctrack->req_retrans_counter++;
if (ctrack->req_retrans_counter >= threshold)
{
DLOG("req retrans threshold reached : %u/%u\n", ctrack->req_retrans_counter, threshold);
DLOG("retrans threshold reached : %u/%u\n", ctrack->req_retrans_counter, threshold);
ctrack_stop_retrans_counter(ctrack);
ctrack->retrans_detect_finalized = true;
return true;
}
DLOG("req retrans counter : %u/%u\n", ctrack->req_retrans_counter, threshold);
DLOG("retrans counter : %u/%u\n", ctrack->req_retrans_counter, threshold);
}
return false;
}
@@ -1169,17 +1178,6 @@ static uint8_t dpi_desync_tcp_packet_play(
DLOG("not applying tampering to HTTP without Host:\n");
goto pass;
}
if (ctrack)
{
// we do not reassemble http
if (!ctrack->req_seq_present)
{
ctrack->req_seq_start = ctrack->pos.seq_last;
ctrack->req_seq_end = ctrack->pos.pos_orig - 1;
ctrack->req_seq_present = ctrack->req_seq_finalized = true;
DLOG("req retrans : tcp seq interval %u-%u\n", ctrack->req_seq_start, ctrack->req_seq_end);
}
}
}
else if (IsTLSClientHello(rdata_payload, rlen_payload, TLS_PARTIALS_ENABLE))
{
@@ -1198,27 +1196,12 @@ static uint8_t dpi_desync_tcp_packet_play(
if (ctrack && !(params.reasm_payload_disable && l7_payload_match(l7payload, params.reasm_payload_disable)))
{
// do not reasm retransmissions
if (!bReqFull && ReasmIsEmpty(&ctrack->reasm_orig) && !ctrack->req_seq_abandoned &&
!(ctrack->req_seq_finalized && seq_within(ctrack->pos.seq_last, ctrack->req_seq_start, ctrack->req_seq_end)))
if (!bReqFull && ReasmIsEmpty(&ctrack->reasm_orig) && !is_retransmission(ctrack))
{
// do not reconstruct unexpected large payload (they are feeding garbage ?)
if (!reasm_orig_start(ctrack, IPPROTO_TCP, TLSRecordLen(dis->data_payload), TCP_MAX_REASM, dis->data_payload, dis->len_payload))
goto pass_reasm_cancel;
}
if (!ctrack->req_seq_finalized)
{
if (!ctrack->req_seq_present)
{
// lower bound of request seq interval
ctrack->req_seq_start = ctrack->pos.seq_last;
ctrack->req_seq_present = true;
}
// upper bound of request seq interval
// it can grow on every packet until request is complete. then interval is finalized and never touched again.
ctrack->req_seq_end = ctrack->pos.pos_orig - 1;
DLOG("req retrans : seq interval %u-%u\n", ctrack->req_seq_start, ctrack->req_seq_end);
ctrack->req_seq_finalized |= bReqFull;
}
if (!ReasmIsEmpty(&ctrack->reasm_orig))
{
@@ -1259,12 +1242,6 @@ static uint8_t dpi_desync_tcp_packet_play(
};
protocol_probe(testers, sizeof(testers) / sizeof(*testers), dis->data_payload, dis->len_payload, ctrack, &l7proto, &l7payload);
}
if (ctrack && ctrack->req_seq_finalized)
{
uint32_t dseq = ctrack->pos.seq_last - ctrack->req_seq_end;
// do not react to 32-bit overflowed sequence numbers. allow 16 Mb grace window then cutoff.
if (dseq >= 0x1000000 && !(dseq & 0x80000000)) ctrack->req_seq_abandoned = true;
}
if (bHaveHost)
{
@@ -1327,7 +1304,6 @@ static uint8_t dpi_desync_tcp_packet_play(
DLOG("desync profile changed by revealed l7 protocol or hostname !\n");
}
}
if (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp))
{
if (!bCheckDone)

View File

@@ -1291,17 +1291,21 @@ void lua_pushf_ctrack(const t_ctrack *ctrack, const t_ctrack_position *pos)
if (ctrack->ipproto == IPPROTO_TCP)
{
lua_pushliteral(params.L, "tcp");
lua_createtable(params.L, 0, 14);
lua_createtable(params.L, 0, 18);
lua_pushf_lint("seq0", pos->seq0);
lua_pushf_lint("seq", pos->seq_last);
lua_pushf_lint("ack0", pos->ack0);
lua_pushf_lint("ack", pos->ack_last);
lua_pushf_int("pos_orig", pos->pos_orig - pos->seq0);
lua_pushf_int("uppos_orig", pos->uppos_orig - pos->seq0);
lua_pushf_int("uppos_orig_prev", pos->uppos_orig_prev - pos->seq0);
lua_pushf_int("winsize_orig", pos->winsize_orig);
lua_pushf_int("winsize_orig_calc", pos->winsize_orig_calc);
lua_pushf_int("scale_orig", pos->scale_orig);
lua_pushf_int("mss_orig", pos->mss_orig);
lua_pushf_int("pos_reply", pos->pos_reply - pos->ack0);
lua_pushf_int("uppos_reply", pos->uppos_reply - pos->ack0);
lua_pushf_int("uppos_reply_prev", pos->uppos_reply_prev - pos->ack0);
lua_pushf_int("winsize_reply", pos->winsize_reply);
lua_pushf_int("winsize_reply_calc", pos->winsize_reply_calc);
lua_pushf_int("scale_reply", pos->scale_reply);

View File

@@ -1434,6 +1434,7 @@ static void exithelp(void)
" --hostlist-auto-fail-threshold=<int>\t\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n"
" --hostlist-auto-fail-time=<int>\t\t\t; all failed attemps must be within these seconds (default : %d)\n"
" --hostlist-auto-retrans-threshold=<int>\t\t; how many request retransmissions cause attempt to fail (default : %d)\n"
" --hostlist-auto-retrans-maxseq=<int>\t\t\t; count retransmissions only within this relative sequence (default : %u)\n"
" --hostlist-auto-debug=<logfile>\t\t\t; debug auto hostlist positives (global parameter)\n"
"\nLUA PACKET PASS MODE:\n"
" --payload=type[,type]\t\t\t\t\t; set payload types following LUA functions should process : %s\n"
@@ -1448,7 +1449,8 @@ static void exithelp(void)
IPCACHE_LIFETIME,
LUA_GC_INTERVAL,
all_protos,
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT,
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT,
HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT, HOSTLIST_AUTO_RETRANS_MAXSEQ,
all_payloads
);
exit(1);
@@ -1545,6 +1547,7 @@ enum opt_indices {
IDX_HOSTLIST_AUTO_FAIL_THRESHOLD,
IDX_HOSTLIST_AUTO_FAIL_TIME,
IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD,
IDX_HOSTLIST_AUTO_RETRANS_MAXSEQ,
IDX_HOSTLIST_AUTO_DEBUG,
IDX_NEW,
IDX_SKIP,
@@ -1629,6 +1632,7 @@ static const struct option long_options[] = {
[IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {"hostlist-auto-fail-threshold", required_argument, 0, 0},
[IDX_HOSTLIST_AUTO_FAIL_TIME] = {"hostlist-auto-fail-time", required_argument, 0, 0},
[IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {"hostlist-auto-retrans-threshold", required_argument, 0, 0},
[IDX_HOSTLIST_AUTO_RETRANS_MAXSEQ] = {"hostlist-auto-retrans-maxseq", required_argument, 0, 0},
[IDX_HOSTLIST_AUTO_DEBUG] = {"hostlist-auto-debug", required_argument, 0, 0},
[IDX_NEW] = {"new", no_argument, 0, 0},
[IDX_SKIP] = {"skip", no_argument, 0, 0},
@@ -1691,6 +1695,22 @@ static const struct option long_options[] = {
int main(int argc, char **argv)
{
/*
t_reassemble t;
ReasmInit(&t,16,-10);
memset(t.packet,0,16);
bool b;
b=ReasmFeed(&t,-10,"0123456789",10);
printf("b=%u size=%zu seq=%d s=%s\n",b,t.size_present,t.seq,t.packet);
b=ReasmFeed(&t,0,"YOREK",5);
printf("b=%u size=%zu seq=%d s=%s\n",b,t.size_present,t.seq,t.packet);
b=ReasmFeed(&t,-12,"XOR",3);
printf("b=%u size=%zu seq=%d s=%s\n",b,t.size_present,t.seq,t.packet);
b=ReasmFeed(&t,3,"abc",3);
printf("b=%u size=%zu seq=%d s=%s\n",b,t.size_present,t.seq,t.packet);
return 0;
*/
#ifdef __CYGWIN__
if (service_run(argc, argv))
{
@@ -2077,6 +2097,9 @@ int main(int argc, char **argv)
exit_clean(1);
}
break;
case IDX_HOSTLIST_AUTO_RETRANS_MAXSEQ:
dp->hostlist_auto_retrans_maxseq = (uint32_t)atoi(optarg);
break;
case IDX_HOSTLIST_AUTO_DEBUG:
{
FILE *F = fopen(optarg, "a+t");

View File

@@ -342,6 +342,7 @@ void dp_init(struct desync_profile *dp)
dp->hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT;
dp->hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT;
dp->hostlist_auto_retrans_threshold = HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT;
dp->hostlist_auto_retrans_maxseq = HOSTLIST_AUTO_RETRANS_MAXSEQ;
dp->filter_ipv4 = dp->filter_ipv6 = true;
}
static void dp_clear_dynamic(struct desync_profile *dp)

View File

@@ -30,6 +30,7 @@
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
#define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3
#define HOSTLIST_AUTO_RETRANS_MAXSEQ 65536
#define IPCACHE_LIFETIME 7200
@@ -78,6 +79,7 @@ struct desync_profile
// pointer to autohostlist. NULL if no autohostlist for the profile.
struct hostlist_file *hostlist_auto;
int hostlist_auto_fail_threshold, hostlist_auto_fail_time, hostlist_auto_retrans_threshold;
uint32_t hostlist_auto_retrans_maxseq;
hostfail_pool *hostlist_auto_fail_counters;