Features: - Per-Section MAC Exclusions: nft, generator_routes, section.js, i18n - Per-Section Routed DNS: connections, generator_routes, nft, validator, types.ts, section.js, state.uc, reload.uc - Boot Latency Sweep: ui.uc latency boot sweep with fixture dispatchers - WDTT provider: full backend (common, validator, runtime), sing-box outbound, diagnostics, component install, updater, i18n, section.js UI - OlcRTC provider: full backend (common, validator, runtime), sing-box outbound, diagnostics, component install, updater, i18n, section.js UI - WireGuard-over-TURN tunnel (WDTT) and TCP-over-WebRTC tunnel (OlcRTC) as sidecar processes with local SOCKS5, routed through sing-box outbound Bug fixes: - route.uc: restrict hijack-dns to local DNS inbounds only (dns-in, source-dns-in, dns-health-*) — fixes socket collision between sing-box and dnsmasq on port 53 via SO_REUSEADDR transparent write-back sockets - runtime.uc: add Port 53 socket collision diagnostic with auto-fix (sing-box restart to clear parasitic UNCONN sockets with Recv-Q) - runtime.uc: add dnsmasq dns_redirect check with auto-fix — disables firewall4 DNAT that causes TProxy to rewrite destination to router :53 - failover.uc: add wdtt/olcrtc to failover candidate actions - telegram.uc: show WDTT/OlcRTC installed status in /info Tests: 135/155 shell + 591/591 frontend
888 lines
39 KiB
Bash
888 lines
39 KiB
Bash
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
|
NFT_RUNTIME="$ROOT_DIR/tachyon/files/usr/lib/nft/apply.uc"
|
|
WORK_DIR="$(mktemp -d)"
|
|
NFT_LOG="$WORK_DIR/nft.log"
|
|
LOGGER_LOG="$WORK_DIR/logger.log"
|
|
IP_LOG="$WORK_DIR/ip.log"
|
|
SYSCTL_LOG="$WORK_DIR/sysctl.log"
|
|
|
|
nft_ucode() {
|
|
ucode -L "$TACHYON_LIB" "$NFT_RUNTIME" "$@"
|
|
}
|
|
|
|
cleanup() {
|
|
rm -rf "$WORK_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
fail() {
|
|
printf 'FAIL: %s\n' "$1" >&2
|
|
printf 'nft log:\n' >&2
|
|
cat "$NFT_LOG" >&2 2>/dev/null || true
|
|
printf 'logger log:\n' >&2
|
|
cat "$LOGGER_LOG" >&2 2>/dev/null || true
|
|
exit 1
|
|
}
|
|
|
|
assert_contains() {
|
|
local file="$1"
|
|
local expected="$2"
|
|
local label="$3"
|
|
|
|
grep -Fq "$expected" "$file" || fail "$label: expected '$expected'"
|
|
}
|
|
|
|
assert_eq() {
|
|
local expected="$1"
|
|
local actual="$2"
|
|
local label="$3"
|
|
|
|
[ "$actual" = "$expected" ] || fail "$label: expected '$expected', got '$actual'"
|
|
}
|
|
|
|
assert_line_before() {
|
|
local file="$1"
|
|
local first="$2"
|
|
local second="$3"
|
|
local label="$4"
|
|
local first_line
|
|
local second_line
|
|
|
|
first_line="$(awk -v pat="$first" 'index($0, pat) { print NR; exit }' "$file")"
|
|
second_line="$(awk -v pat="$second" 'index($0, pat) { print NR; exit }' "$file")"
|
|
[ -n "$first_line" ] || fail "$label: missing first line '$first'"
|
|
[ -n "$second_line" ] || fail "$label: missing second line '$second'"
|
|
[ "$first_line" -lt "$second_line" ] || fail "$label: expected first line before second"
|
|
}
|
|
|
|
if grep -n 'require("uci").cursor' "$NFT_RUNTIME" >/dev/null 2>&1 ||
|
|
grep -n 'uci -q' "$NFT_RUNTIME" >/dev/null 2>&1 ||
|
|
grep -n '"uci", "-q"' "$NFT_RUNTIME" >/dev/null 2>&1 ||
|
|
grep -n 'command_from_args(\[ "uci"' "$NFT_RUNTIME" >/dev/null 2>&1; then
|
|
fail "nft/apply.uc must use core.uci instead of direct UCI cursor or CLI access"
|
|
fi
|
|
grep -Fq 'require("core.uci")' "$NFT_RUNTIME" ||
|
|
fail "nft/apply.uc must import core.uci"
|
|
|
|
mkdir -p "$WORK_DIR/bin"
|
|
cat >"$WORK_DIR/bin/nft" <<'NFT'
|
|
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
{
|
|
printf 'nft'
|
|
for arg in "$@"; do
|
|
printf '\t%s' "$arg"
|
|
done
|
|
printf '\n'
|
|
} >> "${NFT_LOG:?}"
|
|
|
|
if [ "$1" = "-f" ] && [ -f "$2" ]; then
|
|
while read -r line || [ -n "$line" ]; do
|
|
if [[ "$line" =~ ^add[[:space:]]+element[[:space:]]+inet[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+(.*)$ ]]; then
|
|
printf 'nft\tadd\telement\tinet\t%s\t%s\t%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" >> "${NFT_LOG:?}"
|
|
else
|
|
printf '%s\n' "$line" >> "${NFT_LOG:?}"
|
|
fi
|
|
done < "$2"
|
|
fi
|
|
|
|
if [ "$#" -eq 5 ] && [ "$1" = "list" ] && [ "$2" = "chain" ] &&
|
|
[ "$3" = "inet" ] && [ "$5" = "mangle" ] && [ -n "${NFT_MANGLE_CHAIN_OUTPUT:-}" ]; then
|
|
printf '%s\n' "$NFT_MANGLE_CHAIN_OUTPUT"
|
|
fi
|
|
|
|
if [ "$#" -eq 4 ] && [ "$1" = "list" ] && [ "$2" = "table" ] &&
|
|
[ "$3" = "inet" ]; then
|
|
if [ "${NFT_LIST_TABLE_FAIL:-0}" = "1" ]; then
|
|
printf 'table %s is missing\n' "$4" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
NFT
|
|
chmod 0755 "$WORK_DIR/bin/nft"
|
|
|
|
cat >"$WORK_DIR/bin/ip" <<'IP'
|
|
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
{
|
|
printf 'ip'
|
|
for arg in "$@"; do
|
|
printf '\t%s' "$arg"
|
|
done
|
|
printf '\n'
|
|
} >> "${IP_LOG:?}"
|
|
|
|
if [ "$#" -eq 4 ] && [ "$1" = "route" ] && [ "$2" = "list" ] && [ "$3" = "table" ]; then
|
|
if [ "${IP_ROUTE_LIST_FAIL:-0}" = "1" ]; then
|
|
printf 'Error: ipv4: FIB table does not exist\nDump terminated\n' >&2
|
|
exit 2
|
|
fi
|
|
printf '%s\n' "${IP_ROUTE_OUTPUT:-}"
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$#" -eq 5 ] && [ "$1" = "-6" ] && [ "$2" = "route" ] && [ "$3" = "list" ] && [ "$4" = "table" ]; then
|
|
if [ "${IP_ROUTE6_LIST_FAIL:-0}" = "1" ]; then
|
|
printf 'Error: ipv6: FIB table does not exist\nDump terminated\n' >&2
|
|
exit 2
|
|
fi
|
|
printf '%s\n' "${IP_ROUTE6_OUTPUT:-}"
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$#" -eq 3 ] && [ "$1" = "-4" ] && [ "$2" = "rule" ] && [ "$3" = "list" ]; then
|
|
printf '%s\n' "${IP_RULE_OUTPUT:-}"
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$#" -eq 3 ] && [ "$1" = "-6" ] && [ "$2" = "rule" ] && [ "$3" = "list" ]; then
|
|
printf '%s\n' "${IP_RULE6_OUTPUT:-}"
|
|
exit 0
|
|
fi
|
|
|
|
[ "${IP_FAIL_ADD:-0}" = "1" ] && exit 1
|
|
exit 0
|
|
IP
|
|
chmod 0755 "$WORK_DIR/bin/ip"
|
|
|
|
cat >"$WORK_DIR/bin/lsmod" <<'LSMOD'
|
|
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
printf '%s\n' "${LSMOD_OUTPUT:-}"
|
|
LSMOD
|
|
chmod 0755 "$WORK_DIR/bin/lsmod"
|
|
|
|
cat >"$WORK_DIR/bin/sysctl" <<'SYSCTL'
|
|
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
{
|
|
printf 'sysctl'
|
|
for arg in "$@"; do
|
|
printf '\t%s' "$arg"
|
|
done
|
|
printf '\n'
|
|
} >> "${SYSCTL_LOG:?}"
|
|
|
|
if [ "$#" -eq 2 ] && [ "$1" = "-n" ] && [ "$2" = "net.bridge.bridge-nf-call-iptables" ]; then
|
|
printf '%s\n' "${SYSCTL_BRIDGE_NF_CALL_IPTABLES:-0}"
|
|
exit 0
|
|
fi
|
|
|
|
exit 0
|
|
SYSCTL
|
|
chmod 0755 "$WORK_DIR/bin/sysctl"
|
|
|
|
cat >"$WORK_DIR/bin/logger" <<'LOGGER'
|
|
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
printf '%s\n' "$*" >> "${LOGGER_LOG:?}"
|
|
LOGGER
|
|
chmod 0755 "$WORK_DIR/bin/logger"
|
|
|
|
export PATH="$WORK_DIR/bin:$PATH"
|
|
export NFT_LOG
|
|
export LOGGER_LOG
|
|
export IP_LOG
|
|
export SYSCTL_LOG
|
|
|
|
assert_eq "/tmp/tachyon-cache/condition_rule_1_domain_domains" \
|
|
"$(nft_ucode cache-path 1 /tmp/tachyon-cache condition rule_1 domain domains)" \
|
|
"cache path"
|
|
if nft_ucode cache-path 1 /tmp/tachyon-cache condition 'bad-name' domain domains >/dev/null 2>&1; then
|
|
fail "unsafe cache key should fail"
|
|
fi
|
|
|
|
assert_eq "198.51.100.1,203.0.113.0/24" \
|
|
"$(nft_ucode legacy-condition-csv subnets 1 0 $'198.51.100.1\nbad-value\n203.0.113.0/24' '10.0.0.0/8')" \
|
|
"legacy text-mode subnets"
|
|
assert_eq "10.0.0.0/8,192.0.2.1" \
|
|
"$(nft_ucode legacy-condition-csv subnets 0 0 '198.51.100.1' '10.0.0.0/8 192.0.2.1')" \
|
|
"legacy list priority"
|
|
assert_eq "full:example.com,keyword:ads,regex:^foo" \
|
|
"$(nft_ucode list-value-to-csv 'full:example.com keyword:ads regex:^foo')" \
|
|
"list value csv"
|
|
assert_eq "one.example,list.example,legacy.example" \
|
|
"$(nft_ucode rule-condition-csv domain domains 0 0 legacy.example '' 'full:one.example keyword:skip regex:^skip suffix.example' 'full:list.example keyword:list-skip')" \
|
|
"combined domain plus legacy"
|
|
assert_eq "suffix.example,list.example" \
|
|
"$(nft_ucode rule-condition-csv domain_suffix domains 0 0 legacy.example legacy-list.example 'full:one.example suffix.example keyword:skip' 'full:list-full.example list.example')" \
|
|
"combined suffix ignores legacy"
|
|
assert_eq "xn--80aswg.xn--p1ai" \
|
|
"$(nft_ucode rule-condition-csv domain_suffix domains 0 0 '' '' 'сайт.рф full:пример.испытание keyword:пример regex:^сайт[.]рф$' '')" \
|
|
"combined IDN suffix is punycoded"
|
|
assert_eq "xn--e1afmkfd.xn--80akhbyknj4f" \
|
|
"$(nft_ucode rule-condition-csv domain domains 0 0 '' '' 'сайт.рф full:пример.испытание keyword:пример regex:^сайт[.]рф$' '')" \
|
|
"combined IDN full domain is punycoded"
|
|
assert_eq "xn--e1afmkfd" \
|
|
"$(nft_ucode rule-condition-csv domain_keyword generic 0 0 '' '' 'сайт.рф full:пример.испытание keyword:пример regex:^сайт[.]рф$' '')" \
|
|
"combined IDN keyword is punycoded"
|
|
assert_eq "^xn--80aswg[.]xn--p1ai$" \
|
|
"$(nft_ucode rule-condition-csv domain_regex generic 0 0 '' '' 'сайт.рф full:пример.испытание keyword:пример regex:^сайт[.]рф$' '')" \
|
|
"combined IDN regex is punycoded"
|
|
|
|
nft_ucode nft-create-runtime-base TachyonTable localv4 tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces "br-lan tun0" 0x00100000 0x00200000 198.18.0.0/15 1602 1 "" "" "" "" "" 0
|
|
assert_contains "$NFT_LOG" $'nft\tadd\ttable\tinet\tTachyonTable' "runtime table"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tset\tinet\tTachyonTable\tlocalv4\t{ type ipv4_addr; flags interval; auto-merge; }' "runtime localv4 set"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tset\tinet\tTachyonTable\tlocalv6\t{ type ipv6_addr; flags interval; auto-merge; }' "runtime localv6 set"
|
|
assert_contains "$NFT_LOG" '0.0.0.0/8,10.0.0.0/8,127.0.0.0/8' "runtime localv4 elements"
|
|
assert_contains "$NFT_LOG" '::/128,::1/128,64:ff9b::/96' "runtime localv6 elements"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tset\tinet\tTachyonTable\ttachyon_interfaces\t{ type ifname; flags interval; }' "runtime interface set"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_interfaces\t{ br-lan }' "runtime br-lan interface"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_interfaces\t{ tun0 }' "runtime tun0 interface"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tchain\tinet\tTachyonTable\tmangle\t{ type filter hook prerouting priority -149; policy accept; }' "runtime mangle chain runs after Tailscale connmark restore"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tchain\tinet\tTachyonTable\tpriority_rules\t{ }' "runtime priority chain"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tchain\tinet\tTachyonTable\tpriority_output_rules\t{ }' "runtime priority output chain"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_output_rules\tmeta\tmark\t!=\t0\treturn' "runtime priority output preserves provider marks"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle\tjump\tpriority_rules' "runtime priority jump"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle\tiifname\t@tachyon_interfaces\tip\tdaddr\t@tachyon_subnets\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter' "runtime common tcp rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle\tiifname\t@tachyon_interfaces\tip6\tdaddr\t@tachyon_subnets6\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter' "runtime common6 tcp rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\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\tTachyonTable\tproxy\tmeta\tmark\t&\t0x00100000\t==\t0x00100000\tmeta\tl4proto\ttcp\ttproxy\tip6\tto\t[::1]:1602\tcounter' "runtime proxy6 tcp rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tmeta\tmark\t0x00200000\tcounter\treturn' "runtime outbound return"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tjump\tpriority_output_rules' "runtime priority output jump"
|
|
assert_contains "$NFT_LOG" $'nft\tinsert\trule\tinet\tTachyonTable\tmangle\tudp\tdport\t123\treturn' "runtime ntp exclusion"
|
|
|
|
cat >"$WORK_DIR/runtime-base-uci.state" <<'EOF_UCI'
|
|
tachyon.settings=settings
|
|
tachyon.settings.source_network_interfaces=br-lan tun0
|
|
tachyon.settings.exclude_ntp=1
|
|
EOF_UCI
|
|
: > "$NFT_LOG"
|
|
TACHYON_UCI_STATE_FILE="$WORK_DIR/runtime-base-uci.state" \
|
|
nft_ucode nft-create-runtime-base-from-uci TachyonTable localv4 tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces 0x00100000 0x00200000 198.18.0.0/15 1602
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_interfaces\t{ br-lan }' "runtime base from UCI br-lan interface"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_interfaces\t{ tun0 }' "runtime base from UCI tun0 interface"
|
|
assert_contains "$NFT_LOG" $'nft\tinsert\trule\tinet\tTachyonTable\tmangle\tudp\tdport\t123\treturn' "runtime base from UCI ntp exclusion"
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-create-runtime-base TachyonTable localv4 tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces "br-lan" 0x00100000 0x00200000 198.18.0.0/15 1602 0 "" "" "" "" "" 1
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle\tiifname\t@tachyon_interfaces\tip\tdaddr\t1.1.1.1/32\tmeta\tmark\tset\t0x00100000\tcounter' "runtime doh block marking rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle\tiifname\t@tachyon_interfaces\tip6\tdaddr\t2606:4700:4700::1111/128\tmeta\tmark\tset\t0x00100000\tcounter' "runtime doh6 block marking rule"
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-create-runtime-output-rules TachyonTable localv4 tachyon_subnets tachyon_ports tachyon_ip_ports 0x00100000 198.18.0.0/15
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tip\tdaddr\t@tachyon_subnets\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter' "runtime output common tcp"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tip6\tdaddr\t@tachyon_subnets6\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter' "runtime output common6 tcp"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tip\tdaddr\t.\ttcp\tdport\t@tachyon_ip_ports\tmeta\tmark\tset\t0x00100000\tcounter' "runtime output ip-port tcp"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tip6\tdaddr\t.\ttcp\tdport\t@tachyon_ip6_ports\tmeta\tmark\tset\t0x00100000\tcounter' "runtime output ip6-port tcp"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\ttcp\tdport\t@tachyon_ports\tmeta\tmark\tset\t0x00100000\tcounter' "runtime output port tcp"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tip\tdaddr\t198.18.0.0/15\tmeta\tl4proto\tudp\tmeta\tmark\tset\t0x00100000\tcounter' "runtime output fakeip udp"
|
|
|
|
: > "$NFT_LOG"
|
|
if NFT_LIST_TABLE_FAIL=1 nft_ucode nft-table-present-fixture TachyonTable 2>"$WORK_DIR/nft-table-present.err"; then
|
|
fail "missing nft table should return false"
|
|
fi
|
|
[ ! -s "$WORK_DIR/nft-table-present.err" ] ||
|
|
fail "missing nft table predicate must suppress nft stderr"
|
|
assert_contains "$NFT_LOG" $'nft\tlist\ttable\tinet\tTachyonTable' "nft table presence check"
|
|
|
|
cat >"$WORK_DIR/provider-rules.json" <<'JSON'
|
|
{
|
|
"section": [
|
|
{ ".name": "direct", "enabled": "1", "action": "bypass" },
|
|
{ ".name": "zapret_one", "enabled": "1", "action": "zapret" },
|
|
{ ".name": "zapret_disabled", "enabled": "0", "action": "zapret" },
|
|
{ ".name": "zapret_two", "enabled": "1", "action": "zapret" },
|
|
{ ".name": "zapret2_one", "enabled": "1", "action": "zapret2" }
|
|
]
|
|
}
|
|
JSON
|
|
provider_bin="$WORK_DIR/bin/provider"
|
|
printf '#!/usr/bin/env sh\nexit 0\n' >"$provider_bin"
|
|
chmod 0755 "$provider_bin"
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-create-provider-output-rules-fixture "$WORK_DIR/provider-rules.json" TachyonTable zapret "$provider_bin" 0x01000000 4000 0x40000000 0x20000000
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tmeta\tmark\t&\t0x40000000\t==\t0x40000000\treturn' "zapret desync return"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tmeta\tmark\t0x01000001\tmeta\tl4proto\ttcp\tcounter\tqueue\tnum\t4000\tbypass' "zapret first tcp queue rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tmangle_output\tmeta\tmark\t0x01000002\tmeta\tl4proto\tudp\tcounter\tqueue\tnum\t4001\tbypass' "zapret second udp queue rule"
|
|
if grep -Fq 'zapret_disabled' "$NFT_LOG"; then
|
|
fail "disabled provider section should not create nft rule"
|
|
fi
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-create-provider-output-rules-fixture "$WORK_DIR/provider-rules.json" TachyonTable zapret "$WORK_DIR/bin/missing-provider" 0x01000000 4000 0x40000000 0x20000000
|
|
if [ -s "$NFT_LOG" ]; then
|
|
fail "missing provider binary should skip provider nft output rules"
|
|
fi
|
|
|
|
cat >"$WORK_DIR/priority-rules.json" <<'JSON'
|
|
{
|
|
"section": [
|
|
{
|
|
".name": "bypass_first",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "bypass",
|
|
"ip_cidr": [ "198.51.100.0/24" ]
|
|
},
|
|
{
|
|
".name": "wide_proxy",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"ip_cidr": [ "0.0.0.0/0" ]
|
|
},
|
|
{
|
|
".name": "port_bypass",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "bypass",
|
|
"ports": [ "8443" ]
|
|
},
|
|
{
|
|
".name": "source_bypass",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "bypass",
|
|
"source_ip_cidr": [ "192.168.1.50/32" ]
|
|
},
|
|
{
|
|
".name": "source_proxy",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"source_ip_cidr": [ "192.168.1.60/32" ]
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-add-section-priority-rules-fixture "$WORK_DIR/priority-rules.json" TachyonTable tachyon_interfaces localv4 localv6 0x00100000
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tset\tinet\tTachyonTable\ttachyon_rule_bypass_first_subnets\t{ type ipv4_addr; flags interval; auto-merge; }' "bypass priority subnet set"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\tset\tinet\tTachyonTable\ttachyon_rule_wide_proxy_subnets\t{ type ipv4_addr; flags interval; auto-merge; }' "proxy priority subnet set"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tdaddr\t!=\t@localv4\tip\tdaddr\t@tachyon_rule_bypass_first_subnets\tcounter\taccept' "bypass priority accept rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tdaddr\t!=\t@localv4\tip\tdaddr\t@tachyon_rule_wide_proxy_subnets\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter\taccept' "proxy priority capture tcp rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tdaddr\t!=\t@localv4\tip\tdaddr\t@tachyon_rule_wide_proxy_subnets\tmeta\tl4proto\tudp\tmeta\tmark\tset\t0x00100000\tcounter\taccept' "proxy priority capture udp rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tdaddr\t!=\t@localv4\ttcp\tdport\t@tachyon_rule_port_bypass_ports\tcounter\taccept' "port-only bypass priority rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tsaddr\t@tachyon_rule_source_bypass_sources\tip\tdaddr\t!=\t@localv4\tcounter\taccept' "source-only bypass priority rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tsaddr\t@tachyon_rule_source_proxy_sources\tip\tdaddr\t!=\t@localv4\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter\taccept' "source-only proxy tcp priority rule"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tsaddr\t@tachyon_rule_source_proxy_sources\tip\tdaddr\t!=\t@localv4\tmeta\tl4proto\tudp\tmeta\tmark\tset\t0x00100000\tcounter\taccept' "source-only proxy udp priority rule"
|
|
assert_line_before "$NFT_LOG" \
|
|
$'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tdaddr\t!=\t@localv4\tip\tdaddr\t@tachyon_rule_bypass_first_subnets\tcounter\taccept' \
|
|
$'nft\tadd\trule\tinet\tTachyonTable\tpriority_rules\tiifname\t@tachyon_interfaces\tip\tdaddr\t!=\t@localv4\tip\tdaddr\t@tachyon_rule_wide_proxy_subnets\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter\taccept' \
|
|
"bypass priority order"
|
|
|
|
: > "$IP_LOG"
|
|
: > "$LOGGER_LOG"
|
|
rt_tables="$WORK_DIR/rt_tables"
|
|
IP_ROUTE_LIST_FAIL=1 IP_ROUTE6_LIST_FAIL=1 IP_RULE_OUTPUT='' IP_RULE6_OUTPUT='' \
|
|
nft_ucode ensure-tproxy-route-rule tachyon 0x00100000 "$rt_tables" 2>"$WORK_DIR/tproxy-route-check.err"
|
|
[ ! -s "$WORK_DIR/tproxy-route-check.err" ] ||
|
|
fail "missing tproxy route table predicates must suppress ip stderr"
|
|
assert_contains "$rt_tables" "105 tachyon" "tproxy route table registry"
|
|
assert_contains "$IP_LOG" $'ip\troute\tadd\tlocal\t0.0.0.0/0\tdev\tlo\ttable\ttachyon' "tproxy route add"
|
|
assert_contains "$IP_LOG" $'ip\t-6\troute\tadd\tlocal\t::/0\tdev\tlo\ttable\ttachyon' "tproxy route6 add"
|
|
assert_contains "$IP_LOG" $'ip\t-4\trule\tadd\tfwmark\t0x00100000/0x00100000\ttable\ttachyon\tpriority\t105' "tproxy marking rule add"
|
|
assert_contains "$IP_LOG" $'ip\t-6\trule\tadd\tfwmark\t0x00100000/0x00100000\ttable\ttachyon\tpriority\t105' "tproxy marking rule6 add"
|
|
assert_contains "$LOGGER_LOG" "[debug] Added IPv4 TPROXY route" "tproxy route creation log"
|
|
assert_contains "$LOGGER_LOG" "[debug] Added IPv6 TPROXY route" "tproxy route6 creation log"
|
|
assert_contains "$LOGGER_LOG" "[debug] Creating IPv4 TPROXY marking rule" "tproxy marking rule creation log"
|
|
assert_contains "$LOGGER_LOG" "[debug] Creating IPv6 TPROXY marking rule" "tproxy marking rule6 creation log"
|
|
|
|
: > "$IP_LOG"
|
|
: > "$LOGGER_LOG"
|
|
printf '%s\n' '105 tachyon' >"$rt_tables"
|
|
IP_ROUTE_OUTPUT='local default dev lo scope host' \
|
|
IP_ROUTE6_OUTPUT='local default dev lo metric 1024 pref medium' \
|
|
IP_RULE_OUTPUT='105: from all fwmark 0x100000/0x100000 lookup tachyon' \
|
|
IP_RULE6_OUTPUT='105: from all fwmark 0x100000/0x100000 lookup tachyon' \
|
|
nft_ucode ensure-tproxy-route-rule tachyon 0x00100000 "$rt_tables"
|
|
if grep -Fq $'\tadd\t' "$IP_LOG"; then
|
|
fail "existing tproxy route/rule should not be added again"
|
|
fi
|
|
assert_contains "$LOGGER_LOG" "[debug] IPv4 TPROXY route already exists" "existing tproxy route log"
|
|
assert_contains "$LOGGER_LOG" "[debug] IPv6 TPROXY route already exists" "existing tproxy route6 log"
|
|
assert_contains "$LOGGER_LOG" "[debug] IPv4 TPROXY marking rule already exists" "existing tproxy marking rule log"
|
|
assert_contains "$LOGGER_LOG" "[debug] IPv6 TPROXY marking rule already exists" "existing tproxy marking rule6 log"
|
|
IP_ROUTE_OUTPUT='local default dev lo scope host' \
|
|
IP_ROUTE6_OUTPUT='local default dev lo metric 1024 pref medium' \
|
|
IP_RULE_OUTPUT='105: from all fwmark 0x100000/0x100000 lookup tachyon' \
|
|
IP_RULE6_OUTPUT='105: from all fwmark 0x100000/0x100000 lookup tachyon' \
|
|
nft_ucode tproxy-route-rule-present tachyon 0x00100000
|
|
if IP_ROUTE_OUTPUT='local default dev lo scope host' \
|
|
IP_ROUTE6_OUTPUT='local default dev lo metric 1024 pref medium' \
|
|
IP_RULE_OUTPUT='105: from all fwmark 0x100000/0x100000 lookup other' \
|
|
IP_RULE6_OUTPUT='105: from all fwmark 0x100000/0x100000 lookup tachyon' \
|
|
nft_ucode tproxy-route-rule-present tachyon 0x00100000 >/dev/null 2>&1; then
|
|
fail "tproxy route/rule presence should require matching lookup table"
|
|
fi
|
|
|
|
# IPv6 disabled: should only require IPv4 route/rule
|
|
TACHYON_ENABLE_IPV6=0 \
|
|
IP_ROUTE_OUTPUT='local default dev lo scope host' \
|
|
IP_ROUTE6_OUTPUT='' \
|
|
IP_RULE_OUTPUT='105: from all fwmark 0x100000/0x100000 lookup tachyon' \
|
|
IP_RULE6_OUTPUT='' \
|
|
nft_ucode tproxy-route-rule-present tachyon 0x00100000
|
|
|
|
: > "$SYSCTL_LOG"
|
|
: > "$LOGGER_LOG"
|
|
LSMOD_OUTPUT='br_netfilter 32768 0' SYSCTL_BRIDGE_NF_CALL_IPTABLES=1 nft_ucode ensure-bridge-netfilter-disabled
|
|
assert_contains "$SYSCTL_LOG" $'sysctl\t-n\tnet.bridge.bridge-nf-call-iptables' "bridge netfilter sysctl check"
|
|
assert_contains "$SYSCTL_LOG" $'sysctl\t-w\tnet.bridge.bridge-nf-call-iptables=0' "bridge netfilter ipv4 disable"
|
|
assert_contains "$SYSCTL_LOG" $'sysctl\t-w\tnet.bridge.bridge-nf-call-ip6tables=0' "bridge netfilter ipv6 disable"
|
|
assert_contains "$LOGGER_LOG" "[debug] br_netfilter is enabled; disabling it for transparent proxy routing" "bridge netfilter disable log"
|
|
|
|
: > "$SYSCTL_LOG"
|
|
LSMOD_OUTPUT='' SYSCTL_BRIDGE_NF_CALL_IPTABLES=1 nft_ucode ensure-bridge-netfilter-disabled
|
|
if [ -s "$SYSCTL_LOG" ]; then
|
|
fail "bridge netfilter absent should not call sysctl"
|
|
fi
|
|
|
|
input="$WORK_DIR/ips.txt"
|
|
cat >"$input" <<'EOF_INPUT'
|
|
198.51.100.1
|
|
bad-value
|
|
203.0.113.0/24
|
|
EOF_INPUT
|
|
|
|
nft_ucode nft-add-file-chunks-to-set "$input" TachyonTable tachyon_subnets ips "" 2
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_subnets\t{ 198.51.100.1,203.0.113.0/24 }' "nft chunked ips"
|
|
assert_contains "$LOGGER_LOG" "[debug] 'bad-value' is not IP or CIDR" "invalid element log"
|
|
assert_contains "$LOGGER_LOG" "[debug] Adding 2 elements to nft set tachyon_subnets" "chunk count log"
|
|
|
|
: > "$NFT_LOG"
|
|
ports_input="$WORK_DIR/ip-ports.txt"
|
|
cat >"$ports_input" <<'EOF_INPUT'
|
|
198.51.100.1
|
|
203.0.113.0/24
|
|
EOF_INPUT
|
|
|
|
nft_ucode nft-add-file-chunks-to-set "$ports_input" TachyonTable tachyon_ip_ports ip-port-from-ip "80,443-444" 3
|
|
assert_contains "$NFT_LOG" $'198.51.100.1 . 80,198.51.100.1 . 443-444,203.0.113.0/24 . 80' "ip-port chunk"
|
|
assert_contains "$NFT_LOG" $'203.0.113.0/24 . 443-444' "ip-port second chunk"
|
|
|
|
cat >"$WORK_DIR/populate-fixture.json" <<'JSON'
|
|
{
|
|
"section": [
|
|
{
|
|
".name": "disabled",
|
|
".type": "section",
|
|
"enabled": "0",
|
|
"action": "proxy",
|
|
"ip_cidr": [ "192.0.2.1" ],
|
|
"ports": [ "53" ],
|
|
"fully_routed_ips": [ "192.168.1.50/32" ]
|
|
},
|
|
{
|
|
".name": "deferred",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"ip_cidr": [ "192.0.2.2" ],
|
|
"ports": [ "54" ],
|
|
"fully_routed_ips": [ "192.168.1.51/32" ]
|
|
},
|
|
{
|
|
".name": "inline",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"ip_cidr": [ "198.51.100.1", "203.0.113.0/24", "2001:db8::1" ],
|
|
"ports": [ "80", "443-444" ],
|
|
"fully_routed_ips": [ "192.168.1.20/32", "192.168.1.20/32", "2001:db8::20/128" ]
|
|
},
|
|
{
|
|
".name": "inline_no_ports",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "bypass",
|
|
"ip_cidr": [ "198.51.100.200", "2001:db8::200" ]
|
|
},
|
|
{
|
|
".name": "ports_only",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"ports": [ "53", "853" ],
|
|
"ports_text": "5353, 853"
|
|
},
|
|
{
|
|
".name": "ports_with_domain",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"domain_suffix": [ "example.org" ],
|
|
"ports": [ "8443" ]
|
|
},
|
|
{
|
|
".name": "dns_only",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "dns",
|
|
"domain_suffix": [ "dns-only.example" ],
|
|
"ip_cidr": [ "192.0.2.53" ],
|
|
"ports": [ "5353" ],
|
|
"fully_routed_ips": [ "192.168.1.53/32" ]
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
|
|
plain_subnets="$WORK_DIR/plain-subnets.txt"
|
|
cat >"$plain_subnets" <<'EOF_INPUT'
|
|
198.51.100.210
|
|
203.0.113.0/24
|
|
2001:db8::210
|
|
EOF_INPUT
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-add-subnet-file-for-section-fixture "$WORK_DIR/populate-fixture.json" inline_no_ports "$plain_subnets" TachyonTable tachyon_subnets tachyon_ip_ports 5000
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_subnets\t{ 198.51.100.210,203.0.113.0/24 }' "plain subnet import without ports"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_subnets6\t{ 2001:db8::210 }' "plain subnet6 import without ports"
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-add-subnet-file-for-section-fixture "$WORK_DIR/populate-fixture.json" ports_only "$plain_subnets" TachyonTable tachyon_subnets tachyon_ip_ports 3
|
|
assert_contains "$NFT_LOG" $'198.51.100.210 . 53,198.51.100.210 . 853,198.51.100.210 . 5353' "plain subnet import scoped first chunk"
|
|
assert_contains "$NFT_LOG" $'203.0.113.0/24 . 53,203.0.113.0/24 . 853,203.0.113.0/24 . 5353' "plain subnet import scoped second chunk"
|
|
assert_contains "$NFT_LOG" $'2001:db8::210 . 53,2001:db8::210 . 853,2001:db8::210 . 5353' "plain subnet6 import scoped chunk"
|
|
|
|
json_ruleset="$WORK_DIR/subnets-ruleset.json"
|
|
cat >"$json_ruleset" <<'JSON'
|
|
{
|
|
"version": 3,
|
|
"rules": [
|
|
{ "ip_cidr": [ "198.51.100.220", "2001:db8::220" ] },
|
|
{ "ip_cidr": [ "198.51.100.221" ], "port": [ 443 ] },
|
|
{
|
|
"type": "logical",
|
|
"mode": "and",
|
|
"rules": [
|
|
{ "ip_cidr": [ "198.51.100.222" ] },
|
|
{ "port_range": [ "1000:1002" ] }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
|
|
: > "$NFT_LOG"
|
|
unscoped_json="$WORK_DIR/unscoped-json.txt"
|
|
scoped_json="$WORK_DIR/scoped-json.txt"
|
|
nft_ucode nft-add-json-ruleset-subnets-for-section-fixture "$WORK_DIR/populate-fixture.json" inline_no_ports "$json_ruleset" "fixture json" TachyonTable tachyon_subnets tachyon_ip_ports "$unscoped_json" "$scoped_json" 5000
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_subnets\t{ 198.51.100.220 }' "json ruleset unscoped import"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_subnets6\t{ 2001:db8::220 }' "json ruleset unscoped6 import"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_ip_ports\t{ 198.51.100.221 . 443,198.51.100.222 . 1000-1002 }' "json ruleset own port filters"
|
|
|
|
: > "$NFT_LOG"
|
|
unscoped_json="$WORK_DIR/unscoped-json-ports.txt"
|
|
scoped_json="$WORK_DIR/scoped-json-ports.txt"
|
|
nft_ucode nft-add-json-ruleset-subnets-for-section-fixture "$WORK_DIR/populate-fixture.json" ports_only "$json_ruleset" "fixture json ports" TachyonTable tachyon_subnets tachyon_ip_ports "$unscoped_json" "$scoped_json" 4
|
|
assert_contains "$NFT_LOG" $'198.51.100.220 . 53,198.51.100.220 . 5353,198.51.100.220 . 853' "json ruleset scoped import"
|
|
if grep -Fq '198.51.100.221' "$NFT_LOG" || grep -Fq '198.51.100.222' "$NFT_LOG"; then
|
|
fail "json ruleset section ports should intersect rule-owned port filters"
|
|
fi
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-add-community-subnet-file-for-section-fixture "$WORK_DIR/populate-fixture.json" inline_no_ports discord "$plain_subnets" TachyonTable tachyon_subnets tachyon_ip_ports tachyon_interfaces tachyon_discord_subnets 0x00100000 5000
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_subnets\t{ 198.51.100.210,203.0.113.0/24 }' "discord community subnet import"
|
|
|
|
: > "$NFT_LOG"
|
|
NFT_MANGLE_CHAIN_OUTPUT=$'iifname @tachyon_interfaces ip daddr @tachyon_discord_subnets udp dport { 19000-20000, 50000-65535 } meta mark set 0x00100000 counter\niifname @tachyon_interfaces ip6 daddr @tachyon_discord_subnets6 udp dport { 19000-20000, 50000-65535 } meta mark set 0x00100000 counter' \
|
|
nft_ucode nft-add-community-subnet-file-for-section-fixture "$WORK_DIR/populate-fixture.json" inline_no_ports discord "$plain_subnets" TachyonTable tachyon_subnets tachyon_ip_ports tachyon_interfaces tachyon_discord_subnets 0x00100000 5000
|
|
if grep -Fq $'nft\tadd\trule' "$NFT_LOG"; then
|
|
fail "discord community existing rule should not insert"
|
|
fi
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-add-community-subnet-file-for-section-fixture "$WORK_DIR/populate-fixture.json" ports_only discord "$plain_subnets" TachyonTable tachyon_subnets tachyon_ip_ports tachyon_interfaces tachyon_discord_subnets 0x00100000 3
|
|
if grep -Fq 'tachyon_discord_subnets' "$NFT_LOG"; then
|
|
fail "discord community with section ports should not use the discord nft set"
|
|
fi
|
|
assert_contains "$NFT_LOG" $'198.51.100.210 . 53,198.51.100.210 . 853,198.51.100.210 . 5353' "discord community with ports uses scoped import"
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-populate-runtime-sets-fixture "$WORK_DIR/populate-fixture.json" 1 "deferred" TachyonTable tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces localv4 0x00100000
|
|
assert_contains "$NFT_LOG" $'198.51.100.1 . 80,198.51.100.1 . 443-444,203.0.113.0/24 . 80' "populate inline ip-port first chunk"
|
|
assert_contains "$NFT_LOG" $'203.0.113.0/24 . 443-444' "populate inline ip-port second chunk"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_ip_ports\t{ 198.51.100.1 . 80,198.51.100.1 . 443-444,203.0.113.0/24 . 80,203.0.113.0/24 . 443-444 }' "populate inline priority ip-port set"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_ip6_ports\t{ 2001:db8::1 . 80,2001:db8::1 . 443-444 }' "populate inline priority ip6-port set"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_subnets\t{ 198.51.100.200 }' "populate inline ip without ports"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_inline_no_ports_subnets6\t{ 2001:db8::200 }' "populate inline ip6 without ports"
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_ports_only_ports\t{ 53,853,5353 }' "populate ports-only set"
|
|
assert_contains "$NFT_LOG" $'nft\tinsert\trule\tinet\tTachyonTable\tmangle\tiifname\t@tachyon_interfaces\tip\tsaddr\t192.168.1.20/32\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter' "populate fully routed tcp"
|
|
assert_contains "$NFT_LOG" $'nft\tinsert\trule\tinet\tTachyonTable\tmangle\tiifname\t@tachyon_interfaces\tip6\tsaddr\t2001:db8::20/128\tmeta\tl4proto\ttcp\tmeta\tmark\tset\t0x00100000\tcounter' "populate fully routed6 tcp"
|
|
if grep -Fq '192.0.2.1' "$NFT_LOG"; then
|
|
fail "disabled section should not populate nft"
|
|
fi
|
|
if grep -Fq '192.0.2.2' "$NFT_LOG"; then
|
|
fail "deferred section should not populate nft"
|
|
fi
|
|
if grep -Fq $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_rule_ports_with_domain_ports\t{ 8443 }' "$NFT_LOG"; then
|
|
fail "ports with destination matchers should not populate global port set"
|
|
fi
|
|
if grep -Fq '192.0.2.53' "$NFT_LOG" || grep -Fq 'tachyon_rule_dns_only' "$NFT_LOG"; then
|
|
fail "DNS action should not populate routing nft sets"
|
|
fi
|
|
if grep -Fq $'mangle\tiifname\t@tachyon_interfaces\tip\tsaddr\t192.168.1.53' "$NFT_LOG"; then
|
|
fail "DNS action should not create fully routed mangle rules"
|
|
fi
|
|
assert_contains "$NFT_LOG" $'nft\tadd\telement\tinet\tTachyonTable\ttachyon_dns_sources\t{ 192.168.1.53/32 }' "DNS action populates dns sources set"
|
|
if [ "$(grep -F $'ip\tsaddr\t192.168.1.20/32' "$NFT_LOG" | wc -l | tr -d ' ')" != "3" ]; then
|
|
fail "duplicate fully routed source should insert exactly one tcp/udp/local rule set"
|
|
fi
|
|
|
|
cat >"$WORK_DIR/signature-fixture.json" <<'JSON'
|
|
{
|
|
"settings": {
|
|
"source_network_interfaces": [ "br-lan", "tun0" ],
|
|
"exclude_ntp": "1"
|
|
},
|
|
"section": [
|
|
{
|
|
".name": "disabled",
|
|
".type": "section",
|
|
"enabled": "0",
|
|
"action": "proxy",
|
|
"ip_cidr": [ "192.0.2.10" ],
|
|
"ports": [ "1234" ],
|
|
"community_lists": [ "discord" ]
|
|
},
|
|
{
|
|
".name": "text_rule",
|
|
".type": "section",
|
|
"enabled": "1",
|
|
"action": "bypass",
|
|
"ip_cidr_text_mode": "1",
|
|
"ip_cidr_text": "198.51.100.1\nbad-value\n203.0.113.0/24",
|
|
"ip_cidr": [ "10.0.0.0/8" ],
|
|
"ports": [ "443", "bad-value", "443" ],
|
|
"ports_text": "80, 443-444\n0\n65536",
|
|
"fully_routed_ips": [ "192.168.1.10/32" ],
|
|
"community_lists": [ "geoblock", "meta", "telegram", "youtube", "discord" ],
|
|
"remote_subnet_lists": [ "https://example.com/subnets.lst" ],
|
|
"rule_set_with_subnets": [ "/tmp/local.json" ],
|
|
"domain_ip_lists": [ "https://example.com/mixed.lst" ]
|
|
},
|
|
{
|
|
".name": "default_enabled",
|
|
".type": "section",
|
|
"action": "proxy",
|
|
"ip_cidr": [ "10.0.0.0/8", "192.0.2.1" ],
|
|
"ports_text": "53 853",
|
|
"community_lists": [ "youtube", "cloudflare", "roblox" ]
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
|
|
cat >"$WORK_DIR/signature-expected.txt" <<'EOF_EXPECTED'
|
|
[settings.source_network_interfaces]
|
|
br-lan tun0
|
|
[settings.exclude_ntp]
|
|
1
|
|
[settings.block_doh]
|
|
0
|
|
[settings.game_console_optimizer]
|
|
0
|
|
[settings.game_console_ips]
|
|
|
|
[settings.excluded_clients]
|
|
|
|
[settings.excluded_ips]
|
|
|
|
[rule.text_rule.action]
|
|
bypass
|
|
[rule.text_rule.ip_cidr]
|
|
198.51.100.1,203.0.113.0/24
|
|
[rule.text_rule.source_ip_cidr]
|
|
|
|
[rule.text_rule.source_aware_dns]
|
|
1
|
|
[rule.text_rule.ports]
|
|
443,80,443-444
|
|
[rule.text_rule.fully_routed_ips]
|
|
192.168.1.10/32
|
|
[rule.text_rule.excluded_ips]
|
|
|
|
[rule.text_rule.excluded_protocol]
|
|
|
|
[rule.text_rule.routed_dns_enabled]
|
|
0
|
|
[rule.text_rule.routed_dns_type]
|
|
|
|
[rule.text_rule.routed_dns_server]
|
|
|
|
[rule.text_rule.protocol]
|
|
|
|
[rule.text_rule.community_subnet_lists]
|
|
meta telegram discord
|
|
[rule.text_rule.remote_subnet_lists]
|
|
https://example.com/subnets.lst
|
|
[rule.text_rule.rule_set_with_subnets]
|
|
/tmp/local.json
|
|
[rule.text_rule.domain_ip_lists]
|
|
https://example.com/mixed.lst
|
|
[rule.text_rule.dscp]
|
|
|
|
[rule.default_enabled.action]
|
|
proxy
|
|
[rule.default_enabled.ip_cidr]
|
|
10.0.0.0/8,192.0.2.1
|
|
[rule.default_enabled.source_ip_cidr]
|
|
|
|
[rule.default_enabled.source_aware_dns]
|
|
1
|
|
[rule.default_enabled.ports]
|
|
53,853
|
|
[rule.default_enabled.fully_routed_ips]
|
|
|
|
[rule.default_enabled.excluded_ips]
|
|
|
|
[rule.default_enabled.excluded_protocol]
|
|
|
|
[rule.default_enabled.routed_dns_enabled]
|
|
0
|
|
[rule.default_enabled.routed_dns_type]
|
|
|
|
[rule.default_enabled.routed_dns_server]
|
|
|
|
[rule.default_enabled.protocol]
|
|
|
|
[rule.default_enabled.community_subnet_lists]
|
|
cloudflare roblox
|
|
[rule.default_enabled.remote_subnet_lists]
|
|
|
|
[rule.default_enabled.rule_set_with_subnets]
|
|
|
|
[rule.default_enabled.domain_ip_lists]
|
|
|
|
[rule.default_enabled.dscp]
|
|
|
|
EOF_EXPECTED
|
|
expected_signature="$(md5sum "$WORK_DIR/signature-expected.txt" | awk '{print $1}')"
|
|
assert_eq "$expected_signature" \
|
|
"$(nft_ucode nft-runtime-signature-fixture "$WORK_DIR/signature-fixture.json")" \
|
|
"nft runtime signature fixture"
|
|
|
|
cat >"$WORK_DIR/signature-uci.state" <<'EOF_UCI'
|
|
tachyon.settings=settings
|
|
tachyon.settings.source_network_interfaces=br-lan tun0
|
|
tachyon.settings.exclude_ntp=1
|
|
tachyon.disabled=section
|
|
tachyon.disabled.enabled=0
|
|
tachyon.disabled.ip_cidr=192.0.2.10
|
|
tachyon.enabled=section
|
|
tachyon.enabled.enabled=1
|
|
tachyon.enabled.action=bypass
|
|
tachyon.enabled.ip_cidr=10.0.0.0/8 192.0.2.1
|
|
tachyon.enabled.ports=443 bad-value
|
|
tachyon.enabled.ports_text=80 443-444 65536
|
|
tachyon.enabled.fully_routed_ips=192.168.1.10/32
|
|
tachyon.enabled.community_lists=geoblock meta telegram youtube discord
|
|
tachyon.enabled.remote_subnet_lists=https://example.com/subnets.lst
|
|
tachyon.enabled.rule_set_with_subnets=/tmp/local.json
|
|
tachyon.enabled.domain_ip_lists=https://example.com/mixed.lst
|
|
EOF_UCI
|
|
cat >"$WORK_DIR/signature-uci-expected.txt" <<'EOF_EXPECTED'
|
|
[settings.source_network_interfaces]
|
|
br-lan tun0
|
|
[settings.exclude_ntp]
|
|
1
|
|
[settings.block_doh]
|
|
0
|
|
[settings.game_console_optimizer]
|
|
0
|
|
[settings.game_console_ips]
|
|
|
|
[settings.excluded_clients]
|
|
|
|
[settings.excluded_ips]
|
|
|
|
[rule.enabled.action]
|
|
bypass
|
|
[rule.enabled.ip_cidr]
|
|
10.0.0.0/8,192.0.2.1
|
|
[rule.enabled.source_ip_cidr]
|
|
|
|
[rule.enabled.source_aware_dns]
|
|
1
|
|
[rule.enabled.ports]
|
|
443,80,443-444
|
|
[rule.enabled.fully_routed_ips]
|
|
192.168.1.10/32
|
|
[rule.enabled.excluded_ips]
|
|
|
|
[rule.enabled.excluded_protocol]
|
|
|
|
[rule.enabled.routed_dns_enabled]
|
|
0
|
|
[rule.enabled.routed_dns_type]
|
|
|
|
[rule.enabled.routed_dns_server]
|
|
|
|
[rule.enabled.protocol]
|
|
|
|
[rule.enabled.community_subnet_lists]
|
|
meta telegram discord
|
|
[rule.enabled.remote_subnet_lists]
|
|
https://example.com/subnets.lst
|
|
[rule.enabled.rule_set_with_subnets]
|
|
/tmp/local.json
|
|
[rule.enabled.domain_ip_lists]
|
|
https://example.com/mixed.lst
|
|
[rule.enabled.dscp]
|
|
|
|
EOF_EXPECTED
|
|
expected_signature="$(md5sum "$WORK_DIR/signature-uci-expected.txt" | awk '{print $1}')"
|
|
assert_eq "$expected_signature" \
|
|
"$(TACHYON_UCI_STATE_FILE="$WORK_DIR/signature-uci.state" nft_ucode nft-runtime-signature)" \
|
|
"nft runtime signature from UCI state"
|
|
|
|
cat >"$WORK_DIR/signature-defaults-fixture.json" <<'JSON'
|
|
{}
|
|
JSON
|
|
cat >"$WORK_DIR/signature-defaults-expected.txt" <<'EOF_EXPECTED'
|
|
[settings.source_network_interfaces]
|
|
br-lan
|
|
[settings.exclude_ntp]
|
|
0
|
|
[settings.block_doh]
|
|
0
|
|
[settings.game_console_optimizer]
|
|
0
|
|
[settings.game_console_ips]
|
|
|
|
[settings.excluded_clients]
|
|
|
|
[settings.excluded_ips]
|
|
|
|
EOF_EXPECTED
|
|
expected_signature="$(md5sum "$WORK_DIR/signature-defaults-expected.txt" | awk '{print $1}')"
|
|
assert_eq "$expected_signature" \
|
|
"$(nft_ucode nft-runtime-signature-fixture "$WORK_DIR/signature-defaults-fixture.json")" \
|
|
"nft runtime signature defaults"
|
|
|
|
cat >"$WORK_DIR/signature-excluded-clients-fixture.json" <<'JSON'
|
|
{
|
|
"settings": {
|
|
"excluded_clients": "192.168.1.50 00:11:22:33:44:55"
|
|
}
|
|
}
|
|
JSON
|
|
sig_with_excluded="$(nft_ucode nft-runtime-signature-fixture "$WORK_DIR/signature-excluded-clients-fixture.json")"
|
|
if [ "$sig_with_excluded" = "$expected_signature" ]; then
|
|
fail "nft runtime signature must change when excluded_clients is set"
|
|
fi
|
|
|
|
: > "$NFT_LOG"
|
|
nft_ucode nft-populate-runtime-sets-fixture "$WORK_DIR/populate-fixture.json" 0 "" TachyonTable tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces localv4 0x00100000
|
|
[ ! -s "$NFT_LOG" ] || fail "disabled nft population should not call nft"
|
|
|
|
: > "$NFT_LOG"
|
|
NFT_MANGLE_CHAIN_OUTPUT='ip saddr 192.168.1.20 meta l4proto tcp meta mark set 0x00100000 counter' \
|
|
nft_ucode nft-populate-runtime-sets-fixture "$WORK_DIR/populate-fixture.json" 1 "disabled deferred inline inline_no_ports ports_only ports_with_domain dns_only" TachyonTable tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces localv4 0x00100000
|
|
if grep -Fq $'nft\tinsert\trule' "$NFT_LOG"; then
|
|
fail "existing nft-normalized /32 fully routed source should not insert"
|
|
fi
|
|
|
|
printf 'NFT apply checks passed\n'
|
|
|
|
|