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 21 typedef struct VhostUserState { 22 NetClientState nc; 23 CharDriverState *chr; 24 VHostNetState *vhost_net; 25 } VhostUserState; 26 27 typedef struct VhostUserChardevProps { 28 bool is_socket; 29 bool is_unix; 30 } VhostUserChardevProps; 31 32 VHostNetState *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 39 static int vhost_user_running(VhostUserState *s) 40 { 41 return (s->vhost_net) ? 1 : 0; 42 } 43 44 static 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 64 static 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 101 err: 102 vhost_user_stop(i + 1, ncs); 103 return -1; 104 } 105 106 static 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 135 static 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 147 static 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 154 static 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 161 static 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 170 static 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 203 static 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 231 static 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 251 static 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 279 static 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 301 int 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; 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