{"record":{"id":"7651e9ecd8225f62","repo":"slackhq/nebula","slug":"bad-ipv4-ihl-d","errorCode":null,"errorMessage":"bad IPv4 IHL: %d","messagePattern":"bad IPv4 IHL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tio/virtio/segment_linux.go","lineNumber":183,"sourceCode":"\treturn n\n}\n\n// basePseudoSum folds the part of the L4 pseudo-header sum that is identical\n// for every segment: the source and destination addresses plus the protocol\n// number. The per-segment L4 length is added by the caller inside the loop.\nfunc basePseudoSum(pkt []byte, isV4 bool, proto uint32) uint32 {\n\tif isV4 {\n\t\treturn uint32(checksum.Checksum(pkt[ipv4SrcOff:ipv4AddrsEnd], 0)) + proto\n\t}\n\treturn uint32(checksum.Checksum(pkt[ipv6SrcOff:ipv6AddrsEnd], 0)) + proto\n}\n\n// baseIPv4HdrSum folds the IPv4 header checksum over the fields that stay constant across segments.\n// csumStart is the L3 header length, which bounds a valid IHL.\nfunc baseIPv4HdrSum(pkt []byte, csumStart int) (uint32, error) {\n\tihl := int(pkt[0]&0x0f) * 4\n\tif ihl < ipv4HeaderMinLen || ihl > csumStart {\n\t\treturn 0, fmt.Errorf(\"bad IPv4 IHL: %d\", ihl)\n\t}\n\t// total_len, the ID, and the checksum field itself are excluded: all three are rewritten per segment.\n\tsum := uint32(checksum.Checksum(pkt[:ihl], 0))\n\tsum += uint32(^binary.BigEndian.Uint16(pkt[ipv4TotalLenOff : ipv4TotalLenOff+2]))\n\tsum += uint32(^binary.BigEndian.Uint16(pkt[ipv4ChecksumOff : ipv4ChecksumOff+2]))\n\tsum += uint32(^binary.BigEndian.Uint16(pkt[ipv4IDOff : ipv4IDOff+2]))\n\tsum = (sum & 0xffff) + (sum >> 16)\n\tsum = (sum & 0xffff) + (sum >> 16)\n\treturn sum, nil\n}\n\n// baseTCPHdrSum folds the TCP header checksum over everything the segment loop does not rewrite\nfunc baseTCPHdrSum(pkt []byte, csumStart, headerLen int) uint32 {\n\tseq := binary.BigEndian.Uint32(pkt[csumStart+tcpSeqOff : csumStart+tcpSeqOff+4])\n\tflags := uint16(pkt[csumStart+tcpFlagsOff])\n\n\tsum := uint32(checksum.Checksum(pkt[csumStart:headerLen], 0))\n\tsum += uint32(^uint16(seq >> 16))","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tio/virtio/segment_linux.go#L165-L201","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: csumStart computed from a fixed guess\nseg.SegmentTCP(pkt, hdrLen, 14, gsoSize, yield) // 14 assumed ethernet+ VLAN mismatch\n// after: derive csumStart from the actual L3 header\nif pkt[0]>>4 != 4 { return errors.New(\"not IPv4\") }\ncsumStart := int(pkt[0]&0x0f) * 4\nseg.SegmentTCP(pkt, hdrLen, uint16(csumStart), gsoSize, yield)","handlingStrategy":"validation","validationCode":"func validIPv4(pkt []byte, csumStart int) bool {\n    if len(pkt) < 20 || pkt[0]>>4 != 4 { return false }\n    ihl := int(pkt[0]&0x0f) * 4\n    return ihl >= 20 && ihl <= csumStart\n}\n// if !validIPv4(pkt, csumStart) { skip/error before calling SegmentTCP/SegmentUDP }","typeGuard":"func isIPv4(pkt []byte) bool { return len(pkt) >= 20 && pkt[0]>>4 == 4 }","tryCatchPattern":"segs, err := segmenter.SegmentTCP(pkt, hdrLen, csumStart, gsoSize, yield)\nif err != nil {\n    var badIHL bool = strings.Contains(err.Error(), \"bad IPv4 IHL\")\n    if badIHL { log.Warn(\"dropping malformed IPv4 frame\"); return nil /* skip packet */ }\n    return err\n}","preventionTips":["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."],"tags":["network","ipv4","checksum","packet-parsing"],"backgroundTag":"malformed-ipv4-header","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}