XTLS/Xray-core · error

empty domain rule list

Error message

empty domain rule list

What it means

Returned by MphDomainMatcherFactory.BuildMatcher when the rules slice is empty. The minimal perfect hash (MPH) matcher used on desktop platforms requires at least one domain rule to build its strmatcher.MphValueMatcher; an empty set is treated as a caller bug or an upstream routing misconfiguration rather than 'match nothing'.

Source

Thrown at common/geodata/domain_matcher.go:63

			sb.WriteString(":")
			sb.WriteString(v.Geosite.Code)
			sb.WriteString("@")
			sb.WriteString(v.Geosite.Attrs)
			sb.WriteString(",")
		default:
			panic("unknown domain rule type")
		}
	}
	if !cache {
		return ""
	}
	return sb.String()
}

// BuildMatcher implements DomainMatcherFactory.
func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatcher, error) {
	if len(rules) == 0 {
		return nil, errors.New("empty domain rule list")
	}
	key := buildDomainRulesKey(rules)
	if key != "" {
		f.Lock()
		defer f.Unlock()
		if g, ok := f.shared.Load(key); ok {
			errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
			return g, nil
		}
		errors.LogDebug(context.Background(), "geodata mph domain matcher cache MISS for ", len(rules), " rules")
	}
	g := strmatcher.NewMphValueMatcher()
	for i, r := range rules {
		switch v := r.Value.(type) {
		case *DomainRule_Custom:
			m, err := parseDomain(v.Custom)
			if err != nil {
				return nil, err

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Check len(rules) == 0 before building and skip/return a nil-matcher no-op policy for that rule.
  2. Fix the source of emptiness: verify the geosite code exists (geosite:cn etc.) and that rule parsing actually appended entries.
  3. Validate routing config so rules with empty effective domain lists are rejected at load time.

Example fix

// before
m, err := factory.BuildMatcher(rules) // rules may be empty -> error

// after
if len(rules) == 0 {
    return nil // nothing to match; skip this rule
}
m, err := factory.BuildMatcher(rules)
Defensive patterns

Strategy: validation

Validate before calling

if len(rules) == 0 { return nil /* skip rule */ }
m, err := factory.BuildMatcher(rules)

Type guard

func hasDomainRules(rules []*geodata.DomainRule) bool { return len(rules) > 0 }

Try / catch

m, err := factory.BuildMatcher(rules)
if err != nil && strings.Contains(err.Error(), "empty domain rule list") {
    return nil // treat as no-op rule
}

Prevention

When it happens

Trigger: Calling BuildMatcher with a zero-length []*geodata.DomainRule — e.g. a routing rule whose geosite/geoip resolution produced no entries (mistyped geosite code that matched nothing via fallback), or programmatically building a matcher from an empty filtered rule list.

Common situations: Routing configs referencing a geosite code that does not exist in geosite.dat (loader returns nothing); custom code that filters domain rules down to zero and still tries to build a matcher; empty 'domainStrategy' rule lists after JSON merge.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/dd1b284c6ee84889. Report an issue: GitHub.