mirror of
https://github.com/MetaCubeX/Clash.Meta.git
synced 2025-04-11 04:50:56 +00:00
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:
parent
dee5898e36
commit
1739283a27
11 changed files with 644 additions and 208 deletions
266
config/config.go
266
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
|
||||
}
|
||||
|
||||
|
|
60
config/dns_policy.go
Normal file
60
config/dns_policy.go
Normal 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
72
config/dns_policy_rule.go
Normal 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
262
config/dns_policy_test.go
Normal 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
61
dns/fakeip.go
Normal 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() {}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
56
dns/util.go
56
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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue