slackhq/nebula · error
encoded Subnets should be in pairs, an odd number was found
Error message
encoded Subnets should be in pairs, an odd number was found
What it means
Like IPs, Subnets (unsafe_routes) in a v1 certificate are stored as (network, mask) uint32 pairs. An odd number of entries means a pair is incomplete, so the unmarshaler rejects the certificate instead of producing mis-paired subnets.
Source
Thrown at cert/cert_v1.go:421
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,
},
signature: make([]byte, len(rc.Signature)),
}
copy(nc.signature, rc.Signature)View on GitHub (pinned to dd8f660c0a)
Solutions
- Make every subnet a network,mask pair (even-length Subnets array) before marshaling
- Re-issue or regenerate the certificate using the library's marshaling functions
- Fix the tooling/script that mutates the Subnets array
- Validate with a quick len check on Subnets when constructing RawNebulaCertificate
Example fix
// before
rc.Details.Subnets = []uint32{subnetToUint32(sub)} // odd length
// after
rc.Details.Subnets = []uint32{subnetToUint32(sub), maskToUint32(mask)} Defensive patterns
Strategy: validation
Validate before calling
func subnetsArePaired(subnets []uint32) bool { return len(subnets)%2 == 0 }
if !subnetsArePaired(rc.Details.Subnets) {
return fmt.Errorf("Subnets must be (network,mask) pairs before marshaling")
}
b, err := proto.Marshal(rc) Type guard
func validSubnetPairCount(n int) bool { return n >= 0 && n%2 == 0 } Try / catch
c, err := unmarshalCertificateV1(b, nil)
if err != nil {
if strings.Contains(err.Error(), "Subnets should be in pairs") {
return nil, fmt.Errorf("corrupt certificate: odd Subnets length; regenerate cert")
}
return nil, err
} Prevention
- Write unsafe_routes via pairing helpers (network+mask always together)
- Round-trip test certificates that manipulate Subnets
- Reject odd-length Subnets arrays at build time in automation tooling
- Regenerate certificates rather than editing raw protobuf fields
When it happens
Trigger: unmarshalCertificateV1 receives a certificate whose rc.Details.Subnets has odd length — an unsafe_route written with a network but no mask, a dropped element, or corruption.
Common situations: Custom automation editing Subnets for unsafe_routes; a marshaling bug in generated tooling; truncated certificate data; hand-built test certificates.
Related errors
- encoded IPs 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/74389a5e46f1e98a.
Report an issue: GitHub.