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
- Keep constructing the transport layer strictly from the ipLayer returned by buildIPLayer in the same call chain
- Re-run the same-family validation from buildIPLayer (SrcIP.Is4() == DstIP.Is4()) before assembling layers
- Pin and review the gopacket version after upgrades; the error text from gopacket names the size mismatch, which identifies the offending layer
- 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
- Never assemble the TCP layer separately from the IP layer produced in the same Build
- Lock gopacket version and re-run tracer tests on upgrade
- Add TCP v4/v6 golden-packet tests so checksum regressions are caught in CI
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
- set network layer for UDP checksum: %w
- mixed address families: src=%s dst=%s
- serialize packet: %w
- build packet: %w
- set network layer for checksum: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/ec0c6d445ef5294d.
Report an issue: GitHub.