-----BEGIN PGP SIGNATURE-----

Version: GnuPG v1
 
 iQEcBAABAgAGBQJig2G8AAoJEO8Ells5jWIR7ocH/j+bduaZwE9HM+a9CbemkdOz
 7iPPEq8eKPvLrcURa8DNmguwGul+NSXv1jonb1m1P/C5Lox/PagdC219irk43WGc
 DJBzkAo+wOetmvVeCCQl4ovJbYF4PBftYaxkBfm8KbavnoD1aSUPL70QHVvGmuKi
 kxmf9WMQgksw+LirSI4avZSJc1YIsjruEYDagbZmA5r/kTNbm4zobLe8/BV/2mgJ
 j5bHbrPeci8feBFhjuBVgZKkbUU7HrJj9kLAzbObdQ8a9VDtaWWfQiwmpeAaItl0
 5OHSD+tFsGt3MSZn+/LXP9emJeKDnLt+LCxvT0yzGw06iF9N0WFJlSUdd4eYkjI=
 =5fRf
 -----END PGP SIGNATURE-----

Merge tag 'net-pull-request' of https://github.com/jasowang/qemu into staging

# -----BEGIN PGP SIGNATURE-----
# Version: GnuPG v1
#
# iQEcBAABAgAGBQJig2G8AAoJEO8Ells5jWIR7ocH/j+bduaZwE9HM+a9CbemkdOz
# 7iPPEq8eKPvLrcURa8DNmguwGul+NSXv1jonb1m1P/C5Lox/PagdC219irk43WGc
# DJBzkAo+wOetmvVeCCQl4ovJbYF4PBftYaxkBfm8KbavnoD1aSUPL70QHVvGmuKi
# kxmf9WMQgksw+LirSI4avZSJc1YIsjruEYDagbZmA5r/kTNbm4zobLe8/BV/2mgJ
# j5bHbrPeci8feBFhjuBVgZKkbUU7HrJj9kLAzbObdQ8a9VDtaWWfQiwmpeAaItl0
# 5OHSD+tFsGt3MSZn+/LXP9emJeKDnLt+LCxvT0yzGw06iF9N0WFJlSUdd4eYkjI=
# =5fRf
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 17 May 2022 01:50:04 AM PDT
# gpg:                using RSA key EF04965B398D6211
# gpg: Good signature from "Jason Wang (Jason Wang on RedHat) <jasowang@redhat.com>" [undefined]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 215D 46F4 8246 689E C77F  3562 EF04 965B 398D 6211

* tag 'net-pull-request' of https://github.com/jasowang/qemu:
  tulip: Assign default MAC address if not specified
  net/vmnet: update hmp-commands.hx
  net/vmnet: update qemu-options.hx
  net/vmnet: implement bridged mode (vmnet-bridged)
  net/vmnet: implement host mode (vmnet-host)
  net/vmnet: implement shared mode (vmnet-shared)
  net/vmnet: add vmnet backends to qapi/net
  net/vmnet: add vmnet dependency and customizable option

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
master
Richard Henderson 2022-05-17 21:32:37 -07:00
commit bcf0a3a422
15 changed files with 1044 additions and 6 deletions

View File

