From f317baa8def8bbcdef0fa8259d12d8181ec8fe19 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Wed, 12 Jun 2024 15:25:34 +0800 Subject: [PATCH] feat: add `respect-rules` for dns --- config/config.go | 27 +++++++---- dns/util.go | 118 +++++++++++++++++++++++++++-------------------- docs/config.yaml | 10 +++- tunnel/tunnel.go | 6 +-- 4 files changed, 98 insertions(+), 63 deletions(-) diff --git a/config/config.go b/config/config.go index 74a2053e3..fd12d2dbd 100644 --- a/config/config.go +++ b/config/config.go @@ -212,6 +212,7 @@ type RawDNS struct { IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"` UseHosts bool `yaml:"use-hosts" json:"use-hosts"` UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"` + RespectRules bool `yaml:"respect-rules" json:"respect-rules"` NameServer []string `yaml:"nameserver" json:"nameserver"` Fallback []string `yaml:"fallback" json:"fallback"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"` @@ -1039,7 +1040,7 @@ func hostWithDefaultPort(host string, defPort string) (string, error) { return net.JoinHostPort(hostname, port), nil } -func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) { +func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.NameServer, error) { var nameservers []dns.NameServer for idx, server := range servers { @@ -1114,6 +1115,10 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } + if respectRules && len(proxyName) == 0 { + proxyName = dns.RespectRules + } + nameservers = append( nameservers, dns.NameServer{ @@ -1130,7 +1135,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) func init() { dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard - return parseNameServer(servers, false) + return parseNameServer(servers, false, false) } } @@ -1156,7 +1161,7 @@ func parsePureDNSServer(server string) string { } } } -func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) { +func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) { policy := orderedmap.New[string, []dns.NameServer]() updatedPolicy := orderedmap.New[string, any]() re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`) @@ -1202,7 +1207,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro if err != nil { return nil, err } - nameservers, err := parseNameServer(servers, preferH3) + nameservers, err := parseNameServer(servers, respectRules, preferH3) if err != nil { return nil, err } @@ -1296,6 +1301,10 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty") } + if cfg.RespectRules && len(cfg.ProxyServerNameserver) == 0 { + return nil, fmt.Errorf("if “respect-rules” is turned on, “proxy-server-nameserver” cannot be empty") + } + dnsCfg := &DNS{ Enable: cfg.Enable, Listen: cfg.Listen, @@ -1310,26 +1319,26 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul }, } var err error - if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.PreferH3); err != nil { + if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.RespectRules, cfg.PreferH3); err != nil { return nil, err } - if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.PreferH3); err != nil { + if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.RespectRules, cfg.PreferH3); err != nil { return nil, err } - if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.PreferH3); err != nil { + if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil { return nil, err } - if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, cfg.PreferH3); err != nil { + if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, false, cfg.PreferH3); err != nil { return nil, err } if len(cfg.DefaultNameserver) == 0 { return nil, errors.New("default nameserver should have at least one nameserver") } - if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, cfg.PreferH3); err != nil { + if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, false, cfg.PreferH3); err != nil { return nil, err } // check default nameserver is pure ip addr diff --git a/dns/util.go b/dns/util.go index 516c63fb8..9b84a5075 100644 --- a/dns/util.go +++ b/dns/util.go @@ -7,7 +7,6 @@ import ( "fmt" "net" "net/netip" - "strconv" "strings" "time" @@ -175,6 +174,8 @@ func msgToDomain(msg *D.Msg) string { return "" } +const RespectRules = "RULES" + type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error) func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) dialHandler { @@ -183,54 +184,67 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts = append(opts, dialer.WithResolver(r)) return dialer.DialContext(ctx, network, addr, opts...) } else { - host, port, err := net.SplitHostPort(addr) + metadata := &C.Metadata{ + NetWork: C.TCP, + Type: C.INNER, + } + err := metadata.SetRemoteAddress(addr) // tcp can resolve host by remote if err != nil { return nil, err } - uintPort, err := strconv.ParseUint(port, 10, 16) - if err != nil { - return nil, err + if !strings.Contains(network, "tcp") { + metadata.NetWork = C.UDP + if !metadata.Resolved() { + // udp must resolve host first + dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r) + if err != nil { + return nil, err + } + metadata.DstIP = dstIP + } } + if proxyAdapter == nil { - var ok bool - proxyAdapter, ok = tunnel.Proxies()[proxyName] - if !ok { - opts = append(opts, dialer.WithInterface(proxyName)) + if proxyName == RespectRules { + if !metadata.Resolved() { + // resolve here before ResolveMetadata to avoid its inner resolver.ResolveIP + dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r) + if err != nil { + return nil, err + } + metadata.DstIP = dstIP + } + proxyAdapter, _, err = tunnel.ResolveMetadata(metadata) + if err != nil { + return nil, err + } + } else { + var ok bool + proxyAdapter, ok = tunnel.Proxies()[proxyName] + if !ok { + opts = append(opts, dialer.WithInterface(proxyName)) + } } } if strings.Contains(network, "tcp") { - // tcp can resolve host by remote - metadata := &C.Metadata{ - NetWork: C.TCP, - Host: host, - DstPort: uint16(uintPort), - } if proxyAdapter != nil { if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback - dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r) - if err != nil { - return nil, err + if !metadata.Resolved() { + dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r) + if err != nil { + return nil, err + } + metadata.DstIP = dstIP } - metadata.Host = "" - metadata.DstIP = dstIP + metadata.Host = "" // clear host to avoid double resolve in proxy } + log.Debugln("%s", metadata.RemoteAddress()) return proxyAdapter.DialContext(ctx, metadata, opts...) } opts = append(opts, dialer.WithResolver(r)) return dialer.DialContext(ctx, network, addr, opts...) } else { - // udp must resolve host first - dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r) - if err != nil { - return nil, err - } - metadata := &C.Metadata{ - NetWork: C.UDP, - Host: "", - DstIP: dstIP, - DstPort: uint16(uintPort), - } if proxyAdapter == nil { return dialer.DialContext(ctx, network, addr, opts...) } @@ -251,33 +265,37 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, } func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) { - host, port, err := net.SplitHostPort(addr) + metadata := &C.Metadata{ + NetWork: C.UDP, + } + err := metadata.SetRemoteAddress(addr) if err != nil { return nil, err } - uintPort, err := strconv.ParseUint(port, 10, 16) - if err != nil { - return nil, err + if !metadata.Resolved() { + // udp must resolve host first + dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r) + if err != nil { + return nil, err + } + metadata.DstIP = dstIP } + if proxyAdapter == nil { - var ok bool - proxyAdapter, ok = tunnel.Proxies()[proxyName] - if !ok { - opts = append(opts, dialer.WithInterface(proxyName)) + if proxyName == RespectRules { + proxyAdapter, _, err = tunnel.ResolveMetadata(metadata) + if err != nil { + return nil, err + } + } else { + var ok bool + proxyAdapter, ok = tunnel.Proxies()[proxyName] + if !ok { + opts = append(opts, dialer.WithInterface(proxyName)) + } } } - // udp must resolve host first - dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r) - if err != nil { - return nil, err - } - metadata := &C.Metadata{ - NetWork: C.UDP, - Host: "", - DstIP: dstIP, - DstPort: uint16(uintPort), - } if proxyAdapter == nil { return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) } diff --git a/docs/config.yaml b/docs/config.yaml index fe8501636..bd263c144 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -209,7 +209,7 @@ tunnels: # one line config dns: cache-algorithm: arc enable: false # 关闭将使用系统 DNS - prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试 + prefer-h3: false # 是否开启 DoH 支持 HTTP/3,将并发尝试 listen: 0.0.0.0:53 # 开启 DNS 服务器监听 # ipv6: false # false 将返回 AAAA 的空结果 # ipv6-timeout: 300 # 单位:ms,内部双栈并发时,向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms @@ -227,6 +227,13 @@ dns: # use-hosts: true # 查询 hosts + # 配置后面的nameserver、fallback和nameserver-policy向dns服务器的连接过程是否遵守遵守rules规则 + # 如果为false(默认值)则这三部分的dns服务器在未特别指定的情况下会直连 + # 如果为true,将会按照rules的规则匹配链接方式(走代理或直连),如果有特别指定则任然以指定值为准 + # 仅当proxy-server-nameserver非空时可以开启此选项, 强烈不建议和prefer-h3一起使用 + # 此外,这三者配置中的dns服务器如果出现域名会采用default-nameserver配置项解析,也请确保正确配置default-nameserver + respect-rules: false + # 配置不使用 fake-ip 的域名 # fake-ip-filter: # - '*.lan' @@ -244,6 +251,7 @@ dns: - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3 - dhcp://en0 # dns from dhcp - quic://dns.adguard.com:784 # DNS over QUIC + # - '8.8.8.8#RULES' # 效果同respect-rules,但仅对该服务器生效 # - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡 # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置 diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 608ab2c5b..78628a568 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -278,7 +278,7 @@ func preHandleMetadata(metadata *C.Metadata) error { return nil } -func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) { +func ResolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) { if metadata.SpecialProxy != "" { var exist bool proxy, exist = proxies[metadata.SpecialProxy] @@ -375,7 +375,7 @@ func handleUDPConn(packet C.PacketAdapter) { cond.Broadcast() }() - proxy, rule, err := resolveMetadata(metadata) + proxy, rule, err := ResolveMetadata(metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) return @@ -486,7 +486,7 @@ func handleTCPConn(connCtx C.ConnContext) { }() } - proxy, rule, err := resolveMetadata(metadata) + proxy, rule, err := ResolveMetadata(metadata) if err != nil { log.Warnln("[Metadata] parse failed: %s", err.Error()) return