diff --git a/cmd/server.go b/cmd/server.go index 796a35d..b2e074e 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -85,9 +85,10 @@ func serverHandle(_ *cobra.Command, _ []string) { return } log.Info("Nodes started") - dns := os.Getenv("XRAY_DNS_PATH") + xdns := os.Getenv("XRAY_DNS_PATH") + sdns := os.Getenv("SING_DNS_PATH") if watch { - err = c.Watch(config, dns, func() { + err = c.Watch(config, xdns, sdns, func() { nodes.Close() err = vc.Close() if err != nil { diff --git a/conf/sing.go b/conf/sing.go index 9501e67..a334190 100644 --- a/conf/sing.go +++ b/conf/sing.go @@ -5,9 +5,10 @@ import ( ) type SingConfig struct { - LogConfig SingLogConfig `json:"Log"` - NtpConfig SingNtpConfig `json:"NTP"` - OriginalPath string `json:"OriginalPath"` + LogConfig SingLogConfig `json:"Log"` + NtpConfig SingNtpConfig `json:"NTP"` + DnsConfigPath string `json:"DnsConfigPath"` + OriginalPath string `json:"OriginalPath"` } type SingLogConfig struct { @@ -35,6 +36,7 @@ type SingOptions struct { EnableProxyProtocol bool `json:"EnableProxyProtocol"` TCPFastOpen bool `json:"EnableTFO"` SniffEnabled bool `json:"EnableSniff"` + EnableDNS bool `json:"EnableDNS"` DomainStrategy option.DomainStrategy `json:"DomainStrategy"` SniffOverrideDestination bool `json:"SniffOverrideDestination"` FallBackConfigs *FallBackConfigForSing `json:"FallBackConfigs"` @@ -59,6 +61,7 @@ type FallBack struct { func NewSingOptions() *SingOptions { return &SingOptions{ + EnableDNS: false, EnableProxyProtocol: false, TCPFastOpen: false, SniffEnabled: true, diff --git a/conf/watch.go b/conf/watch.go index 81c6440..73921c7 100644 --- a/conf/watch.go +++ b/conf/watch.go @@ -5,10 +5,12 @@ import ( "github.com/fsnotify/fsnotify" "log" "path" + "path/filepath" + "strings" "time" ) -func (p *Conf) Watch(filePath, dnsPath string, reload func()) error { +func (p *Conf) Watch(filePath, xDnsPath string, sDnsPath string, reload func()) error { watcher, err := fsnotify.NewWatcher() if err != nil { return fmt.Errorf("new watcher error: %s", err) @@ -28,9 +30,10 @@ func (p *Conf) Watch(filePath, dnsPath string, reload func()) error { pre = time.Now() go func() { time.Sleep(5 * time.Second) - if e.Name == dnsPath { + switch filepath.Base(strings.TrimSuffix(e.Name, "~")) { + case filepath.Base(xDnsPath), filepath.Base(sDnsPath): log.Println("DNS file changed, reloading...") - } else { + default: log.Println("config dir changed, reloading...") } *p = *New() @@ -52,8 +55,14 @@ func (p *Conf) Watch(filePath, dnsPath string, reload func()) error { if err != nil { return fmt.Errorf("watch file error: %s", err) } - if dnsPath != "" { - err = watcher.Add(path.Dir(dnsPath)) + if xDnsPath != "" { + err = watcher.Add(path.Dir(xDnsPath)) + if err != nil { + return fmt.Errorf("watch dns file error: %s", err) + } + } + if sDnsPath != "" { + err = watcher.Add(path.Dir(sDnsPath)) if err != nil { return fmt.Errorf("watch dns file error: %s", err) } diff --git a/core/sing/dns.go b/core/sing/dns.go new file mode 100644 index 0000000..c843dde --- /dev/null +++ b/core/sing/dns.go @@ -0,0 +1,81 @@ +package sing + +import ( + "bytes" + "github.com/InazumaV/V2bX/api/panel" + "github.com/goccy/go-json" + log "github.com/sirupsen/logrus" + "os" + "strings" +) + +func updateDNSConfig(node *panel.NodeInfo) (err error) { + dnsPath := os.Getenv("SING_DNS_PATH") + if len(node.RawDNS.DNSJson) != 0 { + err = saveDnsConfig(node.RawDNS.DNSJson, dnsPath) + } else if len(node.RawDNS.DNSMap) != 0 { + dnsConfig := DNSConfig{ + Servers: []map[string]interface{}{ + { + "tag": "default", + "address": "https://8.8.8.8/dns-query", + "detour": "direct", + }, + }, + } + for id, value := range node.RawDNS.DNSMap { + dnsConfig.Servers = append(dnsConfig.Servers, + map[string]interface{}{ + "tag": id, + "address": value["address"], + "address_resolver": "default", + "detour": "direct", + }, + ) + rule := map[string]interface{}{ + "server": id, + "disable_cache": true, + } + for _, ruleType := range []string{"domain_suffix", "domain_keyword", "domain_regex", "geosite"} { + var domains []string + for _, v := range value["domains"].([]string) { + split := strings.SplitN(v, ":", 2) + prefix := strings.ToLower(split[0]) + if prefix == ruleType || (prefix == "domain" && ruleType == "domain_suffix") { + if len(split) > 1 { + domains = append(domains, split[1]) + } + if len(domains) > 0 { + rule[ruleType] = domains + } + } + } + } + dnsConfig.Rules = append(dnsConfig.Rules, rule) + } + dnsConfigJSON, err := json.MarshalIndent(dnsConfig, "", " ") + if err != nil { + log.WithField("err", err).Error("Error marshaling dnsConfig to JSON") + return err + } + err = saveDnsConfig(dnsConfigJSON, dnsPath) + } + return err +} + +func saveDnsConfig(dns []byte, dnsPath string) (err error) { + currentData, err := os.ReadFile(dnsPath) + if err != nil { + log.WithField("err", err).Error("Failed to read SING_DNS_PATH") + return err + } + if !bytes.Equal(currentData, dns) { + if err = os.Truncate(dnsPath, 0); err != nil { + log.WithField("err", err).Error("Failed to clear SING DNS PATH file") + } + if err = os.WriteFile(dnsPath, dns, 0644); err != nil { + log.WithField("err", err).Error("Failed to write DNS to SING DNS PATH file") + } + } + return err +} diff --git a/core/sing/node.go b/core/sing/node.go index ea8bd69..6573503 100644 --- a/core/sing/node.go +++ b/core/sing/node.go @@ -30,6 +30,10 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio if err != nil { return option.Inbound{}, fmt.Errorf("the listen ip not vail") } + var domainStrategy option.DomainStrategy + if c.SingOptions.EnableDNS { + domainStrategy = c.SingOptions.DomainStrategy + } listen := option.ListenOptions{ Listen: (*option.ListenAddress)(&addr), ListenPort: uint16(info.Common.ServerPort), @@ -38,7 +42,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio InboundOptions: option.InboundOptions{ SniffEnabled: c.SingOptions.SniffEnabled, SniffOverrideDestination: c.SingOptions.SniffOverrideDestination, - DomainStrategy: c.SingOptions.DomainStrategy, + DomainStrategy: domainStrategy, }, } var tls option.InboundTLSOptions @@ -203,6 +207,10 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio } func (b *Box) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error { + err := updateDNSConfig(info) + if err != nil { + return fmt.Errorf("build dns error: %s", err) + } c, err := getInboundOptions(tag, info, config) if err != nil { return err diff --git a/core/sing/sing.go b/core/sing/sing.go index 7164a76..4075488 100644 --- a/core/sing/sing.go +++ b/core/sing/sing.go @@ -26,6 +26,11 @@ import ( var _ adapter.Service = (*Box)(nil) +type DNSConfig struct { + Servers []map[string]interface{} `json:"servers"` + Rules []map[string]interface{} `json:"rules"` +} + type Box struct { createdAt time.Time router adapter.Router @@ -68,6 +73,17 @@ func New(c *conf.CoreConfig) (vCore.Core, error) { ServerPort: c.SingConfig.NtpConfig.ServerPort, }, } + os.Setenv("SING_DNS_PATH", "") + if c.SingConfig.DnsConfigPath != "" { + if f, err := os.Open(c.SingConfig.DnsConfigPath); err != nil { + log.Error("Failed to read DNS config file") + } else { + if err = json.NewDecoder(f).Decode(&option.DNSOptions{}); err != nil { + log.Error("Failed to unmarshal DNS config") + } + } + os.Setenv("SING_DNS_PATH", c.SingConfig.DnsConfigPath) + } ctx := context.Background() ctx = service.ContextWithDefaultRegistry(ctx) ctx = pause.ContextWithDefaultManager(ctx) diff --git a/core/xray/dns.go b/core/xray/dns.go index 0dc1301..8ceecde 100644 --- a/core/xray/dns.go +++ b/core/xray/dns.go @@ -7,7 +7,6 @@ import ( log "github.com/sirupsen/logrus" coreConf "github.com/xtls/xray-core/infra/conf" "os" - "time" ) func updateDNSConfig(node *panel.NodeInfo) (err error) { @@ -57,7 +56,5 @@ func saveDnsConfig(dns []byte, dnsPath string) (err error) { log.WithField("err", err).Error("Failed to write DNS to XRAY DNS PATH file") } } - log.Println("reloading config") - time.Sleep(5 * time.Second) return err } diff --git a/example/config.json b/example/config.json index 79a27e0..0b65060 100644 --- a/example/config.json +++ b/example/config.json @@ -10,6 +10,7 @@ "Level": "error", "Timestamp": true }, + "DnsConfigPath": "/etc/V2bX/dns.json", "NTP": { "Enable": true, "Server": "time.apple.com", @@ -28,6 +29,7 @@ "ListenIP": "0.0.0.0", "SendIP": "0.0.0.0", "EnableProxyProtocol": false, + "EnableDNS": true "DomainStrategy": "ipv4_only", "LimitConfig": { "EnableRealtime": false, diff --git a/example/config_full.json b/example/config_full.json index c84404e..71b9e5d 100644 --- a/example/config_full.json +++ b/example/config_full.json @@ -26,6 +26,7 @@ "Server": "time.apple.com", "ServerPort": 0 }, + "DnsConfigPath": "/etc/V2bX/dns.json", // SingBox源配置文件目录,用于引用标准SingBox配置文件 "OriginalPath": "/etc/V2bX/sing_origin.json" }, @@ -113,7 +114,9 @@ // 开启 TCP Fast Open "EnableTFO": true, - // 设置 Domain Strategy + // 开启 DNS + "EnableDNS" : true, + // 设置 Domain Strategy 需要开启 DNS ,默认 AsIS // 可选 prefer_ipv4 / prefer_ipv6 / ipv4_only / ipv6_only "DomainStrategy": "ipv4_only", diff --git a/example/config_full_node1.json b/example/config_full_node1.json index d422922..85b1caf 100644 --- a/example/config_full_node1.json +++ b/example/config_full_node1.json @@ -9,5 +9,5 @@ "SendIP": "0.0.0.0", "EnableProxyProtocol": false, "EnableTFO": true, - "DomainStrategy": "ipv4_only" + "DNSType": "ipv4_only" } \ No newline at end of file