@ -1269,7 +1269,11 @@ ERST
{
.name = "netdev_add",
.args_type = "netdev:O",
.params = "[user|tap|socket|vde|bridge|hubport|netmap|vhost-user],id=str[,prop=value][,...]",
.params = "[user|tap|socket|vde|bridge|hubport|netmap|vhost-user"
#ifdef CONFIG_VMNET
"|vmnet-host|vmnet-shared|vmnet-bridged"
#endif
"],id=str[,prop=value][,...]",
.help = "add host network device",
.cmd = hmp_netdev_add,
.command_completion = netdev_add_completion,

View File

@ -967,6 +967,8 @@ static void pci_tulip_realize(PCIDevice *pci_dev, Error **errp)
pci_conf = s->dev.config;
pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
qemu_macaddr_default_if_unset(&s->c.macaddr);
s->eeprom = eeprom93xx_new(&pci_dev->qdev, 64);
tulip_fill_eeprom(s);
@ -981,8 +983,6 @@ static void pci_tulip_realize(PCIDevice *pci_dev, Error **errp)
s->irq = pci_allocate_irq(&s->dev);
qemu_macaddr_default_if_unset(&s->c.macaddr);
s->nic = qemu_new_nic(&net_tulip_info, &s->c,
object_get_typename(OBJECT(pci_dev)),
pci_dev->qdev.id, s);

View File

@ -580,6 +580,18 @@ if cocoa.found() and get_option('gtk').enabled()
error('Cocoa and GTK+ cannot be enabled at the same time')
endif
vmnet = dependency('appleframeworks', modules: 'vmnet', required: get_option('vmnet'))
if vmnet.found() and not cc.has_header_symbol('vmnet/vmnet.h',
'VMNET_BRIDGED_MODE',
dependencies: vmnet)
vmnet = not_found
if get_option('vmnet').enabled()
error('vmnet.framework API is outdated')
else
warning('vmnet.framework API is outdated, disabling')
endif
endif
seccomp = not_found
if not get_option('seccomp').auto() or have_system or have_tools
seccomp = dependency('libseccomp', version: '>=2.3.0',
@ -1741,6 +1753,7 @@ config_host_data.set('CONFIG_VHOST_KERNEL', have_vhost_kernel)
config_host_data.set('CONFIG_VHOST_USER', have_vhost_user)
config_host_data.set('CONFIG_VHOST_CRYPTO', have_vhost_user_crypto)
config_host_data.set('CONFIG_VHOST_VDPA', have_vhost_vdpa)
config_host_data.set('CONFIG_VMNET', vmnet.found())
config_host_data.set('CONFIG_VHOST_USER_BLK_SERVER', have_vhost_user_blk_server)
config_host_data.set('CONFIG_PNG', png.found())
config_host_data.set('CONFIG_VNC', vnc.found())
@ -3897,7 +3910,8 @@ summary(summary_info, bool_yn: true, section: 'Crypto')
# Libraries
summary_info = {}
if targetos == 'darwin'
summary_info += {'Cocoa support': cocoa}
summary_info += {'Cocoa support': cocoa}
summary_info += {'vmnet.framework support': vmnet}
endif
summary_info += {'SDL support': sdl}
summary_info += {'SDL image support': sdl_image}

View File

@ -197,6 +197,8 @@ option('netmap', type : 'feature', value : 'auto',
description: 'netmap network backend support')
option('vde', type : 'feature', value : 'auto',
description: 'vde network backend support')
option('vmnet', type : 'feature', value : 'auto',
description: 'vmnet.framework network backend support')
option('virglrenderer', type : 'feature', value : 'auto',
description: 'virgl rendering support')
option('png', type : 'feature', value : 'auto',

View File

@ -63,4 +63,15 @@ int net_init_vhost_user(const Netdev *netdev, const char *name,
int net_init_vhost_vdpa(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp);
#ifdef CONFIG_VMNET
int net_init_vmnet_host(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp);
int net_init_vmnet_shared(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp);
int net_init_vmnet_bridged(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp);
#endif /* CONFIG_VMNET */
#endif /* QEMU_NET_CLIENTS_H */

View File

@ -44,4 +44,11 @@ if have_vhost_net_vdpa
softmmu_ss.add(files('vhost-vdpa.c'))
endif
vmnet_files = files(
'vmnet-common.m',
'vmnet-bridged.m',
'vmnet-host.c',
'vmnet-shared.c'
)
softmmu_ss.add(when: vmnet, if_true: vmnet_files)
subdir('can')

View File

@ -1020,6 +1020,11 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])(
#ifdef CONFIG_L2TPV3
[NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3,
#endif
#ifdef CONFIG_VMNET
[NET_CLIENT_DRIVER_VMNET_HOST] = net_init_vmnet_host,
[NET_CLIENT_DRIVER_VMNET_SHARED] = net_init_vmnet_shared,
[NET_CLIENT_DRIVER_VMNET_BRIDGED] = net_init_vmnet_bridged,
#endif /* CONFIG_VMNET */
};
@ -1105,6 +1110,11 @@ void show_netdevs(void)
#endif
#ifdef CONFIG_VHOST_VDPA
"vhost-vdpa",
#endif
#ifdef CONFIG_VMNET
"vmnet-host",
"vmnet-shared",
"vmnet-bridged",
#endif
};

152
net/vmnet-bridged.m Normal file
View File

@ -0,0 +1,152 @@
/*
* vmnet-bridged.m
*
* Copyright(c) 2022 Vladislav Yaroshchuk <vladislav.yaroshchuk@jetbrains.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 "qapi/qapi-types-net.h"
#include "qapi/error.h"
#include "clients.h"
#include "vmnet_int.h"
#include <vmnet/vmnet.h>
static bool validate_ifname(const char *ifname)
{
xpc_object_t shared_if_list = vmnet_copy_shared_interface_list();
bool match = false;
if (!xpc_array_get_count(shared_if_list)) {
goto done;
}
match = !xpc_array_apply(
shared_if_list,
^bool(size_t index, xpc_object_t value) {
return strcmp(xpc_string_get_string_ptr(value), ifname) != 0;
});
done:
xpc_release(shared_if_list);
return match;
}
static char* get_valid_ifnames()
{
xpc_object_t shared_if_list = vmnet_copy_shared_interface_list();
__block char *if_list = NULL;
__block char *if_list_prev = NULL;
if (!xpc_array_get_count(shared_if_list)) {
goto done;
}
xpc_array_apply(
shared_if_list,
^bool(size_t index, xpc_object_t value) {
/* build list of strings like "en0 en1 en2 " */
if_list = g_strconcat(xpc_string_get_string_ptr(value),
" ",
if_list_prev,
NULL);
g_free(if_list_prev);
if_list_prev = if_list;
return true;
});
done:
xpc_release(shared_if_list);
return if_list;
}
static bool validate_options(const Netdev *netdev, Error **errp)
{
const NetdevVmnetBridgedOptions *options = &(netdev->u.vmnet_bridged);
char* if_list;
if (!validate_ifname(options->ifname)) {
if_list = get_valid_ifnames();
if (if_list) {
error_setg(errp,
"unsupported ifname '%s', expected one of [ %s]",
options->ifname,
if_list);
g_free(if_list);
} else {
error_setg(errp,
"unsupported ifname '%s', no supported "
"interfaces available",
options->ifname);
}
return false;
}
#if !defined(MAC_OS_VERSION_11_0) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0
if (options->has_isolated) {
error_setg(errp,
"vmnet-bridged.isolated feature is "
"unavailable: outdated vmnet.framework API");
return false;
}
#endif
return true;
}
static xpc_object_t build_if_desc(const Netdev *netdev)
{
const NetdevVmnetBridgedOptions *options = &(netdev->u.vmnet_bridged);
xpc_object_t if_desc = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_uint64(if_desc,
vmnet_operation_mode_key,
VMNET_BRIDGED_MODE
);
xpc_dictionary_set_string(if_desc,
vmnet_shared_interface_name_key,
options->ifname);
#if defined(MAC_OS_VERSION_11_0) && \
MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_11_0
xpc_dictionary_set_bool(if_desc,
vmnet_enable_isolation_key,
options->isolated);
#endif
return if_desc;
}
static NetClientInfo net_vmnet_bridged_info = {
.type = NET_CLIENT_DRIVER_VMNET_BRIDGED,
.size = sizeof(VmnetState),
.receive = vmnet_receive_common,
.cleanup = vmnet_cleanup_common,
};
int net_init_vmnet_bridged(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp)
{
NetClientState *nc = qemu_new_net_client(&net_vmnet_bridged_info,
peer, "vmnet-bridged", name);
xpc_object_t if_desc;
int result = -1;
if (!validate_options(netdev, errp)) {
return result;
}
if_desc = build_if_desc(netdev);
result = vmnet_if_create(nc, if_desc, errp);
xpc_release(if_desc);
return result;
}

