feat(dns): nameserver-policy add rule & add fakeip dns server

1. rule match, use network type TCP and domain to match rule, result is one of proxy, direct, reject
2.  add fakeip dns server, use config in  fake-ip-range
This commit is contained in:
qianlongzt 2025-03-16 16:34:48 +08:00
parent dee5898e36
commit 1739283a27
11 changed files with 644 additions and 208 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
}

72
config/dns_policy_rule.go Normal file
View file

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

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

61
dns/fakeip.go Normal file
View file

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

View file

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

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

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

View file

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