Skip to content

Commit c96860c

Browse files
committed
fix: guard malformed domains before xray dns routing
1 parent bc05ee5 commit c96860c

2 files changed

Lines changed: 122 additions & 2 deletions

File tree

backend/xray/config.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,9 @@ func filterRules(rules []json.RawMessage, apiTag string) ([]json.RawMessage, err
428428
if outboundTagValue, ok := obj["outboundTag"].(string); ok && outboundTagValue == apiTag {
429429
continue
430430
}
431+
if ruleTagValue, ok := obj["ruleTag"].(string); ok && ruleTagValue == malformedDomainGuardRuleTag {
432+
continue
433+
}
431434

432435
filtered = append(filtered, raw)
433436
}
@@ -603,6 +606,48 @@ func (c *Config) outboundProtocolByTag() map[string]string {
603606
return out
604607
}
605608

609+
const malformedDomainGuardRuleTag = "PG_NODE_MALFORMED_DOMAIN_GUARD"
610+
611+
func (c *Config) blackholeOutboundTag() string {
612+
protocolByTag := c.outboundProtocolByTag()
613+
if len(protocolByTag) == 0 {
614+
return ""
615+
}
616+
617+
tags := make([]string, 0, len(protocolByTag))
618+
for tag := range protocolByTag {
619+
tags = append(tags, tag)
620+
}
621+
sort.Strings(tags)
622+
623+
for _, tag := range tags {
624+
if strings.EqualFold(protocolByTag[tag], "blackhole") {
625+
return tag
626+
}
627+
}
628+
629+
return ""
630+
}
631+
632+
func malformedDomainGuardRule(outboundTag string) (json.RawMessage, error) {
633+
rule := map[string]any{
634+
"domain": []string{
635+
"regexp:^.{254,}$",
636+
"regexp:(^|\\.)[^.]{64,}(\\.|$)",
637+
},
638+
"outboundTag": outboundTag,
639+
"ruleTag": malformedDomainGuardRuleTag,
640+
"type": "field",
641+
}
642+
643+
rawBytes, err := json.Marshal(rule)
644+
if err != nil {
645+
return nil, err
646+
}
647+
648+
return json.RawMessage(rawBytes), nil
649+
}
650+
606651
var observatoryExcludedProtocols = map[string]struct{}{
607652
"blackhole": {},
608653
"dns": {},
@@ -711,7 +756,16 @@ func (c *Config) ApplyAPI(apiPort, metricPort int) (err error) {
711756

712757
newRaw := json.RawMessage(rawBytes)
713758

714-
c.RouterConfig.RuleList = append([]json.RawMessage{newRaw}, c.RouterConfig.RuleList...)
759+
prependRules := []json.RawMessage{newRaw}
760+
if blockTag := c.blackholeOutboundTag(); blockTag != "" {
761+
guardRaw, err := malformedDomainGuardRule(blockTag)
762+
if err != nil {
763+
return err
764+
}
765+
prependRules = append(prependRules, guardRaw)
766+
}
767+
768+
c.RouterConfig.RuleList = append(prependRules, c.RouterConfig.RuleList...)
715769

716770
return nil
717771
}

backend/xray/latency_test.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package xray
22

3-
import "testing"
3+
import (
4+
"encoding/json"
5+
"strings"
6+
"testing"
7+
8+
"github.com/xtls/xray-core/infra/conf"
9+
)
410

511
func TestShouldIncludeObservatoryOutbound(t *testing.T) {
612
protocolByTag := map[string]string{
@@ -58,3 +64,63 @@ func TestApplyAPISanitizesObservatorySelectors(t *testing.T) {
5864
t.Fatalf("unexpected burst observatory selectors: %#v", burst)
5965
}
6066
}
67+
68+
func TestApplyAPIAddsMalformedDomainGuardWhenBlackholeExists(t *testing.T) {
69+
cfg := &Config{
70+
InboundConfigs: []*Inbound{},
71+
OutboundConfigs: []any{
72+
map[string]any{"tag": "direct", "protocol": "freedom"},
73+
map[string]any{"tag": "Block", "protocol": "blackhole"},
74+
},
75+
}
76+
77+
if err := cfg.ApplyAPI(10001, 10002); err != nil {
78+
t.Fatal(err)
79+
}
80+
81+
if len(cfg.RouterConfig.RuleList) < 2 {
82+
t.Fatalf("expected API and malformed-domain guard rules, got %d", len(cfg.RouterConfig.RuleList))
83+
}
84+
85+
rule := string(cfg.RouterConfig.RuleList[1])
86+
if !containsAll(rule, malformedDomainGuardRuleTag, `"outboundTag":"Block"`, `regexp:^.{254,}$`) {
87+
t.Fatalf("unexpected malformed-domain guard rule: %s", rule)
88+
}
89+
}
90+
91+
func TestApplyAPIRemovesExistingMalformedDomainGuard(t *testing.T) {
92+
cfg := &Config{
93+
InboundConfigs: []*Inbound{},
94+
OutboundConfigs: []any{
95+
map[string]any{"tag": "Block", "protocol": "blackhole"},
96+
},
97+
RouterConfig: &conf.RouterConfig{
98+
RuleList: []json.RawMessage{
99+
json.RawMessage(`{"type":"field","ruleTag":"PG_NODE_MALFORMED_DOMAIN_GUARD","outboundTag":"old","domain":["regexp:^.{254,}$"]}`),
100+
},
101+
},
102+
}
103+
104+
if err := cfg.ApplyAPI(10001, 10002); err != nil {
105+
t.Fatal(err)
106+
}
107+
108+
count := 0
109+
for _, raw := range cfg.RouterConfig.RuleList {
110+
if strings.Contains(string(raw), malformedDomainGuardRuleTag) {
111+
count++
112+
}
113+
}
114+
if count != 1 {
115+
t.Fatalf("expected one malformed-domain guard rule, got %d", count)
116+
}
117+
}
118+
119+
func containsAll(s string, values ...string) bool {
120+
for _, value := range values {
121+
if !strings.Contains(s, value) {
122+
return false
123+
}
124+
}
125+
return true
126+
}

0 commit comments

Comments
 (0)