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 "hw/virtio/vhost-user.h"
  16#include "chardev/char-fe.h"
  17#include "qapi/error.h"
  18#include "qapi/qapi-commands-net.h"
  19#include "qemu/config-file.h"
  20#include "qemu/error-report.h"
  21#include "qemu/option.h"
  22#include "trace.h"
  23
  24typedef struct NetVhostUserState {
  25    NetClientState nc;
  26    CharBackend chr; /* only queue index 0 */
  27    VhostUserState *vhost_user;
  28    VHostNetState *vhost_net;
  29    guint watch;
  30    uint64_t acked_features;
  31    bool started;
  32} NetVhostUserState;
  33
  34VHostNetState *vhost_user_get_vhost_net(NetClientState *nc)
  35{
  36    NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
  37    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
  38    return s->vhost_net;
  39}
  40
  41uint64_t vhost_user_get_acked_features(NetClientState *nc)
  42{
  43    NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
  44    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
  45    return s->acked_features;
  46}
  47
  48static void vhost_user_stop(int queues, NetClientState *ncs[])
  49{
  50    NetVhostUserState *s;
  51    int i;
  52
  53    for (i = 0; i < queues; i++) {
  54        assert(ncs[i]->info->type == NET_CLIENT_DRIVER_VHOST_USER);
  55
  56        s = DO_UPCAST(NetVhostUserState, nc, ncs[i]);
  57
  58        if (s->vhost_net) {
  59            /* save acked features */
  60            uint64_t features = vhost_net_get_acked_features(s->vhost_net);
  61            if (features) {
  62                s->acked_features = features;
  63            }
  64            vhost_net_cleanup(s->vhost_net);
  65        }
  66    }
  67}
  68
  69static int vhost_user_start(int queues, NetClientState *ncs[],
  70                            VhostUserState *be)
  71{
  72    VhostNetOptions options;
  73    struct vhost_net *net = NULL;
  74    NetVhostUserState *s;
  75    int max_queues;
  76    int i;
  77
  78    options.backend_type = VHOST_BACKEND_TYPE_USER;
  79
  80    for (i = 0; i < queues; i++) {
  81        assert(ncs[i]->info->type == NET_CLIENT_DRIVER_VHOST_USER);
  82
  83        s = DO_UPCAST(NetVhostUserState, nc, ncs[i]);
  84
  85        options.net_backend = ncs[i];
  86        options.opaque      = be;
  87        options.busyloop_timeout = 0;
  88        net = vhost_net_init(&options);
  89        if (!net) {
  90            error_report("failed to init vhost_net for queue %d", i);
  91            goto err;
  92        }
  93
  94        if (i == 0) {
  95            max_queues = vhost_net_get_max_queues(net);
  96            if (queues > max_queues) {
  97                error_report("you are asking more queues than supported: %d",
  98                             max_queues);
  99                goto err;
 100            }
 101        }
 102
 103        if (s->vhost_net) {
 104            vhost_net_cleanup(s->vhost_net);
 105            g_free(s->vhost_net);
 106        }
 107        s->vhost_net = net;
 108    }
 109
 110    return 0;
 111
 112err:
 113    if (net) {
 114        vhost_net_cleanup(net);
 115        g_free(net);
 116    }
 117    vhost_user_stop(i, ncs);
 118    return -1;
 119}
 120
 121static ssize_t vhost_user_receive(NetClientState *nc, const uint8_t *buf,
 122                                  size_t size)
 123{
 124    /* In case of RARP (message size is 60) notify backup to send a fake RARP.
 125       This fake RARP will be sent by backend only for guest
 126       without GUEST_ANNOUNCE capability.
 127     */
 128    if (size == 60) {
 129        NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
 130        int r;
 131        static int display_rarp_failure = 1;
 132        char mac_addr[6];
 133
 134        /* extract guest mac address from the RARP message */
 135        memcpy(mac_addr, &buf[6], 6);
 136
 137        r = vhost_net_notify_migration_done(s->vhost_net, mac_addr);
 138
 139        if ((r != 0) && (display_rarp_failure)) {
 140            fprintf(stderr,
 141                    "Vhost user backend fails to broadcast fake RARP\n");
 142            fflush(stderr);
 143            display_rarp_failure = 0;
 144        }
 145    }
 146
 147    return size;
 148}
 149
 150static void net_vhost_user_cleanup(NetClientState *nc)
 151{
 152    NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
 153
 154    if (s->vhost_net) {
 155        vhost_net_cleanup(s->vhost_net);
 156        g_free(s->vhost_net);
 157        s->vhost_net = NULL;
 158    }
 159    if (nc->queue_index == 0) {
 160        if (s->watch) {
 161            g_source_remove(s->watch);
 162            s->watch = 0;
 163        }
 164        qemu_chr_fe_deinit(&s->chr, true);
 165        if (s->vhost_user) {
 166            vhost_user_cleanup(s->vhost_user);
 167            g_free(s->vhost_user);
 168            s->vhost_user = NULL;
 169        }
 170    }
 171
 172    qemu_purge_queued_packets(nc);
 173}
 174
 175static bool vhost_user_has_vnet_hdr(NetClientState *nc)
 176{
 177    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
 178
 179    return true;
 180}
 181
 182static bool vhost_user_has_ufo(NetClientState *nc)
 183{
 184    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
 185
 186    return true;
 187}
 188
 189static NetClientInfo net_vhost_user_info = {
 190        .type = NET_CLIENT_DRIVER_VHOST_USER,
 191        .size = sizeof(NetVhostUserState),
 192        .receive = vhost_user_receive,
 193        .cleanup = net_vhost_user_cleanup,
 194        .has_vnet_hdr = vhost_user_has_vnet_hdr,
 195        .has_ufo = vhost_user_has_ufo,
 196};
 197
 198static gboolean net_vhost_user_watch(GIOChannel *chan, GIOCondition cond,
 199                                           void *opaque)
 200{
 201    NetVhostUserState *s = opaque;
 202
 203    qemu_chr_fe_disconnect(&s->chr);
 204
 205    return TRUE;
 206}
 207
 208static void net_vhost_user_event(void *opaque, int event);
 209
 210static void chr_closed_bh(void *opaque)
 211{
 212    const char *name = opaque;
 213    NetClientState *ncs[MAX_QUEUE_NUM];
 214    NetVhostUserState *s;
 215    Error *err = NULL;
 216    int queues;
 217
 218    queues = qemu_find_net_clients_except(name, ncs,
 219                                          NET_CLIENT_DRIVER_NIC,
 220                                          MAX_QUEUE_NUM);
 221    assert(queues < MAX_QUEUE_NUM);
 222
 223    s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
 224
 225    qmp_set_link(name, false, &err);
 226    vhost_user_stop(queues, ncs);
 227
 228    qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, net_vhost_user_event,
 229                             NULL, opaque, NULL, true);
 230
 231    if (err) {
 232        error_report_err(err);
 233    }
 234}
 235
 236static void net_vhost_user_event(void *opaque, int event)
 237{
 238    const char *name = opaque;
 239    NetClientState *ncs[MAX_QUEUE_NUM];
 240    NetVhostUserState *s;
 241    Chardev *chr;
 242    Error *err = NULL;
 243    int queues;
 244
 245    queues = qemu_find_net_clients_except(name, ncs,
 246                                          NET_CLIENT_DRIVER_NIC,
 247                                          MAX_QUEUE_NUM);
 248    assert(queues < MAX_QUEUE_NUM);
 249
 250    s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
 251    chr = qemu_chr_fe_get_driver(&s->chr);
 252    trace_vhost_user_event(chr->label, event);
 253    switch (event) {
 254    case CHR_EVENT_OPENED:
 255        if (vhost_user_start(queues, ncs, s->vhost_user) < 0) {
 256            qemu_chr_fe_disconnect(&s->chr);
 257            return;
 258        }
 259        s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP,
 260                                         net_vhost_user_watch, s);
 261        qmp_set_link(name, true, &err);
 262        s->started = true;
 263        break;
 264    case CHR_EVENT_CLOSED:
 265        /* a close event may happen during a read/write, but vhost
 266         * code assumes the vhost_dev remains setup, so delay the
 267         * stop & clear to idle.
 268         * FIXME: better handle failure in vhost code, remove bh
 269         */
 270        if (s->watch) {
 271            AioContext *ctx = qemu_get_current_aio_context();
 272
 273            g_source_remove(s->watch);
 274            s->watch = 0;
 275            qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL,
 276                                     NULL, NULL, false);
 277
 278            aio_bh_schedule_oneshot(ctx, chr_closed_bh, opaque);
 279        }
 280        break;
 281    }
 282
 283    if (err) {
 284        error_report_err(err);
 285    }
 286}
 287
 288static int net_vhost_user_init(NetClientState *peer, const char *device,
 289                               const char *name, Chardev *chr,
 290                               int queues)
 291{
 292    Error *err = NULL;
 293    NetClientState *nc, *nc0 = NULL;
 294    VhostUserState *user = NULL;
 295    NetVhostUserState *s = NULL;
 296    int i;
 297
 298    assert(name);
 299    assert(queues > 0);
 300
 301    user = vhost_user_init();
 302    if (!user) {
 303        error_report("failed to init vhost_user");
 304        goto err;
 305    }
 306
 307    for (i = 0; i < queues; i++) {
 308        nc = qemu_new_net_client(&net_vhost_user_info, peer, device, name);
 309        snprintf(nc->info_str, sizeof(nc->info_str), "vhost-user%d to %s",
 310                 i, chr->label);
 311        nc->queue_index = i;
 312        if (!nc0) {
 313            nc0 = nc;
 314            s = DO_UPCAST(NetVhostUserState, nc, nc);
 315            if (!qemu_chr_fe_init(&s->chr, chr, &err)) {
 316                error_report_err(err);
 317                goto err;
 318            }
 319            user->chr = &s->chr;
 320        }
 321        s = DO_UPCAST(NetVhostUserState, nc, nc);
 322        s->vhost_user = user;
 323    }
 324
 325    s = DO_UPCAST(NetVhostUserState, nc, nc0);
 326    do {
 327        if (qemu_chr_fe_wait_connected(&s->chr, &err) < 0) {
 328            error_report_err(err);
 329            goto err;
 330        }
 331        qemu_chr_fe_set_handlers(&s->chr, NULL, NULL,
 332                                 net_vhost_user_event, NULL, nc0->name, NULL,
 333                                 true);
 334    } while (!s->started);
 335
 336    assert(s->vhost_net);
 337
 338    return 0;
 339
 340err:
 341    if (user) {
 342        vhost_user_cleanup(user);
 343        g_free(user);
 344        if (s) {
 345            s->vhost_user = NULL;
 346        }
 347    }
 348    if (nc0) {
 349        qemu_del_net_client(nc0);
 350    }
 351
 352    return -1;
 353}
 354
 355static Chardev *net_vhost_claim_chardev(
 356    const NetdevVhostUserOptions *opts, Error **errp)
 357{
 358    Chardev *chr = qemu_chr_find(opts->chardev);
 359
 360    if (chr == NULL) {
 361        error_setg(errp, "chardev \"%s\" not found", opts->chardev);
 362        return NULL;
 363    }
 364
 365    if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE)) {
 366        error_setg(errp, "chardev \"%s\" is not reconnectable",
 367                   opts->chardev);
 368        return NULL;
 369    }
 370    if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_FD_PASS)) {
 371        error_setg(errp, "chardev \"%s\" does not support FD passing",
 372                   opts->chardev);
 373        return NULL;
 374    }
 375
 376    return chr;
 377}
 378
 379static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp)
 380{
 381    const char *name = opaque;
 382    const char *driver, *netdev;
 383
 384    driver = qemu_opt_get(opts, "driver");
 385    netdev = qemu_opt_get(opts, "netdev");
 386
 387    if (!driver || !netdev) {
 388        return 0;
 389    }
 390
 391    if (strcmp(netdev, name) == 0 &&
 392        !g_str_has_prefix(driver, "virtio-net-")) {
 393        error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
 394        return -1;
 395    }
 396
 397    return 0;
 398}
 399
 400int net_init_vhost_user(const Netdev *netdev, const char *name,
 401                        NetClientState *peer, Error **errp)
 402{
 403    int queues;
 404    const NetdevVhostUserOptions *vhost_user_opts;
 405    Chardev *chr;
 406
 407    assert(netdev->type == NET_CLIENT_DRIVER_VHOST_USER);
 408    vhost_user_opts = &netdev->u.vhost_user;
 409
 410    chr = net_vhost_claim_chardev(vhost_user_opts, errp);
 411    if (!chr) {
 412        return -1;
 413    }
 414
 415    /* verify net frontend */
 416    if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net,
 417                          (char *)name, errp)) {
 418        return -1;
 419    }
 420
 421    queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
 422    if (queues < 1 || queues > MAX_QUEUE_NUM) {
 423        error_setg(errp,
 424                   "vhost-user number of queues must be in range [1, %d]",
 425                   MAX_QUEUE_NUM);
 426        return -1;
 427    }
 428
 429    return net_vhost_user_init(peer, "vhost_user", name, chr, queues);
 430}
 431