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()

Reply via email to