slackhq/nebula · error

unable to determine IP version from packet

Error message

unable to determine IP version from packet

What it means

Write() inspects the first byte of the packet to determine IPv4 vs IPv6 so it can prepend the correct OpenBSD tun address family header (AF_INET/AF_INET6 in the 4-byte head). If the IP version byte is neither 4 nor 6, the packet is not a valid IP packet and the write is rejected. This guards against writing garbage or truncated frames to the tun device.

Source

Thrown at overlay/tun_openbsd.go:190

	}
	return n - 4, nil
}

// Write pushes one IP packet onto the tun device.
func (t *tun) Write(from []byte) (int, error) {
	if len(from) == 0 {
		return 0, syscall.EIO
	}

	ipVer := from[0] >> 4
	var head [4]byte
	switch ipVer {
	case 4:
		head[3] = syscall.AF_INET
	case 6:
		head[3] = syscall.AF_INET6
	default:
		return 0, fmt.Errorf("unable to determine IP version from packet")
	}

	// Grab rc as a local so the compiler can devirtualize the call and keep the closure on the stack.
	rc, err := t.f.SyscallConn()
	if err != nil {
		return 0, err
	}

	var n int
	var callErr error
	err = rc.Write(func(fd uintptr) bool {
		iovecs := []unix.Iovec{
			{Base: &head[0], Len: 4},
			{Base: &from[0], Len: uint64(len(from))},
		}
		n, callErr = tunWritev(int(fd), iovecs)
		// Type-assert to syscall.Errno so the EAGAIN/EWOULDBLOCK/EINTR check doesn't box the errno
		// constants into error interfaces on every call.

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Verify the packet slice starts at the IP header (offset 0)
  2. Check minimum packet length (>=1 byte) and version nibble before writing
  3. Log/inspect the first byte to find who produced the malformed packet
  4. Fix upstream framing so only complete IP packets reach tun.Write

Example fix

// before
b := buf[off:] // off misaligned, first byte not IP version
t.Write(b)
// after
if len(buf) > 0 && (buf[0]>>4 == 4 || buf[0]>>4 == 6) {
    t.Write(buf)
}
Defensive patterns

Strategy: validation

Validate before calling

func isIPPacket(b []byte) bool {
    return len(b) >= 20 && (b[0]>>4 == 4 || b[0]>>4 == 6)
}
if !isIPPacket(pkt) {
    return fmt.Errorf("refusing to write non-IP packet, first byte=0x%02x", pkt[0])
}

Try / catch

n, err := t.Write(pkt)
if err != nil && strings.Contains(err.Error(), "unable to determine IP version") {
    log.Warn("dropping malformed packet", "firstByte", pkt[0])
    return // drop, don't crash
}

Prevention

When it happens

Trigger: Calling t.Write() with a buffer whose first byte is not 0x4x or 0x6x — e.g. an empty/truncated packet, non-IP frames, or a misaligned read offset into a receive buffer.

Common situations: Custom handoff code writing raw non-IP payloads into the tun; packet parsing bugs that pass wrong offsets; corrupted buffers from upstream decryption failures.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/e404f6aab90c0fbb. Report an issue: GitHub.