package core import ( "encoding/json" "fmt" "net" ) // GenerateSingBoxConfig generates a full JSON config for sing-box (v1.14+). // sing-box acts as frontend: creates a TUN interface, // captures system traffic and routes it to xray via SOCKS5. func GenerateSingBoxConfig(serverAddress string, socksPort int, tunName string) (string, error) { if socksPort == 0 { socksPort = DefaultSocksPort } if tunName == "" { tunName = "kettuTun" } config := map[string]any{ "log": buildSingBoxLog(), "dns": buildSingBoxDns(), "inbounds": []any{buildTunInbound(tunName)}, "outbounds": buildSingBoxOutbounds(socksPort), "route": buildSingBoxRoute(serverAddress), } data, err := json.MarshalIndent(config, "", " ") if err != nil { return "", fmt.Errorf("failed to marshal sing-box config: %w", err) } return string(data), nil } func buildSingBoxLog() map[string]any { return map[string]any{ "level": "info", "timestamp": true, } } func buildSingBoxDns() map[string]any { return map[string]any{ "servers": []any{ map[string]any{ "type": "https", "tag": "remote-dns", "server": "1.1.1.1", "server_port": 443, "path": "/dns-query", "detour": "proxy-out", }, map[string]any{ "type": "https", "tag": "local-dns", "server": "8.8.8.8", "server_port": 443, "path": "/dns-query", }, }, } } func buildTunInbound(tunName string) map[string]any { tun := map[string]any{ "type": "tun", "tag": "tun-in", "address": []string{ "172.18.0.1/30", "fdfe:dcba:9876::1/126", }, "mtu": 1400, "auto_route": true, "strict_route": false, "stack": "gvisor", } if tunName != "" && tunName != "kettuTun" { tun["interface_name"] = tunName } return tun } func buildSingBoxOutbounds(socksPort int) []any { return []any{ map[string]any{ "type": "socks", "tag": "proxy-out", "server": "127.0.0.1", "server_port": socksPort, "version": "5", "udp_over_tcp": false, }, map[string]any{ "type": "direct", "tag": "direct-out", }, } } func buildSingBoxRoute(serverAddress string) map[string]any { rules := []any{ // Anti routing loop: bypass VPN core processes map[string]any{ "process_name": []string{"xray.exe", "sing-box.exe", "kettuRay.exe"}, "action": "route", "outbound": "direct-out", }, } // Anti routing loop: bypass remote VPN server bypassRule := map[string]any{ "action": "route", "outbound": "direct-out", } if ip := net.ParseIP(serverAddress); ip != nil { prefix := "32" if ip.To4() == nil { prefix = "128" } bypassRule["ip_cidr"] = []string{serverAddress + "/" + prefix} } else { bypassRule["domain"] = []string{serverAddress} } rules = append(rules, bypassRule) // Sniff rule rules = append(rules, map[string]any{ "action": "sniff", "timeout": "300ms", }) // DNS hijack rules = append(rules, map[string]any{ "protocol": "dns", "action": "hijack-dns", }) // Private IPs go direct rules = append(rules, map[string]any{ "ip_is_private": true, "action": "route", "outbound": "direct-out", }) return map[string]any{ "rules": rules, "default_domain_resolver": map[string]any{ "server": "local-dns", }, "final": "proxy-out", "auto_detect_interface": true, } }