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        options.nvqs = 2;
  89        net = vhost_net_init(&options);
  90        if (!net) {
  91            error_report("failed to init vhost_net for queue %d", i);
  92            goto err;
  93        }
  94
  95        if (i == 0) {
  96            max_queues = vhost_net_get_max_queues(net);
  97            if (queues > max_queues) {
  98                error_report("you are asking more queues than supported: %d",
  99                             max_queues);
 100                goto err;
 101            }
 102        }
 103
 104        if (s->vhost_net) {
 105            vhost_net_cleanup(s->vhost_net);
 106            g_free(s->vhost_net);
 107        }
 108        s->vhost_net = net;
 109    }
 110
 111    return 0;
 112
 113err:
 114    if (net) {
 115        vhost_net_cleanup(net);
 116        g_free(net);
 117    }
 118    vhost_user_stop(i, ncs);
 119    return -1;
 120}
 121
 122static ssize_t vhost_user_receive(NetClientState *nc, const uint8_t *buf,
 123                                  size_t size)
 124{
 125    /* In case of RARP (message size is 60) notify backup to send a fake RARP.
 126       This fake RARP will be sent by backend only for guest
 127       without GUEST_ANNOUNCE capability.
 128     */
 129    if (size == 60) {
 130        NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
 131        int r;
 132        static int display_rarp_failure = 1;
 133        char mac_addr[6];
 134
 135        /* extract guest mac address from the RARP message */
 136        memcpy(mac_addr, &buf[6], 6);
 137
 138        r = vhost_net_notify_migration_done(s->vhost_net, mac_addr);
 139
 140        if ((r != 0) && (display_rarp_failure)) {
 141            fprintf(stderr,
 142                    "Vhost user backend fails to broadcast fake RARP\n");
 143            fflush(stderr);
 144            display_rarp_failure = 0;
 145        }
 146    }
 147
 148    return size;
 149}
 150
 151static void net_vhost_user_cleanup(NetClientState *nc)
 152{
 153    NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
 154
 155    if (s->vhost_net) {
 156        vhost_net_cleanup(s->vhost_net);
 157        g_free(s->vhost_net);
 158        s->vhost_net = NULL;
 159    }
 160    if (nc->queue_index == 0) {
 161        if (s->watch) {
 162            g_source_remove(s->watch);
 163            s->watch = 0;
 164        }
 165        qemu_chr_fe_deinit(&s->chr, true);
 166        if (s->vhost_user) {
 167            vhost_user_cleanup(s->vhost_user);
 168            g_free(s->vhost_user);
 169            s->vhost_user = NULL;
 170        }
 171    }
 172
 173    qemu_purge_queued_packets(nc);
 174}
 175
 176static int vhost_user_set_vnet_endianness(NetClientState *nc,
 177                                          bool enable)
 178{
 179    /* Nothing to do.  If the server supports
 180     * VHOST_USER_PROTOCOL_F_CROSS_ENDIAN, it will get the
 181     * vnet header endianness from there.  If it doesn't, negotiation
 182     * fails.
 183     */
 184    return 0;
 185}
 186
 187static bool vhost_user_has_vnet_hdr(NetClientState *nc)
 188{
 189    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
 190
 191    return true;
 192}
 193
 194static bool vhost_user_has_ufo(NetClientState *nc)
 195{
 196    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
 197
 198    return true;
 199}
 200
 201static bool vhost_user_check_peer_type(NetClientState *nc, ObjectClass *oc,
 202                                       Error **errp)
 203{
 204    const char *driver = object_class_get_name(oc);
 205
 206    if (!g_str_has_prefix(driver, "virtio-net-")) {
 207        error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
 208        return false;
 209    }
 210
 211    return true;
 212}
 213
 214static NetClientInfo net_vhost_user_info = {
 215        .type = NET_CLIENT_DRIVER_VHOST_USER,
 216        .size = sizeof(NetVhostUserState),
 217        .receive = vhost_user_receive,
 218        .cleanup = net_vhost_user_cleanup,
 219        .has_vnet_hdr = vhost_user_has_vnet_hdr,
 220        .has_ufo = vhost_user_has_ufo,
 221        .set_vnet_be = vhost_user_set_vnet_endianness,
 222        .set_vnet_le = vhost_user_set_vnet_endianness,
 223        .check_peer_type = vhost_user_check_peer_type,
 224};
 225
 226static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
 227                                     void *opaque)
 228{
 229    NetVhostUserState *s = opaque;
 230
 231    qemu_chr_fe_disconnect(&s->chr);
 232
 233    return TRUE;
 234}
 235
 236static void net_vhost_user_event(void *opaque, QEMUChrEvent event);
 237
 238static void chr_closed_bh(void *opaque)
 239{
 240    const char *name = opaque;
 241    NetClientState *ncs[MAX_QUEUE_NUM];
 242    NetVhostUserState *s;
 243    Error *err = NULL;
 244    int queues, i;
 245
 246    queues = qemu_find_net_clients_except(name, ncs,
 247                                          NET_CLIENT_DRIVER_NIC,
 248                                          MAX_QUEUE_NUM);
 249    assert(queues < MAX_QUEUE_NUM);
 250
 251    s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
 252
 253    for (i = queues -1; i >= 0; i--) {
 254        s = DO_UPCAST(NetVhostUserState, nc, ncs[i]);
 255
 256        if (s->vhost_net) {
 257            s->acked_features = vhost_net_get_acked_features(s->vhost_net);
 258        }
 259    }
 260
 261    qmp_set_link(name, false, &err);
 262
 263    qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, net_vhost_user_event,
 264                             NULL, opaque, NULL, true);
 265
 266    if (err) {
 267        error_report_err(err);
 268    }
 269}
 270
 271static void net_vhost_user_event(void *opaque, QEMUChrEvent event)
 272{
 273    const char *name = opaque;
 274    NetClientState *ncs[MAX_QUEUE_NUM];
 275    NetVhostUserState *s;
 276    Chardev *chr;
 277    Error *err = NULL;
 278    int queues;
 279
 280    queues = qemu_find_net_clients_except(name, ncs,
 281                                          NET_CLIENT_DRIVER_NIC,
 282                                          MAX_QUEUE_NUM);
 283    assert(queues < MAX_QUEUE_NUM);
 284
 285    s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
 286    chr = qemu_chr_fe_get_driver(&s->chr);
 287    trace_vhost_user_event(chr->label, event);
 288    switch (event) {
 289    case CHR_EVENT_OPENED:
 290        if (vhost_user_start(queues, ncs, s->vhost_user) < 0) {
 291            qemu_chr_fe_disconnect(&s->chr);
 292            return;
 293        }
 294        s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP,
 295                                         net_vhost_user_watch, s);
 296        qmp_set_link(name, true, &err);
 297        s->started = true;
 298        break;
 299    case CHR_EVENT_CLOSED:
 300        /* a close event may happen during a read/write, but vhost
 301         * code assumes the vhost_dev remains setup, so delay the
 302         * stop & clear to idle.
 303         * FIXME: better handle failure in vhost code, remove bh
 304         */
 305        if (s->watch) {
 306            AioContext *ctx = qemu_get_current_aio_context();
 307
 308            g_source_remove(s->watch);
 309            s->watch = 0;
 310            qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL,
 311                                     NULL, NULL, false);
 312
 313            aio_bh_schedule_oneshot(ctx, chr_closed_bh, opaque);
 314        }
 315        break;
 316    case CHR_EVENT_BREAK:
 317    case CHR_EVENT_MUX_IN:
 318    case CHR_EVENT_MUX_OUT:
 319        /* Ignore */
 320        break;
 321    }
 322
 323    if (err) {
 324        error_report_err(err);
 325    }
 326}
 327
 328static int net_vhost_user_init(NetClientState *peer, const char *device,
 329                               const char *name, Chardev *chr,
 330                               int queues)
 331{
 332    Error *err = NULL;
 333    NetClientState *nc, *nc0 = NULL;
 334    NetVhostUserState *s = NULL;
 335    VhostUserState *user;
 336    int i;
 337
 338    assert(name);
 339    assert(queues > 0);
 340
 341    user = g_new0(struct VhostUserState, 1);
 342    for (i = 0; i < queues; i++) {
 343        nc = qemu_new_net_client(&net_vhost_user_info, peer, device, name);
 344        snprintf(nc->info_str, sizeof(nc->info_str), "vhost-user%d to %s",
 345                 i, chr->label);
 346        nc->queue_index = i;
 347        if (!nc0) {
 348            nc0 = nc;
 349            s = DO_UPCAST(NetVhostUserState, nc, nc);
 350            if (!qemu_chr_fe_init(&s->chr, chr, &err) ||
 351                !vhost_user_init(user, &s->chr, &err)) {
 352                error_report_err(err);
 353                goto err;
 354            }
 355        }
 356        s = DO_UPCAST(NetVhostUserState, nc, nc);
 357        s->vhost_user = user;
 358    }
 359
 360    s = DO_UPCAST(NetVhostUserState, nc, nc0);
 361    do {
 362        if (qemu_chr_fe_wait_connected(&s->chr, &err) < 0) {
 363            error_report_err(err);
 364            goto err;
 365        }
 366        qemu_chr_fe_set_handlers(&s->chr, NULL, NULL,
 367                                 net_vhost_user_event, NULL, nc0->name, NULL,
 368                                 true);
 369    } while (!s->started);
 370
 371    assert(s->vhost_net);
 372
 373    return 0;
 374
 375err:
 376    if (user) {
 377        vhost_user_cleanup(user);
 378        g_free(user);
 379        if (s) {
 380            s->vhost_user = NULL;
 381        }
 382    }
 383    if (nc0) {
 384        qemu_del_net_client(nc0);
 385    }
 386
 387    return -1;
 388}
 389
 390static Chardev *net_vhost_claim_chardev(
 391    const NetdevVhostUserOptions *opts, Error **errp)
 392{
 393    Chardev *chr = qemu_chr_find(opts->chardev);
 394
 395    if (chr == NULL) {
 396        error_setg(errp, "chardev \"%s\" not found", opts->chardev);
 397        return NULL;
 398    }
 399
 400    if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE)) {
 401        error_setg(errp, "chardev \"%s\" is not reconnectable",
 402                   opts->chardev);
 403        return NULL;
 404    }
 405    if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_FD_PASS)) {
 406        error_setg(errp, "chardev \"%s\" does not support FD passing",
 407                   opts->chardev);
 408        return NULL;
 409    }
 410
 411    return chr;
 412}
 413
 414int net_init_vhost_user(const Netdev *netdev, const char *name,
 415                        NetClientState *peer, Error **errp)
 416{
 417    int queues;
 418    const NetdevVhostUserOptions *vhost_user_opts;
 419    Chardev *chr;
 420
 421    assert(netdev->type == NET_CLIENT_DRIVER_VHOST_USER);
 422    vhost_user_opts = &netdev->u.vhost_user;
 423
 424    chr = net_vhost_claim_chardev(vhost_user_opts, errp);
 425    if (!chr) {
 426        return -1;
 427    }
 428
 429    queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
 430    if (queues < 1 || queues > MAX_QUEUE_NUM) {
 431        error_setg(errp,
 432                   "vhost-user number of queues must be in range [1, %d]",
 433                   MAX_QUEUE_NUM);
 434        return -1;
 435    }
 436
 437    return net_vhost_user_init(peer, "vhost_user", name, chr, queues);
 438}
 439