2020-11-25 11:01:53 +00:00
package dns
import (
"context"
2021-03-18 15:24:24 +00:00
"net/url"
"strings"
"time"
2020-11-25 11:01:53 +00:00
2021-03-18 15:24:24 +00:00
"github.com/xtls/xray-core/common/errors"
2021-03-24 15:01:20 +00:00
"github.com/xtls/xray-core/common/matcher/geoip"
"github.com/xtls/xray-core/common/matcher/str"
2020-12-04 01:36:16 +00:00
"github.com/xtls/xray-core/common/net"
2021-03-18 15:24:24 +00:00
core "github.com/xtls/xray-core/core"
2021-03-07 04:39:50 +00:00
"github.com/xtls/xray-core/features/dns"
2021-03-18 15:24:24 +00:00
"github.com/xtls/xray-core/features/routing"
2020-11-25 11:01:53 +00:00
)
2021-03-18 15:24:24 +00:00
// Server is the interface for Name Server.
type Server interface {
2020-11-25 11:01:53 +00:00
// Name of the Client.
Name ( ) string
// QueryIP sends IP queries to its configured server.
2021-04-09 15:36:36 +00:00
QueryIP ( ctx context . Context , domain string , clientIP net . IP , option dns . IPOption , cs CacheStrategy ) ( [ ] net . IP , error )
2020-11-25 11:01:53 +00:00
}
2021-03-18 15:24:24 +00:00
// Client is the interface for DNS client.
type Client struct {
2021-07-11 14:02:55 +00:00
server Server
clientIP net . IP
skipFallback bool
expectIPs [ ] * geoip . GeoIPMatcher
domainMatcher str . MatcherGroup
originRules [ ] * NameServer_OriginalRule
}
func ( c Client ) findRule ( idx uint32 ) string {
for _ , r := range c . originRules {
if idx <= r . Size {
return r . Rule
}
idx -= r . Size
}
return "unknown rule"
2020-11-25 11:01:53 +00:00
}
2021-03-18 15:24:24 +00:00
var errExpectedIPNonMatch = errors . New ( "expectIPs not match" )
// NewServer creates a name server object according to the network destination url.
func NewServer ( dest net . Destination , dispatcher routing . Dispatcher ) ( Server , error ) {
if address := dest . Address ; address . Family ( ) . IsDomain ( ) {
u , err := url . Parse ( address . Domain ( ) )
if err != nil {
return nil , err
}
switch {
case strings . EqualFold ( u . String ( ) , "localhost" ) :
return NewLocalNameServer ( ) , nil
case strings . EqualFold ( u . Scheme , "https" ) : // DOH Remote mode
return NewDoHNameServer ( u , dispatcher )
case strings . EqualFold ( u . Scheme , "https+local" ) : // DOH Local mode
return NewDoHLocalNameServer ( u ) , nil
case strings . EqualFold ( u . Scheme , "quic+local" ) : // DNS-over-QUIC Local mode
return NewQUICNameServer ( u )
case strings . EqualFold ( u . String ( ) , "fakedns" ) :
return NewFakeDNSServer ( ) , nil
}
}
if dest . Network == net . Network_Unknown {
dest . Network = net . Network_UDP
}
if dest . Network == net . Network_UDP { // UDP classic DNS mode
return NewClassicNameServer ( dest , dispatcher ) , nil
2020-11-25 11:01:53 +00:00
}
2021-03-18 15:24:24 +00:00
return nil , newError ( "No available name server could be created from " , dest ) . AtWarning ( )
}
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
2021-07-11 14:02:55 +00:00
func NewClient ( ctx context . Context , ns * NameServer , clientIP net . IP , container geoip . GeoIPMatcherContainer ) ( * Client , error ) {
2021-03-18 15:24:24 +00:00
client := & Client { }
2021-04-09 16:07:08 +00:00
2021-03-18 15:24:24 +00:00
err := core . RequireFeatures ( ctx , func ( dispatcher routing . Dispatcher ) error {
// Create a new server for each client for now
server , err := NewServer ( ns . Address . AsDestination ( ) , dispatcher )
if err != nil {
return newError ( "failed to create nameserver" ) . Base ( err ) . AtWarning ( )
}
// Priotize local domains with specific TLDs or without any dot to local DNS
if _ , isLocalDNS := server . ( * LocalNameServer ) ; isLocalDNS {
ns . PrioritizedDomain = append ( ns . PrioritizedDomain , localTLDsAndDotlessDomains ... )
ns . OriginalRules = append ( ns . OriginalRules , localTLDsAndDotlessDomainsRule )
// The following lines is a solution to avoid core panics( rule index out of range) when setting `localhost` DNS client in config.
2021-07-11 14:02:55 +00:00
// Because the `localhost` DNS client will append len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
2021-03-18 15:24:24 +00:00
// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
2021-07-11 14:02:55 +00:00
// ;)
/ *
for i := 0 ; i < len ( localTLDsAndDotlessDomains ) ; i ++ {
* matcherInfos = append ( * matcherInfos , DomainMatcherInfo {
clientIdx : uint16 ( 0 ) ,
domainRuleIdx : uint16 ( 0 ) ,
} )
}
* /
2021-03-18 15:24:24 +00:00
}
// Establish domain rules
2021-07-11 14:02:55 +00:00
var domainMatcher = str . MatcherGroup { }
2021-03-18 15:24:24 +00:00
for _ , domain := range ns . PrioritizedDomain {
2021-03-24 15:01:20 +00:00
domainRule , err := toStrMatcher ( domain . Type , domain . Value )
2021-03-18 15:24:24 +00:00
if err != nil {
return newError ( "failed to create prioritized domain" ) . Base ( err ) . AtWarning ( )
}
2021-07-11 14:02:55 +00:00
domainMatcher . Add ( domainRule )
2021-03-18 15:24:24 +00:00
}
// Establish expected IPs
2021-07-11 14:02:55 +00:00
var ipMatchers [ ] * geoip . GeoIPMatcher
2021-03-18 15:24:24 +00:00
for _ , geoip := range ns . Geoip {
matcher , err := container . Add ( geoip )
if err != nil {
return newError ( "failed to create ip matcher" ) . Base ( err ) . AtWarning ( )
}
2021-07-11 14:02:55 +00:00
ipMatchers = append ( ipMatchers , matcher )
2021-03-18 15:24:24 +00:00
}
if len ( clientIP ) > 0 {
switch ns . Address . Address . GetAddress ( ) . ( type ) {
case * net . IPOrDomain_Domain :
newError ( "DNS: client " , ns . Address . Address . GetDomain ( ) , " uses clientIP " , clientIP . String ( ) ) . AtInfo ( ) . WriteToLog ( )
case * net . IPOrDomain_Ip :
newError ( "DNS: client " , ns . Address . Address . GetIp ( ) , " uses clientIP " , clientIP . String ( ) ) . AtInfo ( ) . WriteToLog ( )
}
}
2020-11-25 11:01:53 +00:00
2021-03-18 15:24:24 +00:00
client . server = server
client . clientIP = clientIP
2021-07-11 14:02:55 +00:00
client . expectIPs = ipMatchers
client . originRules = ns . OriginalRules
client . domainMatcher = domainMatcher
2021-03-18 15:24:24 +00:00
return nil
} )
return client , err
2020-11-25 11:01:53 +00:00
}
2021-03-18 15:24:24 +00:00
// NewSimpleClient creates a DNS client with a simple destination.
func NewSimpleClient ( ctx context . Context , endpoint * net . Endpoint , clientIP net . IP ) ( * Client , error ) {
client := & Client { }
err := core . RequireFeatures ( ctx , func ( dispatcher routing . Dispatcher ) error {
server , err := NewServer ( endpoint . AsDestination ( ) , dispatcher )
if err != nil {
return newError ( "failed to create nameserver" ) . Base ( err ) . AtWarning ( )
}
client . server = server
client . clientIP = clientIP
return nil
} )
if len ( clientIP ) > 0 {
switch endpoint . Address . GetAddress ( ) . ( type ) {
case * net . IPOrDomain_Domain :
newError ( "DNS: client " , endpoint . Address . GetDomain ( ) , " uses clientIP " , clientIP . String ( ) ) . AtInfo ( ) . WriteToLog ( )
case * net . IPOrDomain_Ip :
newError ( "DNS: client " , endpoint . Address . GetIp ( ) , " uses clientIP " , clientIP . String ( ) ) . AtInfo ( ) . WriteToLog ( )
}
}
return client , err
2020-11-25 11:01:53 +00:00
}
2021-03-18 15:24:24 +00:00
// Name returns the server name the client manages.
func ( c * Client ) Name ( ) string {
return c . server . Name ( )
}
// QueryIP send DNS query to the name server with the client's IP.
2021-04-09 15:36:36 +00:00
func ( c * Client ) QueryIP ( ctx context . Context , domain string , option dns . IPOption , cs CacheStrategy ) ( [ ] net . IP , error ) {
2021-03-18 15:24:24 +00:00
ctx , cancel := context . WithTimeout ( ctx , 4 * time . Second )
2021-04-09 15:36:36 +00:00
ips , err := c . server . QueryIP ( ctx , domain , c . clientIP , option , cs )
2021-03-18 15:24:24 +00:00
cancel ( )
if err != nil {
return ips , err
}
return c . MatchExpectedIPs ( domain , ips )
}
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
func ( c * Client ) MatchExpectedIPs ( domain string , ips [ ] net . IP ) ( [ ] net . IP , error ) {
if len ( c . expectIPs ) == 0 {
return ips , nil
}
newIps := [ ] net . IP { }
for _ , ip := range ips {
for _ , matcher := range c . expectIPs {
if matcher . Match ( ip ) {
newIps = append ( newIps , ip )
break
}
}
}
if len ( newIps ) == 0 {
return nil , errExpectedIPNonMatch
2020-11-25 11:01:53 +00:00
}
2021-03-18 15:24:24 +00:00
newError ( "domain " , domain , " expectIPs " , newIps , " matched at server " , c . Name ( ) ) . AtDebug ( ) . WriteToLog ( )
return newIps , nil
2020-11-25 11:01:53 +00:00
}