From 46ea3468e296cb6b201ca7eab6f44afacd2e0a15 Mon Sep 17 00:00:00 2001 From: Asofwar <7397608+Asofwar@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:31:29 +0300 Subject: [PATCH] nft: never TPROXY a client DNS query redirected to the router mangle (priority -149) marks the first packet of a flow before dns_redirect (-101) rewrites it. A LAN query to a resolver inside a captured list (8.8.8.8 via the Google lists) reached the proxy chain redirected to the router's :53 (or :1603) and still marked, so sing-box answered it from a transparent socket bound to 192.168.1.1:53. That socket then took the LAN's queries meant for dnsmasq and never read them: DNS for every client except the source-aware ones timed out until the socket expired, and phones fell back to real IPs outside Prokop. The proxy chain now returns for DNAT packets before any TPROXY rule. --- prokop/files/usr/lib/nft/apply.uc | 8 ++++++++ tests/nft_apply.sh | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/prokop/files/usr/lib/nft/apply.uc b/prokop/files/usr/lib/nft/apply.uc index c8114d2c8..8b367c1c0 100644 --- a/prokop/files/usr/lib/nft/apply.uc +++ b/prokop/files/usr/lib/nft/apply.uc @@ -1561,6 +1561,14 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po !nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", fakeip_range, "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) || !nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", fakeip6_range, "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) || !nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", fakeip6_range, "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) || + // mangle (-149) marks the first packet of a flow before dns_redirect + // (-101) rewrites it, so a client query to a captured resolver such + // as 8.8.8.8 arrives here redirected to the router's own :53 or + // :1603 and still marked. TPROXY of that packet makes sing-box answer + // from a transparent socket bound to that local address, which then + // takes the LAN's queries meant for dnsmasq and never reads them. + // A redirected packet is already delivered locally: never TPROXY it. + !nft_add_rule(table, "proxy", [ "ct", "status", "dnat", "return" ]) || !nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "tcp", "tproxy", "ip", "to", ":" + as_string(tproxy_port), "counter" ]) || !nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "udp", "tproxy", "ip", "to", ":" + as_string(tproxy_port), "counter" ]) || !nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "tcp", "tproxy", "ip6", "to", core_ip.format_ipv6_tproxy_target(tproxy6_address, tproxy_port), "counter" ]) || diff --git a/tests/nft_apply.sh b/tests/nft_apply.sh index 7e8093264..a43369aac 100644 --- a/tests/nft_apply.sh +++ b/tests/nft_apply.sh @@ -231,6 +231,11 @@ for shared_set in prokop_subnets prokop_subnets6 prokop_ports prokop_ip_ports pr done assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tProkopTable\tproxy\tmeta\tmark\t&\t0x00100000\t==\t0x00100000\tmeta\tl4proto\ttcp\ttproxy\tip\tto\t:1602\tcounter' "runtime proxy tcp rule" assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tProkopTable\tproxy\tmeta\tmark\t&\t0x00100000\t==\t0x00100000\tmeta\tl4proto\ttcp\ttproxy\tip6\tto\t[::1]:1602\tcounter' "runtime proxy6 tcp rule" +# mangle marks a client query to a captured resolver (8.8.8.8) before +# dns_redirect sends it to the router's :53; TPROXY of that redirected packet +# left sing-box sockets on 192.168.1.1:53 that swallowed the LAN's DNS. +[ "$(grep -F $'\tProkopTable\tproxy\t' "$NFT_LOG" | grep -F $'\trule\t' | head -n 1 | cut -f7- | tr '\t' ' ')" = 'ct status dnat return' ] || + fail "proxy must skip redirected (DNAT) packets before any TPROXY rule" assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tProkopTable\tmangle_output\tmeta\tmark\t&\t0xff2000ff\t==\t0x00200000\tcounter\treturn' "runtime outbound return on Prokop's mark bits" assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tProkopTable\tmangle_output\tip6\tdaddr\t@localv6\tip6\tdaddr\t!=\tfc00::/18\treturn' "runtime output local6 return preserves FakeIP6 capture" assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tProkopTable\tmangle_output\tjump\tpriority_output_rules' "runtime priority output jump"