qemu/net/vhost-user.c
<<
>>
Prefs
   1/*
   2 * vhost-user.c
   3 *
   4 * Copyright (c) 2013 Virtual Open Systems Sarl.
   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
  11#include "qemu/osdep.h"
  12#include "clients.h"
  13#include "net/vhost_net.h"
  14#include "net/vhost-user.h"
  15#include "sysemu/char.h"
  16#include "qemu/config-file.h"
  17#include "qemu/error-report.h"
  18#include "qmp-commands.h"
  19#include "trace.h"
  20
  21typedef struct VhostUserState {
  22    NetClientState nc;
  23    CharDriverState *chr;
  24    VHostNetState *vhost_net;
  25} VhostUserState;
  26
  27typedef struct VhostUserChardevProps {
  28    bool is_socket;
  29    bool is_unix;
  30} VhostUserChardevProps;
  31
  32VHostNetState *vhost_user_get_vhost_net(NetClientState *nc)
  33{
  34    VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc);
  35    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
  36    return s->vhost_net;
  37}
  38
  39static int vhost_user_running(VhostUserState *s)
  40{
  41    return (s->vhost_net) ? 1 : 0;
  42}
  43
  44static void vhost_user_stop(int queues, NetClientState *ncs[])
  45{
  46    VhostUserState *s;
  47    int i;
  48
  49    for (i = 0; i < queues; i++) {
  50        assert (ncs[i]->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
  51
  52        s = DO_UPCAST(VhostUserState, nc, ncs[i]);
  53        if (!vhost_user_running(s)) {
  54            continue;
  55        }
  56
  57        if (s->vhost_net) {
  58            vhost_net_cleanup(s->vhost_net);
  59            s->vhost_net = NULL;
  60        }
  61    }
  62}
  63
  64static int vhost_user_start(int queues, NetClientState *ncs[])
  65{
  66    VhostNetOptions options;
  67    VhostUserState *s;
  68    int max_queues;
  69    int i;
  70
  71    options.backend_type = VHOST_BACKEND_TYPE_USER;
  72
  73    for (i = 0; i < queues; i++) {
  74        assert (ncs[i]->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
  75
  76        s = DO_UPCAST(VhostUserState, nc, ncs[i]);
  77        if (vhost_user_running(s)) {
  78            continue;
  79        }
  80
  81        options.net_backend = ncs[i];
  82        options.opaque      = s->chr;
  83        s->vhost_net = vhost_net_init(&options);
  84        if (!s->vhost_net) {
  85            error_report("failed to init vhost_net for queue %d", i);
  86            goto err;
  87        }
  88
  89        if (i == 0) {
  90            max_queues = vhost_net_get_max_queues(s->vhost_net);
  91            if (queues > max_queues) {
  92                error_report("you are asking more queues than supported: %d",
  93                             max_queues);
  94                goto err;
  95            }
  96        }
  97    }
  98
  99    return 0;
 100
 101err:
 102    vhost_user_stop(i + 1, ncs);
 103    return -1;
 104}
 105
 106static ssize_t vhost_user_receive(NetClientState *nc, const uint8_t *buf,
 107                                  size_t size)
 108{
 109    /* In case of RARP (message size is 60) notify backup to send a fake RARP.
 110       This fake RARP will be sent by backend only for guest
 111       without GUEST_ANNOUNCE capability.
 112     */
 113    if (size == 60) {
 114        VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc);
 115        int r;
 116        static int display_rarp_failure = 1;
 117        char mac_addr[6];
 118
 119        /* extract guest mac address from the RARP message */
 120        memcpy(mac_addr, &buf[6], 6);
 121
 122        r = vhost_net_notify_migration_done(s->vhost_net, mac_addr);
 123
 124        if ((r != 0) && (display_rarp_failure)) {
 125            fprintf(stderr,
 126                    "Vhost user backend fails to broadcast fake RARP\n");
 127            fflush(stderr);
 128            display_rarp_failure = 0;
 129        }
 130    }
 131
 132    return size;
 133}
 134
 135static void vhost_user_cleanup(NetClientState *nc)
 136{
 137    VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc);
 138
 139    if (s->vhost_net) {
 140        vhost_net_cleanup(s->vhost_net);
 141        s->vhost_net = NULL;
 142    }
 143
 144    qemu_purge_queued_packets(nc);
 145}
 146
 147static bool vhost_user_has_vnet_hdr(NetClientState *nc)
 148{
 149    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
 150
 151    return true;
 152}
 153
 154static bool vhost_user_has_ufo(NetClientState *nc)
 155{
 156    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
 157
 158    return true;
 159}
 160
 161static NetClientInfo net_vhost_user_info = {
 162        .type = NET_CLIENT_OPTIONS_KIND_VHOST_USER,
 163        .size = sizeof(VhostUserState),
 164        .receive = vhost_user_receive,
 165        .cleanup = vhost_user_cleanup,
 166        .has_vnet_hdr = vhost_user_has_vnet_hdr,
 167        .has_ufo = vhost_user_has_ufo,
 168};
 169
 170static void net_vhost_user_event(void *opaque, int event)
 171{
 172    const char *name = opaque;
 173    NetClientState *ncs[MAX_QUEUE_NUM];
 174    VhostUserState *s;
 175    Error *err = NULL;
 176    int queues;
 177
 178    queues = qemu_find_net_clients_except(name, ncs,
 179                                          NET_CLIENT_OPTIONS_KIND_NIC,
 180                                          MAX_QUEUE_NUM);
 181    assert(queues < MAX_QUEUE_NUM);
 182
 183    s = DO_UPCAST(VhostUserState, nc, ncs[0]);
 184    trace_vhost_user_event(s->chr->label, event);
 185    switch (event) {
 186    case CHR_EVENT_OPENED:
 187        if (vhost_user_start(queues, ncs) < 0) {
 188            exit(1);
 189        }
 190        qmp_set_link(name, true, &err);
 191        break;
 192    case CHR_EVENT_CLOSED:
 193        qmp_set_link(name, false, &err);
 194        vhost_user_stop(queues, ncs);
 195        break;
 196    }
 197
 198    if (err) {
 199        error_report_err(err);
 200    }
 201}
 202
 203static int net_vhost_user_init(NetClientState *peer, const char *device,
 204                               const char *name, CharDriverState *chr,
 205                               int queues)
 206{
 207    NetClientState *nc;
 208    VhostUserState *s;
 209    int i;
 210
 211    assert(name);
 212    assert(queues > 0);
 213
 214    for (i = 0; i < queues; i++) {
 215        nc = qemu_new_net_client(&net_vhost_user_info, peer, device, name);
 216
 217        snprintf(nc->info_str, sizeof(nc->info_str), "vhost-user%d to %s",
 218                 i, chr->label);
 219
 220        nc->queue_index = i;
 221
 222        s = DO_UPCAST(VhostUserState, nc, nc);
 223        s->chr = chr;
 224    }
 225
 226    qemu_chr_add_handlers(chr, NULL, NULL, net_vhost_user_event, nc[0].name);
 227
 228    return 0;
 229}
 230
 231static int net_vhost_chardev_opts(void *opaque,
 232                                  const char *name, const char *value,
 233                                  Error **errp)
 234{
 235    VhostUserChardevProps *props = opaque;
 236
 237    if (strcmp(name, "backend") == 0 && strcmp(value, "socket") == 0) {
 238        props->is_socket = true;
 239    } else if (strcmp(name, "path") == 0) {
 240        props->is_unix = true;
 241    } else if (strcmp(name, "server") == 0) {
 242    } else {
 243        error_setg(errp,
 244                   "vhost-user does not support a chardev with option %s=%s",
 245                   name, value);
 246        return -1;
 247    }
 248    return 0;
 249}
 250
 251static CharDriverState *net_vhost_parse_chardev(
 252    const NetdevVhostUserOptions *opts, Error **errp)
 253{
 254    CharDriverState *chr = qemu_chr_find(opts->chardev);
 255    VhostUserChardevProps props;
 256
 257    if (chr == NULL) {
 258        error_setg(errp, "chardev \"%s\" not found", opts->chardev);
 259        return NULL;
 260    }
 261
 262    /* inspect chardev opts */
 263    memset(&props, 0, sizeof(props));
 264    if (qemu_opt_foreach(chr->opts, net_vhost_chardev_opts, &props, errp)) {
 265        return NULL;
 266    }
 267
 268    if (!props.is_socket || !props.is_unix) {
 269        error_setg(errp, "chardev \"%s\" is not a unix socket",
 270                   opts->chardev);
 271        return NULL;
 272    }
 273
 274    qemu_chr_fe_claim_no_fail(chr);
 275
 276    return chr;
 277}
 278
 279static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp)
 280{
 281    const char *name = opaque;
 282    const char *driver, *netdev;
 283    const char virtio_name[] = "virtio-net-";
 284
 285    driver = qemu_opt_get(opts, "driver");
 286    netdev = qemu_opt_get(opts, "netdev");
 287
 288    if (!driver || !netdev) {
 289        return 0;
 290    }
 291
 292    if (strcmp(netdev, name) == 0 &&
 293        strncmp(driver, virtio_name, strlen(virtio_name)) != 0) {
 294        error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
 295        return -1;
 296    }
 297
 298    return 0;
 299}
 300
 301int net_init_vhost_user(const NetClientOptions *opts, const char *name,
 302                        NetClientState *peer, Error **errp)
 303{
 304    int queues;
 305    const NetdevVhostUserOptions *vhost_user_opts;
 306    CharDriverState *chr;
 307
 308    assert(opts->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
 309    vhost_user_opts = opts->u.vhost_user.data;
 310
 311    chr = net_vhost_parse_chardev(vhost_user_opts, errp);
 312    if (!chr) {
 313        return -1;
 314    }
 315
 316    /* verify net frontend */
 317    if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net,
 318                          (char *)name, errp)) {
 319        return -1;
 320    }
 321
 322    queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
 323    if (queues < 1 || queues > MAX_QUEUE_NUM) {
 324        error_setg(errp,
 325                   "vhost-user number of queues must be in range [1, %d]",
 326                   MAX_QUEUE_NUM);
 327        return -1;
 328    }
 329
 330    return net_vhost_user_init(peer, "vhost_user", name, chr, queues);
 331}
 332