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 int vhost_user_set_vnet_endianness(NetClientState *nc,
 176                                          bool enable)
 177{
 178    /* Nothing to do.  If the server supports
 179     * VHOST_USER_PROTOCOL_F_CROSS_ENDIAN, it will get the
 180     * vnet header endianness from there.  If it doesn't, negotiation
 181     * fails.
 182     */
 183    return 0;
 184}
 185
 186static bool vhost_user_has_vnet_hdr(NetClientState *nc)
 187{
 188    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
 189
 190    return true;
 191}
 192
 193static bool vhost_user_has_ufo(NetClientState *nc)
 194{
 195    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
 196
 197    return true;
 198}
 199
 200static NetClientInfo net_vhost_user_info = {
 201        .type = NET_CLIENT_DRIVER_VHOST_USER,
 202        .size = sizeof(NetVhostUserState),
 203        .receive = vhost_user_receive,
 204        .cleanup = net_vhost_user_cleanup,
 205        .has_vnet_hdr = vhost_user_has_vnet_hdr,
 206        .has_ufo = vhost_user_has_ufo,
 207        .set_vnet_be = vhost_user_set_vnet_endianness,
 208        .set_vnet_le = vhost_user_set_vnet_endianness,
 209};
 210
 211static gboolean net_vhost_user_watch(GIOChannel *chan, GIOCondition cond,
 212                                           void *opaque)
 213{
 214    NetVhostUserState *s = opaque;
 215
 216    qemu_chr_fe_disconnect(&s->chr);
 217
 218    return TRUE;
 219}
 220
 221static void net_vhost_user_event(void *opaque, int event);
 222
 223static void chr_closed_bh(void *opaque)
 224{
 225    const char *name = opaque;
 226    NetClientState *ncs[MAX_QUEUE_NUM];
 227    NetVhostUserState *s;
 228    Error *err = NULL;
 229    int queues;
 230
 231    queues = qemu_find_net_clients_except(name, ncs,
 232                                          NET_CLIENT_DRIVER_NIC,
 233                                          MAX_QUEUE_NUM);
 234    assert(queues < MAX_QUEUE_NUM);
 235
 236    s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
 237
 238    if (s->vhost_net) {
 239        s->acked_features = vhost_net_get_acked_features(s->vhost_net);
 240    }
 241
 242    qmp_set_link(name, false, &err);
 243
 244    qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, net_vhost_user_event,
 245                             NULL, opaque, NULL, true);
 246
 247    if (err) {
 248        error_report_err(err);
 249    }
 250}
 251
 252static void net_vhost_user_event(void *opaque, int event)
 253{
 254    const char *name = opaque;
 255    NetClientState *ncs[MAX_QUEUE_NUM];
 256    NetVhostUserState *s;
 257    Chardev *chr;
 258    Error *err = NULL;
 259    int queues;
 260
 261    queues = qemu_find_net_clients_except(name, ncs,
 262                                          NET_CLIENT_DRIVER_NIC,
 263                                          MAX_QUEUE_NUM);
 264    assert(queues < MAX_QUEUE_NUM);
 265
 266    s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
 267    chr = qemu_chr_fe_get_driver(&s->chr);
 268    trace_vhost_user_event(chr->label, event);
 269    switch (event) {
 270    case CHR_EVENT_OPENED:
 271        if (vhost_user_start(queues, ncs, s->vhost_user) < 0) {
 272            qemu_chr_fe_disconnect(&s->chr);
 273            return;
 274        }
 275        s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP,
 276                                         net_vhost_user_watch, s);
 277        qmp_set_link(name, true, &err);
 278        s->started = true;
 279        break;
 280    case CHR_EVENT_CLOSED:
 281        /* a close event may happen during a read/write, but vhost
 282         * code assumes the vhost_dev remains setup, so delay the
 283         * stop & clear to idle.
 284         * FIXME: better handle failure in vhost code, remove bh
 285         */
 286        if (s->watch) {
 287            AioContext *ctx = qemu_get_current_aio_context();
 288
 289            g_source_remove(s->watch);
 290            s->watch = 0;
 291            qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL,
 292                                     NULL, NULL, false);
 293
 294            aio_bh_schedule_oneshot(ctx, chr_closed_bh, opaque);
 295        }
 296        break;
 297    }
 298
 299    if (err) {
 300        error_report_err(err);
 301    }
 302}
 303
 304static int net_vhost_user_init(NetClientState *peer, const char *device,
 305                               const char *name, Chardev *chr,
 306                               int queues)
 307{
 308    Error *err = NULL;
 309    NetClientState *nc, *nc0 = NULL;
 310    NetVhostUserState *s = NULL;
 311    VhostUserState *user;
 312    int i;
 313
 314    assert(name);
 315    assert(queues > 0);
 316
 317    user = g_new0(struct VhostUserState, 1);
 318    for (i = 0; i < queues; i++) {
 319        nc = qemu_new_net_client(&net_vhost_user_info, peer, device, name);
 320        snprintf(nc->info_str, sizeof(nc->info_str), "vhost-user%d to %s",
 321                 i, chr->label);
 322        nc->queue_index = i;
 323        if (!nc0) {
 324            nc0 = nc;
 325            s = DO_UPCAST(NetVhostUserState, nc, nc);
 326            if (!qemu_chr_fe_init(&s->chr, chr, &err) ||
 327                !vhost_user_init(user, &s->chr, &err)) {
 328                error_report_err(err);
 329                goto err;
 330            }
 331        }
 332        s = DO_UPCAST(NetVhostUserState, nc, nc);
 333        s->vhost_user = user;
 334    }
 335
 336    s = DO_UPCAST(NetVhostUserState, nc, nc0);
 337    do {
 338        if (qemu_chr_fe_wait_connected(&s->chr, &err) < 0) {
 339            error_report_err(err);
 340            goto err;
 341        }
 342        qemu_chr_fe_set_handlers(&s->chr, NULL, NULL,
 343                                 net_vhost_user_event, NULL, nc0->name, NULL,
 344                                 true);
 345    } while (!s->started);
 346
 347    assert(s->vhost_net);
 348
 349    return 0;
 350
 351err:
 352    if (user) {
 353        vhost_user_cleanup(user);
 354        g_free(user);
 355        if (s) {
 356            s->vhost_user = NULL;
 357        }
 358    }
 359    if (nc0) {
 360        qemu_del_net_client(nc0);
 361    }
 362
 363    return -1;
 364}
 365
 366static Chardev *net_vhost_claim_chardev(
 367    const NetdevVhostUserOptions *opts, Error **errp)
 368{
 369    Chardev *chr = qemu_chr_find(opts->chardev);
 370
 371    if (chr == NULL) {
 372        error_setg(errp, "chardev \"%s\" not found", opts->chardev);
 373        return NULL;
 374    }
 375
 376    if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE)) {
 377        error_setg(errp, "chardev \"%s\" is not reconnectable",
 378                   opts->chardev);
 379        return NULL;
 380    }
 381    if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_FD_PASS)) {
 382        error_setg(errp, "chardev \"%s\" does not support FD passing",
 383                   opts->chardev);
 384        return NULL;
 385    }
 386
 387    return chr;
 388}
 389
 390static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp)
 391{
 392    const char *name = opaque;
 393    const char *driver, *netdev;
 394
 395    driver = qemu_opt_get(opts, "driver");
 396    netdev = qemu_opt_get(opts, "netdev");
 397
 398    if (!driver || !netdev) {
 399        return 0;
 400    }
 401
 402    if (strcmp(netdev, name) == 0 &&
 403        !g_str_has_prefix(driver, "virtio-net-")) {
 404        error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
 405        return -1;
 406    }
 407
 408    return 0;
 409}
 410
 411int net_init_vhost_user(const Netdev *netdev, const char *name,
 412                        NetClientState *peer, Error **errp)
 413{
 414    int queues;
 415    const NetdevVhostUserOptions *vhost_user_opts;
 416    Chardev *chr;
 417
 418    assert(netdev->type == NET_CLIENT_DRIVER_VHOST_USER);
 419    vhost_user_opts = &netdev->u.vhost_user;
 420
 421    chr = net_vhost_claim_chardev(vhost_user_opts, errp);
 422    if (!chr) {
 423        return -1;
 424    }
 425
 426    /* verify net frontend */
 427    if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net,
 428                          (char *)name, errp)) {
 429        return -1;
 430    }
 431
 432    queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
 433    if (queues < 1 || queues > MAX_QUEUE_NUM) {
 434        error_setg(errp,
 435                   "vhost-user number of queues must be in range [1, %d]",
 436                   MAX_QUEUE_NUM);
 437        return -1;
 438    }
 439
 440    return net_vhost_user_init(peer, "vhost_user", name, chr, queues);
 441}
 442