feat(routing): route router's own traffic through designated section

This commit is contained in:
Dushnilin
2026-09-15 09:30:04 +03:00
parent 45e6a6ae60
commit 2d00141696
14 changed files with 329 additions and 1 deletions
+12
View File
@@ -6742,3 +6742,15 @@ msgstr "Блокировать: "
msgid "Traffic from these IP addresses will never be routed through this section, even if other conditions match." msgid "Traffic from these IP addresses will never be routed through this section, even if other conditions match."
msgstr "Трафик от этих IP-адресов никогда не будет маршрутизирован через эту секцию, даже если другие условия совпадают." msgstr "Трафик от этих IP-адресов никогда не будет маршрутизирован через эту секцию, даже если другие условия совпадают."
msgid "Route Router's Own Traffic"
msgstr "Маршрутизировать собственный трафик роутера"
msgid "Route local traffic originating directly from the router (e.g. wget, curl, opkg, torrent clients on the router) through Tachyon."
msgstr "Направлять локальный трафик, исходящий непосредственно с роутера (например, wget, curl, opkg, торрент-клиент на роутере), через Tachyon."
msgid "Router Traffic Target Section"
msgstr "Секция для трафика роутера"
msgid "Select the section to route router's own traffic through."
msgstr "Выберите секцию для маршрутизации трафика роутера."
@@ -1806,6 +1806,31 @@ function createSettingsContent(section, capabilities) {
return true; return true;
}; };
o = section.taboption(
"network",
form.Flag,
"route_router_traffic",
_("Route Router's Own Traffic"),
_(
"Route local traffic originating directly from the router (e.g. wget, curl, opkg, torrent clients on the router) through Tachyon.",
),
);
configureDownloadViaProxyFlag(o, "route_router_traffic_section");
o = section.taboption(
"network",
form.ListValue,
"route_router_traffic_section",
_("Router Traffic Target Section"),
_("Select the section to route router's own traffic through."),
);
o.depends("route_router_traffic", "1");
configureDownloadSectionOption(
o,
"route_router_traffic_section",
capabilities,
);
o = section.taboption( o = section.taboption(
"services", "services",
form.Flag, form.Flag,
+12
View File
@@ -6742,3 +6742,15 @@ msgstr "Блокировать: "
msgid "Traffic from these IP addresses will never be routed through this section, even if other conditions match." msgid "Traffic from these IP addresses will never be routed through this section, even if other conditions match."
msgstr "Трафик от этих IP-адресов никогда не будет маршрутизирован через эту секцию, даже если другие условия совпадают." msgstr "Трафик от этих IP-адресов никогда не будет маршрутизирован через эту секцию, даже если другие условия совпадают."
msgid "Route Router's Own Traffic"
msgstr "Маршрутизировать собственный трафик роутера"
msgid "Route local traffic originating directly from the router (e.g. wget, curl, opkg, torrent clients on the router) through Tachyon."
msgstr "Направлять локальный трафик, исходящий непосредственно с роутера (например, wget, curl, opkg, торрент-клиент на роутере), через Tachyon."
msgid "Router Traffic Target Section"
msgstr "Секция для трафика роутера"
msgid "Select the section to route router's own traffic through."
msgstr "Выберите секцию для маршрутизации трафика роутера."
+65
View File
@@ -2570,6 +2570,63 @@ function nft_guest_mode_signature_body(body, guest_mode) {
return body; return body;
} }
function router_output_intercept_enabled(settings) {
if (type(settings) != "object")
settings = uci_settings();
return bool_option(settings, "route_router_traffic", false) &&
option(settings, "route_router_traffic_section", "") != "";
}
function nft_disable_router_output_intercept(table) {
run_args_quiet([ "nft", "delete", "chain", "inet", as_string(table), "output_redirect" ]);
return true;
}
function nft_enable_router_output_intercept(table, localv4_set, outbound_mark, exclude_ntp) {
table = as_string(table);
localv4_set = as_string(localv4_set || "localv4");
outbound_mark = as_string(outbound_mark || runtime_constants.OUTBOUND_MARK);
let port = as_string(runtime_constants.REDIRECT_INBOUND_PORT);
if (!run_args_quiet([ "nft", "flush", "chain", "inet", table, "output_redirect" ])) {
if (!nft_create_chain(table, "output_redirect", "{ type nat hook output priority -100; policy accept; }"))
return false;
}
if (!nft_add_rule(table, "output_redirect", [ "ct", "status", "dnat", "return" ]))
return false;
if (!nft_add_rule(table, "output_redirect", [ "meta", "l4proto", "icmp", "return" ]))
return false;
if (!nft_add_rule(table, "output_redirect", [ "ip", "daddr", "@" + localv4_set, "return" ]))
return false;
if (!nft_add_rule(table, "output_redirect", [ "tcp", "dport", "53", "return" ]))
return false;
if (!nft_add_rule(table, "output_redirect", [ "udp", "dport", "53", "return" ]))
return false;
if (arg_bool(exclude_ntp) && !nft_add_rule(table, "output_redirect", [ "udp", "dport", "123", "return" ]))
return false;
if (outbound_mark != "" && !nft_add_rule(table, "output_redirect", [ "meta", "mark", outbound_mark, "return" ]))
return false;
return nft_add_rule(table, "output_redirect", [
"meta", "l4proto", "tcp", "counter", "redirect", "to", ":" + port
]);
}
function nft_sync_router_output_intercept(table, localv4_set, outbound_mark) {
table = as_string(table || "tachyon");
localv4_set = as_string(localv4_set || "localv4");
outbound_mark = as_string(outbound_mark || runtime_constants.OUTBOUND_MARK);
let settings = uci_settings();
if (!router_output_intercept_enabled(settings))
return nft_disable_router_output_intercept(table);
return nft_enable_router_output_intercept(
table,
localv4_set,
outbound_mark,
option(settings, "exclude_ntp", "0")
);
}
function nft_runtime_signature_from_settings_and_sections(settings, sections, schedules, profiles, guest_modes) { function nft_runtime_signature_from_settings_and_sections(settings, sections, schedules, profiles, guest_modes) {
let body = ""; let body = "";
@@ -2580,6 +2637,8 @@ function nft_runtime_signature_from_settings_and_sections(settings, sections, sc
body = signature_add_value(body, "settings.game_console_ips", option(settings, "game_console_ips", "")); body = signature_add_value(body, "settings.game_console_ips", option(settings, "game_console_ips", ""));
body = signature_add_value(body, "settings.excluded_clients", option(settings, "excluded_clients", "")); body = signature_add_value(body, "settings.excluded_clients", option(settings, "excluded_clients", ""));
body = signature_add_value(body, "settings.excluded_ips", option(settings, "excluded_ips", "")); body = signature_add_value(body, "settings.excluded_ips", option(settings, "excluded_ips", ""));
body = signature_add_value(body, "settings.route_router_traffic", bool_option(settings, "route_router_traffic", false) ? "1" : "0");
body = signature_add_value(body, "settings.route_router_traffic_section", option(settings, "route_router_traffic_section", ""));
for (let section in sections) for (let section in sections)
body = nft_rule_signature_body(body, object_or_empty(section)); body = nft_rule_signature_body(body, object_or_empty(section));
@@ -3248,6 +3307,12 @@ else if (mode == "tproxy-route-rule-present")
exit(tproxy_route_rule_present(ARGV[1], ARGV[2]) ? 0 : 1); exit(tproxy_route_rule_present(ARGV[1], ARGV[2]) ? 0 : 1);
else if (mode == "ensure-bridge-netfilter-disabled") else if (mode == "ensure-bridge-netfilter-disabled")
exit(ensure_bridge_netfilter_disabled() ? 0 : 1); exit(ensure_bridge_netfilter_disabled() ? 0 : 1);
else if (mode == "nft-enable-router-output-intercept")
exit(nft_enable_router_output_intercept(ARGV[1], ARGV[2], ARGV[3], ARGV[4]) ? 0 : 1);
else if (mode == "nft-disable-router-output-intercept")
exit(nft_disable_router_output_intercept(ARGV[1]) ? 0 : 1);
else if (mode == "nft-sync-router-output-intercept")
exit(nft_sync_router_output_intercept(ARGV[1], ARGV[2], ARGV[3]) ? 0 : 1);
else { else {
warn("Usage: nft/apply.uc <operation> ...\n"); warn("Usage: nft/apply.uc <operation> ...\n");
exit(1); exit(1);
+4
View File
@@ -28,6 +28,8 @@ let strings = {
isolate_p2p: "Isolate P2P", isolate_p2p: "Isolate P2P",
log_level: "Log Level", log_level: "Log Level",
exclude_ntp: "Exclude NTP", exclude_ntp: "Exclude NTP",
route_router_traffic: "Route Router Traffic",
route_router_traffic_section: "Router Traffic Section",
shutdown_correctly: "Clean Shutdown", shutdown_correctly: "Clean Shutdown",
smart_detect: "Smart Detect", smart_detect: "Smart Detect",
smart_detect_sections: "Smart Detect Sections", smart_detect_sections: "Smart Detect Sections",
@@ -582,6 +584,8 @@ let strings = {
isolate_p2p: "Изолировать P2P", isolate_p2p: "Изолировать P2P",
log_level: "Уровень логов", log_level: "Уровень логов",
exclude_ntp: "Исключить NTP", exclude_ntp: "Исключить NTP",
route_router_traffic: "Маршрутизировать трафик роутера",
route_router_traffic_section: "Секция трафика роутера",
shutdown_correctly: "Корректное завершение", shutdown_correctly: "Корректное завершение",
smart_detect: "Smart Detect", smart_detect: "Smart Detect",
smart_detect_sections: "Секции Smart Detect", smart_detect_sections: "Секции Smart Detect",
@@ -926,6 +926,8 @@ function start_main() {
return status; return status;
} }
module_status(NFT_UC, [ "nft-sync-router-output-intercept", NFT_TABLE_NAME, NFT_LOCALV4_SET_NAME, NFT_OUTBOUND_MARK ]);
status = module_status(PRIORITY_UC, [ "start-runtime" ]); status = module_status(PRIORITY_UC, [ "start-runtime" ]);
if (status != 0) { if (status != 0) {
log_message("Failed to start Priority runtime. Aborted.", "fatal"); log_message("Failed to start Priority runtime. Aborted.", "fatal");
@@ -1039,6 +1041,8 @@ function stop_main() {
module_success(PARENTAL_QUOTA_UC, [ "remove-cron" ]); module_success(PARENTAL_QUOTA_UC, [ "remove-cron" ]);
module_success(TELEGRAM_UC, [ "stop-runtime" ]); module_success(TELEGRAM_UC, [ "stop-runtime" ]);
module_success(NFT_UC, [ "nft-disable-router-output-intercept", NFT_TABLE_NAME ]);
if (command_success_from_args([ "nft", "list", "table", "inet", NFT_TABLE_NAME ])) { if (command_success_from_args([ "nft", "list", "table", "inet", NFT_TABLE_NAME ])) {
if (!command_success_from_args([ "nft", "delete", "table", "inet", NFT_TABLE_NAME ])) if (!command_success_from_args([ "nft", "delete", "table", "inet", NFT_TABLE_NAME ]))
log_message("Failed to delete nft table " + NFT_TABLE_NAME, "warn"); log_message("Failed to delete nft table " + NFT_TABLE_NAME, "warn");
@@ -1539,6 +1543,8 @@ function reload(reason) {
module_success(PARENTAL_QUOTA_UC, [ "install-cron" ]); module_success(PARENTAL_QUOTA_UC, [ "install-cron" ]);
module_success(PARENTAL_QUOTA_UC, [ "tick" ]); module_success(PARENTAL_QUOTA_UC, [ "tick" ]);
module_status(NFT_UC, [ "nft-sync-router-output-intercept", NFT_TABLE_NAME, NFT_LOCALV4_SET_NAME, NFT_OUTBOUND_MARK ]);
if (plan.needs_dnsmasq_configure == 1) { if (plan.needs_dnsmasq_configure == 1) {
status = dnsmasq_configure(true); status = dnsmasq_configure(true);
if (status != 0) if (status != 0)
+4
View File
@@ -999,6 +999,8 @@ function nft_runtime_signature_body(settings, sections, schedules, profiles, gue
body = signature_add_value(body, "settings.game_console_ips", option(settings, "game_console_ips", "")); body = signature_add_value(body, "settings.game_console_ips", option(settings, "game_console_ips", ""));
body = signature_add_value(body, "settings.excluded_clients", option(settings, "excluded_clients", "")); body = signature_add_value(body, "settings.excluded_clients", option(settings, "excluded_clients", ""));
body = signature_add_value(body, "settings.excluded_ips", option(settings, "excluded_ips", "")); body = signature_add_value(body, "settings.excluded_ips", option(settings, "excluded_ips", ""));
body = signature_add_value(body, "settings.route_router_traffic", bool_option(settings, "route_router_traffic", false) ? "1" : "0");
body = signature_add_value(body, "settings.route_router_traffic_section", option(settings, "route_router_traffic_section", ""));
for (let section in sections) { for (let section in sections) {
section = object_or_empty(section); section = object_or_empty(section);
@@ -1662,6 +1664,8 @@ function sing_box_signature_body(settings, sections, servers, mwan3_active, sche
body = signature_add_value(body, "settings.download_lists_via_proxy_section", option(settings, "download_lists_via_proxy_section", "")); body = signature_add_value(body, "settings.download_lists_via_proxy_section", option(settings, "download_lists_via_proxy_section", ""));
if (download_via_proxy_enabled(settings, "components")) if (download_via_proxy_enabled(settings, "components"))
body = signature_add_value(body, "settings.download_components_via_proxy_section", option(settings, "download_components_via_proxy_section", "")); body = signature_add_value(body, "settings.download_components_via_proxy_section", option(settings, "download_components_via_proxy_section", ""));
body = signature_add_value(body, "settings.route_router_traffic", bool_option_value(settings, "route_router_traffic", false));
body = signature_add_value(body, "settings.route_router_traffic_section", option(settings, "route_router_traffic_section", ""));
for (let section in sections) for (let section in sections)
body = append_sing_box_rule_signature_body(body, object_or_empty(section), sections); body = append_sing_box_rule_signature_body(body, object_or_empty(section), sections);
+3 -1
View File
@@ -611,6 +611,8 @@ let setting_schema = {
isolate_p2p: "isolate_p2p", isolate_p2p: "isolate_p2p",
log_level: "log_level", log_level: "log_level",
exclude_ntp: "exclude_ntp", exclude_ntp: "exclude_ntp",
route_router_traffic: "route_router_traffic",
route_router_traffic_section: "route_router_traffic_section",
shutdown_correctly: "shutdown_correctly", shutdown_correctly: "shutdown_correctly",
smart_detect: "smart_detect", smart_detect: "smart_detect",
smart_detect_sections: "smart_detect_sections" smart_detect_sections: "smart_detect_sections"
@@ -671,7 +673,7 @@ function is_boolean_key(key) {
"dns_detour_enabled", "enable_output_network_interface", "enable_badwan_interface_monitoring", "dns_detour_enabled", "enable_output_network_interface", "enable_badwan_interface_monitoring",
"enable_yacd", "disable_quic", "block_doh", "list_update_enabled", "component_update_check_enabled", "enable_yacd", "disable_quic", "block_doh", "list_update_enabled", "component_update_check_enabled",
"download_lists_via_proxy", "download_components_via_proxy", "dont_touch_dhcp", "download_lists_via_proxy", "download_components_via_proxy", "dont_touch_dhcp",
"isolate_p2p", "exclude_ntp", "shutdown_correctly", "smart_detect", "isolate_p2p", "exclude_ntp", "route_router_traffic", "shutdown_correctly", "smart_detect",
"notify_crash", "notify_restart", "notify_server_switch", "notify_subscription", "notify_cert", "notify_dns_leak", "notify_crash", "notify_restart", "notify_server_switch", "notify_subscription", "notify_cert", "notify_dns_leak",
"daily_report_enabled", "quiet_hours_enabled"]; "daily_report_enabled", "quiet_hours_enabled"];
for (let x in b) if (x == key) return true; for (let x in b) if (x == key) return true;
@@ -28,6 +28,9 @@ const DNS_SOURCE6_SET = "tachyon_dns_sources6";
const SERVICE_MIXED_INBOUND_TAG = "service-mixed-in"; const SERVICE_MIXED_INBOUND_TAG = "service-mixed-in";
const SERVICE_MIXED_INBOUND_ADDRESS = "127.0.0.1"; const SERVICE_MIXED_INBOUND_ADDRESS = "127.0.0.1";
const SERVICE_MIXED_INBOUND_PORT = 4534; const SERVICE_MIXED_INBOUND_PORT = 4534;
const REDIRECT_INBOUND_TAG = "redirect-in";
const REDIRECT_INBOUND_ADDRESS = "127.0.0.1";
const REDIRECT_INBOUND_PORT = 1604;
const DIRECT_BYPASS_INBOUND_TAG = "direct-bypass-in"; const DIRECT_BYPASS_INBOUND_TAG = "direct-bypass-in";
const DIRECT_BYPASS_OUTBOUND_TAG = "direct-bypass-out"; const DIRECT_BYPASS_OUTBOUND_TAG = "direct-bypass-out";
const DIRECT_BYPASS_DEFAULT_PORT = 2080; const DIRECT_BYPASS_DEFAULT_PORT = 2080;
@@ -142,6 +145,9 @@ return {
SERVICE_MIXED_INBOUND_TAG, SERVICE_MIXED_INBOUND_TAG,
SERVICE_MIXED_INBOUND_ADDRESS, SERVICE_MIXED_INBOUND_ADDRESS,
SERVICE_MIXED_INBOUND_PORT, SERVICE_MIXED_INBOUND_PORT,
REDIRECT_INBOUND_TAG,
REDIRECT_INBOUND_ADDRESS,
REDIRECT_INBOUND_PORT,
DIRECT_BYPASS_INBOUND_TAG, DIRECT_BYPASS_INBOUND_TAG,
DIRECT_BYPASS_OUTBOUND_TAG, DIRECT_BYPASS_OUTBOUND_TAG,
DIRECT_BYPASS_DEFAULT_PORT, DIRECT_BYPASS_DEFAULT_PORT,
@@ -827,6 +827,42 @@ function add_service_mixed_proxy(config, settings, sections) {
runtime_generate_unsupported("download components via proxy section is not set"); runtime_generate_unsupported("download components via proxy section is not set");
} }
function router_traffic_section(settings) {
if (!bool_option(settings, "route_router_traffic", false))
return "";
return option(settings, "route_router_traffic_section", "");
}
function add_router_traffic_redirect(config, settings) {
let section_name = router_traffic_section(settings);
if (section_name == "")
return;
push(config.inbounds, {
type: "redirect",
tag: runtime_constants.REDIRECT_INBOUND_TAG,
listen: runtime_constants.REDIRECT_INBOUND_ADDRESS,
listen_port: runtime_constants.REDIRECT_INBOUND_PORT
});
let sec_out = outbound_tag(section_name);
if (sec_out != "") {
let target_idx = 0;
if (type(config.route) == "object" && type(config.route.rules) == "array") {
for (let i = 0; i < length(config.route.rules); i++) {
let r = config.route.rules[i];
if (r.action == "sniff" || r.action == "hijack-dns")
target_idx = i + 1;
}
splice(config.route.rules, target_idx, 0, {
action: "route",
inbound: [ runtime_constants.REDIRECT_INBOUND_TAG ],
outbound: sec_out
});
}
}
}
// ─── Content blocking (parental control domains) ───────────────────────────── // ─── Content blocking (parental control domains) ─────────────────────────────
// Each enabled `config schedule` with blocked_domains generates: // Each enabled `config schedule` with blocked_domains generates:
// 1. DNS rules (on the dedicated dns-block-in inbound) that reject the // 1. DNS rules (on the dedicated dns-block-in inbound) that reject the
@@ -1368,6 +1404,7 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
add_service_mixed_proxy(config, settings, sections); add_service_mixed_proxy(config, settings, sections);
for (let section in sections) for (let section in sections)
add_mixed_proxy_for_section(config, section, service_address); add_mixed_proxy_for_section(config, section, service_address);
add_router_traffic_redirect(config, settings);
add_content_blocking(config); add_content_blocking(config);
+2
View File
@@ -78,6 +78,8 @@ function config(settings, runtime) {
if (type(runtime) == "object" && type(runtime.dns_health_inbounds) == "array") if (type(runtime) == "object" && type(runtime.dns_health_inbounds) == "array")
for (let inbound in runtime.dns_health_inbounds) for (let inbound in runtime.dns_health_inbounds)
push(dns_hijack_inbounds, inbound); push(dns_hijack_inbounds, inbound);
if (bool_option(settings, "route_router_traffic", false) && option(settings, "route_router_traffic_section", "") != "")
push(sniff_inbounds, runtime_constants.REDIRECT_INBOUND_TAG);
let rules = [ let rules = [
{ action: "sniff", inbound: sniff_inbounds }, { action: "sniff", inbound: sniff_inbounds },
{ action: "hijack-dns", inbound: dns_hijack_inbounds } { action: "hijack-dns", inbound: dns_hijack_inbounds }
+12
View File
@@ -687,6 +687,10 @@ br-lan tun0
[settings.excluded_ips] [settings.excluded_ips]
[settings.route_router_traffic]
0
[settings.route_router_traffic_section]
[rule.text_rule.action] [rule.text_rule.action]
bypass bypass
[rule.text_rule.ip_cidr] [rule.text_rule.ip_cidr]
@@ -795,6 +799,10 @@ br-lan tun0
[settings.excluded_ips] [settings.excluded_ips]
[settings.route_router_traffic]
0
[settings.route_router_traffic_section]
[rule.enabled.action] [rule.enabled.action]
bypass bypass
[rule.enabled.ip_cidr] [rule.enabled.ip_cidr]
@@ -853,6 +861,10 @@ br-lan
[settings.excluded_ips] [settings.excluded_ips]
[settings.route_router_traffic]
0
[settings.route_router_traffic_section]
EOF_EXPECTED EOF_EXPECTED
expected_signature="$(md5sum "$WORK_DIR/signature-defaults-expected.txt" | awk '{print $1}')" expected_signature="$(md5sum "$WORK_DIR/signature-defaults-expected.txt" | awk '{print $1}')"
assert_eq "$expected_signature" \ assert_eq "$expected_signature" \
+137
View File
@@ -0,0 +1,137 @@
#!/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"
export NFT_LOG LOGGER_LOG
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
exit 1
}
assert_contains() {
local file="$1"
local expected="$2"
local label="$3"
grep -Fq "$expected" "$file" || fail "$label: expected '$expected'"
}
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 [ "$#" -ge 5 ] && [ "$1" = "flush" ] && [ "$2" = "chain" ]; then
if [ ! -f "$WORK_DIR/chain_exists" ]; then
exit 1
fi
fi
if [ "$#" -ge 5 ] && [ "$1" = "add" ] && [ "$2" = "chain" ]; then
touch "$WORK_DIR/chain_exists"
fi
if [ "$#" -ge 5 ] && [ "$1" = "delete" ] && [ "$2" = "chain" ]; then
rm -f "$WORK_DIR/chain_exists"
fi
NFT
chmod 0755 "$WORK_DIR/bin/nft"
cat >"$WORK_DIR/bin/logger" <<'LOGGER'
#!/usr/bin/env bash
set -eo pipefail
printf 'logger\t%s\n' "$*" >> "${LOGGER_LOG:?}"
LOGGER
chmod 0755 "$WORK_DIR/bin/logger"
export PATH="$WORK_DIR/bin:$PATH"
nft_ucode() {
ucode -S -L "$TACHYON_LIB" "$NFT_RUNTIME" "$@"
}
# Test 1: Direct enable call
: > "$NFT_LOG"
rm -f "$WORK_DIR/chain_exists"
nft_ucode nft-enable-router-output-intercept TachyonTable localv4 0x00100000 1
assert_contains "$NFT_LOG" $'nft\tadd\tchain\tinet\tTachyonTable\toutput_redirect\t{ type nat hook output priority -100; policy accept; }' "enable: creates chain output_redirect"
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\toutput_redirect\tct\tstatus\tdnat\treturn' "enable: ct status dnat return"
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\toutput_redirect\tip\tdaddr\t@localv4\treturn' "enable: localv4 bypass"
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\toutput_redirect\ttcp\tdport\t53\treturn' "enable: port 53 bypass"
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\toutput_redirect\tudp\tdport\t123\treturn' "enable: exclude_ntp bypass"
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\toutput_redirect\tmeta\tmark\t0x00100000\treturn' "enable: outbound_mark bypass"
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\toutput_redirect\tmeta\tl4proto\ttcp\tcounter\tredirect\tto\t:1604' "enable: tcp redirect to 1604"
# Test 2: Direct disable call
: > "$NFT_LOG"
nft_ucode nft-disable-router-output-intercept TachyonTable
assert_contains "$NFT_LOG" $'nft\tdelete\tchain\tinet\tTachyonTable\toutput_redirect' "disable: deletes chain output_redirect"
# Test 3: Sync from UCI - when enabled
STATE_ENABLED="$WORK_DIR/enabled.state"
cat >"$STATE_ENABLED" <<'STATE'
tachyon.settings=settings
tachyon.settings.route_router_traffic=1
tachyon.settings.route_router_traffic_section=proxy_sec
tachyon.settings.exclude_ntp=1
tachyon.proxy_sec=section
tachyon.proxy_sec.enabled=1
tachyon.proxy_sec.routing_mode=proxy
tachyon.proxy_sec.mark=354
tachyon.proxy_sec.tproxy_port=10080
STATE
: > "$NFT_LOG"
rm -f "$WORK_DIR/chain_exists"
TACHYON_UCI_STATE_FILE="$STATE_ENABLED" nft_ucode nft-sync-router-output-intercept TachyonTable localv4 0x00100000
assert_contains "$NFT_LOG" $'nft\tadd\tchain\tinet\tTachyonTable\toutput_redirect\t{ type nat hook output priority -100; policy accept; }' "sync(enabled): creates chain"
assert_contains "$NFT_LOG" $'nft\tadd\trule\tinet\tTachyonTable\toutput_redirect\tmeta\tl4proto\ttcp\tcounter\tredirect\tto\t:1604' "sync(enabled): redirects to port"
# Test 4: Sync from UCI - when disabled
STATE_DISABLED="$WORK_DIR/disabled.state"
cat >"$STATE_DISABLED" <<'STATE'
tachyon.settings=settings
tachyon.settings.route_router_traffic=0
tachyon.settings.route_router_traffic_section=proxy_sec
tachyon.proxy_sec=section
tachyon.proxy_sec.enabled=1
tachyon.proxy_sec.routing_mode=proxy
tachyon.proxy_sec.mark=354
STATE
: > "$NFT_LOG"
TACHYON_UCI_STATE_FILE="$STATE_DISABLED" nft_ucode nft-sync-router-output-intercept TachyonTable localv4 0x00100000
assert_contains "$NFT_LOG" $'nft\tdelete\tchain\tinet\tTachyonTable\toutput_redirect' "sync(disabled): deletes chain"
# Test 5: Verify UCI setting loads route_router_traffic
CHECK_SETTINGS=$(TACHYON_UCI_STATE_FILE="$STATE_DISABLED" ucode -S -L "$TACHYON_LIB" -e '
let uci = require("core.uci");
let val = uci.get("tachyon.settings.route_router_traffic");
print("val=" + val);
')
if [ "$CHECK_SETTINGS" != "val=0" ]; then
fail "UCI route_router_traffic expected val=0, got '$CHECK_SETTINGS'"
fi
printf 'ALL TESTS PASSED for route_router_traffic\n'
+4
View File
@@ -591,6 +591,10 @@ secret
0 0
[settings.download_components_via_proxy] [settings.download_components_via_proxy]
0 0
[settings.route_router_traffic]
0
[settings.route_router_traffic_section]
[rule.proxy1.action] [rule.proxy1.action]
proxy proxy
[rule.proxy1.connection_urls] [rule.proxy1.connection_urls]