This commit is contained in:
qianlongzt 2025-04-03 11:42:40 +08:00 committed by GitHub
commit 31f1bb1874
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 694 additions and 228 deletions

View file

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

60
config/dns_policy.go Normal file
View file

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

89
config/dns_policy_rule.go Normal file
View file

@ -0,0 +1,89 @@
package config
import (
"errors"
"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 {
meta := &C.Metadata{
NetWork: C.TCP,
Type: C.INNER, // avoid process lookup
Host: domain,
}
p, r, err := tunnel.ResolveMetadata(meta, &tunnel.ResolveOption{SkipResolveIP: true})
if err != nil {
log.Warnln("[DNS] ruleMatcher: match(%s) got err %v", domain, err.Error())
return false
}
// parse multi-layer nesting
adapter := p
for {
next := adapter.Unwrap(meta, false)
if next != nil {
adapter = next
} else {
break
}
}
log.Debugln("[DNS] ruleMatcher: domain(%s, %s) rule(%s)",
domain, typ.String(), r)
typ = adapter.Type()
cache.Set(domain, typ)
}
ret := ruleMatch(typ, r)
log.Debugln("[DNS] ruleMatcher: domain(%s, %s) rule(%s), match: %v",
domain, typ.String(), r.typ, ret)
return ret
}
func ruleMatch(typ C.AdapterType, r *ruleMatcher) bool {
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)
}
}

262
config/dns_policy_test.go Normal file
View file

@ -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)
}
})
}
}

83
dns/fakeip.go Normal file
View file

@ -0,0 +1,83 @@
package dns
import (
stdCtx "context"
"strings"
"github.com/metacubex/mihomo/component/fakeip"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/context"
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) {
fakeip := resolver.DefaultHostMapper.(*ResolverEnhancer).fakePool
dnsCtx := context.NewDNSContext(ctx, r)
return fakeipExchange(dnsCtx, r, &fakeipExchangeOption{
fakePool: fakeip,
forceFakeip: true,
back: func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
return f.back.ExchangeContext(ctx.Context, r)
},
})
}
type fakeipExchangeOption struct {
fakePool *fakeip.Pool
forceFakeip bool
back handler
}
func fakeipExchange(ctx *context.DNSContext, r *D.Msg, option *fakeipExchangeOption) (*D.Msg, error) {
q := r.Question[0]
host := strings.TrimRight(q.Name, ".")
fakePool := option.fakePool
back := option.back
if !option.forceFakeip {
if fakePool.ShouldSkipped(host) {
return back(ctx, r)
}
}
switch q.Qtype {
case D.TypeAAAA, D.TypeSVCB, D.TypeHTTPS:
return handleMsgWithEmptyAnswer(r), nil
}
if q.Qtype != D.TypeA {
return back(ctx, r)
}
ctx.SetType(context.DNSTypeFakeIP)
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, nil
}
func (r fakeIPclient) Address() string {
return "fakeip://" + r.back.Address()
}
func (r fakeIPclient) ResetConnection() {}

View file

@ -144,36 +144,11 @@ func withMapping(mapping *lru.LruCache[netip.Addr, string]) middleware {
func withFakeIP(fakePool *fakeip.Pool) middleware {
return func(next handler) handler {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0]
host := strings.TrimRight(q.Name, ".")
if fakePool.ShouldSkipped(host) {
return next(ctx, r)
}
switch q.Qtype {
case D.TypeAAAA, D.TypeSVCB, D.TypeHTTPS:
return handleMsgWithEmptyAnswer(r), nil
}
if q.Qtype != D.TypeA {
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(ctx, r, &fakeipExchangeOption{
fakePool: fakePool,
forceFakeip: false,
back: next,
})
}
}
}

View file

@ -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

View file

@ -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 {

View file

@ -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
@ -902,7 +913,7 @@ proxies: # socks5
# - http/1.1
# skip-cert-verify: true
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
- name: "dns-out"
type: dns
proxy-groups:
@ -989,8 +1000,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:
@ -1073,9 +1084,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
@ -1304,14 +1315,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

View file

@ -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, &ResolveOption{SkipResolveIP: true})
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, &ResolveOption{SkipResolveIP: true})
if err != nil {
return nil, err
}

View file

@ -324,7 +324,11 @@ func preHandleMetadata(metadata *C.Metadata) error {
return nil
}
func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
type ResolveOption struct {
SkipResolveIP bool
}
func ResolveMetadata(metadata *C.Metadata, option *ResolveOption) (proxy C.Proxy, rule C.Rule, err error) {
if metadata.SpecialProxy != "" {
var exist bool
proxy, exist = proxies[metadata.SpecialProxy]
@ -341,7 +345,7 @@ func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err erro
proxy = proxies["GLOBAL"]
// Rule
default:
proxy, rule, err = match(metadata)
proxy, rule, err = match(metadata, option)
}
return
}
@ -393,7 +397,7 @@ func handleUDPConn(packet C.PacketAdapter) {
return nil, nil, err
}
proxy, rule, err := resolveMetadata(metadata)
proxy, rule, err := ResolveMetadata(metadata, &ResolveOption{})
if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return nil, nil, err
@ -489,7 +493,7 @@ func handleTCPConn(connCtx C.ConnContext) {
}()
}
proxy, rule, err := resolveMetadata(metadata)
proxy, rule, err := ResolveMetadata(metadata, &ResolveOption{})
if err != nil {
log.Warnln("[Metadata] parse failed: %s", err.Error())
return
@ -592,7 +596,7 @@ func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
return rule.ShouldResolveIP() && metadata.Host != "" && !metadata.DstIP.IsValid()
}
func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
func match(metadata *C.Metadata, option *ResolveOption) (C.Proxy, C.Rule, error) {
configMux.RLock()
defer configMux.RUnlock()
var (
@ -606,7 +610,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
}
for _, rule := range getRules(metadata) {
if !resolved && shouldResolveIP(rule, metadata) {
if !resolved && !option.SkipResolveIP && shouldResolveIP(rule, metadata) {
func() {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()