qemu/net/vmnet-common.m
William Hooper 79b6a98587 net/vmnet: Pad short Ethernet frames
At least on macOS 12.7.2, vmnet doesn't pad Ethernet frames, such as the
host's ARP replies, to the minimum size (60 bytes before the frame check
sequence) defined in IEEE Std 802.3-2022, so guests' Ethernet device
drivers may drop them with "frame too short" errors.

This patch calls eth_pad_short_frame() to add padding, as in net/tap.c
and net/slirp.c. Thanks to Bin Meng, Philippe Mathieu-Daudé, and Phil
Dennis-Jordan for reviewing earlier versions.

Signed-off-by: William Hooper <wsh@wshooper.org>
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2058
Reviewed-by: Phil Dennis-Jordan <phil@philjordan.eu>
Message-ID: <20241102205653.30476-1-wsh@wshooper.org>
Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
2024-12-31 21:21:34 +01:00

414 lines
12 KiB
Objective-C

/*
* 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 "system/runstate.h"
#include "net/eth.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";
case VMNET_SHARING_SERVICE_BUSY:
return "conflict, sharing service is in use";
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)
{
uint8_t *pkt;
size_t pktsz;
uint8_t min_pkt[ETH_ZLEN];
size_t min_pktsz;
ssize_t size;
while (s->packets_send_current_pos < s->packets_send_end_pos) {
pkt = s->iov_buf[s->packets_send_current_pos].iov_base;
pktsz = s->packets_buf[s->packets_send_current_pos].vm_pkt_size;
if (net_peer_needs_padding(&s->nc)) {
min_pktsz = sizeof(min_pkt);
if (eth_pad_short_frame(min_pkt, &min_pktsz, pkt, pktsz)) {
pkt = min_pkt;
pktsz = min_pktsz;
}
}
size = qemu_send_packet_async(&s->nc, pkt, pktsz,
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);
}
}