diff --git a/backend/internal/converter/singbox.go b/backend/internal/converter/singbox.go index 8bf1966..ea10923 100644 --- a/backend/internal/converter/singbox.go +++ b/backend/internal/converter/singbox.go @@ -95,10 +95,9 @@ func buildSingboxSubscriptionDoc(outbounds []map[string]interface{}) map[string] defaultSingboxTUNInbound(), }, "outbounds": outbounds, - // Route: private traffic stays local; DNS is hijacked into the dns module; - // everything else uses the proxy selector. Domain names of outbounds are - // resolved via local DNS so we do not create a DNS↔proxy dependency loop - // (a common cause of "works but extremely laggy" on SFA). + // Route uses rule actions (sniff / hijack-dns) instead of legacy inbound fields + // (removed in 1.13). ip_is_private on *route* rules is still valid; DNS rules + // must not use bare ip_is_private without match_response (deprecated 1.14). "route": map[string]interface{}{ "rules": []map[string]interface{}{ {"action": "sniff"}, @@ -122,8 +121,10 @@ func buildSingboxSubscriptionDoc(outbounds []map[string]interface{}) map[string] } } -// defaultSingboxDNS: resolve outbound server names via local; general queries via -// DoH through the proxy selector (after the outbound domain is known). +// defaultSingboxDNS uses the post-1.12 server object format (type/tag/server) +// and avoids deprecated DNS rule address filters (ip_is_private / ip_cidr without +// match_response — removed path in 1.16). Private destinations are handled by +// route rules instead. See https://sing-box.sagernet.org/migration/ func defaultSingboxDNS() map[string]interface{} { return map[string]interface{}{ "servers": []map[string]interface{}{ @@ -139,17 +140,17 @@ func defaultSingboxDNS() map[string]interface{} { "detour": "proxy", }, }, - "rules": []map[string]interface{}{ - // Prefer local for private reverse lookups / plain local names. - {"ip_is_private": true, "action": "route", "server": "local"}, - }, "final": "remote", "strategy": "prefer_ipv4", } } -// defaultSingboxTUNInbound mirrors common SFA templates without stack/auto_redirect -// (auto_redirect is Linux-oriented and often hurts mobile clients). +// defaultSingboxTUNInbound follows current Tun schema: +// - address (not inet4_address / inet6_address, removed in 1.12) +// - no stack (deprecated 1.15, removed in 1.17 — client uses sing-tun default) +// - no inbound sniff / domain_strategy (removed in 1.13; use route rule actions) +// - dns_mode hijack (1.14+) with route hijack-dns rules +// https://sing-box.sagernet.org/configuration/inbound/tun/ func defaultSingboxTUNInbound() map[string]interface{} { return map[string]interface{}{ "type": "tun", diff --git a/backend/internal/converter/singbox_tun_test.go b/backend/internal/converter/singbox_tun_test.go index a0086cc..525ad15 100644 --- a/backend/internal/converter/singbox_tun_test.go +++ b/backend/internal/converter/singbox_tun_test.go @@ -41,6 +41,17 @@ func TestBuildSingboxSubscriptionDocIncludesTUN(t *testing.T) { if !ok || dns["final"] != "remote" { t.Fatalf("dns: %#v", parsed["dns"]) } + // 1.14+: DNS rules must not use legacy address filters without match_response. + if rules, ok := dns["rules"].([]interface{}); ok { + for _, r := range rules { + m, _ := r.(map[string]interface{}) + if _, has := m["ip_is_private"]; has { + if match, _ := m["match_response"].(bool); !match { + t.Fatalf("deprecated DNS ip_is_private without match_response: %#v", m) + } + } + } + } route, ok := parsed["route"].(map[string]interface{}) if !ok { t.Fatalf("missing route") diff --git a/docs/subscription-formats.md b/docs/subscription-formats.md index ad24ff5..5ab98a0 100644 --- a/docs/subscription-formats.md +++ b/docs/subscription-formats.md @@ -11,7 +11,7 @@ Public token URL (path may use `web_path` / custom sub base): | *(empty / clash / default)* | Mihomo / Clash Meta YAML | | `v2ray` / `base64` | **Always** standard Base64 of newline-separated share links (`vless://`, `vmess://`, `tuic://`, …). Independent of HTML page “encrypt URI list”. | | `uri` / `raw` | Same links; may be plaintext when encrypt is off | -| `singbox` / `sing-box` | sing-box JSON with default **TUN** + **DNS** (remote via proxy) + `route.rules` sniff/hijack-dns + `route.final=proxy` (SFI/SFA mobile-friendly MTU) (SFI/SFM) and node outbounds | +| `singbox` / `sing-box` | sing-box JSON with default **TUN** (no `stack`, `address` only) + **DNS** (typed servers, no legacy address filters) + `route.rules` sniff/hijack-dns + private→direct + `route.final=proxy` (aligned with sing-box migration 1.11–1.15) (SFI/SFM) and node outbounds | UA auto-detection may choose Clash vs v2ray-style when `target` is omitted.