qemu/tests/qtest/virtio-net-test.c
<<
>>
Prefs
   1/*
   2 * QTest testcase for VirtIO NIC
   3 *
   4 * Copyright (c) 2014 SUSE LINUX Products GmbH
   5 *
   6 * This work is licensed under the terms of the GNU GPL, version 2 or later.
   7 * See the COPYING file in the top-level directory.
   8 */
   9
  10#include "qemu/osdep.h"
  11#include "qemu-common.h"
  12#include "libqtest-single.h"
  13#include "qemu/iov.h"
  14#include "qemu/module.h"
  15#include "qapi/qmp/qdict.h"
  16#include "hw/virtio/virtio-net.h"
  17#include "libqos/qgraph.h"
  18#include "libqos/virtio-net.h"
  19
  20#ifndef ETH_P_RARP
  21#define ETH_P_RARP 0x8035
  22#endif
  23
  24#define PCI_SLOT_HP             0x06
  25#define PCI_SLOT                0x04
  26
  27#define QVIRTIO_NET_TIMEOUT_US (30 * 1000 * 1000)
  28#define VNET_HDR_SIZE sizeof(struct virtio_net_hdr_mrg_rxbuf)
  29
  30#ifndef _WIN32
  31
  32static void rx_test(QVirtioDevice *dev,
  33                    QGuestAllocator *alloc, QVirtQueue *vq,
  34                    int socket)
  35{
  36    QTestState *qts = global_qtest;
  37    uint64_t req_addr;
  38    uint32_t free_head;
  39    char test[] = "TEST";
  40    char buffer[64];
  41    int len = htonl(sizeof(test));
  42    struct iovec iov[] = {
  43        {
  44            .iov_base = &len,
  45            .iov_len = sizeof(len),
  46        }, {
  47            .iov_base = test,
  48            .iov_len = sizeof(test),
  49        },
  50    };
  51    int ret;
  52
  53    req_addr = guest_alloc(alloc, 64);
  54
  55    free_head = qvirtqueue_add(qts, vq, req_addr, 64, true, false);
  56    qvirtqueue_kick(qts, dev, vq, free_head);
  57
  58    ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test));
  59    g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len));
  60
  61    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
  62                           QVIRTIO_NET_TIMEOUT_US);
  63    memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test));
  64    g_assert_cmpstr(buffer, ==, "TEST");
  65
  66    guest_free(alloc, req_addr);
  67}
  68
  69static void tx_test(QVirtioDevice *dev,
  70                    QGuestAllocator *alloc, QVirtQueue *vq,
  71                    int socket)
  72{
  73    QTestState *qts = global_qtest;
  74    uint64_t req_addr;
  75    uint32_t free_head;
  76    uint32_t len;
  77    char buffer[64];
  78    int ret;
  79
  80    req_addr = guest_alloc(alloc, 64);
  81    memwrite(req_addr + VNET_HDR_SIZE, "TEST", 4);
  82
  83    free_head = qvirtqueue_add(qts, vq, req_addr, 64, false, false);
  84    qvirtqueue_kick(qts, dev, vq, free_head);
  85
  86    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
  87                           QVIRTIO_NET_TIMEOUT_US);
  88    guest_free(alloc, req_addr);
  89
  90    ret = qemu_recv(socket, &len, sizeof(len), 0);
  91    g_assert_cmpint(ret, ==, sizeof(len));
  92    len = ntohl(len);
  93
  94    ret = qemu_recv(socket, buffer, len, 0);
  95    g_assert_cmpstr(buffer, ==, "TEST");
  96}
  97
  98static void rx_stop_cont_test(QVirtioDevice *dev,
  99                              QGuestAllocator *alloc, QVirtQueue *vq,
 100                              int socket)
 101{
 102    QTestState *qts = global_qtest;
 103    uint64_t req_addr;
 104    uint32_t free_head;
 105    char test[] = "TEST";
 106    char buffer[64];
 107    int len = htonl(sizeof(test));
 108    QDict *rsp;
 109    struct iovec iov[] = {
 110        {
 111            .iov_base = &len,
 112            .iov_len = sizeof(len),
 113        }, {
 114            .iov_base = test,
 115            .iov_len = sizeof(test),
 116        },
 117    };
 118    int ret;
 119
 120    req_addr = guest_alloc(alloc, 64);
 121
 122    free_head = qvirtqueue_add(qts, vq, req_addr, 64, true, false);
 123    qvirtqueue_kick(qts, dev, vq, free_head);
 124
 125    rsp = qmp("{ 'execute' : 'stop'}");
 126    qobject_unref(rsp);
 127
 128    ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test));
 129    g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len));
 130
 131    /* We could check the status, but this command is more importantly to
 132     * ensure the packet data gets queued in QEMU, before we do 'cont'.
 133     */
 134    rsp = qmp("{ 'execute' : 'query-status'}");
 135    qobject_unref(rsp);
 136    rsp = qmp("{ 'execute' : 'cont'}");
 137    qobject_unref(rsp);
 138
 139    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
 140                           QVIRTIO_NET_TIMEOUT_US);
 141    memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test));
 142    g_assert_cmpstr(buffer, ==, "TEST");
 143
 144    guest_free(alloc, req_addr);
 145}
 146
 147static void send_recv_test(void *obj, void *data, QGuestAllocator *t_alloc)
 148{
 149    QVirtioNet *net_if = obj;
 150    QVirtioDevice *dev = net_if->vdev;
 151    QVirtQueue *rx = net_if->queues[0];
 152    QVirtQueue *tx = net_if->queues[1];
 153    int *sv = data;
 154
 155    rx_test(dev, t_alloc, rx, sv[0]);
 156    tx_test(dev, t_alloc, tx, sv[0]);
 157}
 158
 159static void stop_cont_test(void *obj, void *data, QGuestAllocator *t_alloc)
 160{
 161    QVirtioNet *net_if = obj;
 162    QVirtioDevice *dev = net_if->vdev;
 163    QVirtQueue *rx = net_if->queues[0];
 164    int *sv = data;
 165
 166    rx_stop_cont_test(dev, t_alloc, rx, sv[0]);
 167}
 168
 169#endif
 170
 171static void hotplug(void *obj, void *data, QGuestAllocator *t_alloc)
 172{
 173    QVirtioPCIDevice *dev = obj;
 174    QTestState *qts = dev->pdev->bus->qts;
 175    const char *arch = qtest_get_arch();
 176
 177    qtest_qmp_device_add(qts, "virtio-net-pci", "net1",
 178                         "{'addr': %s}", stringify(PCI_SLOT_HP));
 179
 180    if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
 181        qpci_unplug_acpi_device_test(qts, "net1", PCI_SLOT_HP);
 182    }
 183}
 184
 185static void announce_self(void *obj, void *data, QGuestAllocator *t_alloc)
 186{
 187    int *sv = data;
 188    char buffer[60];
 189    int len;
 190    QDict *rsp;
 191    int ret;
 192    uint16_t *proto = (uint16_t *)&buffer[12];
 193    size_t total_received = 0;
 194    uint64_t start, now, last_rxt, deadline;
 195
 196    /* Send a set of packets over a few second period */
 197    rsp = qmp("{ 'execute' : 'announce-self', "
 198                  " 'arguments': {"
 199                      " 'initial': 20, 'max': 100,"
 200                      " 'rounds': 300, 'step': 10, 'id': 'bob' } }");
 201    assert(!qdict_haskey(rsp, "error"));
 202    qobject_unref(rsp);
 203
 204    /* Catch the first packet and make sure it's a RARP */
 205    ret = qemu_recv(sv[0], &len, sizeof(len), 0);
 206    g_assert_cmpint(ret, ==,  sizeof(len));
 207    len = ntohl(len);
 208
 209    ret = qemu_recv(sv[0], buffer, len, 0);
 210    g_assert_cmpint(*proto, ==, htons(ETH_P_RARP));
 211
 212    /*
 213     * Stop the announcment by settings rounds to 0 on the
 214     * existing timer.
 215     */
 216    rsp = qmp("{ 'execute' : 'announce-self', "
 217                  " 'arguments': {"
 218                      " 'initial': 20, 'max': 100,"
 219                      " 'rounds': 0, 'step': 10, 'id': 'bob' } }");
 220    assert(!qdict_haskey(rsp, "error"));
 221    qobject_unref(rsp);
 222
 223    /* Now make sure the packets stop */
 224
 225    /* Times are in us */
 226    start = g_get_monotonic_time();
 227    /* 30 packets, max gap 100ms, * 4 for wiggle */
 228    deadline = start + 1000 * (100 * 30 * 4);
 229    last_rxt = start;
 230
 231    while (true) {
 232        int saved_err;
 233        ret = qemu_recv(sv[0], buffer, 60, MSG_DONTWAIT);
 234        saved_err = errno;
 235        now = g_get_monotonic_time();
 236        g_assert_cmpint(now, <, deadline);
 237
 238        if (ret >= 0) {
 239            if (ret) {
 240                last_rxt = now;
 241            }
 242            total_received += ret;
 243
 244            /* Check it's not spewing loads */
 245            g_assert_cmpint(total_received, <, 60 * 30 * 2);
 246        } else {
 247            g_assert_cmpint(saved_err, ==, EAGAIN);
 248
 249            /* 400ms, i.e. 4 worst case gaps */
 250            if ((now - last_rxt) > (1000 * 100 * 4)) {
 251                /* Nothings arrived for a while - must have stopped */
 252                break;
 253            };
 254
 255            /* 100ms */
 256            g_usleep(1000 * 100);
 257        }
 258    };
 259}
 260
 261static void virtio_net_test_cleanup(void *sockets)
 262{
 263    int *sv = sockets;
 264
 265    close(sv[0]);
 266    qos_invalidate_command_line();
 267    close(sv[1]);
 268    g_free(sv);
 269}
 270
 271static void *virtio_net_test_setup(GString *cmd_line, void *arg)
 272{
 273    int ret;
 274    int *sv = g_new(int, 2);
 275
 276    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv);
 277    g_assert_cmpint(ret, !=, -1);
 278
 279    g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ", sv[1]);
 280
 281    g_test_queue_destroy(virtio_net_test_cleanup, sv);
 282    return sv;
 283}
 284
 285static void large_tx(void *obj, void *data, QGuestAllocator *t_alloc)
 286{
 287    QVirtioNet *dev = obj;
 288    QVirtQueue *vq = dev->queues[1];
 289    uint64_t req_addr;
 290    uint32_t free_head;
 291    size_t alloc_size = (size_t)data / 64;
 292    QTestState *qts = global_qtest;
 293    int i;
 294
 295    /* Bypass the limitation by pointing several descriptors to a single
 296     * smaller area */
 297    req_addr = guest_alloc(t_alloc, alloc_size);
 298    free_head = qvirtqueue_add(qts, vq, req_addr, alloc_size, false, true);
 299
 300    for (i = 0; i < 64; i++) {
 301        qvirtqueue_add(qts, vq, req_addr, alloc_size, false, i != 63);
 302    }
 303    qvirtqueue_kick(qts, dev->vdev, vq, free_head);
 304
 305    qvirtio_wait_used_elem(qts, dev->vdev, vq, free_head, NULL,
 306                           QVIRTIO_NET_TIMEOUT_US);
 307    guest_free(t_alloc, req_addr);
 308}
 309
 310static void *virtio_net_test_setup_nosocket(GString *cmd_line, void *arg)
 311{
 312    g_string_append(cmd_line, " -netdev hubport,hubid=0,id=hs0 ");
 313    return arg;
 314}
 315
 316static void register_virtio_net_test(void)
 317{
 318    QOSGraphTestOptions opts = {
 319        .before = virtio_net_test_setup,
 320    };
 321
 322    qos_add_test("hotplug", "virtio-net-pci", hotplug, &opts);
 323#ifndef _WIN32
 324    qos_add_test("basic", "virtio-net", send_recv_test, &opts);
 325    qos_add_test("rx_stop_cont", "virtio-net", stop_cont_test, &opts);
 326#endif
 327    qos_add_test("announce-self", "virtio-net", announce_self, &opts);
 328
 329    /* These tests do not need a loopback backend.  */
 330    opts.before = virtio_net_test_setup_nosocket;
 331    opts.arg = (gpointer)UINT_MAX;
 332    qos_add_test("large_tx/uint_max", "virtio-net", large_tx, &opts);
 333    opts.arg = (gpointer)NET_BUFSIZE;
 334    qos_add_test("large_tx/net_bufsize", "virtio-net", large_tx, &opts);
 335}
 336
 337libqos_init(register_virtio_net_test);
 338