package config import "vpnem/internal/models" type SingBoxConfig struct { DNS map[string]any `json:"dns"` Inbounds []map[string]any `json:"inbounds"` Outbounds []map[string]any `json:"outbounds"` Route map[string]any `json:"route"` Experimental map[string]any `json:"experimental,omitempty"` } const ( LocalProxyHost = "127.0.0.1" LocalProxyPort = 10800 TunInterfaceName = "vpnem" ) func BuildConfig(server models.Server, mode Mode, ruleSets []models.RuleSet, serverIPs []string) SingBoxConfig { return BuildConfigFull(server, mode, ruleSets, serverIPs, nil, nil) } // BuildConfigFull — exact vpn.py config. Fast, proven. func BuildConfigFull(server models.Server, mode Mode, ruleSets []models.RuleSet, serverIPs []string, customBypass []string, policy *models.RoutingPolicy) SingBoxConfig { return BuildConfigFullWithLocalProxy(server, mode, ruleSets, serverIPs, customBypass, LocalProxyPort, policy) } func BuildConfigFullWithLocalProxy(server models.Server, mode Mode, ruleSets []models.RuleSet, serverIPs []string, customBypass []string, localProxyPort int, policy *models.RoutingPolicy) SingBoxConfig { if hy2, ok := findCompanionProtocol(server, "hysteria2"); ok && (server.Type == "vless-reality" || server.Type == "vless") { return BuildSplitRoutingConfig(server, hy2, mode, ruleSets, serverIPs, customBypass, localProxyPort, policy) } effectivePolicy := EffectiveRoutingPolicy(policy) bypassIPs := BuildBypassIPs(effectivePolicy, serverIPs) bypassProcs := BuildBypassProcesses(effectivePolicy, customBypass) var rules []map[string]any rules = append(rules, map[string]any{"action": "sniff"}) rules = append(rules, map[string]any{"protocol": "dns", "action": "hijack-dns"}) rules = append(rules, map[string]any{"ip_is_private": true, "outbound": "direct"}) rules = append(rules, map[string]any{"ip_cidr": effectivePolicy.ReservedCIDRs, "outbound": "direct"}) rules = append(rules, map[string]any{"ip_cidr": bypassIPs, "outbound": "direct"}) rules = append(rules, map[string]any{"process_name": bypassProcs, "outbound": "direct"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.WindowsNCSIDomains, "outbound": "direct"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.LocalDomainSuffixes, "outbound": "direct"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.InfraBypassDomains, "outbound": "direct"}) rules = append(rules, map[string]any{"process_path_regex": effectivePolicy.LovenseProcessRegex, "outbound": "proxy"}) rules = append(rules, map[string]any{"ip_cidr": effectivePolicy.ForcedProxyIPs, "outbound": "proxy"}) rules = append(rules, map[string]any{"process_name": effectivePolicy.TelegramProcesses, "outbound": "proxy"}) rules = append(rules, map[string]any{"process_path_regex": effectivePolicy.TelegramProcessRegex, "outbound": "proxy"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.TelegramDomains, "outbound": "proxy"}) rules = append(rules, map[string]any{"domain_regex": effectivePolicy.TelegramDomainRegex, "outbound": "proxy"}) rules = append(rules, map[string]any{"ip_cidr": effectivePolicy.TelegramIPs, "outbound": "proxy"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.BlockedDomains, "outbound": "proxy"}) for _, r := range mode.Rules { rule := map[string]any{"outbound": r.Outbound} if len(r.DomainSuffix) > 0 { rule["domain_suffix"] = r.DomainSuffix } if len(r.DomainRegex) > 0 { rule["domain_regex"] = r.DomainRegex } if len(r.IPCIDR) > 0 { rule["ip_cidr"] = r.IPCIDR } if len(r.RuleSet) > 0 { rule["rule_set"] = r.RuleSet } if len(r.Network) > 0 { rule["network"] = r.Network } if len(r.PortRange) > 0 { rule["port_range"] = r.PortRange } rules = append(rules, rule) } if len(effectivePolicy.PreferDirectProcesses) > 0 { rules = append(rules, map[string]any{"process_name": effectivePolicy.PreferDirectProcesses, "outbound": "direct"}) } var ruleSetDefs []map[string]any for _, rs := range ruleSets { if rs.URL == "" { continue } ruleSetDefs = append(ruleSetDefs, map[string]any{ "tag": rs.Tag, "type": "local", "format": rs.Format, "path": rs.LocalPath, }) } route := map[string]any{ "auto_detect_interface": true, "final": mode.Final, "rules": rules, } if len(ruleSetDefs) > 0 { route["rule_set"] = ruleSetDefs } return SingBoxConfig{ DNS: map[string]any{ "servers": []map[string]any{ {"tag": "proxy-dns", "type": "https", "server": "8.8.8.8", "detour": "proxy"}, {"tag": "direct-dns", "type": "https", "server": "1.1.1.1"}, }, "rules": []map[string]any{ {"outbound": "proxy", "server": "proxy-dns"}, {"outbound": "direct", "server": "direct-dns"}, }, "strategy": "ipv4_only", }, Inbounds: []map[string]any{ { "type": "tun", "tag": "tun-in", "interface_name": TunInterfaceName, "address": []string{"172.19.0.1/30"}, "auto_route": true, "strict_route": false, "stack": "gvisor", }, { "type": "socks", "tag": "socks-in", "listen": LocalProxyHost, "listen_port": defaultInt(localProxyPort, LocalProxyPort), }, }, Outbounds: []map[string]any{ BuildOutbound(server), {"type": "direct", "tag": "direct"}, }, Route: route, Experimental: map[string]any{ "cache_file": map[string]any{ "enabled": true, "path": "cache.db", }, }, } } func BuildSplitRoutingConfig(vlessServer models.Server, hy2Server models.Server, mode Mode, ruleSets []models.RuleSet, serverIPs []string, customBypass []string, localProxyPort int, policy *models.RoutingPolicy) SingBoxConfig { effectivePolicy := EffectiveRoutingPolicy(policy) bypassIPs := BuildBypassIPs(effectivePolicy, serverIPs) bypassProcs := BuildBypassProcesses(effectivePolicy, customBypass) var rules []map[string]any rules = append(rules, map[string]any{"action": "sniff"}) rules = append(rules, map[string]any{"protocol": "dns", "action": "hijack-dns"}) rules = append(rules, map[string]any{"ip_is_private": true, "outbound": "direct"}) rules = append(rules, map[string]any{"ip_cidr": effectivePolicy.ReservedCIDRs, "outbound": "direct"}) rules = append(rules, map[string]any{"ip_cidr": bypassIPs, "outbound": "direct"}) rules = append(rules, map[string]any{"process_name": bypassProcs, "outbound": "direct"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.WindowsNCSIDomains, "outbound": "direct"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.LocalDomainSuffixes, "outbound": "direct"}) rules = append(rules, map[string]any{"domain_suffix": effectivePolicy.InfraBypassDomains, "outbound": "direct"}) rules = appendSplitProxyRule(rules, map[string]any{"process_path_regex": effectivePolicy.LovenseProcessRegex}) rules = appendSplitProxyRule(rules, map[string]any{"ip_cidr": effectivePolicy.ForcedProxyIPs}) rules = appendSplitProxyRule(rules, map[string]any{"process_name": effectivePolicy.TelegramProcesses}) rules = appendSplitProxyRule(rules, map[string]any{"process_path_regex": effectivePolicy.TelegramProcessRegex}) rules = appendSplitProxyRule(rules, map[string]any{"domain_suffix": effectivePolicy.TelegramDomains}) rules = appendSplitProxyRule(rules, map[string]any{"domain_regex": effectivePolicy.TelegramDomainRegex}) rules = appendSplitProxyRule(rules, map[string]any{"ip_cidr": effectivePolicy.TelegramIPs}) rules = appendSplitProxyRule(rules, map[string]any{"domain_suffix": effectivePolicy.BlockedDomains}) for _, r := range mode.Rules { rule := map[string]any{} if len(r.DomainSuffix) > 0 { rule["domain_suffix"] = r.DomainSuffix } if len(r.DomainRegex) > 0 { rule["domain_regex"] = r.DomainRegex } if len(r.IPCIDR) > 0 { rule["ip_cidr"] = r.IPCIDR } if len(r.RuleSet) > 0 { rule["rule_set"] = r.RuleSet } if len(r.Network) > 0 { rule["network"] = r.Network } if len(r.PortRange) > 0 { rule["port_range"] = r.PortRange } if r.Outbound == "proxy" { rules = appendSplitProxyRule(rules, rule) } else { rule["outbound"] = r.Outbound rules = append(rules, rule) } } if len(effectivePolicy.PreferDirectProcesses) > 0 { rules = append(rules, map[string]any{"process_name": effectivePolicy.PreferDirectProcesses, "outbound": "direct"}) } if mode.Final == "proxy" { rules = append(rules, map[string]any{"network": []string{"udp"}, "outbound": "hysteria2-out"}, map[string]any{"network": []string{"tcp"}, "outbound": "vless-out"}, ) } var ruleSetDefs []map[string]any for _, rs := range ruleSets { if rs.URL == "" { continue } ruleSetDefs = append(ruleSetDefs, map[string]any{ "tag": rs.Tag, "type": "remote", "format": rs.Format, "url": rs.URL, "download_detour": "direct", "update_interval": "1d", }) } route := map[string]any{ "auto_detect_interface": true, "final": splitFinalOutbound(mode.Final), "rules": rules, "default_domain_resolver": map[string]any{ "server": "direct-dns", "strategy": "ipv4_only", }, } if len(ruleSetDefs) > 0 { route["rule_set"] = ruleSetDefs } return SingBoxConfig{ DNS: map[string]any{ "servers": []map[string]any{ {"tag": "proxy-dns", "type": "https", "server": "8.8.8.8", "detour": "vless-out"}, {"tag": "direct-dns", "type": "udp", "server": "1.1.1.1", "server_port": 53}, }, "rules": []map[string]any{ {"outbound": "vless-out", "server": "proxy-dns"}, {"outbound": "hysteria2-out", "server": "proxy-dns"}, {"outbound": "direct", "server": "direct-dns"}, }, "strategy": "ipv4_only", }, Inbounds: []map[string]any{ { "type": "tun", "tag": "tun-in", "interface_name": TunInterfaceName, "address": []string{"172.19.0.1/30"}, "auto_route": true, "strict_route": false, "stack": "gvisor", }, { "type": "socks", "tag": "socks-in", "listen": LocalProxyHost, "listen_port": defaultInt(localProxyPort, LocalProxyPort), }, }, Outbounds: []map[string]any{ BuildOutboundWithTag(vlessServer, "vless-out"), BuildOutboundWithTag(hy2Server, "hysteria2-out"), {"type": "direct", "tag": "direct"}, }, Route: route, Experimental: map[string]any{ "cache_file": map[string]any{ "enabled": true, "path": "cache.db", }, }, } } func findCompanionProtocol(server models.Server, protocolType string) (models.Server, bool) { for _, companion := range server.Companions { if companion.Type == protocolType { return companion, true } } return models.Server{}, false } func splitFinalOutbound(final string) string { if final == "proxy" { return "vless-out" } return final } func appendSplitProxyRule(rules []map[string]any, base map[string]any) []map[string]any { if rule, ok := splitRuleForNetwork(base, "tcp", "vless-out"); ok { rules = append(rules, rule) } if rule, ok := splitRuleForNetwork(base, "udp", "hysteria2-out"); ok { rules = append(rules, rule) } return rules } func splitRuleForNetwork(base map[string]any, network string, outbound string) (map[string]any, bool) { rule := copyRule(base) if networks, ok := rule["network"].([]string); ok && len(networks) > 0 { if !containsString(networks, network) { return nil, false } rule["network"] = []string{network} } else { rule["network"] = []string{network} } rule["outbound"] = outbound return rule, true } func copyRule(in map[string]any) map[string]any { out := make(map[string]any, len(in)+1) for k, v := range in { out[k] = v } return out } func containsString(values []string, target string) bool { for _, value := range values { if value == target { return true } } return false } func defaultInt(value, fallback int) int { if value > 0 { return value } return fallback }