Update with test
This commit is contained in:
247
nbd_server_v9.py
Normal file
247
nbd_server_v9.py
Normal file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
NBD server v9 - PERC H710 chunk translation + merged GDT overlay only.
|
||||
|
||||
No feature flag patching. No checksum zeroing. No journal disabling.
|
||||
Just serves the filesystem exactly as it was written, with a complete
|
||||
GDT built by merging primary (for non-metadata-chunk entries) and
|
||||
backup group 1 (for entries that fall in metadata chunks).
|
||||
|
||||
Primary and backup GDT bad entries are completely disjoint (0 overlap),
|
||||
so this gives 100% GDT coverage with authentic data and valid checksums.
|
||||
|
||||
Usage:
|
||||
# First build the merged GDT:
|
||||
# python3 build_merged_gdt.py (saves /tmp/merged_gdt.bin)
|
||||
|
||||
python3 nbd_server_v9.py &
|
||||
nbd-client 127.0.0.1 10809 /dev/nbd0 -N ""
|
||||
ext4magic /dev/nbd0 -s 4096 -n 32768 -R -I 1585918 -a $(date -d "2023-01-01" +%s) -d /mnt/recovered
|
||||
"""
|
||||
|
||||
import socket, struct, threading, sys, os
|
||||
|
||||
DEV = '/dev/md0'
|
||||
MERGED_GDT = '/tmp/merged_gdt.bin'
|
||||
CHUNK_BYTES = 128 * 512
|
||||
LV_PHYS_START = 5120000 * 512
|
||||
VIRT_SIZE = 9365766144 * 512
|
||||
|
||||
# GDT location in virtual address space
|
||||
BSIZE = 4096
|
||||
GDT_ENTRY_SZ = 64
|
||||
NUM_GROUPS = 35728
|
||||
GDT_VIRT_START = BSIZE # block 1 = byte 4096
|
||||
GDT_VIRT_END = BSIZE + NUM_GROUPS * GDT_ENTRY_SZ
|
||||
|
||||
# NBD protocol
|
||||
NBDMAGIC = 0x4e42444d41474943
|
||||
IHAVEOPT = 0x49484156454F5054
|
||||
REPLYMAGIC = 0x3e889045565a9
|
||||
NBD_OPT_EXPORT_NAME = 1
|
||||
NBD_OPT_ABORT = 2
|
||||
NBD_OPT_LIST = 3
|
||||
NBD_OPT_GO = 7
|
||||
NBD_REP_ACK = 1
|
||||
NBD_REP_SERVER = 2
|
||||
NBD_REP_INFO = 3
|
||||
NBD_REP_ERR_UNSUP = (1 << 31) | 1
|
||||
NBD_INFO_EXPORT = 0
|
||||
NBD_FLAG_HAS_FLAGS = 1 << 0
|
||||
NBD_FLAG_READ_ONLY = 1 << 1
|
||||
NBD_FLAG_SEND_FLUSH = 1 << 2
|
||||
NBD_REQUEST_MAGIC = 0x25609513
|
||||
NBD_REPLY_MAGIC = 0x67446698
|
||||
NBD_CMD_READ = 0
|
||||
NBD_CMD_DISC = 2
|
||||
NBD_CMD_FLUSH = 3
|
||||
TX_FLAGS = NBD_FLAG_HAS_FLAGS | NBD_FLAG_READ_ONLY | NBD_FLAG_SEND_FLUSH
|
||||
|
||||
# Load merged GDT at startup
|
||||
print(f'Loading merged GDT from {MERGED_GDT}...')
|
||||
if not os.path.exists(MERGED_GDT):
|
||||
print(f'ERROR: {MERGED_GDT} not found.')
|
||||
print('Run the GDT builder script first.')
|
||||
sys.exit(1)
|
||||
|
||||
with open(MERGED_GDT, 'rb') as f:
|
||||
MERGED_GDT_DATA = f.read()
|
||||
|
||||
expected = NUM_GROUPS * GDT_ENTRY_SZ
|
||||
if len(MERGED_GDT_DATA) != expected:
|
||||
print(f'ERROR: merged GDT is {len(MERGED_GDT_DATA)} bytes, '
|
||||
f'expected {expected}')
|
||||
sys.exit(1)
|
||||
|
||||
print(f'Merged GDT loaded: {len(MERGED_GDT_DATA)//1024}KB '
|
||||
f'({NUM_GROUPS} groups)')
|
||||
|
||||
|
||||
def raw_read(virt_offset, length):
|
||||
"""Pure PERC chunk translation, no modifications."""
|
||||
result = bytearray(length)
|
||||
pos = virt_offset
|
||||
remaining = length
|
||||
with open(DEV, 'rb') as f:
|
||||
while remaining > 0:
|
||||
group = pos // (5 * CHUNK_BYTES)
|
||||
in_group = pos % (5 * CHUNK_BYTES)
|
||||
chunk_idx = in_group // CHUNK_BYTES
|
||||
intra = in_group % CHUNK_BYTES
|
||||
seg_len = min(CHUNK_BYTES - intra, remaining)
|
||||
dst_off = pos - virt_offset
|
||||
if chunk_idx != 4:
|
||||
phys = (LV_PHYS_START
|
||||
+ group * 4 * CHUNK_BYTES
|
||||
+ chunk_idx * CHUNK_BYTES
|
||||
+ intra)
|
||||
f.seek(phys)
|
||||
data = f.read(seg_len)
|
||||
result[dst_off:dst_off + len(data)] = data
|
||||
pos += seg_len
|
||||
remaining -= seg_len
|
||||
return result
|
||||
|
||||
|
||||
def read_virtual(virt_offset, length):
|
||||
"""
|
||||
Read with merged GDT overlay.
|
||||
Only the primary GDT region (bytes 4096 to 4096+NUM_GROUPS*64)
|
||||
is modified — replaced with the pre-built merged GDT.
|
||||
Everything else is pure chunk translation, unmodified.
|
||||
"""
|
||||
data = raw_read(virt_offset, length)
|
||||
req_end = virt_offset + length
|
||||
|
||||
# Overlay merged GDT where request overlaps primary GDT region
|
||||
if virt_offset < GDT_VIRT_END and req_end > GDT_VIRT_START:
|
||||
ol_start = max(virt_offset, GDT_VIRT_START)
|
||||
ol_end = min(req_end, GDT_VIRT_END)
|
||||
src_off = ol_start - GDT_VIRT_START
|
||||
dst_off = ol_start - virt_offset
|
||||
n = ol_end - ol_start
|
||||
data[dst_off:dst_off + n] = MERGED_GDT_DATA[src_off:src_off + n]
|
||||
|
||||
return bytes(data)
|
||||
|
||||
|
||||
def recv_all(conn, n):
|
||||
buf = b''
|
||||
while len(buf) < n:
|
||||
d = conn.recv(n - len(buf))
|
||||
if not d:
|
||||
raise ConnectionError('disconnected')
|
||||
buf += d
|
||||
return buf
|
||||
|
||||
|
||||
def send_reply(conn, opt, rtype, data=b''):
|
||||
conn.sendall(struct.pack('>Q', REPLYMAGIC))
|
||||
conn.sendall(struct.pack('>I', opt))
|
||||
conn.sendall(struct.pack('>I', rtype))
|
||||
conn.sendall(struct.pack('>I', len(data)))
|
||||
if data:
|
||||
conn.sendall(data)
|
||||
|
||||
|
||||
def handle_client(conn, addr):
|
||||
print(f'[nbd] {addr} connected')
|
||||
try:
|
||||
conn.sendall(struct.pack('>Q', NBDMAGIC))
|
||||
conn.sendall(struct.pack('>Q', IHAVEOPT))
|
||||
conn.sendall(struct.pack('>H', 0x0003))
|
||||
recv_all(conn, 4)
|
||||
|
||||
while True:
|
||||
hdr = recv_all(conn, 16)
|
||||
_, opt, opt_len = struct.unpack('>QII', hdr)
|
||||
opt_data = recv_all(conn, opt_len) if opt_len else b''
|
||||
|
||||
if opt == NBD_OPT_EXPORT_NAME:
|
||||
conn.sendall(struct.pack('>Q', VIRT_SIZE))
|
||||
conn.sendall(struct.pack('>H', TX_FLAGS))
|
||||
break
|
||||
elif opt == NBD_OPT_GO:
|
||||
info = struct.pack('>HQH', NBD_INFO_EXPORT,
|
||||
VIRT_SIZE, TX_FLAGS)
|
||||
send_reply(conn, opt, NBD_REP_INFO, info)
|
||||
send_reply(conn, opt, NBD_REP_ACK)
|
||||
break
|
||||
elif opt == NBD_OPT_LIST:
|
||||
send_reply(conn, opt, NBD_REP_SERVER,
|
||||
struct.pack('>I', 0))
|
||||
send_reply(conn, opt, NBD_REP_ACK)
|
||||
elif opt == NBD_OPT_ABORT:
|
||||
send_reply(conn, opt, NBD_REP_ACK)
|
||||
return
|
||||
else:
|
||||
send_reply(conn, opt, NBD_REP_ERR_UNSUP)
|
||||
|
||||
print(f'[nbd] {addr} transmission')
|
||||
while True:
|
||||
hdr = recv_all(conn, 28)
|
||||
magic, flags, cmd, handle, offset, length = \
|
||||
struct.unpack('>IHHQQI', hdr)
|
||||
if magic != NBD_REQUEST_MAGIC:
|
||||
return
|
||||
|
||||
if cmd == NBD_CMD_READ:
|
||||
try:
|
||||
payload = read_virtual(offset, length)
|
||||
except Exception as e:
|
||||
print(f'[nbd] read error {offset}+{length}: {e}')
|
||||
payload = b'\x00' * length
|
||||
conn.sendall(struct.pack('>IIQ',
|
||||
NBD_REPLY_MAGIC, 0, handle))
|
||||
conn.sendall(payload)
|
||||
elif cmd == NBD_CMD_FLUSH:
|
||||
conn.sendall(struct.pack('>IIQ',
|
||||
NBD_REPLY_MAGIC, 0, handle))
|
||||
elif cmd == NBD_CMD_DISC:
|
||||
print(f'[nbd] {addr} disconnected')
|
||||
return
|
||||
else:
|
||||
conn.sendall(struct.pack('>IIQ',
|
||||
NBD_REPLY_MAGIC, 1, handle))
|
||||
|
||||
except (ConnectionError, BrokenPipeError, ConnectionResetError):
|
||||
print(f'[nbd] {addr} dropped')
|
||||
except Exception as e:
|
||||
print(f'[nbd] {addr} error: {e}')
|
||||
import traceback; traceback.print_exc()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def main():
|
||||
print('PERC H710 recovery NBD server v9')
|
||||
print(f' device : {DEV}')
|
||||
print(f' lv_start : byte {LV_PHYS_START}')
|
||||
print(f' virt_size : {VIRT_SIZE//1024**3} GB')
|
||||
print(f' GDT region : bytes {GDT_VIRT_START}-{GDT_VIRT_END}')
|
||||
print(f' patch : merged GDT only (primary + backup group 1)')
|
||||
print(f' no patches : superblock, features, checksums all authentic')
|
||||
print()
|
||||
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(('127.0.0.1', 10809))
|
||||
srv.listen(5)
|
||||
print('Listening on 127.0.0.1:10809')
|
||||
print(' nbd-client 127.0.0.1 10809 /dev/nbd0 -N ""')
|
||||
print()
|
||||
print('Then try:')
|
||||
print(' e2fsck -n /dev/nbd0')
|
||||
print(' fls /dev/nbd0 1585918')
|
||||
print(' ext4magic /dev/nbd0 -s 4096 -n 32768 -R -I 1585918 \\')
|
||||
print(' -a $(date -d "2023-01-01" +%s) -d /mnt/recovered')
|
||||
print()
|
||||
|
||||
while True:
|
||||
conn, addr = srv.accept()
|
||||
threading.Thread(target=handle_client, args=(conn, addr),
|
||||
daemon=True).start()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user