slackhq/nebula · error

WriteBatch: len(bufs)=%d != len(addrs)=%d

Error message

WriteBatch: len(bufs)=%d != len(addrs)=%d

What it means

batchWriter.WriteBatch requires the bufs (payloads) and addrs (destinations) slices to have the same length, because each buffer maps to exactly one sendmmsg destination. The library rejects mismatched inputs up front rather than silently dropping packets.

Source

Thrown at udp/udp_linux_writebatch.go:196

	minor, _ = strconv.Atoi(mp)
	return
}

// WriteBatch sends bufs via sendmmsg(2), coalescing runs into UDP_SEGMENT
// entries, so one syscall can mix GSO superpackets and plain datagrams.
// Without GSO support every packet is its own entry.
// Callers shall deliver same-destination packets contiguously and in counter order
//
// Batches larger than the scratch take one sendmmsg per chunk.
// A partial success resumes the same prepared entries at the first unsent entry.
// A zero-sent error means the kernel rejected the first remaining entry:
// its packets are dropped and the rest of the chunk resumes in place.
//
// Returns the number of packets sent. An error means the call itself failed.
// A short count means some destinations were undeliverable.
func (w *batchWriter) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, error) {
	if len(bufs) != len(addrs) {
		return 0, fmt.Errorf("WriteBatch: len(bufs)=%d != len(addrs)=%d", len(bufs), len(addrs))
	}

	// A destination the kernel rejects results in us dropping that entry (one packet, or one same-destination GSO run).
	// We count what actually made it out rather than returning an error.
	written := 0

	i := 0
	for i < len(bufs) {
		entry := 0
		iovIdx := 0
		for entry < len(w.msgs) && i < len(bufs) {
			iovBudget := len(w.iovs) - iovIdx
			if iovBudget < 1 {
				break
			}
			runLen, segSize := w.planRun(bufs, addrs, i, iovBudget)
			if runLen == 0 {
				break

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Fix the caller so every buffer has exactly one corresponding destination address
  2. If intentionally dropping packets, slice both bufs and addrs to the same length before calling
  3. Add a debug assertion/log in the caller when the slices diverge to catch the divergence early

Example fix

// before
w.WriteBatch(bufs[:n], addrs[:m]) // n != m
// after
n := min(n, m)
w.WriteBatch(bufs[:n], addrs[:n])
Defensive patterns

Strategy: validation

Validate before calling

func safeWriteBatch(w *udp.StdConn, bufs [][]byte, addrs []netip.AddrPort) (int, error) {
    n := min(len(bufs), len(addrs))
    return w.WriteBatch(bufs[:n], addrs[:n])
}

Try / catch

n, err := w.WriteBatch(bufs, addrs)
if err != nil {
    if strings.Contains(err.Error(), "len(bufs)") {
        log.Error("caller bug: bufs/addrs length mismatch", "bufs", len(bufs), "addrs", len(addrs))
    }
    return n, err
}

Prevention

When it happens

Trigger: Calling WriteBatch (via StdConn.WriteTo/udpsend batching path) with len(bufs) != len(addrs), typically from a custom caller or a bug in caller-side batching that appends to one slice but not the other.

Common situations: Custom integrations driving the udp package directly; off-by-one when trimming failed entries from addrs but not bufs; refactoring the emit loop in nebula's interface code.

Related errors


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