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
- 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.
- Regenerate/replace the geoip.dat with the official build; custom converters must emit canonical 4-byte IPv4 / 16-byte IPv6 with sane prefix lengths.
- 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
- Emit canonical 4-byte IPv4 / 16-byte IPv6 in custom dat generators
- Use official geoip.dat builds
- Keep core and vendored netipx current
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.