netbirdio/netbird · error

serialize packet: %w

Error message

serialize packet: %w

What it means

SerializeLayers with ComputeChecksums and FixLengths fails when the layer stack cannot be encoded into the serialize buffer: payload sizes that overflow the 16-bit length fields (UDP length or IPv4 total length beyond 65535), an uninitialized required layer (e.g. ICMPv6 without a network layer available for its checksum), or inconsistent layer state that FixLengths cannot repair. The tracer hits it while turning the assembled gopacket layers into raw bytes for injection into the trace path.

Source

Thrown at client/firewall/uspfilter/tracer.go:240

	}
	icmp := &layers.ICMPv4{
		TypeCode: layers.CreateICMPv4TypeCode(p.ICMPType, p.ICMPCode),
	}
	if p.ICMPType == layers.ICMPv4TypeEchoRequest || p.ICMPType == layers.ICMPv4TypeEchoReply {
		icmp.Id = uint16(1)
		icmp.Seq = uint16(1)
	}
	return []gopacket.SerializableLayer{icmp}, nil
}

func serializePacket(layers []gopacket.SerializableLayer) ([]byte, error) {
	buf := gopacket.NewSerializeBuffer()
	opts := gopacket.SerializeOptions{
		ComputeChecksums: true,
		FixLengths:       true,
	}
	if err := gopacket.SerializeLayers(buf, opts, layers...); err != nil {
		return nil, fmt.Errorf("serialize packet: %w", err)
	}
	return buf.Bytes(), nil
}

func getIPProtocolNumber(protocol fw.Protocol, isV6 bool) layers.IPProtocol {
	switch protocol {
	case fw.ProtocolTCP:
		return layers.IPProtocolTCP
	case fw.ProtocolUDP:
		return layers.IPProtocolUDP
	case fw.ProtocolICMP:
		if isV6 {
			return layers.IPProtocolICMPv6
		}
		return layers.IPProtocolICMPv4
	default:
		return 0
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Cap PayloadSize so that IP header + transport header + payload stays under 65535 (and under the path MTU for realistic traces)
  2. Ensure the ICMPv6 path always receives a real gopacket.NetworkLayer so its checksum prerequisite is satisfied before serialization
  3. Read the wrapped gopacket error text; it names the exact layer and constraint that failed
  4. Add a regression test per protocol (TCP/UDP/ICMP/v4/v6) covering minimum and maximum sane sizes

Example fix

// before
p.PayloadSize = 70000

// after
const maxTracePayload = 0xffff - 28 // IPv4 + UDP header worst case
if p.PayloadSize > maxTracePayload {
    p.PayloadSize = maxTracePayload
}
Defensive patterns

Strategy: validation

Validate before calling

const maxPayload = 0xffff - 28 // worst-case IPv4+UDP headers
if p.PayloadSize > maxPayload {
    p.PayloadSize = maxPayload
}
data, err := m.TracePacketFromBuilder(p)

Type guard

func sanePayloadSize(size int) bool {
    return size >= 0 && size <= 0xffff-28
}

Try / catch

if _, err := serializePacket(pktLayers); err != nil {
    if strings.Contains(err.Error(), "serialize packet") {
        // drop optional layers (payload) and retry once with a smaller trace packet
    }
    return err
}

Prevention

When it happens

Trigger: Setting PacketBuilder.PayloadSize large enough that IPv4 total length or the UDP length field overflows 16 bits; serializing ICMPv6 when the ipLayer type assertion to gopacket.NetworkLayer failed (its SetNetworkLayerForChecksum error is deliberately ignored in buildICMPLayer); building a layer combination the library cannot encode.

Common situations: Stress-testing the tracer with jumbo or synthetic payload sizes; tracing ICMPv6 after refactoring the IP layer construction; gopacket version changes introducing stricter length validation.

Related errors


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