378
net/vmnet-common.m Normal file
View File

@ -0,0 +1,378 @@
/*
* 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 <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;
}
}
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_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);
});
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;
}
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);
}
}

128
net/vmnet-host.c Normal file
View File

@ -0,0 +1,128 @@
/*
* vmnet-host.c
*
* Copyright(c) 2022 Vladislav Yaroshchuk <vladislav.yaroshchuk@jetbrains.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/uuid.h"
#include "qapi/qapi-types-net.h"
#include "qapi/error.h"
#include "clients.h"
#include "vmnet_int.h"
#include <vmnet/vmnet.h>
static bool validate_options(const Netdev *netdev, Error **errp)
{
const NetdevVmnetHostOptions *options = &(netdev->u.vmnet_host);
#if defined(MAC_OS_VERSION_11_0) && \
MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_11_0
QemuUUID net_uuid;
if (options->has_net_uuid &&
qemu_uuid_parse(options->net_uuid, &net_uuid) < 0) {
error_setg(errp, "Invalid UUID provided in 'net-uuid'");
return false;
}
#else
if (options->has_isolated) {
error_setg(errp,
"vmnet-host.isolated feature is "
"unavailable: outdated vmnet.framework API");
return false;
}
if (options->has_net_uuid) {
error_setg(errp,
"vmnet-host.net-uuid feature is "
"unavailable: outdated vmnet.framework API");
return false;
}
#endif
if ((options->has_start_address ||
options->has_end_address ||
options->has_subnet_mask) &&
!(options->has_start_address &&
options->has_end_address &&
options->has_subnet_mask)) {
error_setg(errp,
"'start-address', 'end-address', 'subnet-mask' "
"should be provided together");
return false;
}
return true;
}
static xpc_object_t build_if_desc(const Netdev *netdev)
{
const NetdevVmnetHostOptions *options = &(netdev->u.vmnet_host);
xpc_object_t if_desc = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_uint64(if_desc,
vmnet_operation_mode_key,
VMNET_HOST_MODE);
#if defined(MAC_OS_VERSION_11_0) && \
MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_11_0
xpc_dictionary_set_bool(if_desc,
vmnet_enable_isolation_key,
options->isolated);
QemuUUID net_uuid;
if (options->has_net_uuid) {
qemu_uuid_parse(options->net_uuid, &net_uuid);
xpc_dictionary_set_uuid(if_desc,
vmnet_network_identifier_key,
net_uuid.data);
}
#endif
if (options->has_start_address) {
xpc_dictionary_set_string(if_desc,
vmnet_start_address_key,
options->start_address);
xpc_dictionary_set_string(if_desc,
vmnet_end_address_key,
options->end_address);
xpc_dictionary_set_string(if_desc,
vmnet_subnet_mask_key,
options->subnet_mask);
}
return if_desc;
}
static NetClientInfo net_vmnet_host_info = {
.type = NET_CLIENT_DRIVER_VMNET_HOST,
.size = sizeof(VmnetState),
.receive = vmnet_receive_common,
.cleanup = vmnet_cleanup_common,
};
int net_init_vmnet_host(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp)
{
NetClientState *nc = qemu_new_net_client(&net_vmnet_host_info,
peer, "vmnet-host", name);
xpc_object_t if_desc;
int result = -1;
if (!validate_options(netdev, errp)) {
return result;
}
if_desc = build_if_desc(netdev);
result = vmnet_if_create(nc, if_desc, errp);
xpc_release(if_desc);
return result;
}

114
net/vmnet-shared.c Normal file
View File

@ -0,0 +1,114 @@
/*
* vmnet-shared.c
*
* Copyright(c) 2022 Vladislav Yaroshchuk <vladislav.yaroshchuk@jetbrains.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 "qapi/qapi-types-net.h"
#include "qapi/error.h"
#include "vmnet_int.h"
#include "clients.h"
#include <vmnet/vmnet.h>
static bool validate_options(const Netdev *netdev, Error **errp)
{
const NetdevVmnetSharedOptions *options = &(netdev->u.vmnet_shared);
#if !defined(MAC_OS_VERSION_11_0) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_11_0
if (options->has_isolated) {
error_setg(errp,
"vmnet-shared.isolated feature is "
"unavailable: outdated vmnet.framework API");
return false;
}
#endif
if ((options->has_start_address ||
options->has_end_address ||
options->has_subnet_mask) &&
!(options->has_start_address &&
options->has_end_address &&
options->has_subnet_mask)) {
error_setg(errp,
"'start-address', 'end-address', 'subnet-mask' "
"should be provided together"
);
return false;
}
return true;
}
static xpc_object_t build_if_desc(const Netdev *netdev)
{
const NetdevVmnetSharedOptions *options = &(netdev->u.vmnet_shared);
xpc_object_t if_desc = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_uint64(
if_desc,
vmnet_operation_mode_key,
VMNET_SHARED_MODE
);
if (options->has_nat66_prefix) {
xpc_dictionary_set_string(if_desc,
vmnet_nat66_prefix_key,
options->nat66_prefix);
}
if (options->has_start_address) {
xpc_dictionary_set_string(if_desc,
vmnet_start_address_key,
options->start_address);
xpc_dictionary_set_string(if_desc,
vmnet_end_address_key,
options->end_address);
xpc_dictionary_set_string(if_desc,
vmnet_subnet_mask_key,
options->subnet_mask);
}
#if defined(MAC_OS_VERSION_11_0) && \
MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_11_0
xpc_dictionary_set_bool(
if_desc,
vmnet_enable_isolation_key,
options->isolated
);
#endif
return if_desc;
}
static NetClientInfo net_vmnet_shared_info = {
.type = NET_CLIENT_DRIVER_VMNET_SHARED,
.size = sizeof(VmnetState),
.receive = vmnet_receive_common,
.cleanup = vmnet_cleanup_common,
};
int net_init_vmnet_shared(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp)
{
NetClientState *nc = qemu_new_net_client(&net_vmnet_shared_info,
peer, "vmnet-shared", name);
xpc_object_t if_desc;
int result = -1;
if (!validate_options(netdev, errp)) {
return result;
}
if_desc = build_if_desc(netdev);
result = vmnet_if_create(nc, if_desc, errp);
xpc_release(if_desc);
return result;
}

63
net/vmnet_int.h Normal file
View File

@ -0,0 +1,63 @@
/*
* vmnet_int.h
*
* Copyright(c) 2022 Vladislav Yaroshchuk <vladislav.yaroshchuk@jetbrains.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.
*
*/
#ifndef VMNET_INT_H
#define VMNET_INT_H
#include "qemu/osdep.h"
#include "vmnet_int.h"
#include "clients.h"
#include <vmnet/vmnet.h>
#include <dispatch/dispatch.h>
/**
* From vmnet.framework documentation
*
* Each read/write call allows up to 200 packets to be
* read or written for a maximum of 256KB.
*
* Each packet written should be a complete
* ethernet frame.
*
* https://developer.apple.com/documentation/vmnet
*/
#define VMNET_PACKETS_LIMIT 200
typedef struct VmnetState {
NetClientState nc;
interface_ref vmnet_if;
uint64_t mtu;
uint64_t max_packet_size;
dispatch_queue_t if_queue;
QEMUBH *send_bh;
struct vmpktdesc packets_buf[VMNET_PACKETS_LIMIT];
int packets_send_current_pos;
int packets_send_end_pos;
struct iovec iov_buf[VMNET_PACKETS_LIMIT];
} VmnetState;
const char *vmnet_status_map_str(vmnet_return_t status);
int vmnet_if_create(NetClientState *nc,
xpc_object_t if_desc,
Error **errp);
ssize_t vmnet_receive_common(NetClientState *nc,
const uint8_t *buf,
size_t size);
void vmnet_cleanup_common(NetClientState *nc);
#endif /* VMNET_INT_H */

