netbirdio/netbird · error

serialize layers: %w

Error message

serialize layers: %w

What it means

Returned by SrcFaker.SendPkg when gopacket.SerializeLayers fails to serialize the IP header + UDP header + payload into the reuse buffer with ComputeChecksums and FixLengths enabled. gopacket errors here when a layer is internally inconsistent - most commonly the UDP length field overflowing 16 bits (payload too large for one UDP datagram) or the fixed-size IPv4 header receiving invalid values. The prebuilt headers come from NewSrcFaker's prepareHeaders, so in practice the variable is the payload size.

Source

Thrown at client/iface/wgproxy/udp/rawsocket.go:92

	return f, nil
}

func (f *SrcFaker) Close() error {
	return f.rawSocket.Close()
}

func (f *SrcFaker) SendPkg(data []byte) (int, error) {
	defer func() {
		if err := f.layerBuffer.Clear(); err != nil {
			log.Errorf("failed to clear layer buffer: %s", err)
		}
	}()

	payload := gopacket.Payload(data)

	err := gopacket.SerializeLayers(f.layerBuffer, serializeOpts, f.ipH, f.udpH, payload)
	if err != nil {
		return 0, fmt.Errorf("serialize layers: %w", err)
	}
	n, err := f.rawSocket.WriteTo(f.layerBuffer.Bytes(), f.localHostAddr)
	if err != nil {
		return 0, fmt.Errorf("write to raw conn: %w", err)
	}
	return n, nil
}

func prepareHeaders(dstPort int, srcAddr *net.UDPAddr) (gopacket.SerializableLayer, gopacket.SerializableLayer, error) {
	var ipH gopacket.SerializableLayer
	var networkLayer gopacket.NetworkLayer

	// Check if source IP is IPv4 or IPv6
	if srcAddr.IP.To4() != nil {
		// IPv4
		ipv4 := &layers.IPv4{
			DstIP:    localHostNetIPAddrV4.IP,
			SrcIP:    srcAddr.IP,

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Check the interface MTU: keep it in the normal 1280-1500 (or at most ~9000 jumbo) range - `ip link show wt0`
  2. Inspect the wrapped gopacket error to see which layer rejected the write
  3. Restart the agent after fixing MTU so proxies rebuild with a sane buffer size (mtu+bufsize.WGBufferOverhead)
Defensive patterns

Strategy: validation

Validate before calling

// cap payloads to what one UDP datagram can carry before serializing
const maxUDPPayload = 65535 - 8 - 20 // v4 worst case

func fitForDatagram(data []byte) ([]byte, bool) {
    if len(data) <= maxUDPPayload {
        return data, true
    }
    return nil, false
}

Try / catch

if _, err := f.SendPkg(data); err != nil {
    if strings.Contains(err.Error(), "serialize layers") {
        log.Errorf("packet of %d bytes exceeds UDP datagram limits; check interface MTU", len(data))
        return // drop - cannot be sent as one datagram
    }
    return err
}

Prevention

When it happens

Trigger: A proxied packet larger than what a UDP datagram can express (data > ~65507 bytes after headers), which requires an oversized interface MTU (e.g. jumbo/UDP GRO aggregation bugs feeding SendPkg buffers beyond 64KB); or corrupted header state after the buffer was not cleared (the deferred layerBuffer.Clear guards that).

Common situations: MTU misconfiguration on the NetBird interface (values near or above 65535); virtualization/GRO delivering aggregated super-packets; memory corruption. Rare in the field.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/4a357f4acc4010fd. Report an issue: GitHub.