Skip to content

Commit 7365269

Browse files
authored
DNS New Features: disableCache, finalQuery, unexpectedIPs, "*", UseSystem-queryStrategy, useSystemHosts (XTLS#4666)
1 parent 0a36fa7 commit 7365269

7 files changed

Lines changed: 442 additions & 187 deletions

File tree

app/dns/config.pb.go

Lines changed: 148 additions & 101 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/dns/config.proto

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ message NameServer {
2525
}
2626

2727
repeated PriorityDomain prioritized_domain = 2;
28-
repeated xray.app.router.GeoIP geoip = 3;
28+
repeated xray.app.router.GeoIP expected_geoip = 3;
2929
repeated OriginalRule original_rules = 4;
3030
QueryStrategy query_strategy = 7;
31-
bool allowUnexpectedIPs = 8;
31+
bool actPrior = 8;
3232
string tag = 9;
3333
uint64 timeoutMs = 10;
34+
bool disableCache = 11;
35+
bool finalQuery = 12;
36+
repeated xray.app.router.GeoIP unexpected_geoip = 13;
37+
bool actUnprior = 14;
3438
}
3539

3640
enum DomainMatchingType {
@@ -44,6 +48,7 @@ enum QueryStrategy {
4448
USE_IP = 0;
4549
USE_IP4 = 1;
4650
USE_IP6 = 2;
51+
USE_SYS = 3;
4752
}
4853

4954
message Config {

app/dns/dns.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type DNS struct {
2828
ctx context.Context
2929
domainMatcher strmatcher.IndexMatcher
3030
matcherInfos []*DomainMatcherInfo
31+
checkSystem bool
3132
}
3233

3334
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
@@ -47,13 +48,21 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
4748
}
4849

4950
var ipOption dns.IPOption
51+
checkSystem := false
5052
switch config.QueryStrategy {
5153
case QueryStrategy_USE_IP:
5254
ipOption = dns.IPOption{
5355
IPv4Enable: true,
5456
IPv6Enable: true,
5557
FakeEnable: false,
5658
}
59+
case QueryStrategy_USE_SYS:
60+
ipOption = dns.IPOption{
61+
IPv4Enable: true,
62+
IPv6Enable: true,
63+
FakeEnable: false,
64+
}
65+
checkSystem = true
5766
case QueryStrategy_USE_IP4:
5867
ipOption = dns.IPOption{
5968
IPv4Enable: true,
@@ -108,7 +117,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
108117
myClientIP = net.IP(ns.ClientIp)
109118
}
110119

111-
disableCache := config.DisableCache
120+
disableCache := config.DisableCache || ns.DisableCache
112121

113122
var tag = defaultTag
114123
if len(ns.Tag) > 0 {
@@ -118,6 +127,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
118127
if !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {
119128
return nil, errors.New("no QueryStrategy available for ", ns.Address)
120129
}
130+
121131
client, err := NewClient(ctx, ns, myClientIP, disableCache, tag, clientIPOption, &matcherInfos, updateDomain)
122132
if err != nil {
123133
return nil, errors.New("failed to create client").Base(err)
@@ -139,6 +149,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
139149
matcherInfos: matcherInfos,
140150
disableFallback: config.DisableFallback,
141151
disableFallbackIfMatch: config.DisableFallbackIfMatch,
152+
checkSystem: checkSystem,
142153
}, nil
143154
}
144155

@@ -179,8 +190,14 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
179190
return nil, 0, errors.New("empty domain name")
180191
}
181192

182-
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
183-
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
193+
if s.checkSystem {
194+
supportIPv4, supportIPv6 := checkSystemNetwork()
195+
option.IPv4Enable = option.IPv4Enable && supportIPv4
196+
option.IPv6Enable = option.IPv6Enable && supportIPv6
197+
} else {
198+
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
199+
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
200+
}
184201

185202
if !option.IPv4Enable && !option.IPv6Enable {
186203
return nil, 0, dns.ErrEmptyResponse
@@ -227,6 +244,9 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
227244
}
228245
errs = append(errs, err)
229246

247+
if client.IsFinalQuery() {
248+
break
249+
}
230250
}
231251

232252
if len(errs) > 0 {
@@ -302,3 +322,22 @@ func init() {
302322
return New(ctx, config.(*Config))
303323
}))
304324
}
325+
326+
func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) {
327+
conn4, err4 := net.Dial("udp4", "8.8.8.8:53")
328+
if err4 != nil {
329+
supportIPv4 = false
330+
} else {
331+
supportIPv4 = true
332+
conn4.Close()
333+
}
334+
335+
conn6, err6 := net.Dial("udp6", "[2001:4860:4860::8888]:53")
336+
if err6 != nil {
337+
supportIPv6 = false
338+
} else {
339+
supportIPv6 = true
340+
conn6.Close()
341+
}
342+
return
343+
}

