Skip to content

Instantly share code, notes, and snippets.

@hradec
Created September 9, 2019 20:38
Show Gist options
  • Save hradec/e4e4d2f8693b4e3f9e0ec03ebe097c7d to your computer and use it in GitHub Desktop.
Save hradec/e4e4d2f8693b4e3f9e0ec03ebe097c7d to your computer and use it in GitHub Desktop.
#!/usr/bin/python
import struct, socket, sys
# network block device server, substitute for nbd-server. Probably slower.
# But it works! And it's probably a lot easier to improve the
# performance of this Python version than of the C version. This
# Python version is 14% of the size and perhaps 20% of the features of
# the C version. Hmm, that's not so great after all...
# Working:
# - nbd protocol
# - read/write serving up files
# - error handling
# - file size detection
# - in theory, large file support... not really
# - so_reuseaddr
# - nonforking
# Missing:
# - reporting errors to client (in particular writing and reading past end)
# - multiple clients (this probably requires copy-on-write or read-only)
# - copy on write
# - read-only
# - permission tracking
# - idle timeouts
# - running from inetd
# - filename substitution
# - partial file exports
# - exports of large files (bigger than 1/4 of RAM)
# - manual exportsize specification
# - so_keepalive
# - that "split an export file into multiple files" thing that sticks the .0
# on the end of your filename
# - backgrounding
# - daemonizing
class Error(Exception): pass
class buffsock:
"Buffered socket wrapper; always returns the amount of data you want."
def __init__(self, sock): self.sock = sock
def recv(self, nbytes):
rv = ''
while len(rv) < nbytes:
more = self.sock.recv(nbytes - len(rv))
if more == '': raise Error(nbytes)
rv += more
return rv
def send(self, astring): self.sock.send(astring)
def close(self): self.sock.close()
class debugsock:
"Debugging socket wrapper."
def __init__(self, sock): self.sock = sock
def recv(self, nbytes):
print "recv(%d) =" % nbytes,
rv = self.sock.recv(nbytes)
print `rv`
return rv
def send(self, astring):
print "send(%r) =" % astring,
rv = self.sock.send(astring)
print `rv`
return rv
def close(self):
print "close()"
self.sock.close()
def negotiation(exportsize):
"Returns initial NBD negotiation sequence for exportsize in bytes."
return ('NBDMAGIC' + '\x00\x00\x42\x02\x81\x86\x12\x53' +
struct.pack('>Q', exportsize) + '\0' * 128);
def nbd_reply(error=0, handle=1, data=''):
"Construct an NBD reply."
assert type(handle) is type('') and len(handle) == 8
return ('\x67\x44\x66\x98' + struct.pack('>L', error) + handle + data)
# possible request types
read_request = 0
write_request = 1
disconnect_request = 2
class nbd_request:
"Decodes an NBD request off the TCP socket."
def __init__(self, conn):
conn = buffsock(conn)
template = '>LL8sQL'
header = conn.recv(struct.calcsize(template))
(self.magic, self.type, self.handle, self.offset,
self.len) = struct.unpack(template, header)
if self.magic != 0x25609513: raise Error(self.magic)
if self.type == write_request:
self.data = conn.recv(self.len)
assert len(self.data) == self.len
def reply(self, error, data=''):
return nbd_reply(error=error, handle=self.handle, data=data)
def range(self):
return slice(self.offset, self.offset + self.len)
def serveclient(asock, afile):
"Serves a single client until it exits."
afile.seek(0)
abuf = list(afile.read())
asock.send(negotiation(len(abuf)))
while 1:
req = nbd_request(asock)
if req.type == read_request:
asock.send(req.reply(error=0,
data=''.join(abuf[req.range()])))
elif req.type == write_request:
abuf[req.range()] = req.data
afile.seek(req.offset)
afile.write(req.data)
afile.flush()
asock.send(req.reply(error=0))
elif req.type == disconnect_request:
asock.close()
return
def mainloop(listensock, afile):
"Serves clients forever."
while 1:
(sock, addr) = listensock.accept()
print "got conn on", addr
serveclient(sock, afile)
def main(argv):
"Given a port and a filename, serves up the file."
afile = file(argv[2], 'rb+')
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', int(argv[1])))
sock.listen(5)
mainloop(sock, afile)
if __name__ == '__main__': main(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment