Add openvixdisklib as an open NBD replacement for VMware VDDK.

VDDK is no longer publicly distributed, so this library reverse-engineers
the vSphere NFC path and exposes ConnectEx, Open, Read, and Write without
the proprietary SDK.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Lucian Petrut
2026-09-07 11:41:01 +00:00
co-authored by Cursor
commit 4ddf0c015d
31 changed files with 4089 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# VDDK NFC disk write
This document records how `NfcDisk.write` in
`openvixdisklib/nfc_open.py` implements `VixDiskLib_Write` over NFC AIO.
The request layout matches the captured `VixDiskLib_Read` IO message in
`docs/nfc_read.md`. Open flags and the IO direction field were taken
from a `strace` of VDDK 8 writing one sector to a temporary 10 GiB
disk (`docs/reverse_engineering_procedure.md`).
The public `VixDiskLib_Write` prototype is in `.vddk/vixDiskLib.h`:
```
VixError VixDiskLib_Write(VixDiskLibHandle diskHandle,
VixDiskLibSectorType startSector,
VixDiskLibSectorType numSectors,
const uint8 *writeBuffer);
```
`ConnectEx(..., Bool readOnly, ...)` with `readOnly=FALSE` and `Open`
without `VIXDISKLIB_FLAG_OPEN_READ_ONLY` (that flag is `1 << 2` in the
same header) is what produces the writable NFC open below. The ticket
must be `NfcRandomAccessOpenDisk` (`docs/nfc_auth.md`); flags `0x1a`
on a `NfcGetVmFiles` ticket are rejected as `VIX_E_FILE_READ_ONLY`.
## Mapping from VDDK
Units on the wire are **bytes**, as for reads:
```
offset = startSector * sectorSize
length = numSectors * sectorSize
```
| VDDK call | Wire effect |
| ------------------------------------------- | ------------------------------------------------ |
| Open without `VIXDISKLIB_FLAG_OPEN_READ_ONLY` | `OPEN_FILE` flags `0x1a` |
| Open with `VIXDISKLIB_FLAG_OPEN_READ_ONLY` | `OPEN_FILE` flags `0x1e` (read-only) |
| `VixDiskLib_Write(h, start, n, buf)` | IO opcode `0`, then `n * 512` data bytes |
| `VixDiskLib_Read(h, start, n, buf)` | IO opcode `1` |
`0x1e` vs `0x1a` is bit `0x04`, the same value as
`VIXDISKLIB_FLAG_OPEN_READ_ONLY`. Writable opens clear that bit.
VDDK also issues several `DDB_GET` queries and a type-10
`GET_FILE_INFO` (`longContentID`) before the first write. They are not
required to write or read sectors.
## Request (44 bytes + data)
Little-endian, after the usual 16-byte AIO header
(`magic 0xA100DA7A`, type 7, size 44, monotonic `opId`):
| Offset | Type | `Write(start, n)` |
| ------ | -------- | -------------------------------------------------- |
| 0 | `uint64` | File handle from `OPEN_FILE` |
| 8 | `uint64` | `0` (`NFC_AIO_IO_WRITE`; read uses `1`) |
| 16 | `uint64` | Byte offset |
| 24 | `uint64` | Byte length |
| 32 | `uint32` | Byte length (same value) |
| 36 | `uint32` | Byte length (same value) |
| 40 | `uint32` | `0` |
Sector bytes follow the 44-byte payload and are **not** counted in AIO
`size`. VDDK sends header + payload + data in one `write()`. The
replacement may split that into two `sendall`s; TCP does not care.
The server replies with a type-7 header and a 44-byte payload for that
`opId`. There is no extra data on the write reply (unlike reads).
A 1-sector VDDK write was 572 bytes on the wire: 16 + 44 + 512.
## Client-side split
`NfcAioInitSession` advertises a 64 KiB buffer. VDDK splits writes
larger than that into 64 KiB chunks (VDDK programming guide). The
Python client does the same: several IO requests of at most
`NFC_AIO_BUFFER_SIZE` bytes, each with its own `opId`.
## Python replacement
`NfcDisk.write(start_sector, num_sectors, data)` in
`openvixdisklib/nfc_open.py`. `open_disk(..., read_only=False)` selects
flags `0x1a`. The drop-in handle exposes the same shape as VDDK:
`connect(read_only=False)`, `open` without
`VIXDISKLIB_FLAG_OPEN_READ_ONLY`, then `write`.
Integration tests create an empty 10 GiB disk, write known patterns,
and read them back (`tests/integration/test_nfc_read_write.py`,
`tests/integration/test_openvixdisklib.py`). Cross-check tests write
with VDDK and with the replacement and read with both
(`tests/integration/test_crosscheck.py`).