app/dns/dns_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ func TestIPMatch(t *testing.T) {
539539
},
540540
Port: uint32(port),
541541
},
542-
Geoip: []*router.GeoIP{
542+
ExpectedGeoip: []*router.GeoIP{
543543
{
544544
CountryCode: "local",
545545
Cidr: []*router.CIDR{
@@ -563,7 +563,7 @@ func TestIPMatch(t *testing.T) {
563563
},
564564
Port: uint32(port),
565565
},
566-
Geoip: []*router.GeoIP{
566+
ExpectedGeoip: []*router.GeoIP{
567567
{
568568
CountryCode: "test",
569569
Cidr: []*router.CIDR{
@@ -667,7 +667,7 @@ func TestLocalDomain(t *testing.T) {
667667
// Equivalent of dotless:localhost
668668
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
669669
},
670-
Geoip: []*router.GeoIP{
670+
ExpectedGeoip: []*router.GeoIP{
671671
{ // Will match localhost, localhost-a and localhost-b,
672672
CountryCode: "local",
673673
Cidr: []*router.CIDR{
@@ -897,7 +897,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
897897
Domain: "google.com",
898898
},
899899
},
900-
Geoip: []*router.GeoIP{
900+
ExpectedGeoip: []*router.GeoIP{
901901
{ // Will only match 8.8.8.8 and 8.8.4.4
902902
Cidr: []*router.CIDR{
903903
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
@@ -922,7 +922,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
922922
Domain: "google.com",
923923
},
924924
},
925-
Geoip: []*router.GeoIP{
925+
ExpectedGeoip: []*router.GeoIP{
926926
{ // Will match 8.8.8.8 and 8.8.8.7, etc
927927
Cidr: []*router.CIDR{
928928
{Ip: []byte{8, 8, 8, 7}, Prefix: 24},
@@ -946,7 +946,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
946946
Domain: "api.google.com",
947947
},
948948
},
949-
Geoip: []*router.GeoIP{
949+
ExpectedGeoip: []*router.GeoIP{
950950
{ // Will only match 8.8.7.7 (api.google.com)
951951
Cidr: []*router.CIDR{
952952
{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
@@ -970,7 +970,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
970970
Domain: "v2.api.google.com",
971971
},
972972
},
973-
Geoip: []*router.GeoIP{
973+
ExpectedGeoip: []*router.GeoIP{
974974
{ // Will only match 8.8.7.8 (v2.api.google.com)
975975
Cidr: []*router.CIDR{
976976
{Ip: []byte{8, 8, 7, 8}, Prefix: 32},

app/dns/nameserver.go

Lines changed: 76 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@ type Server interface {
2626

2727
// Client is the interface for DNS client.
2828
type Client struct {
29-
server Server
30-
skipFallback bool
31-
domains []string
32-
expectedIPs []*router.GeoIPMatcher
33-
allowUnexpectedIPs bool
34-
tag string
35-
timeoutMs time.Duration
36-
ipOption *dns.IPOption
29+
server Server
30+
skipFallback bool
31+
domains []string
32+
expectedIPs []*router.GeoIPMatcher
33+
unexpectedIPs []*router.GeoIPMatcher
34+
actPrior bool
35+
actUnprior bool
36+
tag string
37+
timeoutMs time.Duration
38+
finalQuery bool
39+
ipOption *dns.IPOption
40+
checkSystem bool
3741
}
3842

3943
// NewServer creates a name server object according to the network destination url.
@@ -150,13 +154,23 @@ func NewClient(
150154
}
151155

152156
// Establish expected IPs
153-
var matchers []*router.GeoIPMatcher
154-
for _, geoip := range ns.Geoip {
157+
var expectedMatchers []*router.GeoIPMatcher
158+
for _, geoip := range ns.ExpectedGeoip {
155159
matcher, err := router.GlobalGeoIPContainer.Add(geoip)
156160
if err != nil {
157-
return errors.New("failed to create ip matcher").Base(err).AtWarning()
161+
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
158162
}
159-
matchers = append(matchers, matcher)
163+
expectedMatchers = append(expectedMatchers, matcher)
164+
}
165+
166+
// Establish unexpected IPs
167+
var unexpectedMatchers []*router.GeoIPMatcher
168+
for _, geoip := range ns.UnexpectedGeoip {
169+
matcher, err := router.GlobalGeoIPContainer.Add(geoip)
170+
if err != nil {
171+
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
172+
}
173+
unexpectedMatchers = append(unexpectedMatchers, matcher)
160174
}
161175

162176
if len(clientIP) > 0 {
@@ -173,14 +187,20 @@ func NewClient(
173187
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
174188
}
175189

190+
checkSystem := ns.QueryStrategy == QueryStrategy_USE_SYS
191+
176192
client.server = server
177193
client.skipFallback = ns.SkipFallback
178194
client.domains = rules
179-
client.expectedIPs = matchers
180-
client.allowUnexpectedIPs = ns.AllowUnexpectedIPs
195+
client.expectedIPs = expectedMatchers
196+
client.unexpectedIPs = unexpectedMatchers
197+
client.actPrior = ns.ActPrior
198+
client.actUnprior = ns.ActUnprior
181199
client.tag = tag
182200
client.timeoutMs = timeoutMs
201+
client.finalQuery = ns.FinalQuery
183202
client.ipOption = &ipOption
203+
client.checkSystem = checkSystem
184204
return nil
185205
})
186206
return client, err
@@ -191,10 +211,21 @@ func (c *Client) Name() string {
191211
return c.server.Name()
192212
}
193213

214+
func (c *Client) IsFinalQuery() bool {
215+
return c.finalQuery
216+
}
217+
194218
// QueryIP sends DNS query to the name server with the client's IP.
195219
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
196-
option.IPv4Enable = option.IPv4Enable && c.ipOption.IPv4Enable
197-
option.IPv6Enable = option.IPv6Enable && c.ipOption.IPv6Enable
220+
if c.checkSystem {
221+
supportIPv4, supportIPv6 := checkSystemNetwork()
222+
option.IPv4Enable = option.IPv4Enable && supportIPv4
223+
option.IPv6Enable = option.IPv6Enable && supportIPv6
224+
} else {
225+
option.IPv4Enable = option.IPv4Enable && c.ipOption.IPv4Enable
226+
option.IPv6Enable = option.IPv6Enable && c.ipOption.IPv6Enable
227+
}
228+
198229
if !option.IPv4Enable && !option.IPv6Enable {
199230
return nil, 0, dns.ErrEmptyResponse
200231
}
@@ -212,39 +243,47 @@ func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption
212243
return nil, 0, dns.ErrEmptyResponse
213244
}
214245

215-
if len(c.expectedIPs) > 0 {
216-
newIps := c.MatchExpectedIPs(domain, ips)
217-
if len(newIps) == 0 {
218-
if !c.allowUnexpectedIPs {
219-
return nil, 0, dns.ErrEmptyResponse
220-
}
221-
} else {
222-
ips = newIps
246+
if len(c.expectedIPs) > 0 && !c.actPrior {
247+
ips = router.MatchIPs(c.expectedIPs, ips, false)
248+
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", ips, " matched at server ", c.Name())
249+
if len(ips) == 0 {
250+
return nil, 0, dns.ErrEmptyResponse
223251
}
224252
}
225253

226-
return ips, ttl, nil
227-
}
254+
if len(c.unexpectedIPs) > 0 && !c.actUnprior {
255+
ips = router.MatchIPs(c.unexpectedIPs, ips, true)
256+
errors.LogDebug(context.Background(), "domain ", domain, " unexpectedIPs ", ips, " matched at server ", c.Name())
257+
if len(ips) == 0 {
258+
return nil, 0, dns.ErrEmptyResponse
259+
}
260+
}
228261

229-
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
230-
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) []net.IP {
231-
var newIps []net.IP
232-
for _, ip := range ips {
233-
for _, matcher := range c.expectedIPs {
234-
if matcher.Match(ip) {
235-
newIps = append(newIps, ip)
236-
break
237-
}
262+
if len(c.expectedIPs) > 0 && c.actPrior {
263+
ipsNew := router.MatchIPs(c.expectedIPs, ips, false)
264+
if len(ipsNew) > 0 {
265+
ips = ipsNew
266+
errors.LogDebug(context.Background(), "domain ", domain, " priorIPs ", ips, " matched at server ", c.Name())
238267
}
239268
}
240-
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", newIps, " matched at server ", c.Name())
241-
return newIps
269+
270+
if len(c.unexpectedIPs) > 0 && c.actUnprior {
271+
ipsNew := router.MatchIPs(c.unexpectedIPs, ips, true)
272+
if len(ipsNew) > 0 {
273+
ips = ipsNew
274+
errors.LogDebug(context.Background(), "domain ", domain, " unpriorIPs ", ips, " matched at server ", c.Name())
275+
}
276+
}
277+
278+
return ips, ttl, nil
242279
}
243280

244281
func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {
245282
switch queryStrategy {
246283
case QueryStrategy_USE_IP:
247284
return ipOption
285+
case QueryStrategy_USE_SYS:
286+
return ipOption
248287
case QueryStrategy_USE_IP4:
249288
return dns.IPOption{
250289
IPv4Enable: ipOption.IPv4Enable,

app/router/condition_geoip.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,29 @@ func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
116116
}
117117

118118
var GlobalGeoIPContainer GeoIPMatcherContainer
119+
120+
func MatchIPs(matchers []*GeoIPMatcher, ips []net.IP, reverse bool) []net.IP {
121+
if len(matchers) == 0 {
122+
panic("GeoIP matchers should not be empty to avoid ambiguity")
123+
}
124+
newIPs := make([]net.IP, 0, len(ips))
125+
var isFound bool
126+
for _, ip := range ips {
127+
isFound = false
128+
for _, matcher := range matchers {
129+
if matcher.Match(ip) {
130+
isFound = true
131+
break
132+
}
133+
}
134+
if isFound && !reverse {
135+
newIPs = append(newIPs, ip)
136+
continue
137+
}
138+
if !isFound && reverse {
139+
newIPs = append(newIPs, ip)
140+
continue
141+
}
142+
}
143+
return newIPs
144+
}

0 commit comments

Comments
 (0)