Fix code formatting - tox -e fmt

This commit is contained in:
Lucian Petrut
2026-09-08 14:59:43 +00:00
parent ffce3a1cd6
commit e595dcd9e7
13 changed files with 615 additions and 492 deletions
+128 -81
View File
@@ -24,7 +24,7 @@ from typing import Optional
from pyVim.connect import Disconnect, SmartConnect
from pyVmomi import vim
from pyVmomi.VmomiSupport import CreateManagedType, F_OPTIONAL, GetVmodlType
from pyVmomi.VmomiSupport import F_OPTIONAL, CreateManagedType, GetVmodlType
NFC_SERVICE_MOID = "nfcService"
AUTHD_DEFAULT_PORT = 902
@@ -59,28 +59,62 @@ def _register_nfc_types() -> None:
"vim.version.version1",
[],
[
("getVmFiles", "NfcGetVmFiles", "vim.version.version1",
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),),
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"), None, None),
("randomAccessOpen", "NfcRandomAccessOpenDisk",
"vim.version.version1",
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
("diskDeviceKey", "int", "vim.version.version1", 0, None),
("hostForAccess", "vim.HostSystem", "vim.version.version1",
F_OPTIONAL, None),),
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"), None, None),
("randomAccessOpenReadonly", "NfcRandomAccessOpenReadonly",
"vim.version.version1",
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
("diskDeviceKey", "int", "vim.version.version1", 0, None),
("hostForAccess", "vim.HostSystem", "vim.version.version1",
F_OPTIONAL, None),),
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"), None, None),
("getServerNfcLibVersion", "NfcGetServerNfcLibVersion",
"vim.version.version1",
(("hostForAccess", "vim.HostSystem", "vim.version.version1",
0, None),),
(0, "int", "int"), None, None),
(
"getVmFiles",
"NfcGetVmFiles",
"vim.version.version1",
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),),
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"),
None,
None,
),
(
"randomAccessOpen",
"NfcRandomAccessOpenDisk",
"vim.version.version1",
(
("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
("diskDeviceKey", "int", "vim.version.version1", 0, None),
(
"hostForAccess",
"vim.HostSystem",
"vim.version.version1",
F_OPTIONAL,
None,
),
),
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"),
None,
None,
),
(
"randomAccessOpenReadonly",
"NfcRandomAccessOpenReadonly",
"vim.version.version1",
(
("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
("diskDeviceKey", "int", "vim.version.version1", 0, None),
(
"hostForAccess",
"vim.HostSystem",
"vim.version.version1",
F_OPTIONAL,
None,
),
),
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"),
None,
None,
),
(
"getServerNfcLibVersion",
"NfcGetServerNfcLibVersion",
"vim.version.version1",
(("hostForAccess", "vim.HostSystem", "vim.version.version1", 0, None),),
(0, "int", "int"),
None,
None,
),
],
)
_NFC_TYPES_REGISTERED = True
@@ -98,12 +132,13 @@ def nfc_service(si: vim.ServiceInstance) -> vim.NfcService:
def connect_vim(
host: str,
username: str,
password: str,
port: int = 443,
thumbprint: Optional[str] = None,
allow_untrusted: bool = False) -> vim.ServiceInstance:
host: str,
username: str,
password: str,
port: int = 443,
thumbprint: Optional[str] = None,
allow_untrusted: bool = False,
) -> vim.ServiceInstance:
"""Login to vCenter or ESXi using pyVim.connect.SmartConnect.
Args:
@@ -124,7 +159,8 @@ def connect_vim(
port=port,
thumbprint=thumbprint,
sslContext=ssl_context,
disableSslCertValidation=allow_untrusted)
disableSslCertValidation=allow_untrusted,
)
def _virtual_disk_key(vm: vim.VirtualMachine, disk_path: str) -> int:
@@ -144,17 +180,17 @@ def _virtual_disk_key(vm: vim.VirtualMachine, disk_path: str) -> int:
if getattr(backing, "fileName", None) == disk_path:
return device.key
backing = getattr(backing, "parent", None)
raise ValueError(
f"VMDK path {disk_path!r} is not attached to {vm._moId}")
raise ValueError(f"VMDK path {disk_path!r} is not attached to {vm._moId}")
def get_nfc_ticket(
si: vim.ServiceInstance,
vm: vim.VirtualMachine,
disk_device_key: Optional[int] = None,
host_for_access: Optional[vim.HostSystem] = None,
read_only: bool = True,
disk_path: Optional[str] = None) -> vim.HostServiceTicket:
si: vim.ServiceInstance,
vm: vim.VirtualMachine,
disk_device_key: Optional[int] = None,
host_for_access: Optional[vim.HostSystem] = None,
read_only: bool = True,
disk_path: Optional[str] = None,
) -> vim.HostServiceTicket:
"""Return a one-time NFC HostServiceTicket for ``vm``.
Matches VDDK: ``NfcGetVmFiles`` when only the VM is known (read-only),
@@ -174,14 +210,12 @@ def get_nfc_ticket(
return nfc.GetVmFiles(vm)
if disk_device_key is None:
if disk_path is None:
raise ValueError(
"writable NFC tickets need disk_path or disk_device_key")
raise ValueError("writable NFC tickets need disk_path or disk_device_key")
disk_device_key = _virtual_disk_key(vm, disk_path)
if host_for_access is None:
host_for_access = vm.runtime.host
if read_only:
return nfc.RandomAccessOpenReadonly(
vm, disk_device_key, host_for_access)
return nfc.RandomAccessOpenReadonly(vm, disk_device_key, host_for_access)
return nfc.RandomAccessOpen(vm, disk_device_key, host_for_access)
@@ -198,11 +232,12 @@ def _normalize_thumbprint(thumbprint: str) -> str:
def get_ssl_cert_thumbprint(
host: str,
port: int = 443,
digest_algorithm: str = "sha1",
ssl_context: Optional[ssl.SSLContext] = None,
timeout: float = 30.0) -> str:
host: str,
port: int = 443,
digest_algorithm: str = "sha1",
ssl_context: Optional[ssl.SSLContext] = None,
timeout: float = 30.0,
) -> str:
"""Return the TLS certificate thumbprint of ``host``:``port``.
Reads the peer certificate in DER form and hashes it with ``hashlib``.
@@ -223,14 +258,11 @@ def get_ssl_cert_thumbprint(
if ssl_context is None:
ssl_context = _ssl_client_context(verify=False)
with socket.create_connection((host, port), timeout=timeout) as sock:
with ssl_context.wrap_socket(
sock, server_hostname=host) as ssock:
with ssl_context.wrap_socket(sock, server_hostname=host) as ssock:
cert = ssock.getpeercert(binary_form=True)
if not cert:
raise ConnectionError(
f"no peer certificate from {host}:{port}")
return _format_thumbprint(
hashlib.new(digest_algorithm, cert).digest())
raise ConnectionError(f"no peer certificate from {host}:{port}")
return _format_thumbprint(hashlib.new(digest_algorithm, cert).digest())
def _readline(sock: socket.socket) -> str:
@@ -248,7 +280,7 @@ def _readline(sock: socket.socket) -> str:
def _expect_code(line: str, code: str, what: str) -> str:
if not line.startswith(code):
raise ConnectionError(f"authd {what} failed: {line}")
return line[len(code):].lstrip()
return line[len(code) :].lstrip()
def nfcssl_service_name(service: str) -> str:
@@ -266,10 +298,11 @@ def nfcssl_service_name(service: str) -> str:
def connect_authd(
ticket: vim.HostServiceTicket,
allow_untrusted: bool = False,
timeout: float = 30.0,
nfc_ssl: bool = True) -> ssl.SSLSocket:
ticket: vim.HostServiceTicket,
allow_untrusted: bool = False,
timeout: float = 30.0,
nfc_ssl: bool = True,
) -> ssl.SSLSocket:
"""Complete the ESXi authd handshake using an NFC HostServiceTicket.
Wire sequence captured from VDDK against authd on TCP 902:
@@ -310,10 +343,12 @@ def connect_authd(
if not allow_untrusted and ticket.sslThumbprint:
peer = _sha1_thumbprint(ssock.getpeercert(True))
if _normalize_thumbprint(peer) != _normalize_thumbprint(
ticket.sslThumbprint):
ticket.sslThumbprint
):
raise ConnectionError(
f"ESXi SSL thumbprint mismatch: got {peer}, "
f"expected {ticket.sslThumbprint}")
f"expected {ticket.sslThumbprint}"
)
ssock.sendall(f"SESSION {ticket.sessionId}\r\n".encode("ascii"))
# Trailing space is part of the BANNER command token used by authd.
@@ -338,11 +373,12 @@ class NfcAuthSession:
"""Authenticated VIM session plus an authd/NFC TLS socket."""
def __init__(
self,
si: vim.ServiceInstance,
ticket: vim.HostServiceTicket,
authd_sock: ssl.SSLSocket,
nfc_ssl: bool = True) -> None:
self,
si: vim.ServiceInstance,
ticket: vim.HostServiceTicket,
authd_sock: ssl.SSLSocket,
nfc_ssl: bool = True,
) -> None:
self.si = si
self.ticket = ticket
self.authd_sock = authd_sock
@@ -363,17 +399,18 @@ class NfcAuthSession:
def authenticate(
host: str,
username: str,
password: str,
vm_moref: str,
port: int = 443,
thumbprint: Optional[str] = None,
allow_untrusted: bool = False,
disk_device_key: Optional[int] = None,
disk_path: Optional[str] = None,
read_only: bool = True,
nfc_ssl: bool = True) -> NfcAuthSession:
host: str,
username: str,
password: str,
vm_moref: str,
port: int = 443,
thumbprint: Optional[str] = None,
allow_untrusted: bool = False,
disk_device_key: Optional[int] = None,
disk_path: Optional[str] = None,
read_only: bool = True,
nfc_ssl: bool = True,
) -> NfcAuthSession:
"""Login to vSphere and complete NFC authd authentication for a VM.
Args:
@@ -393,15 +430,25 @@ def authenticate(
PROXY service used by nbdssl. Pass False for nbd.
"""
si = connect_vim(
host, username, password, port=port,
thumbprint=thumbprint, allow_untrusted=allow_untrusted)
host,
username,
password,
port=port,
thumbprint=thumbprint,
allow_untrusted=allow_untrusted,
)
try:
vm = vim.VirtualMachine(vm_moref, si._stub)
ticket = get_nfc_ticket(
si, vm, disk_device_key=disk_device_key,
disk_path=disk_path, read_only=read_only)
si,
vm,
disk_device_key=disk_device_key,
disk_path=disk_path,
read_only=read_only,
)
authd_sock = connect_authd(
ticket, allow_untrusted=allow_untrusted, nfc_ssl=nfc_ssl)
ticket, allow_untrusted=allow_untrusted, nfc_ssl=nfc_ssl
)
except Exception:
Disconnect(si)
raise
+95 -120
View File
@@ -98,15 +98,14 @@ def takeover_authd_socket(ssock: ssl.SSLSocket) -> socket.socket:
family=ssock.family,
type=ssock.type,
proto=ssock.proto,
fileno=os.dup(ssock.fileno()))
fileno=os.dup(ssock.fileno()),
)
raw.settimeout(timeout)
_enable_tcp_nodelay(raw)
return raw
def wrap_nfcssl_socket(
ssock: ssl.SSLSocket,
server_hostname: str) -> ssl.SSLSocket:
def wrap_nfcssl_socket(ssock: ssl.SSLSocket, server_hostname: str) -> ssl.SSLSocket:
"""Start the second TLS session used by NBDSSL after PROXY.
After ``200 Connect ha-nfcssl``, authd TLS is finished and
@@ -121,8 +120,7 @@ def wrap_nfcssl_socket(
raw = takeover_authd_socket(ssock)
ssl_context = _ssl_client_context(verify=False)
try:
return ssl_context.wrap_socket(
raw, server_hostname=server_hostname)
return ssl_context.wrap_socket(raw, server_hostname=server_hostname)
except Exception:
raw.close()
raise
@@ -139,13 +137,13 @@ def _recvn(sock: socket.socket, size: int) -> bytes:
chunk = sock.recv(size - len(buf))
if not chunk:
raise NfcProtocolError(
f"NFC connection closed, needed {size} bytes, got {len(buf)}")
f"NFC connection closed, needed {size} bytes, got {len(buf)}"
)
buf.extend(chunk)
return bytes(buf)
def _send_nfc_msg(
sock: socket.socket, msg_type: int, body: bytes = b"") -> None:
def _send_nfc_msg(sock: socket.socket, msg_type: int, body: bytes = b"") -> None:
if len(body) > NFC_MSG_SIZE - 4:
raise ValueError("NFC classic message body too large")
frame = struct.pack("<I", msg_type) + body
@@ -159,16 +157,15 @@ def _recv_nfc_msg(sock: socket.socket) -> tuple[int, bytes]:
def _pack_aio_hdr(msg_type: int, payload_size: int, op_id: int) -> bytes:
return struct.pack(
"<IIII", NFC_AIO_MAGIC, msg_type, payload_size, op_id)
return struct.pack("<IIII", NFC_AIO_MAGIC, msg_type, payload_size, op_id)
def _unpack_aio_hdr(hdr: bytes) -> tuple[int, int, int]:
magic, msg_type, size, op_id = struct.unpack_from("<IIII", hdr)
if magic != NFC_AIO_MAGIC:
raise NfcProtocolError(
f"AIO header magic mismatch: 0x{magic:x}, "
f"expected 0x{NFC_AIO_MAGIC:x}")
f"AIO header magic mismatch: 0x{magic:x}, expected 0x{NFC_AIO_MAGIC:x}"
)
if msg_type == NFC_AIO_MSG_ERROR:
raise NfcProtocolError(f"AIO error opId={op_id} size={size}")
return msg_type, size, op_id
@@ -178,12 +175,13 @@ class NfcDisk:
"""An NFC AIO session with one VMDK opened for I/O."""
def __init__(
self,
sock: socket.socket,
path: str,
handle: int,
sector_size: int,
compression: int = NFC_COMPRESSION_NONE) -> None:
self,
sock: socket.socket,
path: str,
handle: int,
sector_size: int,
compression: int = NFC_COMPRESSION_NONE,
) -> None:
"""Wrap an AIO session that already has ``path`` open.
Args:
@@ -207,15 +205,12 @@ class NfcDisk:
self._op_id += 1
return op_id
def _aio_send(
self,
msg_type: int,
payload: bytes,
extra: bytes = b"") -> int:
def _aio_send(self, msg_type: int, payload: bytes, extra: bytes = b"") -> int:
"""Send one AIO request (header, payload, and extra in one write)."""
op_id = self._next_op_id()
self._sock.sendall(
_pack_aio_hdr(msg_type, len(payload), op_id) + payload + extra)
_pack_aio_hdr(msg_type, len(payload), op_id) + payload + extra
)
return op_id
def _aio_recv_reply(self) -> tuple[int, int, bytes]:
@@ -224,27 +219,24 @@ class NfcDisk:
magic, rtype, rsize, rop = struct.unpack_from("<IIII", rhdr)
if magic != NFC_AIO_MAGIC:
raise NfcProtocolError(
f"AIO header magic mismatch: 0x{magic:x}, "
f"expected 0x{NFC_AIO_MAGIC:x}")
f"AIO header magic mismatch: 0x{magic:x}, expected 0x{NFC_AIO_MAGIC:x}"
)
body = _recvn(self._sock, rsize) if rsize else b""
if rtype == NFC_AIO_MSG_ERROR:
raise NfcProtocolError(
f"AIO error opId={rop} size={rsize} {body.hex()}")
raise NfcProtocolError(f"AIO error opId={rop} size={rsize} {body.hex()}")
return rtype, rop, body
def _aio_roundtrip(
self,
msg_type: int,
payload: bytes,
extra: bytes = b"",
extra_recv: int = 0) -> bytes:
self, msg_type: int, payload: bytes, extra: bytes = b"", extra_recv: int = 0
) -> bytes:
"""Send one AIO request and return the reply payload (+ extra)."""
op_id = self._aio_send(msg_type, payload, extra)
rtype, rop, body = self._aio_recv_reply()
if rtype != msg_type or rop != op_id:
raise NfcProtocolError(
f"AIO reply type={rtype} opId={rop}, "
f"expected type={msg_type} opId={op_id}")
f"expected type={msg_type} opId={op_id}"
)
if extra_recv:
body += _recvn(self._sock, extra_recv)
return body
@@ -269,17 +261,10 @@ class NfcDisk:
offset = start_sector * self.sector_size
opcode = NFC_AIO_IO_READ | (self.compression << 32)
payload = struct.pack(
"<QQQQIII",
self.handle,
opcode,
offset,
length,
length,
length,
0)
"<QQQQIII", self.handle, opcode, offset, length, length, length, 0
)
op_id = self._next_op_id()
self._sock.sendall(
_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id) + payload)
self._sock.sendall(_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id) + payload)
data = bytearray(length)
filled = 0
seen: set[int] = set()
@@ -289,11 +274,11 @@ class NfcDisk:
if rtype != NFC_AIO_MSG_IO or rop != op_id:
raise NfcProtocolError(
f"AIO IO reply type={rtype} opId={rop}, "
f"expected type={NFC_AIO_MSG_IO} opId={op_id}")
f"expected type={NFC_AIO_MSG_IO} opId={op_id}"
)
body = _recvn(self._sock, rsize)
if rsize < 36:
raise NfcProtocolError(
f"AIO IO reply payload too short: {rsize}")
raise NfcProtocolError(f"AIO IO reply payload too short: {rsize}")
# Fragments may arrive out of order. Offset 28 is the byte
# offset of this chunk within the request (0, 65536, …),
# not a 0-based index. Offset 32 is the uncompressed
@@ -301,13 +286,11 @@ class NfcDisk:
# (2), extra data is compressed and offset 36 is its size.
opcode = struct.unpack_from("<Q", body, 8)[0]
dest, chunk_len = struct.unpack_from("<II", body, 28)
if (
dest in seen
or chunk_len == 0
or dest + chunk_len > length):
if dest in seen or chunk_len == 0 or dest + chunk_len > length:
raise NfcProtocolError(
f"AIO IO chunk offset={dest} length={chunk_len} invalid, "
f"request {length}")
f"request {length}"
)
seen.add(dest)
ctype = opcode >> 32
if ctype == NFC_COMPRESSION_FASTLZ:
@@ -317,25 +300,21 @@ class NfcDisk:
chunk = fastlz.decompress(extra, chunk_len)
except ValueError as exc:
raise NfcProtocolError(
f"FastLZ read fragment failed: {exc}") from exc
f"FastLZ read fragment failed: {exc}"
) from exc
if len(chunk) != chunk_len:
raise NfcProtocolError(
f"FastLZ read got {len(chunk)} bytes, "
f"expected {chunk_len}")
f"FastLZ read got {len(chunk)} bytes, expected {chunk_len}"
)
elif ctype == NFC_COMPRESSION_NONE:
chunk = _recvn(self._sock, chunk_len)
else:
raise NfcProtocolError(
f"unsupported NFC IO compression type {ctype}")
data[dest:dest + chunk_len] = chunk
raise NfcProtocolError(f"unsupported NFC IO compression type {ctype}")
data[dest : dest + chunk_len] = chunk
filled += chunk_len
return bytes(data)
def write(
self,
start_sector: int,
num_sectors: int,
data: bytes) -> None:
def write(self, start_sector: int, num_sectors: int, data: bytes) -> None:
"""Write ``num_sectors`` starting at ``start_sector``.
Matches ``VixDiskLib_Write``: one ``NFC_AIO_MSG_IO`` ``opId``
@@ -352,19 +331,16 @@ class NfcDisk:
raise ValueError("num_sectors must be at least 1")
length = num_sectors * self.sector_size
if len(data) != length:
raise ValueError(
f"write data is {len(data)} bytes, need {length}")
raise ValueError(f"write data is {len(data)} bytes, need {length}")
disk_offset = start_sector * self.sector_size
op_id = self._next_op_id()
frag_offset = 0
while frag_offset < length:
chunk = data[frag_offset:frag_offset + NFC_AIO_BUFFER_SIZE]
chunk = data[frag_offset : frag_offset + NFC_AIO_BUFFER_SIZE]
extra = chunk
extra_len = len(chunk)
ctype = NFC_COMPRESSION_NONE
if (
self.compression == NFC_COMPRESSION_FASTLZ
and extra_len >= 16):
if self.compression == NFC_COMPRESSION_FASTLZ and extra_len >= 16:
compressed = fastlz.compress(chunk)
if compressed and len(compressed) < extra_len:
extra = compressed
@@ -380,16 +356,18 @@ class NfcDisk:
frag_offset,
len(chunk),
extra_len,
0)
0,
)
self._sock.sendall(
_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id)
+ payload + extra)
_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id) + payload + extra
)
frag_offset += len(chunk)
rtype, rop, _body = self._aio_recv_reply()
if rtype != NFC_AIO_MSG_IO or rop != op_id:
raise NfcProtocolError(
f"AIO IO write reply type={rtype} opId={rop}, "
f"expected type={NFC_AIO_MSG_IO} opId={op_id}")
f"expected type={NFC_AIO_MSG_IO} opId={op_id}"
)
def close(self) -> None:
"""Close the VMDK, the AIO session, and the classic NFC session."""
@@ -397,10 +375,8 @@ class NfcDisk:
return
self._closed = True
try:
self._aio_roundtrip(
NFC_AIO_MSG_CLOSE_FILE, struct.pack("<Q", self.handle))
self._aio_roundtrip(
NFC_AIO_MSG_CLOSE_SESSION, struct.pack("<I", 0))
self._aio_roundtrip(NFC_AIO_MSG_CLOSE_FILE, struct.pack("<Q", self.handle))
self._aio_roundtrip(NFC_AIO_MSG_CLOSE_SESSION, struct.pack("<I", 0))
_send_nfc_msg(self._sock, NFC_MSG_SESSION_COMPLETE)
finally:
try:
@@ -415,11 +391,7 @@ class NfcDisk:
self.close()
def _handshake(
sock: socket.socket,
client_name: str,
op_id: str,
version: int) -> None:
def _handshake(sock: socket.socket, client_name: str, op_id: str, version: int) -> None:
"""Run the classic NFC session handshake used by VDDK NBD."""
_send_nfc_msg(sock, NFC_MSG_HANDSHAKE, b"PlainText")
_send_nfc_msg(sock, NFC_MSG_SESSION_PARAMS)
@@ -427,43 +399,46 @@ def _handshake(
if reply_type != NFC_MSG_SESSION_PARAMS_REPLY:
raise NfcProtocolError(
f"expected session-params reply {NFC_MSG_SESSION_PARAMS_REPLY}, "
f"got {reply_type}")
f"got {reply_type}"
)
_send_nfc_msg(sock, NFC_MSG_VERSION, struct.pack("<I", version))
reply_type, body = _recv_nfc_msg(sock)
if reply_type != NFC_MSG_VERSION:
raise NfcProtocolError(
f"expected version reply {NFC_MSG_VERSION}, got {reply_type}")
f"expected version reply {NFC_MSG_VERSION}, got {reply_type}"
)
remote_version = struct.unpack_from("<I", body)[0]
if remote_version < 3:
raise NfcProtocolError(
f"NFC server version {remote_version} is too old for AIO")
f"NFC server version {remote_version} is too old for AIO"
)
name_b = client_name.encode("ascii")
op_b = op_id.encode("ascii")
_send_nfc_msg(
sock, NFC_MSG_CONNECTION_DATA,
struct.pack("<II", len(name_b), len(op_b)))
sock, NFC_MSG_CONNECTION_DATA, struct.pack("<II", len(name_b), len(op_b))
)
sock.sendall(name_b)
sock.sendall(op_b)
_send_nfc_msg(
sock, NFC_MSG_SESSION_FEATURES,
struct.pack("<I", NFC_SESSION_FEATURE_INTERRUPTION_SWITCH))
sock,
NFC_MSG_SESSION_FEATURES,
struct.pack("<I", NFC_SESSION_FEATURE_INTERRUPTION_SWITCH),
)
_send_nfc_msg(sock, NFC_MSG_AIO_SESSION_OPEN)
reply_type, _ = _recv_nfc_msg(sock)
if reply_type != NFC_MSG_AIO_SESSION_OPEN:
raise NfcProtocolError(
f"expected AIO session-open reply "
f"{NFC_MSG_AIO_SESSION_OPEN}, got {reply_type}")
f"{NFC_MSG_AIO_SESSION_OPEN}, got {reply_type}"
)
def _aio_prepare(disk: NfcDisk) -> None:
disk._aio_roundtrip(
NFC_AIO_MSG_OPEN_SESSION, bytes(16))
disk._aio_roundtrip(
NFC_AIO_MSG_SET_SOCK_OPTS, bytes(12))
disk._aio_roundtrip(
NFC_AIO_MSG_SET_RES_POOL, struct.pack("<I", 1))
disk._aio_roundtrip(NFC_AIO_MSG_OPEN_SESSION, bytes(16))
disk._aio_roundtrip(NFC_AIO_MSG_SET_SOCK_OPTS, bytes(12))
disk._aio_roundtrip(NFC_AIO_MSG_SET_RES_POOL, struct.pack("<I", 1))
def _parse_open_reply(body: bytes) -> tuple[int, int]:
@@ -472,21 +447,21 @@ def _parse_open_reply(body: bytes) -> tuple[int, int]:
handle, file_type, _flags = struct.unpack_from("<QII", body, 8)
sector_size = struct.unpack_from("<I", body, 36)[0]
if file_type != NFC_DISK:
raise NfcProtocolError(
f"opened file type {file_type}, expected NFC_DISK")
raise NfcProtocolError(f"opened file type {file_type}, expected NFC_DISK")
if sector_size == 0:
sector_size = NFC_SECTOR_SIZE
return handle, sector_size
def open_disk(
session: NfcAuthSession,
disk_path: str,
client_name: str = "vddk",
op_id: str = "nbdmode",
version: int = NFC_PROTOCOL_VERSION,
read_only: bool = True,
compression: int = NFC_COMPRESSION_NONE) -> NfcDisk:
session: NfcAuthSession,
disk_path: str,
client_name: str = "vddk",
op_id: str = "nbdmode",
version: int = NFC_PROTOCOL_VERSION,
read_only: bool = True,
compression: int = NFC_COMPRESSION_NONE,
) -> NfcDisk:
"""Open ``disk_path`` over the authenticated authd socket.
Matches VDDK ``VixDiskLib_Open`` of a datastore path after the NFC
@@ -506,31 +481,31 @@ def open_disk(
compression: ``NFC_COMPRESSION_NONE`` or ``NFC_COMPRESSION_FASTLZ``.
OPEN_FILE flags are unchanged; compression is per IO message.
"""
if compression not in (
NFC_COMPRESSION_NONE, NFC_COMPRESSION_FASTLZ):
if compression not in (NFC_COMPRESSION_NONE, NFC_COMPRESSION_FASTLZ):
raise NotImplementedError(
f"NFC compression type {compression} is not supported")
f"NFC compression type {compression} is not supported"
)
if session.nfc_ssl:
sock = wrap_nfcssl_socket(
session.authd_sock, session.ticket.host)
sock = wrap_nfcssl_socket(session.authd_sock, session.ticket.host)
else:
sock = takeover_authd_socket(session.authd_sock)
try:
_handshake(sock, client_name, op_id, version)
disk = NfcDisk(
sock, disk_path, handle=0, sector_size=NFC_SECTOR_SIZE,
compression=compression)
sock,
disk_path,
handle=0,
sector_size=NFC_SECTOR_SIZE,
compression=compression,
)
_aio_prepare(disk)
path_b = disk_path.encode("utf-8")
open_flags = (
NFC_OPEN_FLAGS_READ_ONLY if read_only
else NFC_OPEN_FLAGS_READ_WRITE)
open_body = struct.pack(
"<IIIIII",
len(path_b), 0, 0, 0, NFC_DISK, open_flags)
NFC_OPEN_FLAGS_READ_ONLY if read_only else NFC_OPEN_FLAGS_READ_WRITE
)
open_body = struct.pack("<IIIIII", len(path_b), 0, 0, 0, NFC_DISK, open_flags)
open_body = open_body.ljust(60, b"\x00")
reply = disk._aio_roundtrip(
NFC_AIO_MSG_OPEN_FILE, open_body, extra=path_b)
reply = disk._aio_roundtrip(NFC_AIO_MSG_OPEN_FILE, open_body, extra=path_b)
handle, sector_size = _parse_open_reply(reply)
disk.handle = handle
disk.sector_size = sector_size
+79 -73
View File
@@ -24,8 +24,7 @@ from typing import Iterator, Optional, Union
from pyVim.connect import Disconnect
from pyVmomi import vim
from openvixdisklib import nfc_auth
from openvixdisklib import nfc_open
from openvixdisklib import nfc_auth, nfc_open
LOG = logging.getLogger(__name__)
@@ -50,21 +49,20 @@ def _nfc_compression(flags: int) -> int:
alg = flags & (
VIXDISKLIB_FLAG_OPEN_COMPRESSION_ZLIB
| VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ
| VIXDISKLIB_FLAG_OPEN_COMPRESSION_SKIPZ)
| VIXDISKLIB_FLAG_OPEN_COMPRESSION_SKIPZ
)
if alg == 0:
return nfc_open.NFC_COMPRESSION_NONE
if alg == VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ:
return nfc_open.NFC_COMPRESSION_FASTLZ
if alg & (alg - 1):
raise NotImplementedError(
"Cannot set two or more NBD compression algorithms at the "
"same time")
raise NotImplementedError(
f"NBD compression open flag 0x{alg:x} is not supported")
"Cannot set two or more NBD compression algorithms at the same time"
)
raise NotImplementedError(f"NBD compression open flag 0x{alg:x} is not supported")
VIX_SUPPORTED_COMPATIBILITY_MODES = [
"6.0", "6.5", "6.7", "7.0", "8.0"]
VIX_SUPPORTED_COMPATIBILITY_MODES = ["6.0", "6.5", "6.7", "7.0", "8.0"]
def get_buffer(size: int):
@@ -74,8 +72,7 @@ def get_buffer(size: int):
def _parse_vm_moref(vmx_spec: Optional[str]) -> str:
if not vmx_spec:
raise ValueError(
"vmx_spec is required (for example 'moref=vm-13098')")
raise ValueError("vmx_spec is required (for example 'moref=vm-13098')")
if "=" in vmx_spec:
kind, value = vmx_spec.split("=", 1)
if kind.lower() != "moref" or not value:
@@ -97,21 +94,23 @@ def _select_transport(transport_modes: Optional[str]) -> str:
if mode in ("nbdssl", "nbd"):
return mode
raise NotImplementedError(
f"supported transports are nbdssl and nbd, got {transport_modes!r}")
f"supported transports are nbdssl and nbd, got {transport_modes!r}"
)
class _Connection:
"""VIM session plus the VM moref needed to issue an NFC ticket at Open."""
def __init__(
self,
si: vim.ServiceInstance,
vm_moref: str,
snapshot_ref: Optional[str],
thumbprint: Optional[str],
allow_untrusted: bool,
read_only: bool,
transport_mode: str) -> None:
self,
si: vim.ServiceInstance,
vm_moref: str,
snapshot_ref: Optional[str],
thumbprint: Optional[str],
allow_untrusted: bool,
read_only: bool,
transport_mode: str,
) -> None:
self.si = si
self.vm_moref = vm_moref
self.snapshot_ref = snapshot_ref
@@ -124,11 +123,7 @@ class _Connection:
class _DiskHandle:
"""Opened NFC disk plus the authd TLS socket it was taken from."""
def __init__(
self,
disk: nfc_open.NfcDisk,
authd_sock,
transport_mode: str) -> None:
def __init__(self, disk: nfc_open.NfcDisk, authd_sock, transport_mode: str) -> None:
self.disk = disk
self.authd_sock = authd_sock
self.transport_mode = transport_mode
@@ -138,9 +133,10 @@ class VixDiskLibHandle:
"""VDDK-compatible handle backed by pyVmomi and the NFC replacement."""
def __init__(
self,
config_path: Optional[str] = None,
vixdisklib_compatibility_version: Optional[str] = None) -> None:
self,
config_path: Optional[str] = None,
vixdisklib_compatibility_version: Optional[str] = None,
) -> None:
"""Accept the VDDK wrapper constructor; no native library is loaded.
Args:
@@ -164,18 +160,20 @@ class VixDiskLibHandle:
raise ValueError(
"Unsupported vixDiskLib version format '%s'. vixDiskLib "
"compatibility mode must be of the form "
"'$major.$minor'" % version) from ex
"'$major.$minor'" % version
) from ex
version_used = version
break
if not version_used:
raise Exception(
"Could not initialize vixDiskLib with any of the following "
"versions: %s" % target_versions)
"versions: %s" % target_versions
)
LOG.info(
"Successfully initialized vixDiskLib with target version '%s'",
version_used)
"Successfully initialized vixDiskLib with target version '%s'", version_used
)
@classmethod
def get_vix_disklib_name(cls) -> str:
@@ -194,17 +192,18 @@ class VixDiskLibHandle:
@contextlib.contextmanager
def connect(
self,
server_name: str,
thumbprint: Optional[str],
username: str,
password: str,
vmx_spec: Optional[str] = None,
snapshot_ref: Optional[str] = None,
read_only: bool = True,
transport_modes: Optional[str] = None,
port: int = 443,
allow_untrusted: bool = False) -> Iterator[_Connection]:
self,
server_name: str,
thumbprint: Optional[str],
username: str,
password: str,
vmx_spec: Optional[str] = None,
snapshot_ref: Optional[str] = None,
read_only: bool = True,
transport_modes: Optional[str] = None,
port: int = 443,
allow_untrusted: bool = False,
) -> Iterator[_Connection]:
"""Login to vCenter/ESXi. Matches ``VixDiskLib_ConnectEx``.
The NFC ticket and authd handshake are deferred to ``open``, as in
@@ -235,11 +234,17 @@ class VixDiskLibHandle:
password,
port=port,
thumbprint=thumbprint,
allow_untrusted=allow_untrusted or not thumbprint)
allow_untrusted=allow_untrusted or not thumbprint,
)
conn = _Connection(
si, vm_moref, snapshot_ref, thumbprint,
allow_untrusted or not thumbprint, read_only,
transport_mode)
si,
vm_moref,
snapshot_ref,
thumbprint,
allow_untrusted or not thumbprint,
read_only,
transport_mode,
)
try:
yield conn
finally:
@@ -247,10 +252,11 @@ class VixDiskLibHandle:
@contextlib.contextmanager
def open(
self,
conn: _Connection,
disk_path: str,
flags: int = VIXDISKLIB_FLAG_OPEN_READ_ONLY) -> Iterator[_DiskHandle]:
self,
conn: _Connection,
disk_path: str,
flags: int = VIXDISKLIB_FLAG_OPEN_READ_ONLY,
) -> Iterator[_DiskHandle]:
"""Open ``disk_path`` over NFC. Matches ``VixDiskLib_Open``.
Read-only opens request ``NfcGetVmFiles`` (VM only). The VMDK
@@ -271,22 +277,21 @@ class VixDiskLibHandle:
compression = _nfc_compression(flags)
read_only = bool(flags & VIXDISKLIB_FLAG_OPEN_READ_ONLY)
if not read_only and conn.read_only:
raise NotImplementedError(
"ConnectEx was read-only; cannot open for write")
raise NotImplementedError("ConnectEx was read-only; cannot open for write")
vm = vim.VirtualMachine(conn.vm_moref, conn.si._stub)
nfc_ssl = conn.transport_mode == "nbdssl"
ticket = nfc_auth.get_nfc_ticket(
conn.si, vm, read_only=read_only,
disk_path=None if read_only else disk_path)
conn.si, vm, read_only=read_only, disk_path=None if read_only else disk_path
)
authd_sock = nfc_auth.connect_authd(
ticket, allow_untrusted=conn.allow_untrusted, nfc_ssl=nfc_ssl)
session = nfc_auth.NfcAuthSession(
conn.si, ticket, authd_sock, nfc_ssl=nfc_ssl)
ticket, allow_untrusted=conn.allow_untrusted, nfc_ssl=nfc_ssl
)
session = nfc_auth.NfcAuthSession(conn.si, ticket, authd_sock, nfc_ssl=nfc_ssl)
try:
disk = nfc_open.open_disk(
session, disk_path, read_only=read_only,
compression=compression)
session, disk_path, read_only=read_only, compression=compression
)
except Exception:
authd_sock.close()
raise
@@ -297,11 +302,12 @@ class VixDiskLibHandle:
self.close(handle)
def read(
self,
disk_handle: _DiskHandle,
start_sector: int,
num_sectors: int,
buf: Union[ctypes.Array, bytearray, memoryview]) -> None:
self,
disk_handle: _DiskHandle,
start_sector: int,
num_sectors: int,
buf: Union[ctypes.Array, bytearray, memoryview],
) -> None:
"""Read ``num_sectors`` from ``start_sector`` into ``buf``.
Args:
@@ -313,18 +319,18 @@ class VixDiskLibHandle:
data = disk_handle.disk.read(start_sector, num_sectors)
if isinstance(buf, (bytearray, memoryview)):
if len(buf) < len(data):
raise Exception(
f"read buffer is {len(buf)} bytes, need {len(data)}")
buf[:len(data)] = data
raise Exception(f"read buffer is {len(buf)} bytes, need {len(data)}")
buf[: len(data)] = data
return
ctypes.memmove(buf, data, len(data))
def write(
self,
disk_handle: _DiskHandle,
start_sector: int,
num_sectors: int,
buf: Union[ctypes.Array, bytes, bytearray, memoryview]) -> None:
self,
disk_handle: _DiskHandle,
start_sector: int,
num_sectors: int,
buf: Union[ctypes.Array, bytes, bytearray, memoryview],
) -> None:
"""Write ``num_sectors`` from ``buf`` starting at ``start_sector``.
Args: