fix(routing): resolve real IP for zapret routes, add IPv6 sniffing, and fix monitoring route display
This commit is contained in:
@@ -189,6 +189,7 @@ function buildRouteDisplayNames(sections: Tachyon.ConfigSection[]) {
|
||||
const map: Record<string, string> = {
|
||||
'bypass-out': 'Bypass',
|
||||
'direct-out': 'direct',
|
||||
'tachyon-failover': 'Failover',
|
||||
};
|
||||
const serverMap: Record<string, string> = {};
|
||||
const routeSectionItems: Array<{ sectionName: string; displayName: string }> =
|
||||
@@ -261,12 +262,10 @@ function getRouteDisplayNameByTag(tag: string): string {
|
||||
}
|
||||
|
||||
const manualSection = routeSections.find(({ sectionName }) => {
|
||||
if (!tag.startsWith(`${sectionName}-`) || !tag.endsWith('-out')) {
|
||||
return false;
|
||||
if (tag === sectionName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const middle = tag.slice(sectionName.length + 1, -4);
|
||||
return /^\d+(?:-\d+)?$/.test(middle);
|
||||
return tag.startsWith(`${sectionName}-`) && tag.endsWith('-out');
|
||||
});
|
||||
|
||||
return manualSection?.displayName || '';
|
||||
@@ -1893,3 +1892,5 @@ export async function initController(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { buildRouteDisplayNames, getRouteDisplayNameByTag, getRoute };
|
||||
|
||||
@@ -55,4 +55,85 @@ describe('monitoring initController', () => {
|
||||
const { initController } = await import('../initController');
|
||||
expect(typeof initController).toBe('function');
|
||||
});
|
||||
|
||||
it('should resolve route display names correctly for built-in and section tags', async () => {
|
||||
const { buildRouteDisplayNames, getRouteDisplayNameByTag, getRoute } =
|
||||
await import('../initController');
|
||||
|
||||
buildRouteDisplayNames([
|
||||
{
|
||||
'.name': 'vpn',
|
||||
'.type': 'section',
|
||||
label: 'My VPN',
|
||||
enabled: '1',
|
||||
} as any,
|
||||
{
|
||||
'.name': 'zapret',
|
||||
'.type': 'section',
|
||||
label: 'Zapret YouTube',
|
||||
enabled: '1',
|
||||
} as any,
|
||||
]);
|
||||
|
||||
// Built-in tags
|
||||
expect(getRouteDisplayNameByTag('bypass-out')).toBe('Bypass');
|
||||
expect(getRouteDisplayNameByTag('direct-out')).toBe('direct');
|
||||
expect(getRouteDisplayNameByTag('tachyon-failover')).toBe('Failover');
|
||||
|
||||
// Section root tags
|
||||
expect(getRouteDisplayNameByTag('vpn-out')).toBe('My VPN');
|
||||
expect(getRouteDisplayNameByTag('zapret-out')).toBe('Zapret YouTube');
|
||||
|
||||
// Section sub-outbounds (numeric, named, priority, json)
|
||||
expect(getRouteDisplayNameByTag('vpn-1-out')).toBe('My VPN');
|
||||
expect(getRouteDisplayNameByTag('vpn-priority-1-out')).toBe('My VPN');
|
||||
expect(getRouteDisplayNameByTag('vpn-json-1-out')).toBe('My VPN');
|
||||
expect(getRouteDisplayNameByTag('vpn-interface-1-out')).toBe('My VPN');
|
||||
expect(getRouteDisplayNameByTag('zapret-1-out')).toBe('Zapret YouTube');
|
||||
|
||||
// Direct section name matching
|
||||
expect(getRouteDisplayNameByTag('vpn')).toBe('My VPN');
|
||||
expect(getRouteDisplayNameByTag('zapret')).toBe('Zapret YouTube');
|
||||
|
||||
// Unknown tag returns empty string
|
||||
expect(getRouteDisplayNameByTag('unknown-out')).toBe('');
|
||||
|
||||
// getRoute helper with chains and fallback rule
|
||||
expect(
|
||||
getRoute({
|
||||
id: '1',
|
||||
metadata: {},
|
||||
chains: ['tproxy-in', 'vpn-priority-1-out'],
|
||||
lastSeenAt: Date.now(),
|
||||
} as any),
|
||||
).toBe('My VPN');
|
||||
|
||||
expect(
|
||||
getRoute({
|
||||
id: '2',
|
||||
metadata: {},
|
||||
chains: ['tproxy-in', 'zapret-out'],
|
||||
lastSeenAt: Date.now(),
|
||||
} as any),
|
||||
).toBe('Zapret YouTube');
|
||||
|
||||
expect(
|
||||
getRoute({
|
||||
id: '3',
|
||||
metadata: {},
|
||||
chains: [],
|
||||
rule: 'rule-1 => route("zapret-out")',
|
||||
lastSeenAt: Date.now(),
|
||||
} as any),
|
||||
).toBe('Zapret YouTube');
|
||||
|
||||
expect(
|
||||
getRoute({
|
||||
id: '4',
|
||||
metadata: {},
|
||||
chains: ['tachyon-failover'],
|
||||
lastSeenAt: Date.now(),
|
||||
} as any),
|
||||
).toBe('Failover');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17932,7 +17932,8 @@ function getDisplayName2(section) {
|
||||
function buildRouteDisplayNames(sections) {
|
||||
const map = {
|
||||
"bypass-out": "Bypass",
|
||||
"direct-out": "direct"
|
||||
"direct-out": "direct",
|
||||
"tachyon-failover": "Failover"
|
||||
};
|
||||
const serverMap = {};
|
||||
const routeSectionItems = [];
|
||||
@@ -17983,11 +17984,10 @@ function getRouteDisplayNameByTag(tag) {
|
||||
return routeDisplayNames[tag];
|
||||
}
|
||||
const manualSection = routeSections.find(({ sectionName }) => {
|
||||
if (!tag.startsWith(`${sectionName}-`) || !tag.endsWith("-out")) {
|
||||
return false;
|
||||
if (tag === sectionName) {
|
||||
return true;
|
||||
}
|
||||
const middle = tag.slice(sectionName.length + 1, -4);
|
||||
return /^\d+(?:-\d+)?$/.test(middle);
|
||||
return tag.startsWith(`${sectionName}-`) && tag.endsWith("-out");
|
||||
});
|
||||
return manualSection?.displayName || "";
|
||||
}
|
||||
|
||||
@@ -1375,6 +1375,7 @@ function append_sing_box_rule_signature_body(body, section, sections) {
|
||||
}
|
||||
else if (action == "zapret" || action == "zapret2") {
|
||||
body = signature_add_mixed_proxy_body(body, section, prefix);
|
||||
body = signature_add_value(body, prefix + ".resolve_real_ip_for_routing", "1");
|
||||
}
|
||||
else if (action == "dns") {
|
||||
body = signature_add_value(body, prefix + ".dns_type", option(section, "dns_type", "udp"));
|
||||
|
||||
@@ -646,6 +646,8 @@ function is_valid_detour(config, tag) {
|
||||
if (type(out) == "object" && out.tag == tag) {
|
||||
if (out.tag == "DPI-out" || (out.type == "socks" && (out.server == "127.0.0.1" || out.server == "::1")))
|
||||
return false;
|
||||
if (out.routing_mark != null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ function config(settings, runtime) {
|
||||
let output_network_interface = option(settings, "output_network_interface", "");
|
||||
let mwan3_active = type(runtime) == "object" && bool_value(runtime.mwan3_active);
|
||||
let sniff_inbounds = [ runtime_constants.TPROXY_INBOUND_TAG, runtime_constants.DNS_INBOUND_TAG ];
|
||||
if (common.ipv6_supported())
|
||||
push(sniff_inbounds, runtime_constants.TPROXY_INBOUND6_TAG);
|
||||
if (type(runtime) == "object" && bool_value(runtime.source_aware_dns))
|
||||
push(sniff_inbounds, runtime_constants.SOURCE_DNS_INBOUND_TAG);
|
||||
if (type(runtime) == "object" && type(runtime.dns_health_inbounds) == "array")
|
||||
@@ -136,7 +138,7 @@ function has_resolve_matchers(rule) {
|
||||
|
||||
function resolve_rule_for_section(section, route_rule) {
|
||||
let action = option(section, "action", "");
|
||||
let should_resolve = action == "byedpi" ||
|
||||
let should_resolve = action == "byedpi" || action == "zapret" || action == "zapret2" ||
|
||||
(connections.is_connections_action(action) &&
|
||||
bool_option(section, "resolve_real_ip_for_routing", false));
|
||||
|
||||
|
||||
@@ -122,8 +122,8 @@ grep -Fq '/stat' "$WORK_DIR/lock.uc" ||
|
||||
|
||||
# ucode resolves names lexically and does not hoist, so a call placed above the
|
||||
# declaration silently becomes null and the lock is never released.
|
||||
lock_decl="$(grep -n '^function installer_release_init_lock() {' "$INSTALLER" | head -1 | cut -d: -f1)"
|
||||
lock_call="$(grep -n 'installer_release_init_lock();' "$INSTALLER" | head -1 | cut -d: -f1)"
|
||||
lock_decl="$(grep -m 1 -n '^function installer_release_init_lock() {' "$INSTALLER" | cut -d: -f1)"
|
||||
lock_call="$(grep -m 1 -n 'installer_release_init_lock();' "$INSTALLER" | cut -d: -f1)"
|
||||
[ -n "$lock_decl" ] && [ -n "$lock_call" ] ||
|
||||
fail "installer_release_init_lock must be both declared and called"
|
||||
[ "$lock_decl" -lt "$lock_call" ] ||
|
||||
|
||||
@@ -130,4 +130,55 @@ for (let rs in rulesets) {
|
||||
}
|
||||
' || fail "DPI-out must never be set as download_detour"
|
||||
|
||||
# 3. Verify zapret section generates resolve action for rulesets and domains
|
||||
cat >"$WORK_DIR/fixture_zapret_resolve.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"enabled": "1",
|
||||
"dns_type": "udp",
|
||||
"dns_server": "1.1.1.1",
|
||||
"service_listen_address": "127.0.0.1"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "zapret",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "zapret",
|
||||
"community_lists": [ "youtube" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
output_zapret_resolve="$WORK_DIR/out_zapret_resolve.json"
|
||||
mkdir -p "$output_zapret_resolve.section-cache" "$output_zapret_resolve.rulesets"
|
||||
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
|
||||
"$WORK_DIR/fixture_zapret_resolve.json" "$output_zapret_resolve" "127.0.0.1" "0" "1"
|
||||
|
||||
ucode -e '
|
||||
let fs = require("fs");
|
||||
let raw = fs.readfile("'"$output_zapret_resolve"'");
|
||||
let cfg = json(raw);
|
||||
let rules = cfg.route.rules || [];
|
||||
let found_resolve = false;
|
||||
let found_route = false;
|
||||
for (let r in rules) {
|
||||
if (r.action == "resolve" && r.rule_set)
|
||||
found_resolve = true;
|
||||
if (r.action == "route" && r.outbound == "zapret-out")
|
||||
found_route = true;
|
||||
}
|
||||
if (!found_resolve) {
|
||||
print("ERROR: zapret section did not generate action: resolve rule!\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!found_route) {
|
||||
print("ERROR: zapret section did not generate action: route rule to zapret-out!\n");
|
||||
exit(2);
|
||||
}
|
||||
' || fail "zapret section must generate resolve rule before route rule"
|
||||
|
||||
printf "Route rules coexistence and detour checks passed\n"
|
||||
|
||||
@@ -115,4 +115,36 @@ grep -q '"http_clients"' "$output_v14" || \
|
||||
grep -q '"default_http_client": "ruleset-http"' "$output_v14" || \
|
||||
fail "sing-box 1.14+ must configure default_http_client"
|
||||
|
||||
# 4. Verify zapret section with community_lists does NOT use zapret-out as download_detour
|
||||
cat >"$WORK_DIR/fixture_zapret.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"enabled": "1",
|
||||
"dns_type": "udp",
|
||||
"dns_server": "1.1.1.1",
|
||||
"service_listen_address": "127.0.0.1"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "zapret_sec",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "zapret",
|
||||
"community_lists": [ "youtube" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
output_zapret="$WORK_DIR/out_zapret.json"
|
||||
mkdir -p "$output_zapret.section-cache" "$output_zapret.rulesets"
|
||||
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
|
||||
"$WORK_DIR/fixture_zapret.json" "$output_zapret" "127.0.0.1" "0" "1"
|
||||
|
||||
if grep -q '"download_detour": "zapret_sec-out"' "$output_zapret"; then
|
||||
fail "zapret-out must never be selected as download_detour due to routing_mark"
|
||||
fi
|
||||
|
||||
printf "ruleset download detour checks passed\n"
|
||||
|
||||
Reference in New Issue
Block a user