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

  1. Make every subnet a network,mask pair (even-length Subnets array) before marshaling
  2. Re-issue or regenerate the certificate using the library's marshaling functions
  3. Fix the tooling/script that mutates the Subnets array
  4. 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

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


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