View File

@ -452,6 +452,120 @@
'*vhostdev': 'str',
'*queues': 'int' } }
##
# @NetdevVmnetHostOptions:
#
# vmnet (host mode) network backend.
#
# Allows the vmnet interface to communicate with other vmnet
# interfaces that are in host mode and also with the host.
#
# @start-address: The starting IPv4 address to use for the interface.
# Must be in the private IP range (RFC 1918). Must be
# specified along with @end-address and @subnet-mask.
# This address is used as the gateway address. The
# subsequent address up to and including end-address are
# placed in the DHCP pool.
#
# @end-address: The DHCP IPv4 range end address to use for the
# interface. Must be in the private IP range (RFC 1918).
# Must be specified along with @start-address and
# @subnet-mask.
#
# @subnet-mask: The IPv4 subnet mask to use on the interface. Must
# be specified along with @start-address and @subnet-mask.
#
# @isolated: Enable isolation for this interface. Interface isolation
# ensures that vmnet interface is not able to communicate
# with any other vmnet interfaces. Only communication with
# host is allowed. Requires at least macOS Big Sur 11.0.
#
# @net-uuid: The identifier (UUID) to uniquely identify the isolated
# network vmnet interface should be added to. If
# set, no DHCP service is provided for this interface and
# network communication is allowed only with other interfaces
# added to this network identified by the UUID. Requires
# at least macOS Big Sur 11.0.
#
# Since: 7.1
##
{ 'struct': 'NetdevVmnetHostOptions',
'data': {
'*start-address': 'str',
'*end-address': 'str',
'*subnet-mask': 'str',
'*isolated': 'bool',
'*net-uuid': 'str' },
'if': 'CONFIG_VMNET' }
##
# @NetdevVmnetSharedOptions:
#
# vmnet (shared mode) network backend.
#
# Allows traffic originating from the vmnet interface to reach the
# Internet through a network address translator (NAT).
# The vmnet interface can communicate with the host and with
# other shared mode interfaces on the same subnet. If no DHCP
# settings, subnet mask and IPv6 prefix specified, the interface can
# communicate with any of other interfaces in shared mode.
#
# @start-address: The starting IPv4 address to use for the interface.
# Must be in the private IP range (RFC 1918). Must be
# specified along with @end-address and @subnet-mask.
# This address is used as the gateway address. The
# subsequent address up to and including end-address are
# placed in the DHCP pool.
#
# @end-address: The DHCP IPv4 range end address to use for the
# interface. Must be in the private IP range (RFC 1918).
# Must be specified along with @start-address and @subnet-mask.
#
# @subnet-mask: The IPv4 subnet mask to use on the interface. Must
# be specified along with @start-address and @subnet-mask.
#
# @isolated: Enable isolation for this interface. Interface isolation
# ensures that vmnet interface is not able to communicate
# with any other vmnet interfaces. Only communication with
# host is allowed. Requires at least macOS Big Sur 11.0.
#
# @nat66-prefix: The IPv6 prefix to use into guest network. Must be a
# unique local address i.e. start with fd00::/8 and have
# length of 64.
#
# Since: 7.1
##
{ 'struct': 'NetdevVmnetSharedOptions',
'data': {
'*start-address': 'str',
'*end-address': 'str',
'*subnet-mask': 'str',
'*isolated': 'bool',
'*nat66-prefix': 'str' },
'if': 'CONFIG_VMNET' }
##
# @NetdevVmnetBridgedOptions:
#
# vmnet (bridged mode) network backend.
#
# Bridges the vmnet interface with a physical network interface.
#
# @ifname: The name of the physical interface to be bridged.
#
# @isolated: Enable isolation for this interface. Interface isolation
# ensures that vmnet interface is not able to communicate
# with any other vmnet interfaces. Only communication with
# host is allowed. Requires at least macOS Big Sur 11.0.
#
# Since: 7.1
##
{ 'struct': 'NetdevVmnetBridgedOptions',
'data': {
'ifname': 'str',
'*isolated': 'bool' },
'if': 'CONFIG_VMNET' }
##
# @NetClientDriver:
#
@ -460,10 +574,16 @@
# Since: 2.7
#
# @vhost-vdpa since 5.1
# @vmnet-host since 7.1
# @vmnet-shared since 7.1
# @vmnet-bridged since 7.1
##
{ 'enum': 'NetClientDriver',
'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'vde',
'bridge', 'hubport', 'netmap', 'vhost-user', 'vhost-vdpa' ] }
'bridge', 'hubport', 'netmap', 'vhost-user', 'vhost-vdpa',
{ 'name': 'vmnet-host', 'if': 'CONFIG_VMNET' },
{ 'name': 'vmnet-shared', 'if': 'CONFIG_VMNET' },
{ 'name': 'vmnet-bridged', 'if': 'CONFIG_VMNET' }] }
##
# @Netdev:
@ -477,6 +597,9 @@
# Since: 1.2
#
# 'l2tpv3' - since 2.1
# 'vmnet-host' - since 7.1
# 'vmnet-shared' - since 7.1
# 'vmnet-bridged' - since 7.1
##
{ 'union': 'Netdev',
'base': { 'id': 'str', 'type': 'NetClientDriver' },
@ -492,7 +615,13 @@
'hubport': 'NetdevHubPortOptions',
'netmap': 'NetdevNetmapOptions',
'vhost-user': 'NetdevVhostUserOptions',
'vhost-vdpa': 'NetdevVhostVDPAOptions' } }
'vhost-vdpa': 'NetdevVhostVDPAOptions',
'vmnet-host': { 'type': 'NetdevVmnetHostOptions',
'if': 'CONFIG_VMNET' },
'vmnet-shared': { 'type': 'NetdevVmnetSharedOptions',
'if': 'CONFIG_VMNET' },
'vmnet-bridged': { 'type': 'NetdevVmnetBridgedOptions',
'if': 'CONFIG_VMNET' } } }
##
# @RxState:

