netbirdio/netbird · error

set network layer for TCP checksum: %w

Error message

set network layer for TCP checksum: %w

What it means

gopacket's TCP layer SetNetworkLayerForChecksum returns an error when the supplied network layer's address lengths do not match what the pseudo-header computation expects (a 4-byte expectation met with 16-byte addresses or vice versa). The tracer calls it so SerializeLayers with ComputeChecksums can build a correct TCP pseudo-header. In this code path the IP layer was just built from the same SrcIP/DstIP pair, so the guard in buildIPLayer normally prevents a family mismatch from reaching here; a hit indicates the layers were assembled inconsistently.

Source

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

		return nil, fmt.Errorf("unsupported protocol: %s", p.Protocol)
	}
}

func (p *PacketBuilder) buildTCPLayer(ipLayer gopacket.SerializableLayer) ([]gopacket.SerializableLayer, error) {
	tcp := &layers.TCP{
		SrcPort: layers.TCPPort(p.SrcPort),
		DstPort: layers.TCPPort(p.DstPort),
		Window:  65535,
		SYN:     p.TCPState != nil && p.TCPState.SYN,
		ACK:     p.TCPState != nil && p.TCPState.ACK,
		FIN:     p.TCPState != nil && p.TCPState.FIN,
		RST:     p.TCPState != nil && p.TCPState.RST,
		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
}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Keep constructing the transport layer strictly from the ipLayer returned by buildIPLayer in the same call chain
  2. Re-run the same-family validation from buildIPLayer (SrcIP.Is4() == DstIP.Is4()) before assembling layers
  3. Pin and review the gopacket version after upgrades; the error text from gopacket names the size mismatch, which identifies the offending layer
  4. Add a unit test that builds one TCP trace per family to catch regressions early
Defensive patterns

Strategy: try-catch

Validate before calling

// keep families consistent before building
if b.SrcIP.Is4() != b.DstIP.Is4() {
    return errors.New("refusing to build cross-family packet")
}

Type guard

func (p *PacketBuilder) layersConsistent() bool {
    return p.SrcIP.IsValid() && p.DstIP.IsValid() && p.SrcIP.Is4() == p.DstIP.Is4()
}

Try / catch

if _, err := b.Build(); err != nil {
    if strings.Contains(err.Error(), "checksum") {
        // layer pairing broke: rebuild from a fresh Build() call, never reuse layers
    }
    return err
}

Prevention

When it happens

Trigger: Passing an IPv6 gopacket.NetworkLayer to a TCP layer whose addresses were populated from IPv4 data (or the reverse), typically only after refactoring buildIPLayer's output or reordering layer construction; also possible with a custom SerializableLayer masquerading as a NetworkLayer with wrong address sizes.

Common situations: Extending the tracer to support new protocols or tunneling and constructing the IP layer separately from the transport layer; version drift in gopacket changing what SetNetworkLayerForChecksum validates.

Related errors


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