slackhq/nebula · error

unable to determine IP version from packet

Error message

unable to determine IP version from packet

What it means

NetBSD tun devices in 'link-layer' mode require each outgoing packet to be prefixed with a 4-byte header containing the address family. Write() inspects the first byte(s) to determine IPv4 vs IPv6 and writes AF_INET/AF_INET6 into the header; if the payload does not look like an IP packet (version nibble neither 4 nor 6), it refuses with this error.

Source

Thrown at overlay/tun_netbsd.go:205

		return bytesRead - 4, nil
	}
}

// Write is only valid for single threaded use
func (t *tun) Write(from []byte) (int, error) {
	if len(from) <= 1 {
		return 0, syscall.EIO
	}

	ipVer := from[0] >> 4
	var head [4]byte
	// first 4 bytes is protocol family, in network byte order
	if ipVer == 4 {
		head[3] = syscall.AF_INET
	} else if ipVer == 6 {
		head[3] = syscall.AF_INET6
	} else {
		return 0, fmt.Errorf("unable to determine IP version from packet")
	}

	rc, err := t.f.SyscallConn()
	if err != nil {
		return 0, err
	}

	var errno syscall.Errno
	var n uintptr
	err = rc.Write(func(fd uintptr) bool {
		iovecs := []syscall.Iovec{
			{&head[0], 4},
			{&from[0], uint64(len(from))},
		}

		n, _, errno = syscall.Syscall(syscall.SYS_WRITEV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
		// According to NetBSD documentation for TUN, writes will only return errors in which
		// this packet will never be delivered so just go on living life.

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Only write well-formed IPv4/IPv6 packets to the tun (check buf[0]>>4 is 4 or 6)
  2. Verify you are not passing Ethernet-encapsulated frames (strip the L2 header first)
  3. Check buffer offsets/slicing so the IP packet starts at index 0

Example fix

// before
tun.Write(frame) // frame is an Ethernet frame
// after
if len(frame) > 0 && (frame[0]>>4 == 4 || frame[0]>>4 == 6) {
    tun.Write(frame[14:]) // strip Ethernet header, write IP packet
}
Defensive patterns

Strategy: validation

Validate before calling

func isIPPacket(b []byte) bool {
    if len(b) < 1 { return false }
    v := b[0] >> 4
    return v == 4 || v == 6
}
if !isIPPacket(pkt) { skip or log }

Type guard

func isIPPacket(b []byte) bool {
    return len(b) >= 1 && (b[0]>>4 == 4 || b[0]>>4 == 6)
}

Prevention

When it happens

Trigger: Write() is called with a buffer whose first nibble is not 4 or 6 — e.g. an empty buffer, a truncated/malformed packet, a non-IP frame (ARP, Ethernet header), or data starting at the wrong offset.

Common situations: Writing raw Ethernet frames instead of IP packets; slicing bugs that pass header-less data; corruption or misaligned reads elsewhere in the pipeline.

Related errors


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