XTLS/Xray-core · error

failed to build IPv4 set

Error message

failed to build IPv4 set

What it means

Wrapped error from IPSetFactory.createFrom (common/geodata/ip_matcher.go:914) when netipx.IPSetBuilder.IPSet() fails to materialize the accumulated IPv4 prefixes. Note that obviously invalid CIDRs (bad IP slices, invalid prefix lengths) are skipped with a log line before this point, so this error means the builder itself rejected the (apparently valid) prefix set — e.g. inconsistent prefix families or malformed add state. The base error carries go4.org/netipx's reason.

Source

Thrown at common/geodata/ip_matcher.go:914

		}

		if addr.Is4() {
			ipv4Builder.AddPrefix(prefix)
		} else if addr.Is6() {
			ipv6Builder.AddPrefix(prefix)
		}
	})
	if err != nil {
		return nil, err
	}

	// peak mem
	runtime.GC()
	defer runtime.GC()

	ipv4, err := ipv4Builder.IPSet()
	if err != nil {
		return nil, errors.New("failed to build IPv4 set").Base(err)
	}
	ipv6, err := ipv6Builder.IPSet()
	if err != nil {
		return nil, errors.New("failed to build IPv6 set").Base(err)
	}

	var has4, has6 bool
	var max4, max6 int

	for _, p := range ipv4.Prefixes() {
		has4 = true
		if b := p.Bits(); b > max4 {
			max4 = b
		}
	}
	for _, p := range ipv6.Prefixes() {
		has6 = true
		if b := p.Bits(); b > max6 {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Inspect the Base error for the netipx reason; log the CIDRs fed in (add temporary logging around the yield callback) to find the offending entry.
  2. Regenerate/replace the geoip.dat with the official build; custom converters must emit canonical 4-byte IPv4 / 16-byte IPv6 with sane prefix lengths.
  3. Update xray-core (and its vendored netipx) — acceptance of mapped addresses has evolved between versions.

Example fix

// before (custom dat generator)
cidr.Ip = net.ParseIP("1.2.3.4").To16() // v4-in-v6 slice -> ambiguous family

// after
ip := net.ParseIP("1.2.3.4")
cidr.Ip = ip.To4() // canonical 4-byte IPv4 for IPv4 CIDRs
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-filter CIDRs for family consistency before CreateFromCIDRs:
for _, c := range cidrs {
    if a, ok := netip.AddrFromSlice(c.GetIp()); !ok || (a.Is4() != len(c.GetIp()) == 4) { /* fix or drop */ }
}

Type guard

func isCanonicalCIDR(c *geodata.CIDR) bool {
    a, ok := netip.AddrFromSlice(c.GetIp())
    return ok && netip.PrefixFrom(a, int(c.GetPrefix())).IsValid() && !(a.Is4() && len(c.GetIp()) == 16)
}

Try / catch

set, err := factory.CreateFromCIDRs(cidrs)
if err != nil {
    if strings.Contains(err.Error(), "failed to build IPv4 set") {
        // inspect err base for netipx reason; drop offending prefixes and rebuild
    }
    return err
}

Prevention

When it happens

Trigger: CreateFromCIDRs / createFrom over GeoIP-derived CIDRs where a prefix passes the AddrFromSlice/PrefixFrom validity checks but still breaks IPSetBuilder — mismatched 4/6 handling of mapped addresses (v4-in-v6 byte slices), or data that yields a builder state netipx refuses to combine.

Common situations: Custom geoip.dat files with IPv4 addresses stored as 16-byte v4-in-v6 representations; dat files from converters that emit unusual prefix encodings; core/netipx version changes altering acceptance rules.

Related errors


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