diff --git a/docs/changes.txt b/docs/changes.txt index e6a3366..07bfbf2 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -196,8 +196,11 @@ v0.8.1 * nfqws2: fix critical bug - wrong ipv6 dissection * zapret-auto: fix standard_failure_detector http redirect regression -0.8.7 +0.9.0 * nfqws2: removed hard check for host: presence in http_req * nfqws2: file open test before destroying in-memory content of ipset/hostlist * github actions: lua 5.5 +* nfqws2: enable dead reasm protection in wsize=0 case +* nfqws2: --intercept +* winws2: changed icon to multi-res png up to 256px diff --git a/lua/zapret-antidpi.lua b/lua/zapret-antidpi.lua index 9b12ee5..3872ef5 100644 --- a/lua/zapret-antidpi.lua +++ b/lua/zapret-antidpi.lua @@ -65,8 +65,10 @@ standard ipfrag : * ipfrag[=frag_function] - ipfrag function name. "ipfrag2" by default if empty * ipfrag_disorder - send fragments from last to first -* ipfrag2 : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 8 -* ipfrag2 : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 32 +* ipfrag2 : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 32 +* ipfrag2 : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 8 +* ipfrag2 : ipfrag_pos_icmp - icmp frag position. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 8 +* ipfrag2 : ipfrag_pos - frag position for other L4. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 32 * ipfrag2 : ipfrag_next - next protocol field in ipv6 fragment extenstion header of the second fragment. same as first by default. ]] @@ -114,7 +116,8 @@ end -- standard args : direction function http_domcase(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -140,7 +143,8 @@ end -- arg : spell= . spelling of the "Host" header. must be exactly 4 chars long function http_hostcase(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -171,7 +175,8 @@ end -- NOTE : if using with other http tampering methodeol should be the last ! function http_methodeol(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -202,7 +207,8 @@ end -- standard args : direction function http_unixeol(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -271,7 +277,8 @@ function synack_split(ctx, desync) instance_cutoff_shim(ctx, desync) -- mission complete end else - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end end end @@ -288,7 +295,8 @@ function synack(ctx, desync) instance_cutoff_shim(ctx, desync) -- mission complete end else - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end end end @@ -306,7 +314,8 @@ function wsize(ctx, desync) instance_cutoff_shim(ctx, desync) -- mission complete end else - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end end end @@ -317,7 +326,8 @@ end -- arg : forced_cutoff= - comma separated list of payloads that trigger forced wssize cutoff. by default - any non-empty payload function wssize(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end local verdict = VERDICT_PASS @@ -346,7 +356,8 @@ end -- arg: sni_last - add name to the end function tls_client_hello_clone(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -388,7 +399,8 @@ function syndata(ctx, desync) instance_cutoff_shim(ctx, desync) -- mission complete end else - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end end end @@ -397,7 +409,8 @@ end -- arg : rstack - send RST,ACK instead of RST function rst(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -424,8 +437,8 @@ end -- arg : tls_mod= - comma separated list of tls mods : rnd,rndsni,sni=,dupsid,padencap . sni=%var is supported function fake(ctx, desync) direction_cutoff_opposite(ctx, desync) - -- by default process only outgoing known payloads - if direction_check(desync) and payload_check(desync) then + -- by default process only outgoing known payloads. works only for tcp and udp + if (desync.dis.tcp or desync.dis.udp) and direction_check(desync) and payload_check(desync) then if replay_first(desync) then if not desync.arg.blob then error("fake: 'blob' arg required") @@ -457,7 +470,8 @@ end -- arg : nodrop - do not drop current dissect function multisplit(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -570,7 +584,8 @@ end -- arg : nodrop - do not drop current dissect function multidisorder(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -621,7 +636,8 @@ end -- arg : optional - use zero pattern if seqovl_pattern blob is absent function multidisorder_legacy(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -678,7 +694,8 @@ end -- arg : nodrop - do not drop current dissect function hostfakesplit(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -795,7 +812,8 @@ end -- arg : nodrop - do not drop current dissect function fakedsplit(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -899,7 +917,8 @@ end -- arg : nodrop - do not drop current dissect function fakeddisorder(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -1010,7 +1029,8 @@ end -- arg : optional - skip if blob is absent. use zero pattern if seqovl_pattern blob is absent function tcpseg(ctx, desync) if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -1064,7 +1084,8 @@ end function oob(ctx, desync) if not desync.track then return end if not desync.dis.tcp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end local key = desync.func_instance.."_syn" @@ -1163,7 +1184,8 @@ end -- arg : pattern_offset=N . offset in the pattern. 0 by default function udplen(ctx, desync) if not desync.dis.udp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) @@ -1199,7 +1221,8 @@ end -- arg : dn=N - message starts from "dN". 3 by default function dht_dn(ctx, desync) if not desync.dis.udp then - instance_cutoff_shim(ctx, desync) + -- do not cutoff on related icmp + if not desync.dis.icmp then instance_cutoff_shim(ctx, desync) end return end direction_cutoff_opposite(ctx, desync) diff --git a/lua/zapret-auto.lua b/lua/zapret-auto.lua index 9ed5ed1..1fdba33 100644 --- a/lua/zapret-auto.lua +++ b/lua/zapret-auto.lua @@ -179,7 +179,7 @@ function standard_failure_detector(desync, crec) DLOG("standard_failure_detector: not counting incoming RST s"..seq.." beyond s"..arg.inseq) end end - elseif not arg.no_http_redirect and desync.l7payload=="http_reply" and desync.track and desync.track.hostname then + elseif not arg.no_http_redirect and desync.l7payload=="http_reply" and desync.track.hostname then local hdis = http_dissect_reply(desync.dis.payload) if hdis and (hdis.code==302 or hdis.code==307) then local idx_loc = array_field_search(hdis.headers, "header_low", "location") @@ -403,7 +403,7 @@ function cond_payload_str(desync) if not desync.arg.pattern then error("cond_payload_str: missing 'pattern'") end - return string.find(desync.dis.payload,desync.arg.pattern,1,true) + return desync.dis.payload and string.find(desync.dis.payload,desync.arg.pattern,1,true) end -- check iff function available. error if not function require_iff(desync, name) @@ -458,13 +458,17 @@ end function repeater(ctx, desync) local repeats = tonumber(desync.arg.repeats) if not repeats then - error("repeat: missing 'repeats'") + error("repeater: missing 'repeats'") end local iff = desync.arg.iff or "cond_true" if type(_G[iff])~="function" then - error(name..": invalid 'iff' function '"..iff.."'") + error("repeater: invalid 'iff' function '"..iff.."'") end orchestrate(ctx, desync) + if #desync.plan==0 then + DLOG("repeater: execution plan is empty - nothing to repeat") + return + end local neg = desync.arg.neg local stop = desync.arg.stop local clear = desync.arg.clear diff --git a/lua/zapret-lib.lua b/lua/zapret-lib.lua index 8bb0aa7..4f810a7 100644 --- a/lua/zapret-lib.lua +++ b/lua/zapret-lib.lua @@ -1,4 +1,4 @@ -NFQWS2_COMPAT_VER_REQUIRED=4 +NFQWS2_COMPAT_VER_REQUIRED=5 if NFQWS2_COMPAT_VER~=NFQWS2_COMPAT_VER_REQUIRED then error("Incompatible NFQWS2_COMPAT_VER. Use pktws and lua scripts from the same release !") @@ -629,7 +629,42 @@ function parse_tcp_flags(s) end end return f -end +end + +-- get ip protocol from l3 headers +function ip_proto_l3(dis) + if dis.ip then + return dis.ip.ip_p + elseif dis.ip6 then + return #dis.ip6.exthdr==0 and dis.ip6.ip6_nxt or dis.ip6.exthdr[#dis.ip6.exthdr].next + end +end +-- get ip protocol from l4 headers +function ip_proto_l4(dis) + if dis.tcp then + return IPPROTO_TCP + elseif dis.udp then + return IPPROTO_UDP + elseif dis.ip then + return dis.icmp and IPPROTO_ICMP or nil + elseif dis.ip6 then + return dis.icmp and IPPROTO_ICMPV6 or nil + end +end +function ip_proto(dis) + return ip_proto_l4(dis) or ip_proto_l3(dis) +end +-- discover ip protocol and fix "next" fields +function fix_ip_proto(dis, proto) + local pr = proto or ip_proto(dis) + if pr then + if dis.ip then + dis.ip.ip_p = pr + elseif dis.ip6 then + fix_ip6_next(dis.ip6, pr) + end + end +end -- find first tcp options of specified kind in dissect.tcp.options function find_tcp_option(options, kind) @@ -980,7 +1015,7 @@ function l3_extra_len(dis, ip6_exthdr_last_idx) end elseif dis.ip6 and dis.ip6.exthdr then local ct - if ip6_exthdr_last_idx and ip6_exthdr_last_idx<=#dis.ip6.exthdr then + if ip6_exthdr_last_idx and ip6_exthdr_last_idx>=0 and ip6_exthdr_last_idx<=#dis.ip6.exthdr then ct = ip6_exthdr_last_idx else ct = #dis.ip6.exthdr @@ -1007,6 +1042,8 @@ function l4_base_len(dis) return TCP_BASE_LEN elseif dis.udp then return UDP_BASE_LEN + elseif dis.icmp then + return ICMP_BASE_LEN else return 0 end @@ -1253,6 +1290,31 @@ function host_or_ip(desync) return host_ip(desync) end +-- rate limited update of global ifaddrs +function update_ifaddrs() + if ifaddrs then + local now = os.time() + if not ifaddrs_last then ifaddrs_last = now end + if ifaddrs_last~=now then + ifaddrs = get_ifaddrs() + ifaddrs_last = now + end + else + ifaddrs = get_ifaddrs() + end +end +-- search ifaddrs for ip and return interface name or nil if not found +-- do not call get_ifaddrs too often to avoid overhead +function ip2ifname(ip) + update_ifaddrs() + if not ifaddrs then return nil end + for ifname,ifinfo in pairs(ifaddrs) do + if array_field_search(ifinfo.addr, "addr", ip) then + return ifname + end + end +end + function is_absolute_path(path) if string.sub(path,1,1)=='/' then return true end local un = uname() @@ -1298,8 +1360,10 @@ end -- standard fragmentation to 2 ip fragments -- function returns 2 dissects with fragments --- option : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 8 --- option : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 32 +-- option : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 8 +-- option : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 32 +-- option : ipfrag_pos_icmp - icmp frag position. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 8 +-- option : ipfrag_pos - icmp frag position for other L4. ipv4 : starting from L4 header. ipv6: starting from fragmentable part. must be multiple of 8. default 32 -- option : ipfrag_next - next protocol field in ipv6 fragment extenstion header of the second fragment. same as first by default. function ipfrag2(dis, ipfrag_options) local function frag_idx(exthdr) @@ -1333,6 +1397,8 @@ function ipfrag2(dis, ipfrag_options) pos = ipfrag_options.ipfrag_pos_tcp or 32 elseif dis.udp then pos = ipfrag_options.ipfrag_pos_udp or 8 + elseif dis.icmp then + pos = ipfrag_options.ipfrag_pos_icmp or 8 else pos = ipfrag_options.ipfrag_pos or 32 end @@ -1349,12 +1415,8 @@ function ipfrag2(dis, ipfrag_options) if (pos+l3)>0xFFFF then error("ipfrag2: too high frag offset") end - local plen = l3 + l4_len(dis) + #dis.payload - if (pos+l3)>=plen then - DLOG("ipfrag2: ip frag pos exceeds packet length. ipfrag cancelled.") - return nil - end + local plen = l3 + l4_len(dis) + #dis.payload if dis.ip then -- ipv4 frag is done by both lua and C part -- lua code must correctly set ip_len, IP_MF and ip_off and provide full unfragmented payload @@ -1362,6 +1424,11 @@ function ipfrag2(dis, ipfrag_options) -- ip_off must be set to fragment offset and IP_MF bit must be set if it's not the last fragment -- C code constructs unfragmented packet then moves everything after ip header according to ip_off and ip_len + if (pos+l3)>=plen then + DLOG("ipfrag2: ip frag pos "..pos.." exceeds packet length. ipfrag cancelled.") + return nil + end + -- ip_id must not be zero or fragment will be dropped local ip_id = dis.ip.ip_id==0 and math.random(1,0xFFFF) or dis.ip.ip_id dis1 = deepcopy(dis) @@ -1382,7 +1449,15 @@ function ipfrag2(dis, ipfrag_options) -- C code constructs unfragmented packet then moves fragmentable part as needed local idxfrag = frag_idx(dis.ip6.exthdr) - local l3extra = l3_extra_len(dis, idxfrag-1) + 8 -- all ext headers before frag + 8 bytes for frag header + local l3extra = l3_extra_len(dis, idxfrag-1) -- all ext headers before frag + + l3 = l3_base_len(dis) + l3extra + if (pos+l3)>=plen then + DLOG("ipfrag2: ip frag pos "..pos.." exceeds packet length. ipfrag cancelled.") + return nil + end + + l3extra = l3extra + 8 -- + 8 bytes for frag header local ident = math.random(1,0xFFFFFFFF) dis1 = deepcopy(dis) @@ -1398,7 +1473,6 @@ function ipfrag2(dis, ipfrag_options) end dis2.ip6.ip6_plen = plen - IP6_BASE_LEN + 8 - pos -- packet len without frag + 8 byte frag header - ipv6 base header end - return {dis1,dis2} end diff --git a/lua/zapret-tests.lua b/lua/zapret-tests.lua index 9940eb6..e0531e7 100644 --- a/lua/zapret-tests.lua +++ b/lua/zapret-tests.lua @@ -13,12 +13,14 @@ end function test_all(...) - test_run({test_crypto, test_bin, test_gzip, test_ipstr, test_dissect, test_csum, test_resolve, test_rawsend},...) + test_run({ + test_crypto, test_bin, test_gzip, test_ipstr, test_dissect, test_csum, test_resolve, + test_get_source_ip, test_ifaddrs, test_rawsend},...) end function test_crypto(...) - test_run({test_random, test_aes, test_aes_gcm, test_aes_ctr, test_hkdf, test_hash},...) + test_run({test_random, test_bop, test_aes, test_aes_gcm, test_aes_ctr, test_hkdf, test_hash},...) end function test_random() @@ -31,6 +33,30 @@ function test_random() end end +function test_bop() + for n,test in ipairs( + { + { fb = bxor, fbit = bitxor, nb = "bxor", nbit="bitxor" }, + { fb = bor, fbit = bitor, nb = "bor", nbit="bitor" }, + { fb = band, fbit = bitand, nb = "band", nbit="bitand" } + }) do + for k=1,5 do + local r = {} + for i=1,6 do r[i] = math.random(0,0xFFFFFFFFFFFF) end + local v1 = bu48(r[1])..bu48(r[2])..bu48(r[3]) + local v2 = bu48(r[4])..bu48(r[5])..bu48(r[6]) + print("x1 : "..string2hex(v1)) + print("x2 : "..string2hex(v2)) + local v3 = test.fb(v1,v2) + local v4 = bu48(test.fbit(r[1],r[4]))..bu48(test.fbit(r[2],r[5]))..bu48(test.fbit(r[3],r[6])) + print(test.nb.." : "..string2hex(v3)) + print(test.nbit.." : "..string2hex(v4)) + print("result : "..(v3==v4 and "OK" or "FAIL")) + test_assert(v3==v4) + end + end +end + function test_hash() local hashes={} for i=1,5 do @@ -455,13 +481,56 @@ function test_dissect() } raw1 = reconstruct_dissect(ip_tcp) print("IP+TCP : "..string2hex(raw1)) - dis1 = dissect(raw1); + dis1 = dissect(raw1) raw2 = reconstruct_dissect(dis1) - dis2 = dissect(raw2); + dis2 = dissect(raw2) print("IP+TCP2: "..string2hex(raw2)) print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) test_assert(raw1==raw2) + print("IP standalone") + raw1 = reconstruct_iphdr(ip_tcp.ip) + print("IP1: "..string2hex(raw1)) + dis1 = dissect_iphdr(raw1) + raw2 = reconstruct_iphdr(dis1) + print("IP2: "..string2hex(raw2)) + print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) + test_assert(raw1==raw2) + + print("TCP standalone") + raw1 = reconstruct_tcphdr(ip_tcp.tcp) + print("TCP1: "..string2hex(raw1)) + dis1 = dissect_tcphdr(raw1) + raw2 = reconstruct_tcphdr(dis1) + print("TCP2: "..string2hex(raw2)) + print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) + test_assert(raw1==raw2) + + local ip_icmp = { + ip = { + ip_tos = math.random(0,255), + ip_id = math.random(0,0xFFFF), + ip_off = 0, + ip_ttl = math.random(0,255), + ip_p = IPPROTO_ICMP, + ip_src = brandom(4), + ip_dst = brandom(4), + options = brandom(math.random(0,40)) + }, + icmp = { + icmp_type = ICMP_DEST_UNREACH, icmp_code=ICMP_UNREACH_PORT, + icmp_data = math.random(1,0xFFFFFFFF) + } + } + print("ICMP standalone") + raw1 = reconstruct_icmphdr(ip_icmp.icmp) + print("ICMP1: "..string2hex(raw1)) + dis1 = dissect_icmphdr(raw1) + raw2 = reconstruct_icmphdr(dis1) + print("ICMP2: "..string2hex(raw2)) + print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) + test_assert(raw1==raw2) + local ip6_udp = { ip6 = { ip6_flow = 0x60000000 + math.random(0,0xFFFFFFF), @@ -482,18 +551,37 @@ function test_dissect() raw1 = reconstruct_dissect(ip6_udp) print("IP6+UDP : "..string2hex(raw1)) - dis1 = dissect(raw1); + dis1 = dissect(raw1) raw2 = reconstruct_dissect(dis1) - dis2 = dissect(raw2); + dis2 = dissect(raw2) print("IP6+UDP2: "..string2hex(raw2)) print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) test_assert(raw1==raw2) + + print("UDP standalone") + raw1 = reconstruct_udphdr(ip6_udp.udp) + print("UDP1: "..string2hex(raw1)) + dis1 = dissect_udphdr(raw1) + raw2 = reconstruct_udphdr(dis1) + print("UDP2: "..string2hex(raw2)) + print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) + test_assert(raw1==raw2) + + print("IP6 standalone") + ip6_udp.ip6.ip6_plen = nil; + raw1 = reconstruct_ip6hdr(ip6_udp.ip6,{ip6_last_proto=IPPROTO_UDP}) + print("IP1: "..string2hex(raw1)) + dis1 = dissect_ip6hdr(raw1) + raw2 = reconstruct_ip6hdr(dis1,{ip6_last_proto=IPPROTO_UDP}) + print("IP2: "..string2hex(raw2)) + print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) + test_assert(raw1==raw2) end end function test_csum() local payload = brandom(math.random(10,20)) - local ip4b, ip6b, raw, tcpb, udpb, dis1, dis2 + local ip4b, ip6b, raw, tcpb, udpb, icmpb, dis1, dis2 local ip = { ip_tos = math.random(0,255), ip_id = math.random(0,0xFFFF), @@ -626,6 +714,37 @@ function test_csum() dis2 = dissect(ip6b..udpb..payload) print( dis1.udp.uh_sum==dis2.udp.uh_sum and "UDP+IP6 CSUM OK" or "UDP+IP6 CSUM FAILED" ) test_assert(dis1.udp.uh_sum==dis2.udp.uh_sum) + + local icmp = { + icmp_type = math.random(0,0xFF), icmp_code=math.random(0,0xFF), + icmp_data = math.random(0,0xFFFFFFFF) + } + ip.ip_p = IPPROTO_ICMP + ip4b = reconstruct_iphdr(ip) + ip6.ip6_plen = packet_len({ip6=ip6,icmp=icmp,payload=payload}) - IP6_BASE_LEN + ip6b = reconstruct_ip6hdr(ip6, {ip6_last_proto=IPPROTO_ICMPV6}) + + icmpb = reconstruct_icmphdr(icmp) + raw = bu8(icmp.icmp_type) .. + bu8(icmp.icmp_code) .. + bu16(0) .. + bu32(icmp.icmp_data) + print( raw==icmpb and "ICMP RECONSTRUCT OK" or "ICMP RECONSTRUCT FAILED" ) + test_assert(raw==icmpb) + + raw = reconstruct_dissect({ip=ip, icmp=icmp, payload=payload}) + dis1 = dissect(raw) + icmpb = csum_icmp_fix(ip4b,icmpb,payload) + dis2 = dissect(ip4b..icmpb..payload) + print( dis1.icmp.icmp_cksum==dis2.icmp.icmp_cksum and "ICMP+IP4 CSUM OK" or "ICMP+IP4 CSUM FAILED" ) + test_assert(dis1.icmp.icmp_cksum==dis2.icmp.icmp_cksum) + + raw = reconstruct_dissect({ip6=ip6, icmp=icmp, payload=payload}) + dis1 = dissect(raw) + icmpb = csum_icmp_fix(ip6b,icmpb,payload) + dis2 = dissect(ip6b..icmpb..payload) + print( dis1.icmp.icmp_cksum==dis2.icmp.icmp_cksum and "ICMP+IP6 CSUM OK" or "ICMP+IP6 CSUM FAILED" ) + test_assert(dis1.icmp.icmp_cksum==dis2.icmp.icmp_cksum) end function test_resolve() @@ -677,6 +796,26 @@ function test_resolve() print("resolve_pos http non-existent : "..m.." : "..tostring(pos)) end +function test_get_source_ip(opts) + for k,d in ipairs({ + '127.0.0.1','192.168.1.1','10.1.1.1','1.1.1.1','255.255.255.255', + '::1','fc81::4','2a06::1','2001:470::1','2002:0101:0101::1','::1.1.1.1'}) + do + local src = get_source_ip(pton(d)) + print((src and ntop(src) or "?").." => "..d) + end +end +function test_ifaddrs(opts) + local ifa = get_ifaddrs() + test_assert(ifa) + for ifname,ifinfo in pairs(ifa) do + print(ifname.." index="..tostring(ifinfo.index).." mtu="..tostring(ifinfo.mtu)) + for i,addr in ipairs(ifinfo.addr) do + print(" "..ntop(addr.addr)..(addr.netmask and " mask "..tostring(ntop(addr.netmask)) or "")) + end + end +end + function test_rawsend(opts) local ifout = (opts and opts.ifout) and opts.ifout local function rawsend_fail_warning() @@ -718,14 +857,15 @@ function test_rawsend(opts) local payload = brandom(math.random(100,1200)) local b + local target = pton("192.168.1.2") ip = { ip_tos = 0, ip_id = math.random(0,0xFFFF), ip_off = 0, ip_ttl = 1, ip_p = IPPROTO_UDP, - ip_src = pton("192.168.1.1"), - ip_dst = pton("192.168.1.2") + ip_src = get_source_ip(target), + ip_dst = target } udp = { uh_sport = math.random(0,0xFFFF), @@ -749,11 +889,12 @@ function test_rawsend(opts) print("send ipv4 udp using pure rawsend without dissect") test_assert(rawsend_print(raw, {repeats=5})) + target = pton("fdce:3124:164a:5318::2") ip6 = { ip6_flow = 0x60000000, ip6_hlim = 1, - ip6_src = pton("fdce:3124:164a:5318::1"), - ip6_dst = pton("fdce:3124:164a:5318::2") + ip6_src = get_source_ip(target), + ip6_dst = target } dis = {ip6 = ip6, udp = udp, payload = payload} print("send ipv6 udp") @@ -784,8 +925,8 @@ function test_rawsend(opts) test_assert(rawsend_dissect_print(d)) end - table.insert(ip6.exthdr, { type = IPPROTO_DSTOPTS, data = "\x00\x00\x00\x00\x00\x00" }) - table.insert(ip6.exthdr, { type = IPPROTO_DSTOPTS, data = "\x00\x00\x00\x00\x00\x00" }) + insert_ip6_exthdr(ip6, nil, IPPROTO_DSTOPTS, "\x00\x00\x00\x00\x00\x00") + insert_ip6_exthdr(ip6, nil, IPPROTO_DSTOPTS, "\x00\x00\x00\x00\x00\x00") ip6.ip6_flow = 0x60001234; ddis = ipfrag2(dis, {ipfrag_pos_udp = 80}) for k,d in ipairs(ddis) do @@ -793,13 +934,30 @@ function test_rawsend(opts) test_assert(rawsend_dissect_print(d, {fwmark = 0x50EA})) end - fix_ip6_next(ip6) -- required to forge next proto in the second fragment + fix_ip_proto(dis) -- ip6_preserve_next requires next fields in ip6.exthdr ip6.ip6_flow = 0x6000AE38; - ddis = ipfrag2(dis, {ipfrag_pos_udp = 80, ipfrag_next = IPPROTO_TCP}) + ddis = ipfrag2(dis, {ipfrag_pos_udp = 72, ipfrag_next = IPPROTO_TCP}) for k,d in ipairs(ddis) do print("send ipv6 udp frag "..k.." with hopbyhop, destopt ext headers in unfragmentable part and another destopt ext header in fragmentable part. forge next proto in fragment header of the second fragment to TCP") -- reconstruct dissect using next proto fields in the dissect. do not auto fix next proto chain. -- by default reconstruct fixes next proto chain test_assert(rawsend_dissect_print(d, {fwmark = 0x409A, repeats=2}, {ip6_preserve_next = true})) end + + local icmp = { + icmp_type = ICMP_ECHO, icmp_code=0, + icmp_data = u32(bu16(math.random(1,0xFFFF))..bu16(1)) + } + ip.ip_p = IPPROTO_ICMP + payload=brandom_az09(math.random(10,1100)) + dis = {ip = ip, icmp = icmp, payload = payload} + print("send ipv4 icmp") + test_assert(rawsend_dissect_print(dis, {fwmark = 0xD133, repeats=3})) + + ip6.exthdr={{ type = IPPROTO_HOPOPTS, data = "\x00\x00\x00\x00\x00\x00" }} + ip6.ip6_flow=0x60009E3B; + icmp.icmp_type = ICMP6_ECHO_REQUEST; + dis = {ip6 = ip6, icmp = icmp, payload = payload} + print("send ipv6 icmp") + test_assert(rawsend_dissect_print(dis, {fwmark = 0x8E10, repeats=3})) end diff --git a/lua/zapret-wgobfs.lua b/lua/zapret-wgobfs.lua index 203b463..5be95d5 100644 --- a/lua/zapret-wgobfs.lua +++ b/lua/zapret-wgobfs.lua @@ -14,7 +14,7 @@ function wgobfs(ctx, desync) local function genkey() -- cache key in a global var bound to instance name local key_cache_name = desync.func_instance.."_key" - key = _G[key_cache_name] + local key = _G[key_cache_name] if not key then key = hkdf("sha256", "wgobfs_salt", desync.arg.secret, nil, 16) _G[key_cache_name] = key diff --git a/nfq2/desync.c b/nfq2/desync.c index 21c300c..069ca52 100644 --- a/nfq2/desync.c +++ b/nfq2/desync.c @@ -232,7 +232,7 @@ static bool dp_match( default: if (!ipp_filters_match(&dp->ipf, l3proto)) return false; } - + if (l3proto == IPPROTO_ICMP && !icmp_filters_match(&dp->icf, icmp_type, icmp_code)) // icmp filter does not match return false; diff --git a/nfq2/nfqws.c b/nfq2/nfqws.c index e2cfdb8..2fe8d9d 100644 --- a/nfq2/nfqws.c +++ b/nfq2/nfqws.c @@ -498,7 +498,7 @@ static int dvt_main(void) memset(&bp6, 0, sizeof(bp6)); bp6.sin6_family = AF_INET6; bp6.sin6_port = htons(params.port); - + DLOG_CONDUP("creating divert6 socket\n"); fd[1] = socket_divert(AF_INET6); if (fd[1] == -1) {