There are two python scripts in the attachment: vsock_guest_exec_simple.py - simple example of a client; vsock_guest_exec_test.py - tests with different payload size.
The last file should be copied to a guest VM. Edit SRV_PATH variable in the host copy of the script - there should be path to the directory containing a copy of the script in VM. Execute the host script with net arguments: ./vsock_guest_exec_test.py srv <VM_NAME> -- Best regards, Alexander Ivanov
#!/usr/bin/python3 import sys, os, struct, subprocess, json, socket TYPE_MASK = 0x80000000 def parse_block_header(data): res = struct.unpack('!I', data) size = res[0] if size & TYPE_MASK: size -= TYPE_MASK tp = 'err' else: tp = 'out' return (size, tp) def recv_block(sock): hdr = sock.recv(4) if not hdr: print('ERROR: header receiving') sys.exit(-1) size, tp = parse_block_header(hdr) res = b'' received = 0 while size > 0: part = sock.recv(size) res += part size -= len(part) return (res, tp) def guest_exec(vm_name): print('run guest-exec command...') cmd = ['virsh', 'qemu-agent-command', vm_name, '{"execute":"guest-exec", "arguments":{"path": "bash", "capture-output": "interactive"}}'] p = subprocess.run(cmd, stdout=subprocess.PIPE) response = p.stdout.decode('utf-8') print('response: %s' % response.strip()) response = json.loads(response)['return'] cid = int(response['cid']) port = int(response['port']) return (cid, port) def srv_conn(cid, port): sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) print('connect...') sock.connect((cid, port)) print('connected') return sock def main(): if len(sys.argv) != 2: print('Usage:\n\t%s <VM name>' % sys.argv[0]) return vm_name = sys.argv[1] cid, port = guest_exec(vm_name) sock = srv_conn(cid, port) sock.send(b'echo "Hello world!"\n') data, tp = recv_block(sock) print('Received from %s: "%s"' % (tp, data.decode('utf-8'))) main()
#!/usr/bin/python3 import sys, os, time, struct, hashlib, subprocess, json, socket, random SRV_PATH = '/home/user/' PKT_HDR_FMT = '!Ib32s' TYPE_MASK = 0x80000000 TYPE_OUT = 1 TYPE_ERR = 2 def parse_block_header(data): res = struct.unpack('!I', data) size = res[0] if size & TYPE_MASK: size -= TYPE_MASK tp = 'err' else: tp = 'out' return (size, tp) def make_pkt(size, tp): payload = os.urandom(size) digest = hashlib.sha256(payload).digest() return struct.pack(PKT_HDR_FMT, size, tp, digest) + payload def recv_block(sock): hdr = b'' while len(hdr) < 4: part = sock.recv(4 - len(hdr)) if not part: return (None, None) hdr += part size, tp = parse_block_header(hdr) res = b'' received = 0 while size > 0: part = sock.recv(size) res += part size -= len(part) return (res, tp) def recv_pkt(sock, spkt): hdr_size = struct.calcsize(PKT_HDR_FMT) data = b'' size = None tries = 0 while True: block, btp = recv_block(sock) if block == None: if len(data) == 0: time.sleep(0.01) tries += 1 print('retry') if tries > 10: print('Connection closed') return else: data += block tries = 0 if len(data) < hdr_size: continue if size == None: hdr = data[:hdr_size] data = data[hdr_size:] size, tp, digest = struct.unpack(PKT_HDR_FMT, hdr) tp = 'err' if tp == TYPE_ERR else 'out' if tp != btp: print('ERR type') return None if len(data) < size: continue payload = data[:size] data = data[size:] if size != len(payload): print('ERR size', size, len(payload)) return None if digest != hashlib.sha256(payload).digest(): print('ERR digest', digest, hashlib.sha256(payload).digest()) for i in range(size): if payload[i] != spkt[i + hdr_size]: print('%d: %d != %d\n', i, payload[i], spkt[i + hdr_size]) return None return payload def run_command(vm_name, path): print('run command...') cmd = ['virsh', 'qemu-agent-command', vm_name, '{"execute":"guest-exec", "arguments":{"path": "%s", "arg": ["srv"], "capture-output": "interactive"}}' % path] p = subprocess.run(cmd, stdout=subprocess.PIPE) response = p.stdout.decode('utf-8') print('response: %s' % response) response = json.loads(response)['return'] cid = int(response['cid']) port = int(response['port']) return (cid, port) def srv_conn(cid, port): sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) print('connect...') sock.connect((cid, port)) print('connected') return sock def run_cycle(sock, size): tp = random.choice([TYPE_OUT, TYPE_ERR]) pkt = make_pkt(size, tp) if len(pkt) > 1024*1024: print('Send pkt, size = %d' % len(pkt)) sock.send(pkt) payload = recv_pkt(sock, pkt) def test_sequence(sock, n): for i in range(n): run_cycle(sock, i) def test_rand_small(sock): for i in range(1000): size = random.randint(0, 1024*1024) run_cycle(sock, size) def test_rand_big(sock): for i in range(10): size = random.randint(1024*1024*100, 1024*1024*200) run_cycle(sock, size) def run_client(vm_name): path = SRV_PATH + sys.argv[0] cid, port = run_command(vm_name, path) print('start on %d %d' % (cid, port)) sock = srv_conn(cid, port) print('test 65537') test_sequence(sock, 65537) print('test rand small') test_rand_small(sock) print('test rand big') test_rand_big(sock) # run_cycle(sock, 740471) def run_server(): hdr_size = struct.calcsize(PKT_HDR_FMT) while True: hdr = sys.stdin.buffer.read(hdr_size) if not hdr: return size, tp, digest = struct.unpack(PKT_HDR_FMT, hdr) payload = sys.stdin.buffer.read(size) buf = sys.stderr.buffer if tp == TYPE_ERR else sys.stdout.buffer res = buf.write(hdr + payload) buf.flush() def usage(): fn = sys.argv[0] print('Run as server:\n' + ('\t%s srv\n\n' % fn) + 'Run as client:\n' + ('\t%s clt <VM name>' % fn)) sys.exit(-1) def main(): if len(sys.argv) < 2: usage() side = sys.argv[1] if side == 'srv': run_server() elif side == 'clt' and len(sys.argv) == 3: run_client(sys.argv[2]) else: usage() main()