slackhq/nebula · error
bad IPv4 IHL: %d
Error message
bad IPv4 IHL: %d
What it means
baseIPv4HdrSum folds the IPv4 header checksum over fields that stay constant across TCP/UDP segments. Before doing so it derives the Internet Header Length (IHL) from the first byte (low nibble * 4) and validates it against ipv4HeaderMinLen (20) and csumStart (the L3 header length). If IHL is out of that range the packet is malformed, so it refuses to compute a bogus checksum and returns this error.
Source
Thrown at overlay/tio/virtio/segment_linux.go:183
return n
}
// basePseudoSum folds the part of the L4 pseudo-header sum that is identical
// for every segment: the source and destination addresses plus the protocol
// number. The per-segment L4 length is added by the caller inside the loop.
func basePseudoSum(pkt []byte, isV4 bool, proto uint32) uint32 {
if isV4 {
return uint32(checksum.Checksum(pkt[ipv4SrcOff:ipv4AddrsEnd], 0)) + proto
}
return uint32(checksum.Checksum(pkt[ipv6SrcOff:ipv6AddrsEnd], 0)) + proto
}
// baseIPv4HdrSum folds the IPv4 header checksum over the fields that stay constant across segments.
// csumStart is the L3 header length, which bounds a valid IHL.
func baseIPv4HdrSum(pkt []byte, csumStart int) (uint32, error) {
ihl := int(pkt[0]&0x0f) * 4
if ihl < ipv4HeaderMinLen || ihl > csumStart {
return 0, fmt.Errorf("bad IPv4 IHL: %d", ihl)
}
// total_len, the ID, and the checksum field itself are excluded: all three are rewritten per segment.
sum := uint32(checksum.Checksum(pkt[:ihl], 0))
sum += uint32(^binary.BigEndian.Uint16(pkt[ipv4TotalLenOff : ipv4TotalLenOff+2]))
sum += uint32(^binary.BigEndian.Uint16(pkt[ipv4ChecksumOff : ipv4ChecksumOff+2]))
sum += uint32(^binary.BigEndian.Uint16(pkt[ipv4IDOff : ipv4IDOff+2]))
sum = (sum & 0xffff) + (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16)
return sum, nil
}
// baseTCPHdrSum folds the TCP header checksum over everything the segment loop does not rewrite
func baseTCPHdrSum(pkt []byte, csumStart, headerLen int) uint32 {
seq := binary.BigEndian.Uint32(pkt[csumStart+tcpSeqOff : csumStart+tcpSeqOff+4])
flags := uint16(pkt[csumStart+tcpFlagsOff])
sum := uint32(checksum.Checksum(pkt[csumStart:headerLen], 0))
sum += uint32(^uint16(seq >> 16))View on GitHub (pinned to dd8f660c0a)
Solutions
- Verify the buffer starts at the IPv4 header and that pkt[0]>>4 == 4 before segmenting.
- Check the csumStart/hdrLen values you pass to SegmentTCP/SegmentUDP: csumStart must be >= the actual IHL-derived header length.
- Dump the first 20 bytes of the packet; if the low nibble of byte 0 is < 5 the packet is truncated or misaligned — fix the upstream frame source (tap/virtio queue) instead of the library call.
- Ensure you pass the whole superpacket (starting at L3), not a slice that skips leading bytes.
Example fix
// before: csumStart computed from a fixed guess
seg.SegmentTCP(pkt, hdrLen, 14, gsoSize, yield) // 14 assumed ethernet+ VLAN mismatch
// after: derive csumStart from the actual L3 header
if pkt[0]>>4 != 4 { return errors.New("not IPv4") }
csumStart := int(pkt[0]&0x0f) * 4
seg.SegmentTCP(pkt, hdrLen, uint16(csumStart), gsoSize, yield) Defensive patterns
Strategy: validation
Validate before calling
func validIPv4(pkt []byte, csumStart int) bool {
if len(pkt) < 20 || pkt[0]>>4 != 4 { return false }
ihl := int(pkt[0]&0x0f) * 4
return ihl >= 20 && ihl <= csumStart
}
// if !validIPv4(pkt, csumStart) { skip/error before calling SegmentTCP/SegmentUDP } Type guard
func isIPv4(pkt []byte) bool { return len(pkt) >= 20 && pkt[0]>>4 == 4 } Try / catch
segs, err := segmenter.SegmentTCP(pkt, hdrLen, csumStart, gsoSize, yield)
if err != nil {
var badIHL bool = strings.Contains(err.Error(), "bad IPv4 IHL")
if badIHL { log.Warn("dropping malformed IPv4 frame"); return nil /* skip packet */ }
return err
} Prevention
- Always pass buffers that start exactly at the IPv4 header, not at the link-layer header.
- Check pkt[0]>>4 == 4 and IHL in [5, csumStart/4] before segmentation.
- Keep csumStart derived from the same header parse as hdrLen so they stay consistent.
- Drop truncated frames (len(pkt) < ihl) upstream of the segmenter.
When it happens
Trigger: Calling SegmentTCP or SegmentUDP (directly or via SegmentSuperpacket/collectTCP/collectUDP) on a buffer whose first byte's low nibble encodes IHL < 5 (i.e. header < 20 bytes) or an IHL larger than the supplied csumStart value — e.g. the packet is not actually an IPv4 header, the buffer is offset/misaligned, or the caller passed a csumStart smaller than the real header length.
Common situations: Packets captured or crafted with truncated/corrupt L3 headers; a virtio/netmap header (hdrLenU/csumStartU) that disagrees with the actual packet bytes; feeding a non-IPv4 frame (e.g. ARP, or an IPv6 packet misdetected) into the IPv4 checksum path; off-by-N offsets after manual packet parsing.
Related errors
- ErrIPv6CouldNotFindPayload
- ErrBadDetailsVpnAddr
- ErrPacketTooShort
- ErrUnknownIPVersion
- ErrIPv4InvalidHeaderLength
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/7651e9ecd8225f62.
Report an issue: GitHub.