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
- Verify the packet slice starts at the IP header (offset 0)
- Check minimum packet length (>=1 byte) and version nibble before writing
- Log/inspect the first byte to find who produced the malformed packet
- 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
- Slice packets at offset 0 of the IP header
- Sanity-check the version nibble before handing to tun.Write
- Fix upstream framing/decryption that emits non-IP bytes
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
- newTunFromFd not supported in openbsd
- a device name in the format of /dev/tunN must be specified
- error closing tun file: %w
- failed to set tun address %s: %s
- failed to set route for vpn network %v: %w
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/e404f6aab90c0fbb.
Report an issue: GitHub.