123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- /*
- * vmnet-common.m - network client wrapper for Apple vmnet.framework
- *
- * Copyright(c) 2022 Vladislav Yaroshchuk <vladislav.yaroshchuk@jetbrains.com>
- * Copyright(c) 2021 Phillip Tennen <phillip@axleos.com>
- *
- * This work is licensed under the terms of the GNU GPL, version 2 or later.
- * See the COPYING file in the top-level directory.
- *
- */
- #include "qemu/osdep.h"
- #include "qemu/main-loop.h"
- #include "qemu/log.h"
- #include "qapi/qapi-types-net.h"
- #include "vmnet_int.h"
- #include "clients.h"
- #include "qemu/error-report.h"
- #include "qapi/error.h"
- #include "sysemu/runstate.h"
- #include <vmnet/vmnet.h>
- #include <dispatch/dispatch.h>
- static void vmnet_send_completed(NetClientState *nc, ssize_t len);
- const char *vmnet_status_map_str(vmnet_return_t status)
- {
- switch (status) {
- case VMNET_SUCCESS:
- return "success";
- case VMNET_FAILURE:
- return "general failure (possibly not enough privileges)";
- case VMNET_MEM_FAILURE:
- return "memory allocation failure";
- case VMNET_INVALID_ARGUMENT:
- return "invalid argument specified";
- case VMNET_SETUP_INCOMPLETE:
- return "interface setup is not complete";
- case VMNET_INVALID_ACCESS:
- return "invalid access, permission denied";
- case VMNET_PACKET_TOO_BIG:
- return "packet size is larger than MTU";
- case VMNET_BUFFER_EXHAUSTED:
- return "buffers exhausted in kernel";
- case VMNET_TOO_MANY_PACKETS:
- return "packet count exceeds limit";
- #if defined(MAC_OS_VERSION_11_0) && \
- MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_11_0
- case VMNET_SHARING_SERVICE_BUSY:
- return "conflict, sharing service is in use";
- #endif
- default:
- return "unknown vmnet error";
- }
- }
- /**
- * Write packets from QEMU to vmnet interface.
- *
- * vmnet.framework supports iov, but writing more than
- * one iov into vmnet interface fails with
- * 'VMNET_INVALID_ARGUMENT'. Collecting provided iovs into
- * one and passing it to vmnet works fine. That's the
- * reason why receive_iov() left unimplemented. But it still
- * works with good performance having .receive() only.
- */
- ssize_t vmnet_receive_common(NetClientState *nc,
- const uint8_t *buf,
- size_t size)
- {
- VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
- struct vmpktdesc packet;
- struct iovec iov;
- int pkt_cnt;
- vmnet_return_t if_status;
- if (size > s->max_packet_size) {
- warn_report("vmnet: packet is too big, %zu > %" PRIu64,
- packet.vm_pkt_size,
- s->max_packet_size);
- return -1;
- }
- iov.iov_base = (char *) buf;
- iov.iov_len = size;
- packet.vm_pkt_iovcnt = 1;
- packet.vm_flags = 0;
- packet.vm_pkt_size = size;
- packet.vm_pkt_iov = &iov;
- pkt_cnt = 1;
- if_status = vmnet_write(s->vmnet_if, &packet, &pkt_cnt);
- if (if_status != VMNET_SUCCESS) {
- error_report("vmnet: write error: %s\n",
- vmnet_status_map_str(if_status));
- return -1;
- }
- if (pkt_cnt) {
- return size;
- }
- return 0;
- }
- /**
- * Read packets from vmnet interface and write them
- * to temporary buffers in VmnetState.
- *
- * Returns read packets number (may be 0) on success,
- * -1 on error
- */
- static int vmnet_read_packets(VmnetState *s)
- {
- assert(s->packets_send_current_pos == s->packets_send_end_pos);
- struct vmpktdesc *packets = s->packets_buf;
- vmnet_return_t status;
- int i;
- /* Read as many packets as present */
- s->packets_send_current_pos = 0;
- s->packets_send_end_pos = VMNET_PACKETS_LIMIT;
- for (i = 0; i < s->packets_send_end_pos; ++i) {
- packets[i].vm_pkt_size = s->max_packet_size;
- packets[i].vm_pkt_iovcnt = 1;
- packets[i].vm_flags = 0;
- }
- status = vmnet_read(s->vmnet_if, packets, &s->packets_send_end_pos);
- if (status != VMNET_SUCCESS) {
- error_printf("vmnet: read failed: %s\n",
- vmnet_status_map_str(status));
- s->packets_send_current_pos = 0;
- s->packets_send_end_pos = 0;
- return -1;
- }
- return s->packets_send_end_pos;
- }
- /**
- * Write packets from temporary buffers in VmnetState
- * to QEMU.
- */
- static void vmnet_write_packets_to_qemu(VmnetState *s)
- {
- while (s->packets_send_current_pos < s->packets_send_end_pos) {
- ssize_t size = qemu_send_packet_async(&s->nc,
- s->iov_buf[s->packets_send_current_pos].iov_base,
- s->packets_buf[s->packets_send_current_pos].vm_pkt_size,
- vmnet_send_completed);
- if (size == 0) {
- /* QEMU is not ready to consume more packets -
- * stop and wait for completion callback call */
- return;
- }
- ++s->packets_send_current_pos;
- }
- }
- /**
- * Bottom half callback that transfers packets from vmnet interface
- * to QEMU.
- *
- * The process of transferring packets is three-staged:
- * 1. Handle vmnet event;
- * 2. Read packets from vmnet interface into temporary buffer;
- * 3. Write packets from temporary buffer to QEMU.
- *
- * QEMU may suspend this process on the last stage, returning 0 from
- * qemu_send_packet_async function. If this happens, we should
- * respectfully wait until it is ready to consume more packets,
- * write left ones in temporary buffer and only after this
- * continue reading more packets from vmnet interface.
- *
- * Packets to be transferred are stored into packets_buf,
- * in the window [packets_send_current_pos..packets_send_end_pos)
- * including current_pos, excluding end_pos.
- *
- * Thus, if QEMU is not ready, buffer is not read and
- * packets_send_current_pos < packets_send_end_pos.
- */
- static void vmnet_send_bh(void *opaque)
- {
- NetClientState *nc = (NetClientState *) opaque;
- VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
- /*
- * Do nothing if QEMU is not ready - wait
- * for completion callback invocation
- */
- if (s->packets_send_current_pos < s->packets_send_end_pos) {
- return;
- }
- /* Read packets from vmnet interface */
- if (vmnet_read_packets(s) > 0) {
- /* Send them to QEMU */
- vmnet_write_packets_to_qemu(s);
- }
- }
- /**
- * Completion callback to be invoked by QEMU when it becomes
- * ready to consume more packets.
- */
- static void vmnet_send_completed(NetClientState *nc, ssize_t len)
- {
- VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
- /* Callback is invoked eq queued packet is sent */
- ++s->packets_send_current_pos;
- /* Complete sending packets left in VmnetState buffers */
- vmnet_write_packets_to_qemu(s);
- /* And read new ones from vmnet if VmnetState buffer is ready */
- if (s->packets_send_current_pos < s->packets_send_end_pos) {
- qemu_bh_schedule(s->send_bh);
- }
- }
- static void vmnet_bufs_init(VmnetState *s)
- {
- struct vmpktdesc *packets = s->packets_buf;
- struct iovec *iov = s->iov_buf;
- int i;
- for (i = 0; i < VMNET_PACKETS_LIMIT; ++i) {
- iov[i].iov_len = s->max_packet_size;
- iov[i].iov_base = g_malloc0(iov[i].iov_len);
- packets[i].vm_pkt_iov = iov + i;
- }
- }
- /**
- * Called on state change to un-register/re-register handlers
- */
- static void vmnet_vm_state_change_cb(void *opaque, bool running, RunState state)
- {
- VmnetState *s = opaque;
- if (running) {
- vmnet_interface_set_event_callback(
- s->vmnet_if,
- VMNET_INTERFACE_PACKETS_AVAILABLE,
- s->if_queue,
- ^(interface_event_t event_id, xpc_object_t event) {
- assert(event_id == VMNET_INTERFACE_PACKETS_AVAILABLE);
- /*
- * This function is being called from a non qemu thread, so
- * we only schedule a BH, and do the rest of the io completion
- * handling from vmnet_send_bh() which runs in a qemu context.
- */
- qemu_bh_schedule(s->send_bh);
- });
- } else {
- vmnet_interface_set_event_callback(
- s->vmnet_if,
- VMNET_INTERFACE_PACKETS_AVAILABLE,
- NULL,
- NULL);
- }
- }
- int vmnet_if_create(NetClientState *nc,
- xpc_object_t if_desc,
- Error **errp)
- {
- VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
- dispatch_semaphore_t if_created_sem = dispatch_semaphore_create(0);
- __block vmnet_return_t if_status;
- s->if_queue = dispatch_queue_create(
- "org.qemu.vmnet.if_queue",
- DISPATCH_QUEUE_SERIAL
- );
- xpc_dictionary_set_bool(
- if_desc,
- vmnet_allocate_mac_address_key,
- false
- );
- #ifdef DEBUG
- qemu_log("vmnet.start.interface_desc:\n");
- xpc_dictionary_apply(if_desc,
- ^bool(const char *k, xpc_object_t v) {
- char *desc = xpc_copy_description(v);
- qemu_log(" %s=%s\n", k, desc);
- free(desc);
- return true;
- });
- #endif /* DEBUG */
- s->vmnet_if = vmnet_start_interface(
- if_desc,
- s->if_queue,
- ^(vmnet_return_t status, xpc_object_t interface_param) {
- if_status = status;
- if (status != VMNET_SUCCESS || !interface_param) {
- dispatch_semaphore_signal(if_created_sem);
- return;
- }
- #ifdef DEBUG
- qemu_log("vmnet.start.interface_param:\n");
- xpc_dictionary_apply(interface_param,
- ^bool(const char *k, xpc_object_t v) {
- char *desc = xpc_copy_description(v);
- qemu_log(" %s=%s\n", k, desc);
- free(desc);
- return true;
- });
- #endif /* DEBUG */
- s->mtu = xpc_dictionary_get_uint64(
- interface_param,
- vmnet_mtu_key);
- s->max_packet_size = xpc_dictionary_get_uint64(
- interface_param,
- vmnet_max_packet_size_key);
- dispatch_semaphore_signal(if_created_sem);
- });
- if (s->vmnet_if == NULL) {
- dispatch_release(s->if_queue);
- dispatch_release(if_created_sem);
- error_setg(errp,
- "unable to create interface with requested params");
- return -1;
- }
- dispatch_semaphore_wait(if_created_sem, DISPATCH_TIME_FOREVER);
- dispatch_release(if_created_sem);
- if (if_status != VMNET_SUCCESS) {
- dispatch_release(s->if_queue);
- error_setg(errp,
- "cannot create vmnet interface: %s",
- vmnet_status_map_str(if_status));
- return -1;
- }
- s->send_bh = aio_bh_new(qemu_get_aio_context(), vmnet_send_bh, nc);
- vmnet_bufs_init(s);
- s->packets_send_current_pos = 0;
- s->packets_send_end_pos = 0;
- vmnet_vm_state_change_cb(s, 1, RUN_STATE_RUNNING);
- s->change = qemu_add_vm_change_state_handler(vmnet_vm_state_change_cb, s);
- return 0;
- }
- void vmnet_cleanup_common(NetClientState *nc)
- {
- VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
- dispatch_semaphore_t if_stopped_sem;
- if (s->vmnet_if == NULL) {
- return;
- }
- vmnet_vm_state_change_cb(s, 0, RUN_STATE_SHUTDOWN);
- qemu_del_vm_change_state_handler(s->change);
- if_stopped_sem = dispatch_semaphore_create(0);
- vmnet_stop_interface(
- s->vmnet_if,
- s->if_queue,
- ^(vmnet_return_t status) {
- assert(status == VMNET_SUCCESS);
- dispatch_semaphore_signal(if_stopped_sem);
- });
- dispatch_semaphore_wait(if_stopped_sem, DISPATCH_TIME_FOREVER);
- qemu_purge_queued_packets(nc);
- qemu_bh_delete(s->send_bh);
- dispatch_release(if_stopped_sem);
- dispatch_release(s->if_queue);
- for (int i = 0; i < VMNET_PACKETS_LIMIT; ++i) {
- g_free(s->iov_buf[i].iov_base);
- }
- }
|