zapret-kvn/zapret/lua/zapret-rst-flood.lua
2026-03-19 00:30:27 +03:00

242 lines
8.3 KiB
Lua

--[[
NFQWS2 RST FLOOD - Anti-TSPU IP Throttling Strategy
Obkhod zamedleniya TSPU po IP-adresu. TSPU otslezhivaet TCP-sessii k opredelyonnym IP
i primenyaet throttling cherez N sekund. Eta strategiya:
1. State Table Pollution (faza SYN):
Pered real'nym SYN otpravlyaet N fejkovyh SYN ot sluchajnyh IP s kalibrovannym TTL.
Fejki dohodyat do TSPU no NE do servera. TSPU sozdayot zapisi dlya nesushchestvuyushchih sessij.
2. Periodic RST Reset (faza DATA):
Vo vremya peredachi dannyh periodicheski otpravlyaet RST+ACK s kalibrovannym TTL.
TSPU vidit RST i udalyaet sessiyu iz tablicy. Server RST ne poluchaet.
Sleduyushchie pakety prohodyat kak "novaya" sessiya bez throttling.
Usage:
--lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua --lua-init=@zapret-rst-flood.lua
--lua-desync=rst_flood:flood_count=30:rst_interval=2:ip_autottl=-1,3-20:ttl_fallback=8
--lua-desync=rst_flood:flood_count=50:rst_interval=1.5:fin_mode:src_range=rfc1918
--lua-desync=rst_flood:no_pollution:rst_interval=1:ip_autottl=-2,3-20
--lua-desync=rst_flood:no_pollution:rst_bytes=12000:ip_autottl=-1,3-20:ttl_fallback=4
Parameters:
flood_count=N - number of fake SYNs for pollution (default: 30)
rst_interval=N - seconds between RST resets (default: 0, disabled)
rst_bytes=N - OUTGOING bytes between RST resets (default: 0, disabled)
tracks bytes SENT by client (direct.pbcounter)
client sends ~1KB per ~8KB received, so
rst_bytes=1500 ≈ RST every ~10-15KB from server
at least one of rst_interval/rst_bytes must be set
ttl_fallback=N - fixed TTL when autottl unavailable (default: 8)
fin_mode - alternate RST+ACK and FIN+ACK
src_range=rfc1918 - 10.x.x.x for spoofed source IPs
src_range=cgn - 100.64.x.x CGN range (default)
no_pollution - disable phase 1 (RST only)
no_rst - disable phase 2 (pollution only)
Standard fooling args: ip_autottl, ip_ttl, ip6_ttl, ip6_autottl
Filtering: use --ipset as usual, Lua receives only matching packets
NOTE: Source IP spoofing requires that ISP does not implement BCP38/uRPF ingress filtering.
If spoofed packets are dropped, try src_range=cgn (ISP's own CGN range looks more legitimate).
Phase 2 (RST reset) works without spoofing and is the primary mechanism.
]]
-- ============================================================================
-- HELPERS
-- ============================================================================
-- Random source IPv4 in RFC1918 space (10.0.0.0/8)
local function random_src_rfc1918()
return "\x0a" .. brandom(3)
end
-- Random source IPv4 in CGN space (100.64.0.0/10)
local function random_src_cgn()
return "\x64" .. string.char(64 + math.random(0, 63)) .. brandom(2)
end
-- Get calibrated TTL/hop limit: reaches TSPU but dies before the server
local function get_calibrated_ttl(desync)
-- Try autottl from incoming_ttl (SYN-ACK or ipcache)
if desync.track and desync.track.incoming_ttl and desync.track.incoming_ttl > 0 then
-- Use ip6_autottl for IPv6, ip_autottl for IPv4
local attl_str = desync.dis.ip6 and desync.arg.ip6_autottl or desync.arg.ip_autottl
if attl_str then
local attl = parse_autottl(attl_str)
if attl then
local ttl = autottl(desync.track.incoming_ttl, attl)
if ttl then
return ttl, true
end
end
end
end
-- Fallback to fixed TTL
return tonumber(desync.arg.ttl_fallback) or 8, false
end
-- ============================================================================
-- RST_FLOOD: main strategy function
-- ============================================================================
--[[
rst_flood - anti-TSPU IP throttling via state table pollution + periodic RST
Phase 1 (SYN): flood TSPU state table with fake SYNs from spoofed IPs
Phase 2 (DATA): send calibrated RST by byte count or time to clear TSPU session state
standard args : direction, fooling (ip_autottl), rawsend, reconstruct
arg : flood_count=N (default 30)
arg : rst_interval=N seconds (default 2)
arg : ttl_fallback=N (default 8)
arg : fin_mode - alternate RST and FIN
arg : src_range=rfc1918|cgn (default cgn)
arg : no_pollution - skip phase 1
arg : no_rst - skip phase 2
]]
function rst_flood(ctx, desync)
if not desync.dis.tcp then
if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end
return
end
-- Conntrack required for persistent state and autottl
if not desync.track then return end
-- Only process outgoing packets
direction_cutoff_opposite(ctx, desync)
if not desync.outgoing then return end
-- Persistent state per TCP connection
if not desync.track.lua_state.rst_flood then
desync.track.lua_state.rst_flood = {
last_rst_dt = 0,
last_rst_bytes = 0,
rst_count = 0,
pollution_done = false,
use_fin_next = false,
}
end
local st = desync.track.lua_state.rst_flood
local flags = desync.dis.tcp.th_flags
-- =============================================
-- PHASE 1: State Table Pollution (on outgoing SYN)
-- =============================================
if bitand(flags, TH_SYN + TH_ACK) == TH_SYN
and not desync.arg.no_pollution
and not st.pollution_done
then
local count = tonumber(desync.arg.flood_count) or 30
local ttl, is_auto = get_calibrated_ttl(desync)
local src_range = desync.arg.src_range or "cgn"
local gen_src = (src_range == "rfc1918") and random_src_rfc1918 or random_src_cgn
DLOG("rst_flood: POLLUTION "..count.." fake SYNs, TTL="..ttl
..(is_auto and "(auto)" or "(fallback)")
..", src="..src_range)
for i = 1, count do
local dis = deepcopy(desync.dis)
-- Spoof source IP and set calibrated TTL
if dis.ip then
dis.ip.ip_src = gen_src()
dis.ip.ip_ttl = ttl
elseif dis.ip6 then
-- IPv6: cannot spoof src easily, but set hop limit
dis.ip6.ip6_hlim = ttl
end
-- Random source port and sequence number
dis.tcp.th_sport = math.random(1024, 60000)
dis.tcp.th_seq = math.random(1, 2000000000)
dis.tcp.th_flags = TH_SYN
dis.tcp.th_win = 64240
dis.payload = ""
rawsend_dissect(dis, rawsend_opts(desync))
end
st.pollution_done = true
-- VERDICT_PASS: real SYN goes through unmodified
return
end
-- =============================================
-- PHASE 2: RST Reset (by server bytes or time)
-- =============================================
if not desync.arg.no_rst then
local rst_interval = tonumber(desync.arg.rst_interval) or 0
local rst_bytes = tonumber(desync.arg.rst_bytes) or 0
local dt = desync.track.pos.dt or 0
-- Track outgoing bytes as proxy for connection activity
-- (reverse/server pbcounter stays 0 — range_in=x0 prevents counting)
local dir = desync.track.pos.direct
local out_bytes = dir and dir.pbcounter or 0
-- Trigger RST by outgoing bytes OR by time (whichever fires first)
local need_rst = false
local trigger = ""
-- Byte-based: use outgoing bytes as proxy
-- Client sends ~1KB per ~5-8KB received from server
-- So rst_bytes=1500 ≈ RST every ~10-15KB from server
if rst_bytes > 0 and (out_bytes - st.last_rst_bytes) >= rst_bytes then
need_rst = true
trigger = "out="..out_bytes
-- Time-based: fires on any outgoing packet
elseif rst_interval > 0 and (dt - st.last_rst_dt) >= rst_interval then
need_rst = true
trigger = "t="..string.format("%.1f", dt)
end
if need_rst then
local ttl, is_auto = get_calibrated_ttl(desync)
local dis = deepcopy(desync.dis)
dis.payload = ""
-- Set calibrated TTL/hop limit (reaches TSPU, dies before server)
if dis.ip then
dis.ip.ip_ttl = ttl
elseif dis.ip6 then
dis.ip6.ip6_hlim = ttl
end
-- Choose RST+ACK or FIN+ACK (if fin_mode)
if desync.arg.fin_mode and st.use_fin_next then
dis.tcp.th_flags = TH_FIN + TH_ACK
DLOG("rst_flood: FIN+ACK #"..st.rst_count
.." TTL="..ttl..(is_auto and "(auto)" or "(fb)")
.." ["..trigger.."]")
else
dis.tcp.th_flags = TH_RST + TH_ACK
DLOG("rst_flood: RST+ACK #"..st.rst_count
.." TTL="..ttl..(is_auto and "(auto)" or "(fb)")
.." ["..trigger.."]")
end
rawsend_dissect(dis, rawsend_opts(desync))
st.last_rst_dt = dt
st.last_rst_bytes = out_bytes
st.rst_count = st.rst_count + 1
if desync.arg.fin_mode then
st.use_fin_next = not st.use_fin_next
end
end
end
-- VERDICT_PASS: real packet always goes through unmodified
end