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

  1. Ensure Details.Ips always contains ip,mask pairs (even length) before marshaling
  2. Regenerate or re-issue the certificate with the library's standard marshaling path
  3. Check any custom tooling that manipulates the Ips array for off-by-one/dropped-element bugs
  4. 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

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


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/b1e917076e0180ff. Report an issue: GitHub.