diff --git a/server/internal/httpapi/clientconfig.go b/server/internal/httpapi/clientconfig.go index 426f1e4..af0541d 100644 --- a/server/internal/httpapi/clientconfig.go +++ b/server/internal/httpapi/clientconfig.go @@ -113,6 +113,12 @@ func BuildClientConfig(node *nodes.NodeRow, dpUUID, deriveKey string, opts Clien "auto_route": true, "strict_route": true, "stack": "system", + // 私有 LAN 直连:strict_route 会在 OS 层把所有流量(含 LAN)强抓进隧道, + // 光靠 route.rules 的 ip_cidr→direct 在 macOS 不生效(direct 出站的包被 + // strict_route 重新捕回隧道)。route_exclude_address 在 auto_route 层就把这些 + // 网段排除出隧道,LAN 走系统直连(修「隧道开着连不上局域网/NAS/家里机器」)。 + // **不含 172.16.0.0/12**:隧道自身地址与 DNS(172.19.x)在此段,排除会断 DNS。 + "route_exclude_address": []string{"192.168.0.0/16", "10.0.0.0/8"}, } // 代理出站集合:REALITY 必有;Hy2 仅在启用时加入(否则不进配置/探测组)。 diff --git a/server/internal/httpapi/clientconfig_test.go b/server/internal/httpapi/clientconfig_test.go index a144d1f..5d54d81 100644 --- a/server/internal/httpapi/clientconfig_test.go +++ b/server/internal/httpapi/clientconfig_test.go @@ -193,3 +193,45 @@ func TestRulesHandler(t *testing.T) { t.Errorf("allowed but missing file: code=%d, want 404", code) } } + +// TUN 入站必须把私有 LAN 网段从隧道排除(route_exclude_address),否则 strict_route +// 会在 macOS 把 LAN 强抓进隧道 → 隧道开着连不上局域网/NAS。不得含 172.16/12 +// (隧道自身 172.19.x 在此段,排除会断 DNS)。 +func TestBuildClientConfigLANExclude(t *testing.T) { + cfg, err := BuildClientConfig(testNode(), "uuid-1", "k", ClientConfigOpts{}) + if err != nil { + t.Fatalf("build: %v", err) + } + var m map[string]any + if err := json.Unmarshal(cfg, &m); err != nil { + t.Fatalf("unmarshal: %v", err) + } + var tun map[string]any + for _, in := range m["inbounds"].([]any) { + im := in.(map[string]any) + if im["type"] == "tun" { + tun = im + break + } + } + if tun == nil { + t.Fatal("no tun inbound") + } + exRaw, ok := tun["route_exclude_address"] + if !ok { + t.Fatal("tun inbound missing route_exclude_address (LAN would be captured by strict_route)") + } + got := map[string]bool{} + for _, v := range exRaw.([]any) { + got[v.(string)] = true + } + if !got["192.168.0.0/16"] { + t.Error("route_exclude_address must contain 192.168.0.0/16") + } + if !got["10.0.0.0/8"] { + t.Error("route_exclude_address must contain 10.0.0.0/8") + } + if got["172.16.0.0/12"] { + t.Error("route_exclude_address must NOT contain 172.16.0.0/12 (tunnel DNS 172.19.x lives there)") + } +}