2017-12-19 17:55:09 -05:00
package dns
2016-05-16 02:09:28 -04:00
import (
2017-01-26 14:46:44 -05:00
"context"
2020-12-18 04:24:33 -05:00
"net/url"
2021-02-22 21:17:20 -05:00
"strings"
2020-12-18 04:24:33 -05:00
"time"
2016-05-16 02:09:28 -04:00
2022-01-02 10:16:23 -05:00
core "github.com/v2fly/v2ray-core/v5"
"github.com/v2fly/v2ray-core/v5/app/router"
"github.com/v2fly/v2ray-core/v5/common/errors"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/strmatcher"
"github.com/v2fly/v2ray-core/v5/features/dns"
"github.com/v2fly/v2ray-core/v5/features/routing"
2016-05-16 02:09:28 -04:00
)
2020-12-18 04:24:33 -05:00
// Server is the interface for Name Server.
type Server interface {
2019-02-22 18:01:23 -05:00
// Name of the Client.
2018-11-22 10:29:09 -05:00
Name ( ) string
2019-02-22 18:01:23 -05:00
// QueryIP sends IP queries to its configured server.
2021-02-24 03:03:50 -05:00
QueryIP ( ctx context . Context , domain string , clientIP net . IP , option dns . IPOption , disableCache bool ) ( [ ] net . IP , error )
2016-05-16 02:09:28 -04:00
}
2016-05-16 12:05:01 -04:00
2020-12-18 04:24:33 -05:00
// Client is the interface for DNS client.
type Client struct {
2021-04-08 22:35:26 -04:00
server Server
clientIP net . IP
skipFallback bool
domains [ ] string
expectIPs [ ] * router . GeoIPMatcher
2016-05-16 12:05:01 -04:00
}
2020-12-18 04:24:33 -05:00
var errExpectedIPNonMatch = errors . New ( "expectIPs not match" )
2018-11-19 14:42:02 -05:00
2020-12-18 04:24:33 -05:00
// 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 {
2021-02-22 21:17:20 -05:00
case strings . EqualFold ( u . String ( ) , "localhost" ) :
2020-12-18 04:24:33 -05:00
return NewLocalNameServer ( ) , nil
2021-02-22 21:17:20 -05:00
case strings . EqualFold ( u . Scheme , "https" ) : // DOH Remote mode
2020-12-18 04:24:33 -05:00
return NewDoHNameServer ( u , dispatcher )
2021-02-22 21:17:20 -05:00
case strings . EqualFold ( u . Scheme , "https+local" ) : // DOH Local mode
2020-12-18 04:24:33 -05:00
return NewDoHLocalNameServer ( u ) , nil
2021-02-22 21:17:20 -05:00
case strings . EqualFold ( u . Scheme , "quic+local" ) : // DNS-over-QUIC Local mode
2020-12-25 06:13:50 -05:00
return NewQUICNameServer ( u )
2021-06-04 15:09:06 -04:00
case strings . EqualFold ( u . Scheme , "tcp" ) : // DNS-over-TCP Remote mode
return NewTCPNameServer ( u , dispatcher )
case strings . EqualFold ( u . Scheme , "tcp+local" ) : // DNS-over-TCP Local mode
return NewTCPLocalNameServer ( u )
2021-02-22 21:17:20 -05:00
case strings . EqualFold ( u . String ( ) , "fakedns" ) :
2021-02-08 05:18:52 -05:00
return NewFakeDNSServer ( ) , nil
2020-12-18 04:24:33 -05:00
}
}
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
2018-06-26 09:16:45 -04:00
}
2020-12-18 04:24:33 -05: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.
2020-12-04 09:32:55 -05:00
func NewClient ( ctx context . Context , ns * NameServer , clientIP net . IP , container router . GeoIPMatcherContainer , matcherInfos * [ ] DomainMatcherInfo , updateDomainRule func ( strmatcher . Matcher , int , [ ] DomainMatcherInfo ) error ) ( * Client , error ) {
2020-12-18 04:24:33 -05:00
client := & Client { }
2021-04-08 22:35:26 -04:00
2020-12-18 04:24:33 -05: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 )
2021-03-03 03:53:42 -05:00
// The following lines is a solution to avoid core panics( rule index out of range) when setting `localhost` DNS client in config.
// Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
// 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.
// Related issues:
// https://github.com/v2fly/v2ray-core/issues/529
// https://github.com/v2fly/v2ray-core/issues/719
for i := 0 ; i < len ( localTLDsAndDotlessDomains ) ; i ++ {
2020-12-04 09:32:55 -05:00
* matcherInfos = append ( * matcherInfos , DomainMatcherInfo {
2021-03-03 03:53:42 -05:00
clientIdx : uint16 ( 0 ) ,
domainRuleIdx : uint16 ( 0 ) ,
} )
}
2020-12-18 04:24:33 -05:00
}
// Establish domain rules
var rules [ ] string
ruleCurr := 0
ruleIter := 0
for _ , domain := range ns . PrioritizedDomain {
domainRule , err := toStrMatcher ( domain . Type , domain . Domain )
if err != nil {
return newError ( "failed to create prioritized domain" ) . Base ( err ) . AtWarning ( )
}
originalRuleIdx := ruleCurr
if ruleCurr < len ( ns . OriginalRules ) {
rule := ns . OriginalRules [ ruleCurr ]
if ruleCurr >= len ( rules ) {
rules = append ( rules , rule . Rule )
}
ruleIter ++
if ruleIter >= int ( rule . Size ) {
ruleIter = 0
ruleCurr ++
}
} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
rules = append ( rules , domainRule . String ( ) )
ruleCurr ++
}
2021-03-03 03:53:42 -05:00
err = updateDomainRule ( domainRule , originalRuleIdx , * matcherInfos )
2020-12-18 04:24:33 -05:00
if err != nil {
return newError ( "failed to create prioritized domain" ) . Base ( err ) . AtWarning ( )
}
}
// Establish expected IPs
var matchers [ ] * router . GeoIPMatcher
for _ , geoip := range ns . Geoip {
matcher , err := container . Add ( geoip )
if err != nil {
return newError ( "failed to create ip matcher" ) . Base ( err ) . AtWarning ( )
}
matchers = append ( matchers , matcher )
}
2018-11-19 14:42:02 -05:00
2020-12-18 04:24:33 -05: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 ( )
}
}
client . server = server
client . clientIP = clientIP
2021-04-08 22:35:26 -04:00
client . skipFallback = ns . SkipFallback
2020-12-18 04:24:33 -05:00
client . domains = rules
client . expectIPs = matchers
return nil
} )
return client , err
}
// 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 ( )
}
2018-06-26 09:16:45 -04:00
}
2018-11-19 14:42:02 -05:00
2020-12-18 04:24:33 -05:00
return client , err
}
// Name returns the server name the client manages.
func ( c * Client ) Name ( ) string {
return c . server . Name ( )
2018-06-26 09:16:45 -04:00
}
2020-12-18 04:24:33 -05:00
// QueryIP send DNS query to the name server with the client's IP.
2021-02-24 03:03:50 -05:00
func ( c * Client ) QueryIP ( ctx context . Context , domain string , option dns . IPOption , disableCache bool ) ( [ ] net . IP , error ) {
2020-12-18 04:24:33 -05:00
ctx , cancel := context . WithTimeout ( ctx , 4 * time . Second )
2021-02-24 03:03:50 -05:00
ips , err := c . server . QueryIP ( ctx , domain , c . clientIP , option , disableCache )
2020-12-18 04:24:33 -05:00
cancel ( )
if err != nil {
return ips , err
}
return c . MatchExpectedIPs ( domain , ips )
2018-11-22 10:29:09 -05:00
}
2020-12-18 04:24:33 -05:00
// 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
2018-06-26 09:16:45 -04:00
}
2020-12-18 04:24:33 -05:00
newError ( "domain " , domain , " expectIPs " , newIps , " matched at server " , c . Name ( ) ) . AtDebug ( ) . WriteToLog ( )
return newIps , nil
2016-05-16 12:05:01 -04:00
}