View File

@ -2795,6 +2795,25 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev,
#ifdef __linux__
"-netdev vhost-vdpa,id=str,vhostdev=/path/to/dev\n"
" configure a vhost-vdpa network,Establish a vhost-vdpa netdev\n"
#endif
#ifdef CONFIG_VMNET
"-netdev vmnet-host,id=str[,isolated=on|off][,net-uuid=uuid]\n"
" [,start-address=addr,end-address=addr,subnet-mask=mask]\n"
" configure a vmnet network backend in host mode with ID 'str',\n"
" isolate this interface from others with 'isolated',\n"
" configure the address range and choose a subnet mask,\n"
" specify network UUID 'uuid' to disable DHCP and interact with\n"
" vmnet-host interfaces within this isolated network\n"
"-netdev vmnet-shared,id=str[,isolated=on|off][,nat66-prefix=addr]\n"
" [,start-address=addr,end-address=addr,subnet-mask=mask]\n"
" configure a vmnet network backend in shared mode with ID 'str',\n"
" configure the address range and choose a subnet mask,\n"
" set IPv6 ULA prefix (of length 64) to use for internal network,\n"
" isolate this interface from others with 'isolated'\n"
"-netdev vmnet-bridged,id=str,ifname=name[,isolated=on|off]\n"
" configure a vmnet network backend in bridged mode with ID 'str',\n"
" use 'ifname=name' to select a physical network interface to be bridged,\n"
" isolate this interface from others with 'isolated'\n"
#endif
"-netdev hubport,id=str,hubid=n[,netdev=nd]\n"
" configure a hub port on the hub with ID 'n'\n", QEMU_ARCH_ALL)
@ -2814,6 +2833,9 @@ DEF("nic", HAS_ARG, QEMU_OPTION_nic,
#endif
#ifdef CONFIG_POSIX
"vhost-user|"
#endif
#ifdef CONFIG_VMNET
"vmnet-host|vmnet-shared|vmnet-bridged|"
#endif
"socket][,option][,...][mac=macaddr]\n"
" initialize an on-board / default host NIC (using MAC address\n"
@ -2836,6 +2858,9 @@ DEF("net", HAS_ARG, QEMU_OPTION_net,
#endif
#ifdef CONFIG_NETMAP
"netmap|"
#endif
#ifdef CONFIG_VMNET
"vmnet-host|vmnet-shared|vmnet-bridged|"
#endif
"socket][,option][,option][,...]\n"
" old way to initialize a host network interface\n"

View File

@ -158,6 +158,7 @@ meson_options_help() {
printf "%s\n" ' vhost-kernel vhost kernel backend support'
printf "%s\n" ' vhost-net vhost-net kernel acceleration support'
printf "%s\n" ' vhost-user vhost-user backend support'
printf "%s\n" ' vmnet vmnet.framework network backend support'
printf "%s\n" ' vhost-user-blk-server'
printf "%s\n" ' build vhost-user-blk server'
printf "%s\n" ' vhost-vdpa vhost-vdpa kernel backend support'