Files
kettuRay/core/singbox_config.go
2026-03-31 14:40:03 +03:00

157 lines
3.4 KiB
Go

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,
}
}