netbirdio/netbird · error

set network layer for UDP checksum: %w

Error message

set network layer for UDP checksum: %w

What it means

Same gopacket constraint as the TCP variant: UDP's SetNetworkLayerForChecksum errors when the network layer address size does not match the address family needed for the UDP pseudo-header (4 bytes for IPv4, 16 for IPv6). The tracer sets it so SerializeOptions{ComputeChecksums:true} produces a valid UDP checksum, which matters because many stacks silently drop UDP with a zero/computed-wrong checksum over IPv6.

Source

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

		PSH:     p.TCPState != nil && p.TCPState.PSH,
		URG:     p.TCPState != nil && p.TCPState.URG,
	}
	if nl, ok := ipLayer.(gopacket.NetworkLayer); ok {
		if err := tcp.SetNetworkLayerForChecksum(nl); err != nil {
			return nil, fmt.Errorf("set network layer for TCP checksum: %w", err)
		}
	}
	return []gopacket.SerializableLayer{tcp}, nil
}

func (p *PacketBuilder) buildUDPLayer(ipLayer gopacket.SerializableLayer) ([]gopacket.SerializableLayer, error) {
	udp := &layers.UDP{
		SrcPort: layers.UDPPort(p.SrcPort),
		DstPort: layers.UDPPort(p.DstPort),
	}
	if nl, ok := ipLayer.(gopacket.NetworkLayer); ok {
		if err := udp.SetNetworkLayerForChecksum(nl); err != nil {
			return nil, fmt.Errorf("set network layer for UDP checksum: %w", err)
		}
	}
	return []gopacket.SerializableLayer{udp}, nil
}

func (p *PacketBuilder) buildICMPLayer(ipLayer gopacket.SerializableLayer) ([]gopacket.SerializableLayer, error) {
	if p.SrcIP.Is6() || p.DstIP.Is6() {
		icmp := &layers.ICMPv6{
			TypeCode: layers.CreateICMPv6TypeCode(p.ICMPType, p.ICMPCode),
		}
		if nl, ok := ipLayer.(gopacket.NetworkLayer); ok {
			_ = icmp.SetNetworkLayerForChecksum(nl)
		}
		if p.ICMPType == layers.ICMPv6TypeEchoRequest || p.ICMPType == layers.ICMPv6TypeEchoReply {
			echo := &layers.ICMPv6Echo{
				Identifier: 1,
				SeqNumber:  1,
			}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Always call buildUDPLayer with the exact ipLayer produced by buildIPLayer in the same Build invocation
  2. Validate SrcIP/DstIP family equality up front (the buildIPLayer guard) so mismatched input never reaches layer assembly
  3. Write a trace test covering UDP over both IPv4 and IPv6 to lock the behavior
  4. Check the gopacket error detail to see which side (4 vs 16 bytes) is inconsistent
Defensive patterns

Strategy: try-catch

Validate before calling

if b.SrcIP.Is4() != b.DstIP.Is4() || !b.SrcIP.IsValid() {
    return errors.New("addresses must be valid and same-family")
}

Type guard

func (p *PacketBuilder) udpBuildable() bool {
    return p.SrcIP.IsValid() && p.DstIP.IsValid() && p.SrcIP.Is4() == p.DstIP.Is4() &&
        (p.Protocol == fw.ProtocolUDP || p.Protocol == fw.ProtocolTCP || p.Protocol == fw.ProtocolICMP)
}

Try / catch

if _, err := b.Build(); err != nil {
    var se interface{ Error() string }
    _ = se
    if strings.Contains(err.Error(), "UDP checksum") {
        return fmt.Errorf("rebuild packet with matched families: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Handing the UDP builder an ipLayer whose family differs from the address data used for the ports/addresses, or invoking buildUDPLayer directly with a mismatched layer; realistically only after modifying the layer assembly order in the tracer.

Common situations: Custom diagnostics built on PacketBuilder that assemble layers manually; gopacket dependency upgrades altering NetworkLayer address handling.

Related errors


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