From 201788920713ddc2a630a8e11b76e9957ba8b5f6 Mon Sep 17 00:00:00 2001 From: bol-van Date: Mon, 8 Dec 2025 16:46:04 +0300 Subject: [PATCH] nfqws2: change retransmission detection scheme --- docs/changes.txt | 2 ++ lua/zapret-auto.lua | 7 +----- lua/zapret-lib.lua | 7 +++--- nfq2/conntrack.c | 18 ++++++++++++++- nfq2/conntrack.h | 3 +-- nfq2/conntrack_base.h | 2 ++ nfq2/desync.c | 54 ++++++++++++------------------------------- nfq2/lua.c | 6 ++++- nfq2/nfqws.c | 25 +++++++++++++++++++- nfq2/params.c | 1 + nfq2/params.h | 2 ++ 11 files changed, 74 insertions(+), 53 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index bad0f87..dd0f65d 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -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. + diff --git a/lua/zapret-auto.lua b/lua/zapret-auto.lua index 68f847a..ecc8b00 100644 --- a/lua/zapret-auto.lua +++ b/lua/zapret-auto.lua @@ -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 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 diff --git a/lua/zapret-lib.lua b/lua/zapret-lib.lua index 5db7c35..4e158a9 100644 --- a/lua/zapret-lib.lua +++ b/lua/zapret-lib.lua @@ -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 - diff --git a/nfq2/conntrack.c b/nfq2/conntrack.c index 92f573a..9d8a08d 100644 --- a/nfq2/conntrack.c +++ b/nfq2/conntrack.c @@ -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; diff --git a/nfq2/conntrack.h b/nfq2/conntrack.h index f7d1657..aeb3392 100644 --- a/nfq2/conntrack.h +++ b/nfq2/conntrack.h @@ -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; diff --git a/nfq2/conntrack_base.h b/nfq2/conntrack_base.h index 408dfc8..68883d2 100644 --- a/nfq2/conntrack_base.h +++ b/nfq2/conntrack_base.h @@ -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 diff --git a/nfq2/desync.c b/nfq2/desync.c index 0a04f14..dc303d3 100644 --- a/nfq2/desync.c +++ b/nfq2/desync.c @@ -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) diff --git a/nfq2/lua.c b/nfq2/lua.c index 47df074..a954ee8 100644 --- a/nfq2/lua.c +++ b/nfq2/lua.c @@ -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); diff --git a/nfq2/nfqws.c b/nfq2/nfqws.c index 5e273bb..235e70f 100644 --- a/nfq2/nfqws.c +++ b/nfq2/nfqws.c @@ -1434,6 +1434,7 @@ static void exithelp(void) " --hostlist-auto-fail-threshold=\t\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" " --hostlist-auto-fail-time=\t\t\t; all failed attemps must be within these seconds (default : %d)\n" " --hostlist-auto-retrans-threshold=\t\t; how many request retransmissions cause attempt to fail (default : %d)\n" + " --hostlist-auto-retrans-maxseq=\t\t\t; count retransmissions only within this relative sequence (default : %u)\n" " --hostlist-auto-debug=\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"); diff --git a/nfq2/params.c b/nfq2/params.c index a20569b..f02e427 100644 --- a/nfq2/params.c +++ b/nfq2/params.c @@ -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) diff --git a/nfq2/params.h b/nfq2/params.h index 6bec0be..71320ca 100644 --- a/nfq2/params.h +++ b/nfq2/params.h @@ -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;