slackhq/nebula · error
encoded IPs should be in pairs, an odd number was found
Error message
encoded IPs should be in pairs, an odd number was found
What it means
In v1 certificates, IPs are stored as a flat []uint32 where each IP is a (network, mask) pair. An odd number of entries means one half of a pair is missing, which the unmarshaler rejects rather than silently mis-pairing the data.
Source
Thrown at cert/cert_v1.go:417
// unmarshalCertificateV1 will unmarshal a protobuf byte representation of a nebula cert
// if the publicKey is provided here then it is not required to be present in `b`
func unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error) {
if len(b) == 0 {
return nil, fmt.Errorf("nil byte array")
}
var rc RawNebulaCertificate
err := proto.Unmarshal(b, &rc)
if err != nil {
return nil, err
}
if rc.Details == nil {
return nil, fmt.Errorf("encoded Details was nil")
}
if len(rc.Details.Ips)%2 != 0 {
return nil, fmt.Errorf("encoded IPs should be in pairs, an odd number was found")
}
if len(rc.Details.Subnets)%2 != 0 {
return nil, fmt.Errorf("encoded Subnets should be in pairs, an odd number was found")
}
nc := certificateV1{
details: detailsV1{
name: rc.Details.Name,
groups: make([]string, len(rc.Details.Groups)),
networks: make([]netip.Prefix, len(rc.Details.Ips)/2),
unsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),
notBefore: time.Unix(rc.Details.NotBefore, 0),
notAfter: time.Unix(rc.Details.NotAfter, 0),
publicKey: nil,
isCA: rc.Details.IsCA,
curve: rc.Details.Curve,
},View on GitHub (pinned to dd8f660c0a)
Solutions
- Ensure Details.Ips always contains ip,mask pairs (even length) before marshaling
- Regenerate or re-issue the certificate with the library's standard marshaling path
- Check any custom tooling that manipulates the Ips array for off-by-one/dropped-element bugs
- Compare the failing certificate's Ips length against a known-good certificate
Example fix
// before
rc.Details.Ips = []uint32{ipToUint32(ip)} // odd length
// after
rc.Details.Ips = []uint32{ipToUint32(ip), maskToUint32(mask)} // paired Defensive patterns
Strategy: validation
Validate before calling
func ipsArePaired(ips []uint32) bool { return len(ips)%2 == 0 }
if !ipsArePaired(rc.Details.Ips) {
return fmt.Errorf("Ips must be (ip,mask) pairs before marshaling")
}
b, err := proto.Marshal(rc) Type guard
func validIPPairCount(n int) bool { return n > 0 && n%2 == 0 } Try / catch
c, err := unmarshalCertificateV1(b, nil)
if err != nil {
if strings.Contains(err.Error(), "IPs should be in pairs") {
return nil, fmt.Errorf("corrupt certificate: odd Ips length; regenerate cert")
}
return nil, err
} Prevention
- Always append IP and mask together when building Details.Ips
- Use helper functions that enforce pair semantics instead of raw uint32 slices
- Round-trip (marshal/unmarshal) certificates in tests after any mutation
- Treat odd-length arrays as corruption and re-issue rather than patching
When it happens
Trigger: unmarshalCertificateV1 receives a certificate whose rc.Details.Ips has odd length — produced by appending a lone IP without its mask, corrupting the array, or writing pairs incorrectly with a custom marshaller.
Common situations: Hand-editing or post-processing RawNebulaCertificate.Details.Ips; a bug in tooling that packs IPs; data corruption during storage; building test certificates incorrectly.
Related errors
- encoded Subnets should be in pairs, an odd number was found
- ErrNotCA
- ErrInvalidPublicKey
- encoded Details was nil
- invalid curve: %s
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/b1e917076e0180ff.
Report an issue: GitHub.