{"record":{"id":"18f1b408a361508c","repo":"slackhq/nebula","slug":"unable-to-determine-ip-version-from-packet-18f1b4","errorCode":null,"errorMessage":"unable to determine IP version from packet","messagePattern":"unable to determine IP version from packet","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tun_netbsd.go","lineNumber":205,"sourceCode":"\t\treturn bytesRead - 4, nil\n\t}\n}\n\n// Write is only valid for single threaded use\nfunc (t *tun) Write(from []byte) (int, error) {\n\tif len(from) <= 1 {\n\t\treturn 0, syscall.EIO\n\t}\n\n\tipVer := from[0] >> 4\n\tvar head [4]byte\n\t// first 4 bytes is protocol family, in network byte order\n\tif ipVer == 4 {\n\t\thead[3] = syscall.AF_INET\n\t} else if ipVer == 6 {\n\t\thead[3] = syscall.AF_INET6\n\t} else {\n\t\treturn 0, fmt.Errorf(\"unable to determine IP version from packet\")\n\t}\n\n\trc, err := t.f.SyscallConn()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tvar errno syscall.Errno\n\tvar n uintptr\n\terr = rc.Write(func(fd uintptr) bool {\n\t\tiovecs := []syscall.Iovec{\n\t\t\t{&head[0], 4},\n\t\t\t{&from[0], uint64(len(from))},\n\t\t}\n\n\t\tn, _, errno = syscall.Syscall(syscall.SYS_WRITEV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))\n\t\t// According to NetBSD documentation for TUN, writes will only return errors in which\n\t\t// this packet will never be delivered so just go on living life.","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tun_netbsd.go#L187-L223","documentation":"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.","triggerScenarios":"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.","commonSituations":"Writing raw Ethernet frames instead of IP packets; slicing bugs that pass header-less data; corruption or misaligned reads elsewhere in the pipeline.","solutions":["Only write well-formed IPv4/IPv6 packets to the tun (check buf[0]>>4 is 4 or 6)","Verify you are not passing Ethernet-encapsulated frames (strip the L2 header first)","Check buffer offsets/slicing so the IP packet starts at index 0"],"exampleFix":"// before\ntun.Write(frame) // frame is an Ethernet frame\n// after\nif len(frame) > 0 && (frame[0]>>4 == 4 || frame[0]>>4 == 6) {\n    tun.Write(frame[14:]) // strip Ethernet header, write IP packet\n}","handlingStrategy":"validation","validationCode":"func isIPPacket(b []byte) bool {\n    if len(b) < 1 { return false }\n    v := b[0] >> 4\n    return v == 4 || v == 6\n}\nif !isIPPacket(pkt) { skip or log }","typeGuard":"func isIPPacket(b []byte) bool {\n    return len(b) >= 1 && (b[0]>>4 == 4 || b[0]>>4 == 6)\n}","tryCatchPattern":null,"preventionTips":["Never write Ethernet/L2 frames to a tun device","Validate the IP version nibble before every Write","Guard against zero-length buffers upstream"],"tags":["netbsd","tun","write","packet-format"],"backgroundTag":"invalid-ip-packet","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"}