diff --git a/config/config.go b/config/config.go index ce1e0fb7..1317537c 100644 --- a/config/config.go +++ b/config/config.go @@ -261,7 +261,7 @@ type RawTun struct { MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` GSO bool `yaml:"gso" json:"gso,omitempty"` GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` - //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` + // Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` IPRoute2TableIndex int `yaml:"iproute2-table-index" json:"iproute2-table-index,omitempty"` IPRoute2RuleIndex int `yaml:"iproute2-rule-index" json:"iproute2-rule-index,omitempty"` @@ -571,7 +571,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config := &Config{} - log.Infoln("Start initial configuration in progress") //Segment finished in xxm + log.Infoln("Start initial configuration in progress") // Segment finished in xxm startTime := time.Now() general, err := parseGeneral(rawCfg) @@ -695,7 +695,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms - log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm + log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) // Segment finished in xxm return config, nil } @@ -1148,94 +1148,9 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns. var nameservers []dns.NameServer for idx, server := range servers { - server = parsePureDNSServer(server) - u, err := url.Parse(server) + nameserver, err := parseOneNameServer(server, idx, respectRules, preferH3) if err != nil { - return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) - } - - proxyName := u.Fragment - - var addr, dnsNetType string - params := map[string]string{} - switch u.Scheme { - case "udp": - addr, err = hostWithDefaultPort(u.Host, "53") - dnsNetType = "" // UDP - case "tcp": - addr, err = hostWithDefaultPort(u.Host, "53") - dnsNetType = "tcp" // TCP - case "tls": - addr, err = hostWithDefaultPort(u.Host, "853") - dnsNetType = "tcp-tls" // DNS over TLS - case "http", "https": - addr, err = hostWithDefaultPort(u.Host, "443") - dnsNetType = "https" // DNS over HTTPS - if u.Scheme == "http" { - addr, err = hostWithDefaultPort(u.Host, "80") - } - if err == nil { - proxyName = "" - clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User} - addr = clearURL.String() - if len(u.Fragment) != 0 { - for _, s := range strings.Split(u.Fragment, "&") { - arr := strings.Split(s, "=") - if len(arr) == 0 { - continue - } else if len(arr) == 1 { - proxyName = arr[0] - } else if len(arr) == 2 { - params[arr[0]] = arr[1] - } else { - params[arr[0]] = strings.Join(arr[1:], "=") - } - } - } - } - case "quic": - addr, err = hostWithDefaultPort(u.Host, "853") - dnsNetType = "quic" // DNS over QUIC - case "system": - dnsNetType = "system" // System DNS - case "dhcp": - addr = server[len("dhcp://"):] // some special notation cannot be parsed by url - dnsNetType = "dhcp" // UDP from DHCP - if addr == "system" { // Compatible with old writing "dhcp://system" - dnsNetType = "system" - addr = "" - } - case "rcode": - dnsNetType = "rcode" - addr = u.Host - switch addr { - case "success", - "format_error", - "server_failure", - "name_error", - "not_implemented", - "refused": - default: - err = fmt.Errorf("unsupported RCode type: %s", addr) - } - default: - return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) - } - - if err != nil { - return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) - } - - if respectRules && len(proxyName) == 0 { - proxyName = dns.RespectRules - } - - nameserver := dns.NameServer{ - Net: dnsNetType, - Addr: addr, - ProxyName: proxyName, - Params: params, - PreferH3: preferH3, + return nil, err } if slices.ContainsFunc(nameservers, nameserver.Equal) { continue // skip duplicates nameserver @@ -1246,6 +1161,116 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns. return nameservers, nil } +func parseOneNameServer(server string, idx int, respectRules bool, preferH3 bool) (dns.NameServer, error) { + empty := dns.NameServer{} + server = parsePureDNSServer(server) + u, err := url.Parse(server) + if err != nil { + return empty, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) + } + + proxyName := u.Fragment + + var addr, dnsNetType string + params := map[string]string{} + switch u.Scheme { + case "udp": + addr, err = hostWithDefaultPort(u.Host, "53") + dnsNetType = "" // UDP + case "tcp": + addr, err = hostWithDefaultPort(u.Host, "53") + dnsNetType = "tcp" // TCP + case "tls": + addr, err = hostWithDefaultPort(u.Host, "853") + dnsNetType = "tcp-tls" // DNS over TLS + case "http", "https": + addr, err = hostWithDefaultPort(u.Host, "443") + dnsNetType = "https" // DNS over HTTPS + if u.Scheme == "http" { + addr, err = hostWithDefaultPort(u.Host, "80") + } + if err == nil { + proxyName = "" + clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User} + addr = clearURL.String() + if len(u.Fragment) != 0 { + for _, s := range strings.Split(u.Fragment, "&") { + arr := strings.Split(s, "=") + if len(arr) == 0 { + continue + } else if len(arr) == 1 { + proxyName = arr[0] + } else if len(arr) == 2 { + params[arr[0]] = arr[1] + } else { + params[arr[0]] = strings.Join(arr[1:], "=") + } + } + } + } + case "quic": + addr, err = hostWithDefaultPort(u.Host, "853") + dnsNetType = "quic" // DNS over QUIC + case "system": + dnsNetType = "system" // System DNS + case "dhcp": + addr = server[len("dhcp://"):] // some special notation cannot be parsed by url + dnsNetType = "dhcp" // UDP from DHCP + if addr == "system" { // Compatible with old writing "dhcp://system" + dnsNetType = "system" + addr = "" + } + case "rcode": + dnsNetType = "rcode" + addr = u.Host + switch addr { + case "success", + "format_error", + "server_failure", + "name_error", + "not_implemented", + "refused": + default: + err = fmt.Errorf("unsupported RCode type: %s", addr) + } + case "fakeip": + dnsNetType = "fakeip" + // prase back dns + // fakeip://quic://223.5.5.5 + back := strings.TrimPrefix(server, "fakeip://") + if back != "" { + ns, err := parseOneNameServer(back, idx, respectRules, preferH3) + if err != nil { + return empty, err + } + return dns.NameServer{ + Net: dnsNetType, + Back: &ns, + }, nil + } + + default: + return empty, fmt.Errorf("DNS NameServer[%d](%s) unsupport scheme: %s", + idx, u.String(), u.Scheme) + } + + if err != nil { + return empty, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) + } + + if respectRules && len(proxyName) == 0 { + proxyName = dns.RespectRules + } + + return dns.NameServer{ + Net: dnsNetType, + Addr: addr, + ProxyName: proxyName, + Params: params, + PreferH3: preferH3, + }, nil +} + func init() { dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard return parseNameServer(servers, false, false) @@ -1288,65 +1313,12 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro if err != nil { return nil, err } - kLower := strings.ToLower(k) - if strings.Contains(kLower, ",") { - if strings.Contains(kLower, "geosite:") { - subkeys := strings.Split(k, ":") - subkeys = subkeys[1:] - subkeys = strings.Split(subkeys[0], ",") - for _, subkey := range subkeys { - newKey := "geosite:" + subkey - policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers}) - } - } else if strings.Contains(kLower, "rule-set:") { - subkeys := strings.Split(k, ":") - subkeys = subkeys[1:] - subkeys = strings.Split(subkeys[0], ",") - for _, subkey := range subkeys { - newKey := "rule-set:" + subkey - policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers}) - } - } else { - subkeys := strings.Split(k, ",") - for _, subkey := range subkeys { - policy = append(policy, dns.Policy{Domain: subkey, NameServers: nameservers}) - } - } - } else { - if strings.Contains(kLower, "geosite:") { - policy = append(policy, dns.Policy{Domain: "geosite:" + k[8:], NameServers: nameservers}) - } else if strings.Contains(kLower, "rule-set:") { - policy = append(policy, dns.Policy{Domain: "rule-set:" + k[9:], NameServers: nameservers}) - } else { - policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers}) - } + ps, err := parseDNSPolicy(k, nameservers, ruleProviders) + if err != nil { + return nil, err } + policy = append(policy, ps...) } - - for idx, p := range policy { - domain, nameservers := p.Domain, p.NameServers - - if strings.HasPrefix(domain, "rule-set:") { - domainSetName := domain[9:] - matcher, err := parseDomainRuleSet(domainSetName, "dns.nameserver-policy", ruleProviders) - if err != nil { - return nil, err - } - policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers} - } else if strings.HasPrefix(domain, "geosite:") { - country := domain[8:] - matcher, err := RC.NewGEOSITE(country, "dns.nameserver-policy") - if err != nil { - return nil, err - } - policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers} - } else { - if _, valid := trie.ValidAndSplitDomain(domain); !valid { - return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain) - } - } - } - return policy, nil } diff --git a/config/dns_policy.go b/config/dns_policy.go new file mode 100644 index 00000000..a745f30d --- /dev/null +++ b/config/dns_policy.go @@ -0,0 +1,60 @@ +package config + +import ( + "fmt" + "strings" + + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + providerTypes "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/dns" + RC "github.com/metacubex/mihomo/rules/common" +) + +type matcher func(value string, ruleProviders map[string]providerTypes.RuleProvider) (C.DomainMatcher, error) + +var policyMatcherMap = map[string]matcher{ + "rule-set": func(value string, ruleProviders map[string]providerTypes.RuleProvider) (C.DomainMatcher, error) { + return parseDomainRuleSet(value, "dns.nameserver-policy", ruleProviders) + }, + "geosite": func(value string, _ map[string]providerTypes.RuleProvider) (C.DomainMatcher, error) { + return RC.NewGEOSITE(value, "dns.nameserver-policy") + }, + "rule": newRuleMatcher, +} + +func getMatcher(k string) (string, matcher) { + typ, v, found := strings.Cut(k, ":") + if !found { + return k, nil + } + matcher := policyMatcherMap[strings.ToLower(typ)] + // unknown, keep as original + if matcher == nil { + return k, nil + } + return v, matcher +} + +func parseDNSPolicy(k string, nameservers []dns.NameServer, + ruleProviders map[string]providerTypes.RuleProvider, +) ([]dns.Policy, error) { + var policy []dns.Policy + + v, matcher := getMatcher(k) + for _, subkey := range strings.Split(v, ",") { + if matcher != nil { + m, err := matcher(subkey, ruleProviders) + if err != nil { + return nil, err + } + policy = append(policy, dns.Policy{Matcher: m, NameServers: nameservers}) + } else { + if _, valid := trie.ValidAndSplitDomain(subkey); !valid { + return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", subkey) + } + policy = append(policy, dns.Policy{Domain: subkey, NameServers: nameservers}) + } + } + return policy, nil +} diff --git a/config/dns_policy_rule.go b/config/dns_policy_rule.go new file mode 100644 index 00000000..ba3be5e3 --- /dev/null +++ b/config/dns_policy_rule.go @@ -0,0 +1,72 @@ +package config + +import ( + "errors" + "net/netip" + "strings" + + "github.com/metacubex/mihomo/common/lru" + C "github.com/metacubex/mihomo/constant" + providerTypes "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/log" + + "github.com/metacubex/mihomo/tunnel" +) + +var cache = lru.New[string, C.AdapterType]( + lru.WithAge[string, C.AdapterType](1), +) + +type ruleMatcher struct { + typ string +} + +func (r *ruleMatcher) MatchDomain(domain string) bool { + typ, found := cache.Get(domain) + if !found { + p, _, err := tunnel.ResolveMetadata( + &C.Metadata{ + NetWork: C.TCP, + Type: C.INNER, // avoid process lookup + Host: domain, + DstIP: netip.AddrFrom4([4]byte{}), // avoid dns lookup + }, + ) + if err != nil { + log.Warnln("[DNS] ruleMatcher: match(%s) got err %v", domain, err.Error()) + return false + } + log.Debugln("[DNS] ruleMatcher: match(%s) -> %s", domain, p.Type().String()) + cache.Set(domain, p.Type()) + typ = p.Type() + } + switch typ { + case C.Direct, C.Compatible: + if r.typ == "direct" { + return true + } + case C.Reject, C.RejectDrop: + if r.typ == "reject" { + return true + } + case C.Pass: // should not happen + default: + if r.typ == "proxy" { + return true + } + } + return false +} + +func newRuleMatcher(value string, _ map[string]providerTypes.RuleProvider) (C.DomainMatcher, error) { + switch strings.ToLower(value) { + case "direct": + return &ruleMatcher{typ: "direct"}, nil + case "reject": + return &ruleMatcher{typ: "reject"}, nil + case "proxy": + return &ruleMatcher{typ: "proxy"}, nil + default: + return nil, errors.New("[DNS] rulePolicy: unknown rule type: " + value) + } +} diff --git a/config/dns_policy_test.go b/config/dns_policy_test.go new file mode 100644 index 00000000..90cf65d4 --- /dev/null +++ b/config/dns_policy_test.go @@ -0,0 +1,262 @@ +package config + +import ( + "reflect" + "testing" + + C "github.com/metacubex/mihomo/constant" + providerTypes "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/dns" + RP "github.com/metacubex/mihomo/rules/provider" +) + +func Test_getMatcher(t *testing.T) { + type args struct { + k string + } + tests := []struct { + name string + args args + want string + want1 matcher + }{ + { + name: "rule-set", + args: args{k: "rule-set:cn"}, + want: "cn", + want1: policyMatcherMap["rule-set"], + }, + { + name: "rule-set", + args: args{k: "rULE-set:cn,c2"}, + want: "cn,c2", + want1: policyMatcherMap["rule-set"], + }, + { + name: "unknown", + args: args{k: "xxx:cn,c2"}, + want: "xxx:cn,c2", + want1: nil, + }, + { + name: "domain", + args: args{k: "baidu.com"}, + want: "baidu.com", + want1: nil, + }, + + { + name: "domain list", + args: args{k: "baidu.com,+.baidu.com"}, + want: "baidu.com,+.baidu.com", + want1: nil, + }, + { + name: "rule", + args: args{k: "RuLE:cn,c2"}, + want: "cn,c2", + want1: policyMatcherMap["rule"], + }, + { + name: "geosite", + args: args{k: "geoSiTe:cn,c2"}, + want: "cn,c2", + want1: policyMatcherMap["geosite"], + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := getMatcher(tt.args.k) + if got != tt.want { + t.Errorf("getMatcher() got = %v, want %v", got, tt.want) + } + + if getPointer(got1) != getPointer(tt.want1) { + t.Errorf("getMatcher() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func getPointer(i any) uintptr { + return reflect.ValueOf(i).Pointer() +} + +func Test_parseDNSPolicy(t *testing.T) { + type args struct { + k string + nameservers []dns.NameServer + ruleProviders map[string]providerTypes.RuleProvider + } + ns := []dns.NameServer{ + { + Net: "rcode", + Addr: "success", + }, + } + rp := map[string]providerTypes.RuleProvider{ + "cn": RP.NewInlineProvider("cn", providerTypes.Domain, + []string{ + "www.baidu.com", + }, + nil), + } + getDomainMatcher := func(str string) C.DomainMatcher { + m, err := policyMatcherMap["geosite"](str, rp) + if err != nil { + t.Error(err) + } + return m + } + getRuleSetMatcher := func(str string) C.DomainMatcher { + m, err := policyMatcherMap["rule-set"](str, rp) + if err != nil { + t.Error(err) + } + return m + } + getRuleMatcher := func(str string) C.DomainMatcher { + m, err := policyMatcherMap["rule"](str, rp) + if err != nil { + t.Error(err) + } + return m + } + tests := []struct { + name string + args args + want []dns.Policy + wantErr bool + }{ + { + name: "未知", + args: args{ + k: "xxx:cn", + nameservers: ns, + }, + want: []dns.Policy{ + { + Domain: "xxx:cn", + NameServers: ns, + }, + }, + wantErr: false, + }, + { + name: "普通域名", + args: args{ + k: "www.baidu.com", + nameservers: ns, + }, + want: []dns.Policy{ + { + Domain: "www.baidu.com", + NameServers: ns, + }, + }, + wantErr: false, + }, + { + name: "多个普通域名", + args: args{ + k: "www.baidu.com,+.internal.crop.com", + nameservers: ns, + }, + want: []dns.Policy{ + { + Domain: "www.baidu.com", + NameServers: ns, + }, + { + Domain: "+.internal.crop.com", + NameServers: ns, + }, + }, + wantErr: false, + }, + { + name: "geosite 单个", + args: args{ + k: "GEoSite:cn", + nameservers: ns, + }, + want: []dns.Policy{ + { + Matcher: getDomainMatcher("cn"), + NameServers: ns, + }, + }, + wantErr: false, + }, + { + name: "geosite 多个", + args: args{ + k: "GEoSite:cn,category-ads-all", + nameservers: ns, + }, + want: []dns.Policy{ + { + Matcher: getDomainMatcher("cn"), + NameServers: ns, + }, + { + Matcher: getDomainMatcher("category-ads-all"), + NameServers: ns, + }, + }, + wantErr: false, + }, + + { + name: "rule-set", + args: args{ + k: "RuLe-sEt:cn", + nameservers: ns, + ruleProviders: rp, + }, + want: []dns.Policy{ + { + Matcher: getRuleSetMatcher("cn"), + NameServers: ns, + }, + }, + wantErr: false, + }, + { + name: "rule", + args: args{ + k: "RuLe:direct", + nameservers: ns, + ruleProviders: rp, + }, + want: []dns.Policy{ + { + Matcher: getRuleMatcher("direct"), + NameServers: ns, + }, + }, + wantErr: false, + }, + { + name: "rule unknown", + args: args{ + k: "RuLe:xxxx", + nameservers: ns, + ruleProviders: rp, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseDNSPolicy(tt.args.k, tt.args.nameservers, tt.args.ruleProviders) + if (err != nil) != tt.wantErr { + t.Errorf("parseDNSPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseDNSPolicy() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/dns/fakeip.go b/dns/fakeip.go new file mode 100644 index 00000000..560cd128 --- /dev/null +++ b/dns/fakeip.go @@ -0,0 +1,61 @@ +package dns + +import ( + stdCtx "context" + "strings" + + "github.com/metacubex/mihomo/component/fakeip" + "github.com/metacubex/mihomo/component/resolver" + D "github.com/miekg/dns" +) + +type fakeIPclient struct { + back dnsClient +} + +var _ dnsClient = &fakeIPclient{} + +func newFakeIPClient(back dnsClient) *fakeIPclient { + return &fakeIPclient{back: back} +} + +func (f *fakeIPclient) ExchangeContext(ctx stdCtx.Context, r *D.Msg) (*D.Msg, error) { + q := r.Question[0] + host := strings.TrimRight(q.Name, ".") + + // todo unify + switch q.Qtype { + case D.TypeAAAA, D.TypeSVCB, D.TypeHTTPS: + return handleMsgWithEmptyAnswer(r), nil + } + + if q.Qtype != D.TypeA { + return f.back.ExchangeContext(ctx, r) + } + + fakeip := resolver.DefaultHostMapper.(*ResolverEnhancer).fakePool + + msg := fakeipExchange(q, fakeip, host, r) + return msg, nil +} + +func fakeipExchange(q D.Question, fakePool *fakeip.Pool, host string, r *D.Msg) *D.Msg { + rr := &D.A{} + rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} + ip := fakePool.Lookup(host) + rr.A = ip.AsSlice() + msg := r.Copy() + msg.Answer = []D.RR{rr} + + setMsgTTL(msg, 1) + msg.SetRcode(r, D.RcodeSuccess) + msg.Authoritative = true + msg.RecursionAvailable = true + return msg +} + +func (r fakeIPclient) Address() string { + return "fakeip://" + r.back.Address() +} + +func (r fakeIPclient) ResetConnection() {} diff --git a/dns/middleware.go b/dns/middleware.go index b55adc6b..16b8a395 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -160,20 +160,8 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { return next(ctx, r) } - rr := &D.A{} - rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} - ip := fakePool.Lookup(host) - rr.A = ip.AsSlice() - msg := r.Copy() - msg.Answer = []D.RR{rr} - ctx.SetType(context.DNSTypeFakeIP) - setMsgTTL(msg, 1) - msg.SetRcode(r, D.RcodeSuccess) - msg.Authoritative = true - msg.RecursionAvailable = true - - return msg, nil + return fakeipExchange(q, fakePool, host, r), nil } } } diff --git a/dns/resolver.go b/dns/resolver.go index 9f7e28f3..5b47142d 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -398,6 +398,8 @@ type NameServer struct { ProxyName string Params map[string]string PreferH3 bool + // fakeip will use this + Back *NameServer } func (ns NameServer) Equal(ns2 NameServer) bool { @@ -411,7 +413,8 @@ func (ns NameServer) Equal(ns2 NameServer) bool { ns.ProxyAdapter == ns2.ProxyAdapter && ns.ProxyName == ns2.ProxyName && maps.Equal(ns.Params, ns2.Params) && - ns.PreferH3 == ns2.PreferH3 { + ns.PreferH3 == ns2.PreferH3 && + (ns.Back == ns2.Back || (ns.Back != nil && ns2.Back != nil && ns.Back.Equal(*ns2.Back))) { return true } return false diff --git a/dns/util.go b/dns/util.go index 50459cc1..30e83562 100644 --- a/dns/util.go +++ b/dns/util.go @@ -94,35 +94,44 @@ func isIPRequest(q D.Question) bool { func transform(servers []NameServer, resolver *Resolver) []dnsClient { ret := make([]dnsClient, 0, len(servers)) for _, s := range servers { - switch s.Net { - case "https": - ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)) - continue - case "dhcp": - ret = append(ret, newDHCPClient(s.Addr)) - continue - case "system": - ret = append(ret, newSystemClient()) - continue - case "rcode": - ret = append(ret, newRCodeClient(s.Addr)) - continue - case "quic": - if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil { - ret = append(ret, doq) - } else { - log.Fatalln("DoQ format error: %v", err) - } - continue - } + ret = append(ret, getDnsClient(s, resolver)) + } + return ret +} +func getDnsClient(s NameServer, resolver *Resolver) dnsClient { + switch s.Net { + case "https": + return newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName) + case "dhcp": + return newDHCPClient(s.Addr) + case "system": + return newSystemClient() + case "rcode": + return newRCodeClient(s.Addr) + case "quic": + if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil { + return doq + } else { + log.Fatalln("DoQ format error: %v", err) + return nil + } + case "fakeip": + var back dnsClient + if s.Back == nil { + back = newSystemClient() + } else { + back = getDnsClient(*s.Back, resolver) + } + return newFakeIPClient(back) + default: var options []dialer.Option if s.Interface != "" { options = append(options, dialer.WithInterface(s.Interface)) } host, port, _ := net.SplitHostPort(s.Addr) - ret = append(ret, &client{ + return &client{ Client: &D.Client{ Net: s.Net, TLSConfig: &tls.Config{ @@ -134,9 +143,8 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { port: port, host: host, dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName, options...), - }) + } } - return ret } func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg { diff --git a/docs/config.yaml b/docs/config.yaml index 263d67b6..0fce570d 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -253,7 +253,7 @@ dns: # 配置不使用 fake-ip 的域名 fake-ip-filter: - - '*.lan' + - "*.lan" - localhost.ptlogin2.qq.com # fakeip-filter 为 rule-providers 中的名为 fakeip-filter 规则订阅, # 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则 @@ -333,6 +333,17 @@ dns: ## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则 # "rule-set:global,dns": 8.8.8.8 + ## 使用network TCP 进行代理规则匹配 + ## direct 直连,包含 Direct、Compatible + ## proxy 使用了代理 + ## reject 拒绝, 包含Reject、RejectDrop + ## + ## fakeip 后面可选后备dns,默认为system,当dns查询为txt等非A AAAA SVCB HTTPS时使用 + ## fakeip 使用的 fake-ip-range, + ## 由于全局fakeip优先级高,需要配置 fake-ip-filter-mode、fake-ip-filter 失效从而让nameserver-policy策略生效 + "rule:proxy": "fakeip://quic://223.5.5.5" + "rule:direct": quic://223.5.5.5 + "rule:reject": rcode://success proxies: # socks5 - name: "socks" type: socks5 @@ -527,13 +538,13 @@ proxies: # socks5 # servername: example.com # priority over wss host # network: ws # ws-opts: - # path: /path - # headers: - # Host: v2ray.com - # max-early-data: 2048 - # early-data-header-name: Sec-WebSocket-Protocol - # v2ray-http-upgrade: false - # v2ray-http-upgrade-fast-open: false + # path: /path + # headers: + # Host: v2ray.com + # max-early-data: 2048 + # early-data-header-name: Sec-WebSocket-Protocol + # v2ray-http-upgrade: false + # v2ray-http-upgrade-fast-open: false - name: "vmess-h2" type: vmess @@ -901,7 +912,7 @@ proxies: # socks5 # - http/1.1 # skip-cert-verify: true -# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理 + # dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理 - name: "dns-out" type: dns proxy-groups: @@ -988,8 +999,8 @@ proxy-providers: # size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小 header: User-Agent: - - "Clash/v1.18.0" - - "mihomo/1.18.3" + - "Clash/v1.18.0" + - "mihomo/1.18.3" # Accept: # - 'application/vnd.github.v3.raw' # Authorization: @@ -1072,9 +1083,9 @@ rule-providers: type: inline behavior: domain # classical / ipcidr payload: - - '.blogger.com' - - '*.*.microsoft.com' - - 'books.itunes.apple.com' + - ".blogger.com" + - "*.*.microsoft.com" + - "books.itunes.apple.com" rules: - RULE-SET,rule1,REJECT @@ -1303,14 +1314,14 @@ listeners: # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) stack: system # gvisor / mixed dns-hijack: - - 0.0.0.0:53 # 需要劫持的 DNS + - 0.0.0.0:53 # 需要劫持的 DNS # auto-detect-interface: false # 自动识别出口网卡 # auto-route: false # 配置路由表 # mtu: 9000 # 最大传输单元 inet4-address: # 必须手动设置 ipv4 地址段 - - 198.19.0.1/30 + - 198.19.0.1/30 inet6-address: # 必须手动设置 ipv6 地址段 - - "fdfe:dcba:9877::1/126" + - "fdfe:dcba:9877::1/126" # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问 # inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 # - 0.0.0.0/1 diff --git a/tunnel/dns_dialer.go b/tunnel/dns_dialer.go index 1839869b..8f1b904e 100644 --- a/tunnel/dns_dialer.go +++ b/tunnel/dns_dialer.go @@ -64,7 +64,7 @@ func (d *DNSDialer) DialContext(ctx context.Context, network, addr string) (net. } metadata.DstIP = dstIP } - proxyAdapter, rule, err = resolveMetadata(metadata) + proxyAdapter, rule, err = ResolveMetadata(metadata) if err != nil { return nil, err } @@ -124,7 +124,6 @@ func (d *DNSDialer) DialContext(ctx context.Context, network, addr string) (net. return N.NewBindPacketConn(packetConn, metadata.UDPAddr()), nil } - } func (d *DNSDialer) ListenPacket(ctx context.Context, network, addr string) (net.PacketConn, error) { @@ -152,7 +151,7 @@ func (d *DNSDialer) ListenPacket(ctx context.Context, network, addr string) (net var rule C.Rule if proxyAdapter == nil { if proxyName == DnsRespectRules { - proxyAdapter, rule, err = resolveMetadata(metadata) + proxyAdapter, rule, err = ResolveMetadata(metadata) if err != nil { return nil, err } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index b9507930..4b6f7d96 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -324,7 +324,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] @@ -393,7 +393,7 @@ func handleUDPConn(packet C.PacketAdapter) { return nil, nil, err } - proxy, rule, err := resolveMetadata(metadata) + proxy, rule, err := ResolveMetadata(metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) return nil, nil, err @@ -489,7 +489,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