* [PATCH v4] net/macos: implement vmnet-based netdev @ 2021-02-18 13:49 phillip.ennen 2021-02-20 6:39 ` Howard Spoelstra 2021-02-23 23:24 ` Roman Bolshakov 0 siblings, 2 replies; 5+ messages in thread From: phillip.ennen @ 2021-02-18 13:49 UTC (permalink / raw) To: qemu-devel; +Cc: thuth, stefanha, jasowang, armbru, phillip, hsp.cat7 From: Phillip Tennen <phillip@axleos.com> This patch implements a new netdev device, reachable via -netdev vmnet-macos, that’s backed by macOS’s vmnet framework. The vmnet framework provides native bridging support, and its usage in this patch is intended as a replacement for attempts to use a tap device via the tuntaposx kernel extension. Notably, the tap/tuntaposx approach never would have worked in the first place, as QEMU interacts with the tap device via poll(), and macOS does not support polling device files. vmnet requires either a special entitlement, granted via a provisioning profile, or root access. Otherwise attempts to create the virtual interface will fail with a “generic error” status code. QEMU may not currently be signed with an entitlement granted in a provisioning profile, as this would necessitate pre-signed binary build distribution, rather than source-code distribution. As such, using this netdev currently requires that qemu be run with root access. I’ve opened a feedback report with Apple to allow the use of the relevant entitlement with this use case: https://openradar.appspot.com/radar?id=5007417364447232 vmnet offers three operating modes, all of which are supported by this patch via the “mode=host|shared|bridge” option: * "Host" mode: Allows the vmnet interface to communicate with other * vmnet interfaces that are in host mode and also with the native host. * "Shared" mode: Allows traffic originating from the vmnet interface to reach the Internet through a NAT. The vmnet interface can also communicate with the native host. * "Bridged" mode: Bridges the vmnet interface with a physical network interface. Each of these modes also provide some extra configuration that’s supported by this patch: * "Bridged" mode: The user may specify the physical interface to bridge with. Defaults to en0. * "Host" mode / "Shared" mode: The user may specify the DHCP range and subnet. Allocated by vmnet if not provided. vmnet also offers some extra configuration options that are not supported by this patch: * Enable isolation from other VMs using vmnet * Port forwarding rules * Enabling TCP segmentation offload * Only applicable in "shared" mode: specifying the NAT IPv6 prefix * Only available in "host" mode: specifying the IP address for the VM within an isolated network Note that this patch requires macOS 10.15 as a minimum, as this is when bridging support was implemented in vmnet.framework. Signed-off-by: Phillip Tennen <phillip@axleos.com> --- configure | 2 +- net/clients.h | 6 + net/meson.build | 1 + net/net.c | 3 + net/vmnet-macos.c | 447 ++++++++++++++++++++++++++++++++++++++++++++++ qapi/net.json | 120 ++++++++++++- qemu-options.hx | 9 + 7 files changed, 585 insertions(+), 3 deletions(-) create mode 100644 net/vmnet-macos.c diff --git a/configure b/configure index 4afd22bdf5..f449198db1 100755 --- a/configure +++ b/configure @@ -778,7 +778,7 @@ Darwin) fi audio_drv_list="coreaudio try-sdl" audio_possible_drivers="coreaudio sdl" - QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit $QEMU_LDFLAGS" + QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit -framework vmnet $QEMU_LDFLAGS" # Disable attempts to use ObjectiveC features in os/object.h since they # won't work when we're compiling with gcc as a C compiler. QEMU_CFLAGS="-DOS_OBJECT_USE_OBJC=0 $QEMU_CFLAGS" diff --git a/net/clients.h b/net/clients.h index 92f9b59aed..463a9b2f67 100644 --- a/net/clients.h +++ b/net/clients.h @@ -63,4 +63,10 @@ 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_DARWIN +int net_init_vmnet_macos(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp); +#endif + #endif /* QEMU_NET_CLIENTS_H */ diff --git a/net/meson.build b/net/meson.build index 1076b0a7ab..8c7c32f775 100644 --- a/net/meson.build +++ b/net/meson.build @@ -37,5 +37,6 @@ endif softmmu_ss.add(when: 'CONFIG_POSIX', if_true: files(tap_posix)) softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('tap-win32.c')) softmmu_ss.add(when: 'CONFIG_VHOST_NET_VDPA', if_true: files('vhost-vdpa.c')) +softmmu_ss.add(when: 'CONFIG_DARWIN', if_true: files('vmnet-macos.c')) subdir('can') diff --git a/net/net.c b/net/net.c index c1cd9c75f6..e68a410a89 100644 --- a/net/net.c +++ b/net/net.c @@ -977,6 +977,9 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( #ifdef CONFIG_L2TPV3 [NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3, #endif +#ifdef CONFIG_DARWIN + [NET_CLIENT_DRIVER_VMNET_MACOS] = net_init_vmnet_macos, +#endif }; diff --git a/net/vmnet-macos.c b/net/vmnet-macos.c new file mode 100644 index 0000000000..1a762751dd --- /dev/null +++ b/net/vmnet-macos.c @@ -0,0 +1,447 @@ +/* + * vmnet.framework backed netdev for macOS 10.15+ hosts + * + * 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/error-report.h" +#include "qapi/qapi-types-net.h" +#include "net/net.h" +/* macOS vmnet framework header */ +#include <vmnet/vmnet.h> + +typedef struct vmnet_state { + NetClientState nc; + interface_ref vmnet_iface_ref; + /* Switched on after vmnet informs us that the interface has started */ + bool link_up; + /* + * If qemu_send_packet_async returns 0, this is switched off until our + * delivery callback is invoked + */ + bool qemu_ready_to_receive; +} vmnet_state_t; + +int net_init_vmnet_macos(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp); + +static const char *_vmnet_status_repr(vmnet_return_t status) +{ + switch (status) { + case VMNET_SUCCESS: + return "success"; + case VMNET_FAILURE: + return "generic failure"; + case VMNET_MEM_FAILURE: + return "out of memory"; + case VMNET_INVALID_ARGUMENT: + return "invalid argument"; + case VMNET_SETUP_INCOMPLETE: + return "setup is incomplete"; + case VMNET_INVALID_ACCESS: + return "insufficient permissions"; + case VMNET_PACKET_TOO_BIG: + return "packet size exceeds MTU"; + case VMNET_BUFFER_EXHAUSTED: + return "kernel buffers temporarily exhausted"; + case VMNET_TOO_MANY_PACKETS: + return "number of packets exceeds system limit"; + /* This error code was introduced in macOS 11.0 */ +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 + case VMNET_SHARING_SERVICE_BUSY: + return "sharing service busy"; +#endif + default: + return "unknown status code"; + } +} + +static operating_modes_t _vmnet_operating_mode_enum_compat( + VmnetOperatingMode mode) +{ + switch (mode) { + case VMNET_OPERATING_MODE_HOST: + return VMNET_HOST_MODE; + case VMNET_OPERATING_MODE_SHARED: + return VMNET_SHARED_MODE; + case VMNET_OPERATING_MODE_BRIDGED: + return VMNET_BRIDGED_MODE; + default: + /* Should never happen as the modes are parsed before we get here */ + assert(false); + } +} + +static bool vmnet_can_receive(NetClientState *nc) +{ + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); + return s->link_up; +} + +static ssize_t vmnet_receive_iov(NetClientState *nc, + const struct iovec *iovs, + int iovcnt) +{ + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); + + /* Combine the provided iovs into a single vmnet packet */ + struct vmpktdesc *packet = g_new0(struct vmpktdesc, 1); + packet->vm_pkt_iov = g_new0(struct iovec, iovcnt); + memcpy(packet->vm_pkt_iov, iovs, sizeof(struct iovec) * iovcnt); + packet->vm_pkt_iovcnt = iovcnt; + packet->vm_flags = 0; + + /* Figure out the packet size by iterating the iov's */ + for (int i = 0; i < iovcnt; i++) { + const struct iovec *iov = iovs + i; + packet->vm_pkt_size += iov->iov_len; + } + + /* Finally, write the packet to the vmnet interface */ + int packet_count = 1; + vmnet_return_t result = vmnet_write(s->vmnet_iface_ref, packet, + &packet_count); + if (result != VMNET_SUCCESS || packet_count != 1) { + error_printf("Failed to send packet to host: %s\n", + _vmnet_status_repr(result)); + } + ssize_t wrote_bytes = packet->vm_pkt_size; + g_free(packet->vm_pkt_iov); + g_free(packet); + return wrote_bytes; +} + +static void vmnet_send_completed(NetClientState *nc, ssize_t len) +{ + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); + /* Ready to receive more packets! */ + vmnet_client_state->qemu_ready_to_receive = true; +} + +static NetClientInfo net_vmnet_macos_info = { + .type = NET_CLIENT_DRIVER_VMNET_MACOS, + .size = sizeof(vmnet_state_t), + .receive_iov = vmnet_receive_iov, + .can_receive = vmnet_can_receive, +}; + +static bool _validate_ifname_is_valid_bridge_target(const char *ifname) +{ + /* Iterate available bridge interfaces, ensure the provided one is valid */ + xpc_object_t bridge_interfaces = vmnet_copy_shared_interface_list(); + bool failed_to_match_iface_name = xpc_array_apply( + bridge_interfaces, + ^bool(size_t index, xpc_object_t _Nonnull value) { + if (!strcmp(xpc_string_get_string_ptr(value), ifname)) { + /* The interface name is valid! Stop iterating */ + return false; + } + return true; + }); + + if (failed_to_match_iface_name) { + error_printf("Invalid bridge interface name provided: %s\n", ifname); + error_printf("Valid bridge interfaces:\n"); + xpc_array_apply( + vmnet_copy_shared_interface_list(), + ^bool(size_t index, xpc_object_t _Nonnull value) { + error_printf("\t%s\n", xpc_string_get_string_ptr(value)); + /* Keep iterating */ + return true; + }); + exit(1); + return false; + } + + return true; +} + +static xpc_object_t _construct_vmnet_interface_description( + const NetdevVmnetModeOptions *vmnet_opts) +{ + operating_modes_t mode = _vmnet_operating_mode_enum_compat( + vmnet_opts->mode); + + /* Validate options */ + if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { + NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; + /* If one DHCP parameter is configured, all 3 are required */ + if (mode_opts.has_dhcp_start_address || + mode_opts.has_dhcp_end_address || + mode_opts.has_dhcp_subnet_mask) { + if (!(mode_opts.has_dhcp_start_address && + mode_opts.has_dhcp_end_address && + mode_opts.has_dhcp_subnet_mask)) { + error_printf("Incomplete DHCP configuration provided\n"); + exit(1); + } + } + } else if (mode == VMNET_BRIDGED_MODE) { + /* Nothing to validate */ + } else { + error_printf("Unknown vmnet mode %d\n", mode); + exit(1); + } + + xpc_object_t interface_desc = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_uint64( + interface_desc, + vmnet_operation_mode_key, + mode + ); + + if (mode == VMNET_BRIDGED_MODE) { + /* + * Configure the provided physical interface to act + * as a bridge with QEMU + */ + NetdevVmnetModeOptionsBridged mode_opts = vmnet_opts->u.bridged; + /* Bridge with en0 by default */ + const char *physical_ifname = mode_opts.has_ifname ? mode_opts.ifname : + "en0"; + _validate_ifname_is_valid_bridge_target(physical_ifname); + xpc_dictionary_set_string(interface_desc, + vmnet_shared_interface_name_key, + physical_ifname); + } else if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { + /* Pass the DHCP configuration to vmnet, if the user provided one */ + NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; + if (mode_opts.has_dhcp_start_address) { + /* All DHCP arguments are available, as per the checks above */ + xpc_dictionary_set_string(interface_desc, + vmnet_start_address_key, + mode_opts.dhcp_start_address); + xpc_dictionary_set_string(interface_desc, + vmnet_end_address_key, + mode_opts.dhcp_end_address); + xpc_dictionary_set_string(interface_desc, + vmnet_subnet_mask_key, + mode_opts.dhcp_subnet_mask); + } + } + + return interface_desc; +} + +int net_init_vmnet_macos(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp) +{ + assert(netdev->type == NET_CLIENT_DRIVER_VMNET_MACOS); + + NetdevVmnetModeOptions *vmnet_opts = netdev->u.vmnet_macos.options; + xpc_object_t iface_desc = _construct_vmnet_interface_description(vmnet_opts); + + NetClientState *nc = qemu_new_net_client(&net_vmnet_macos_info, peer, + "vmnet", name); + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); + + dispatch_queue_t vmnet_dispatch_queue = dispatch_queue_create( + "org.qemu.vmnet.iface_queue", + DISPATCH_QUEUE_SERIAL + ); + + __block vmnet_return_t vmnet_start_status = 0; + __block uint64_t vmnet_iface_mtu = 0; + __block uint64_t vmnet_max_packet_size = 0; + __block const char *vmnet_mac_address = NULL; + /* + * We can't refer to an array type directly within a block, + * so hold a pointer instead. + */ + uuid_string_t vmnet_iface_uuid = {0}; + __block uuid_string_t *vmnet_iface_uuid_ptr = &vmnet_iface_uuid; + /* These are only provided in VMNET_HOST_MODE and VMNET_SHARED_MODE */ + bool vmnet_provides_dhcp_info = ( + vmnet_opts->mode == VMNET_OPERATING_MODE_HOST || + vmnet_opts->mode == VMNET_OPERATING_MODE_SHARED); + __block const char *vmnet_subnet_mask = NULL; + __block const char *vmnet_dhcp_range_start = NULL; + __block const char *vmnet_dhcp_range_end = NULL; + + /* Create the vmnet interface */ + dispatch_semaphore_t vmnet_iface_sem = dispatch_semaphore_create(0); + interface_ref vmnet_iface_ref = vmnet_start_interface( + iface_desc, + vmnet_dispatch_queue, + ^(vmnet_return_t status, xpc_object_t _Nullable interface_param) { + vmnet_start_status = status; + if (vmnet_start_status != VMNET_SUCCESS || !interface_param) { + /* Early return if the interface couldn't be started */ + dispatch_semaphore_signal(vmnet_iface_sem); + return; + } + + /* + * Read the configuration that vmnet provided us. + * The provided dictionary is owned by XPC and may be freed + * shortly after this block's execution. + * So, copy data buffers now. + */ + vmnet_iface_mtu = xpc_dictionary_get_uint64( + interface_param, + vmnet_mtu_key + ); + vmnet_max_packet_size = xpc_dictionary_get_uint64( + interface_param, + vmnet_max_packet_size_key + ); + vmnet_mac_address = strdup(xpc_dictionary_get_string( + interface_param, + vmnet_mac_address_key + )); + + const uint8_t *iface_uuid = xpc_dictionary_get_uuid( + interface_param, + vmnet_interface_id_key + ); + uuid_unparse_upper(iface_uuid, *vmnet_iface_uuid_ptr); + + /* If we're in a mode that provides DHCP info, read it out now */ + if (vmnet_provides_dhcp_info) { + vmnet_dhcp_range_start = strdup(xpc_dictionary_get_string( + interface_param, + vmnet_start_address_key + )); + vmnet_dhcp_range_end = strdup(xpc_dictionary_get_string( + interface_param, + vmnet_end_address_key + )); + vmnet_subnet_mask = strdup(xpc_dictionary_get_string( + interface_param, + vmnet_subnet_mask_key + )); + } + dispatch_semaphore_signal(vmnet_iface_sem); + }); + + /* And block until we receive a response from vmnet */ + dispatch_semaphore_wait(vmnet_iface_sem, DISPATCH_TIME_FOREVER); + + /* Did we manage to start the interface? */ + if (vmnet_start_status != VMNET_SUCCESS || !vmnet_iface_ref) { + error_printf("Failed to start interface: %s\n", + _vmnet_status_repr(vmnet_start_status)); + if (vmnet_start_status == VMNET_FAILURE) { + error_printf("Hint: vmnet requires running with root access\n"); + } + return -1; + } + + info_report("Started vmnet interface with configuration:"); + info_report("MTU: %llu", vmnet_iface_mtu); + info_report("Max packet size: %llu", vmnet_max_packet_size); + info_report("MAC: %s", vmnet_mac_address); + if (vmnet_provides_dhcp_info) { + info_report("DHCP IPv4 start: %s", vmnet_dhcp_range_start); + info_report("DHCP IPv4 end: %s", vmnet_dhcp_range_end); + info_report("IPv4 subnet mask: %s", vmnet_subnet_mask); + } + info_report("UUID: %s", vmnet_iface_uuid); + + /* The interface is up! Set a block to run when packets are received */ + vmnet_client_state->vmnet_iface_ref = vmnet_iface_ref; + vmnet_return_t event_cb_stat = vmnet_interface_set_event_callback( + vmnet_iface_ref, + VMNET_INTERFACE_PACKETS_AVAILABLE, + vmnet_dispatch_queue, + ^(interface_event_t event_mask, xpc_object_t _Nonnull event) { + if (event_mask != VMNET_INTERFACE_PACKETS_AVAILABLE) { + error_printf("Unknown vmnet interface event 0x%08x\n", event_mask); + return; + } + + /* If we're unable to handle more packets now, drop this packet */ + if (!vmnet_client_state->qemu_ready_to_receive) { + return; + } + + /* + * TODO(Phillip Tennen <phillip@axleos.com>): There may be more than + * one packet available. + * As an optimization, we could read + * vmnet_estimated_packets_available_key packets now. + */ + char *packet_buf = g_malloc0(vmnet_max_packet_size); + struct iovec *iov = g_new0(struct iovec, 1); + iov->iov_base = packet_buf; + iov->iov_len = vmnet_max_packet_size; + + int pktcnt = 1; + struct vmpktdesc *v = g_new0(struct vmpktdesc, pktcnt); + v->vm_pkt_size = vmnet_max_packet_size; + v->vm_pkt_iov = iov; + v->vm_pkt_iovcnt = 1; + v->vm_flags = 0; + + vmnet_return_t result = vmnet_read(vmnet_iface_ref, v, &pktcnt); + if (result != VMNET_SUCCESS) { + error_printf("Failed to read packet from host: %s\n", + _vmnet_status_repr(result)); + } + + /* Ensure we read exactly one packet */ + assert(pktcnt == 1); + + /* Dispatch this block to a global queue instead of the main queue, + * which is only created when the program has a Cocoa event loop. + * If QEMU is started with -nographic, no Cocoa event loop will be + * created and thus the main queue will be unavailable. + */ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, + 0), + ^{ + qemu_mutex_lock_iothread(); + + /* + * Deliver the packet to the guest + * If the delivery succeeded synchronously, this returns the length + * of the sent packet. + */ + if (qemu_send_packet_async(nc, iov->iov_base, + v->vm_pkt_size, + vmnet_send_completed) == 0) { + vmnet_client_state->qemu_ready_to_receive = false; + } + + /* + * It's safe to free the packet buffers. + * Even if delivery needs to wait, qemu_net_queue_append copies + * the packet buffer. + */ + g_free(v); + g_free(iov); + g_free(packet_buf); + + qemu_mutex_unlock_iothread(); + }); + }); + + /* Did we manage to set an event callback? */ + if (event_cb_stat != VMNET_SUCCESS) { + error_printf("Failed to set up a callback to receive packets: %s\n", + _vmnet_status_repr(vmnet_start_status)); + exit(1); + } + + /* We're now ready to receive packets */ + vmnet_client_state->qemu_ready_to_receive = true; + vmnet_client_state->link_up = true; + + /* Include DHCP info if we're in a relevant mode */ + if (vmnet_provides_dhcp_info) { + snprintf(nc->info_str, sizeof(nc->info_str), + "dhcp_start=%s,dhcp_end=%s,mask=%s", + vmnet_dhcp_range_start, vmnet_dhcp_range_end, + vmnet_subnet_mask); + } else { + snprintf(nc->info_str, sizeof(nc->info_str), + "mac=%s", vmnet_mac_address); + } + + return 0; +} diff --git a/qapi/net.json b/qapi/net.json index c31748c87f..e4d4143243 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -450,6 +450,115 @@ '*vhostdev': 'str', '*queues': 'int' } } +## +# @VmnetOperatingMode: +# +# The operating modes in which a vmnet netdev can run +# Only available on macOS +# +# @host: the guest may communicate with the host +# and other guest network interfaces +# +# @shared: the guest may reach the Internet through a NAT, +# and may communicate with the host and other guest +# network interfaces +# +# @bridged: the guest's traffic is bridged with a +# physical network interface of the host +# +# Since: 6.0 +## +{ 'enum': 'VmnetOperatingMode', + 'data': [ 'host', 'shared', 'bridged' ], + 'if': 'defined(CONFIG_DARWIN)' } + +## +# @NetdevVmnetModeOptionsBridged: +# +# Options for the vmnet-macos netdev +# that are only available in 'bridged' mode +# Only available on macOS +# +# @ifname: the physical network interface to bridge with +# (defaults to en0 if not specified) +# +# Since: 6.0 +## +{ 'struct': 'NetdevVmnetModeOptionsBridged', + 'data': { '*ifname': 'str' }, + 'if': 'defined(CONFIG_DARWIN)' } + +## +# @NetdevVmnetModeOptionsHostOrShared: +# +# Options for the vmnet-macos netdev +# that are only available in 'host' or 'shared' mode +# Only available on macOS +# +# @dhcp-start-address: the gateway address to use for the interface. +# The range to dhcp_end_address is placed in the DHCP pool. +# (only valid with mode=host|shared) +# (must be specified with dhcp-end-address and +# dhcp-subnet-mask) +# (allocated automatically if unset) +# +# @dhcp-end-address: the DHCP IPv4 range end address to use for the interface. +# (only valid with mode=host|shared) +# (must be specified with dhcp-start-address and +# dhcp-subnet-mask) +# (allocated automatically if unset) +# +# @dhcp-subnet-mask: the IPv4 subnet mask (string) to use on the interface. +# (only valid with mode=host|shared) +# (must be specified with dhcp-start-address and +# dhcp-end-address) +# (allocated automatically if unset) +# +# Since: 6.0 +## +{ 'struct': 'NetdevVmnetModeOptionsHostOrShared', + 'data': { + '*dhcp-start-address': 'str' , + '*dhcp-end-address': 'str', + '*dhcp-subnet-mask': 'str' }, + 'if': 'defined(CONFIG_DARWIN)' } + +## +# @NetdevVmnetModeOptions: +# +# Options specific to different operating modes of a vmnet netdev +# Only available on macOS +# +# @mode: the operating mode vmnet should run in +# +# Since: 6.0 +## +{ 'union': 'NetdevVmnetModeOptions', + 'base': { 'mode': 'VmnetOperatingMode' }, + 'discriminator': 'mode', + 'data': { + 'bridged': 'NetdevVmnetModeOptionsBridged', + 'host': 'NetdevVmnetModeOptionsHostOrShared', + 'shared': 'NetdevVmnetModeOptionsHostOrShared' }, + 'if': 'defined(CONFIG_DARWIN)' } + +## +# @NetdevVmnetOptions: +# +# vmnet network backend +# Only available on macOS +# +# @options: a structure specifying the mode and mode-specific options +# (once QAPI supports a union type as a branch to another union type, +# this structure can be changed to a union, and the contents of +# NetdevVmnetModeOptions moved here) +# +# Since: 6.0 +## +{ 'struct': 'NetdevVmnetOptions', + 'data': {'options': 'NetdevVmnetModeOptions' }, + 'if': 'defined(CONFIG_DARWIN)' } + ## # @NetClientDriver: # @@ -458,10 +567,13 @@ # Since: 2.7 # # @vhost-vdpa since 5.1 +# +# @vmnet-macos since 6.0 (only available on macOS) ## { '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-macos', 'if': 'defined(CONFIG_DARWIN)' } ] } ## # @Netdev: @@ -475,6 +587,8 @@ # Since: 1.2 # # 'l2tpv3' - since 2.1 +# +# 'vmnet-macos' since 6.0 (only available on macOS) ## { 'union': 'Netdev', 'base': { 'id': 'str', 'type': 'NetClientDriver' }, @@ -490,7 +604,9 @@ 'hubport': 'NetdevHubPortOptions', 'netmap': 'NetdevNetmapOptions', 'vhost-user': 'NetdevVhostUserOptions', - 'vhost-vdpa': 'NetdevVhostVDPAOptions' } } + 'vhost-vdpa': 'NetdevVhostVDPAOptions', + 'vmnet-macos': { 'type': 'NetdevVmnetOptions', + 'if': 'defined(CONFIG_DARWIN)' } } } ## # @NetFilterDirection: diff --git a/qemu-options.hx b/qemu-options.hx index 9172d51659..ec6b40b079 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2483,6 +2483,15 @@ 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_DARWIN + "-netdev vmnet-macos,id=str,mode=bridged[,ifname=ifname]\n" + " configure a macOS-provided vmnet network in \"physical interface bridge\" mode\n" + " the physical interface to bridge with defaults to en0 if unspecified\n" + "-netdev vmnet-macos,id=str,mode=host|shared\n" + " [,dhcp_start_address=addr,dhcp_end_address=addr,dhcp_subnet_mask=mask]\n" + " configure a macOS-provided vmnet network in \"host\" or \"shared\" mode\n" + " the DHCP configuration will be set automatically if unspecified\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) -- 2.24.3 (Apple Git-128) ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4] net/macos: implement vmnet-based netdev 2021-02-18 13:49 [PATCH v4] net/macos: implement vmnet-based netdev phillip.ennen @ 2021-02-20 6:39 ` Howard Spoelstra 2021-02-23 23:24 ` Roman Bolshakov 1 sibling, 0 replies; 5+ messages in thread From: Howard Spoelstra @ 2021-02-20 6:39 UTC (permalink / raw) To: phillip.ennen Cc: Thomas Huth, Stefan Hajnoczi, jasowang, qemu-devel qemu-devel, Markus Armbruster, phillip On Thu, Feb 18, 2021 at 2:49 PM <phillip.ennen@gmail.com> wrote: > > From: Phillip Tennen <phillip@axleos.com> > > This patch implements a new netdev device, reachable via -netdev > vmnet-macos, that’s backed by macOS’s vmnet framework. > > The vmnet framework provides native bridging support, and its usage in > this patch is intended as a replacement for attempts to use a tap device > via the tuntaposx kernel extension. Notably, the tap/tuntaposx approach > never would have worked in the first place, as QEMU interacts with the > tap device via poll(), and macOS does not support polling device files. > > vmnet requires either a special entitlement, granted via a provisioning > profile, or root access. Otherwise attempts to create the virtual > interface will fail with a “generic error” status code. QEMU may not > currently be signed with an entitlement granted in a provisioning > profile, as this would necessitate pre-signed binary build distribution, > rather than source-code distribution. As such, using this netdev > currently requires that qemu be run with root access. I’ve opened a > feedback report with Apple to allow the use of the relevant entitlement > with this use case: > https://openradar.appspot.com/radar?id=5007417364447232 > > vmnet offers three operating modes, all of which are supported by this > patch via the “mode=host|shared|bridge” option: > > * "Host" mode: Allows the vmnet interface to communicate with other > * vmnet > interfaces that are in host mode and also with the native host. > * "Shared" mode: Allows traffic originating from the vmnet interface to > reach the Internet through a NAT. The vmnet interface can also > communicate with the native host. > * "Bridged" mode: Bridges the vmnet interface with a physical network > interface. > > Each of these modes also provide some extra configuration that’s > supported by this patch: > > * "Bridged" mode: The user may specify the physical interface to bridge > with. Defaults to en0. > * "Host" mode / "Shared" mode: The user may specify the DHCP range and > subnet. Allocated by vmnet if not provided. > > vmnet also offers some extra configuration options that are not > supported by this patch: > > * Enable isolation from other VMs using vmnet > * Port forwarding rules > * Enabling TCP segmentation offload > * Only applicable in "shared" mode: specifying the NAT IPv6 prefix > * Only available in "host" mode: specifying the IP address for the VM > within an isolated network > > Note that this patch requires macOS 10.15 as a minimum, as this is when > bridging support was implemented in vmnet.framework. > > Signed-off-by: Phillip Tennen <phillip@axleos.com> Hi Phillip, Thanks for the updated patch. I have a small problem applying it with either git am or patch. I have to manually fix configure. This has been the case from v1 up to now: hsp@hsps-Catalina-VB qemu-master % patch -p1 < ../patches/qemu/v4-net-macos-implement-vmnet-based-netdev.patch patching file configure Hunk #1 FAILED at 778. 1 out of 1 hunk FAILED -- saving rejects to file configure.rej patching file net/clients.h patching file net/meson.build patching file net/net.c patching file net/vmnet-macos.c patching file qapi/net.json patching file qemu-options.hx Hunk #1 succeeded at 2507 (offset 24 lines). Best, Howard ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] net/macos: implement vmnet-based netdev 2021-02-18 13:49 [PATCH v4] net/macos: implement vmnet-based netdev phillip.ennen 2021-02-20 6:39 ` Howard Spoelstra @ 2021-02-23 23:24 ` Roman Bolshakov 2021-03-03 11:24 ` Phillip Tennen 1 sibling, 1 reply; 5+ messages in thread From: Roman Bolshakov @ 2021-02-23 23:24 UTC (permalink / raw) To: phillip.ennen Cc: thuth, stefanha, jasowang, qemu-devel, armbru, phillip, hsp.cat7 On Thu, Feb 18, 2021 at 02:49:47PM +0100, phillip.ennen@gmail.com wrote: > From: Phillip Tennen <phillip@axleos.com> > > This patch implements a new netdev device, reachable via -netdev > vmnet-macos, that’s backed by macOS’s vmnet framework. > > The vmnet framework provides native bridging support, and its usage in > this patch is intended as a replacement for attempts to use a tap device > via the tuntaposx kernel extension. Notably, the tap/tuntaposx approach > never would have worked in the first place, as QEMU interacts with the > tap device via poll(), and macOS does not support polling device files. > > vmnet requires either a special entitlement, granted via a provisioning > profile, or root access. Otherwise attempts to create the virtual > interface will fail with a “generic error” status code. QEMU may not > currently be signed with an entitlement granted in a provisioning > profile, as this would necessitate pre-signed binary build distribution, > rather than source-code distribution. As such, using this netdev > currently requires that qemu be run with root access. I’ve opened a > feedback report with Apple to allow the use of the relevant entitlement > with this use case: > https://openradar.appspot.com/radar?id=5007417364447232 > > vmnet offers three operating modes, all of which are supported by this > patch via the “mode=host|shared|bridge” option: > > * "Host" mode: Allows the vmnet interface to communicate with other > * vmnet > interfaces that are in host mode and also with the native host. > * "Shared" mode: Allows traffic originating from the vmnet interface to > reach the Internet through a NAT. The vmnet interface can also > communicate with the native host. > * "Bridged" mode: Bridges the vmnet interface with a physical network > interface. > > Each of these modes also provide some extra configuration that’s > supported by this patch: > > * "Bridged" mode: The user may specify the physical interface to bridge > with. Defaults to en0. > * "Host" mode / "Shared" mode: The user may specify the DHCP range and > subnet. Allocated by vmnet if not provided. > > vmnet also offers some extra configuration options that are not > supported by this patch: > > * Enable isolation from other VMs using vmnet > * Port forwarding rules > * Enabling TCP segmentation offload > * Only applicable in "shared" mode: specifying the NAT IPv6 prefix > * Only available in "host" mode: specifying the IP address for the VM > within an isolated network > > Note that this patch requires macOS 10.15 as a minimum, as this is when > bridging support was implemented in vmnet.framework. > > Signed-off-by: Phillip Tennen <phillip@axleos.com> > --- > configure | 2 +- > net/clients.h | 6 + > net/meson.build | 1 + > net/net.c | 3 + > net/vmnet-macos.c | 447 ++++++++++++++++++++++++++++++++++++++++++++++ > qapi/net.json | 120 ++++++++++++- > qemu-options.hx | 9 + > 7 files changed, 585 insertions(+), 3 deletions(-) > create mode 100644 net/vmnet-macos.c > Hi Phillip, Thanks for working on this! Note that the patch doesn't apply to current master and there's a lot of warnings wrt trailing whitespaces: git am v4-net-macos-implement-vmnet-based-netdev.patch Applying: net/macos: implement vmnet-based netdev .git/rebase-apply/patch:462: trailing whitespace. * If QEMU is started with -nographic, no Cocoa event loop will be .git/rebase-apply/patch:465: trailing whitespace. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, .git/rebase-apply/patch:466: trailing whitespace. 0), .git/rebase-apply/patch:532: trailing whitespace. # @host: the guest may communicate with the host .git/rebase-apply/patch:535: trailing whitespace. # @shared: the guest may reach the Internet through a NAT, error: patch failed: configure:778 error: configure: patch does not apply Patch failed at 0001 net/macos: implement vmnet-based netdev hint: Use 'git am --show-current-patch' to see the failed patch When you have resolved this problem, run "git am --continue". If you prefer to skip this patch, run "git am --skip" instead. To restore the original branch and stop patching, run "git am --abort". Also it would be helpful to provide a changelog under commit message delimiter ("---") for reach new version of the patch to provide an overview of what has been changed between the versions. > diff --git a/configure b/configure > index 4afd22bdf5..f449198db1 100755 > --- a/configure > +++ b/configure > @@ -778,7 +778,7 @@ Darwin) > fi > audio_drv_list="coreaudio try-sdl" > audio_possible_drivers="coreaudio sdl" > - QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit $QEMU_LDFLAGS" > + QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit -framework vmnet $QEMU_LDFLAGS" I'm not sure this is right approach. Instead, we need a new configuration option for the feature + proper discovery. Something like this should work: https://github.com/roolebo/qemu/commit/e6c52d6bedb92f16defb5782b696853824b14bd9 > # Disable attempts to use ObjectiveC features in os/object.h since they > # won't work when we're compiling with gcc as a C compiler. > QEMU_CFLAGS="-DOS_OBJECT_USE_OBJC=0 $QEMU_CFLAGS" > diff --git a/net/clients.h b/net/clients.h > index 92f9b59aed..463a9b2f67 100644 > --- a/net/clients.h > +++ b/net/clients.h > @@ -63,4 +63,10 @@ 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_DARWIN Respectively, it would be wrapped with #ifdef CONFIG_VMNET instead of more generic CONFIG_DARWIN. > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > + NetClientState *peer, Error **errp); > +#endif > + > #endif /* QEMU_NET_CLIENTS_H */ > diff --git a/net/meson.build b/net/meson.build > index 1076b0a7ab..8c7c32f775 100644 > --- a/net/meson.build > +++ b/net/meson.build > @@ -37,5 +37,6 @@ endif > softmmu_ss.add(when: 'CONFIG_POSIX', if_true: files(tap_posix)) > softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('tap-win32.c')) > softmmu_ss.add(when: 'CONFIG_VHOST_NET_VDPA', if_true: files('vhost-vdpa.c')) > +softmmu_ss.add(when: 'CONFIG_DARWIN', if_true: files('vmnet-macos.c')) > > subdir('can') > diff --git a/net/net.c b/net/net.c > index c1cd9c75f6..e68a410a89 100644 > --- a/net/net.c > +++ b/net/net.c > @@ -977,6 +977,9 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( > #ifdef CONFIG_L2TPV3 > [NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3, > #endif > +#ifdef CONFIG_DARWIN CONFIG_VMNET should be used here as well. > + [NET_CLIENT_DRIVER_VMNET_MACOS] = net_init_vmnet_macos, > +#endif > }; > > > diff --git a/net/vmnet-macos.c b/net/vmnet-macos.c > new file mode 100644 > index 0000000000..1a762751dd > --- /dev/null > +++ b/net/vmnet-macos.c > @@ -0,0 +1,447 @@ > +/* > + * vmnet.framework backed netdev for macOS 10.15+ hosts > + * > + * 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/error-report.h" > +#include "qapi/qapi-types-net.h" > +#include "net/net.h" > +/* macOS vmnet framework header */ > +#include <vmnet/vmnet.h> > + > +typedef struct vmnet_state { > + NetClientState nc; > + interface_ref vmnet_iface_ref; > + /* Switched on after vmnet informs us that the interface has started */ > + bool link_up; > + /* > + * If qemu_send_packet_async returns 0, this is switched off until our > + * delivery callback is invoked > + */ > + bool qemu_ready_to_receive; > +} vmnet_state_t; > + > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > + NetClientState *peer, Error **errp); > + > +static const char *_vmnet_status_repr(vmnet_return_t status) Underscore may be dropped. > +{ > + switch (status) { > + case VMNET_SUCCESS: > + return "success"; > + case VMNET_FAILURE: > + return "generic failure"; > + case VMNET_MEM_FAILURE: > + return "out of memory"; > + case VMNET_INVALID_ARGUMENT: > + return "invalid argument"; > + case VMNET_SETUP_INCOMPLETE: > + return "setup is incomplete"; > + case VMNET_INVALID_ACCESS: > + return "insufficient permissions"; > + case VMNET_PACKET_TOO_BIG: > + return "packet size exceeds MTU"; > + case VMNET_BUFFER_EXHAUSTED: > + return "kernel buffers temporarily exhausted"; > + case VMNET_TOO_MANY_PACKETS: > + return "number of packets exceeds system limit"; > + /* This error code was introduced in macOS 11.0 */ > +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 > + case VMNET_SHARING_SERVICE_BUSY: > + return "sharing service busy"; > +#endif > + default: > + return "unknown status code"; > + } > +} > + > +static operating_modes_t _vmnet_operating_mode_enum_compat( > + VmnetOperatingMode mode) Underscore may be dropped. > +{ > + switch (mode) { > + case VMNET_OPERATING_MODE_HOST: > + return VMNET_HOST_MODE; > + case VMNET_OPERATING_MODE_SHARED: > + return VMNET_SHARED_MODE; > + case VMNET_OPERATING_MODE_BRIDGED: > + return VMNET_BRIDGED_MODE; > + default: > + /* Should never happen as the modes are parsed before we get here */ > + assert(false); > + } > +} > + > +static bool vmnet_can_receive(NetClientState *nc) > +{ > + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); > + return s->link_up; I'm not sure this is correct. Did you mean s->qemu_ready_to_receive? > +} > + > +static ssize_t vmnet_receive_iov(NetClientState *nc, > + const struct iovec *iovs, > + int iovcnt) > +{ > + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); > + > + /* Combine the provided iovs into a single vmnet packet */ > + struct vmpktdesc *packet = g_new0(struct vmpktdesc, 1); packet_count could be used instead of 1. > + packet->vm_pkt_iov = g_new0(struct iovec, iovcnt); > + memcpy(packet->vm_pkt_iov, iovs, sizeof(struct iovec) * iovcnt); > + packet->vm_pkt_iovcnt = iovcnt; Should we use iov_copy() instead? > + packet->vm_flags = 0; The line is redundant with g_new0. > + > + /* Figure out the packet size by iterating the iov's */ > + for (int i = 0; i < iovcnt; i++) { > + const struct iovec *iov = iovs + i; > + packet->vm_pkt_size += iov->iov_len; > + } I wonder if we should add a check if packet->vm_pkt_size is beyond vmnet_max_packet_size_key? Also I'm not entirely sure that we should at most transmit only one packet, a sort of coalescing might be helpful (Apple claims up ot 200 packets per one vmnet_write) but I'm not an expert of net part of QEMU. Stefan may provide more info on that. > + > + /* Finally, write the packet to the vmnet interface */ > + int packet_count = 1; > + vmnet_return_t result = vmnet_write(s->vmnet_iface_ref, packet, > + &packet_count); > + if (result != VMNET_SUCCESS || packet_count != 1) { > + error_printf("Failed to send packet to host: %s\n", > + _vmnet_status_repr(result)); > + } > + ssize_t wrote_bytes = packet->vm_pkt_size; That's going to mismatch with actual number of bytes written if packet_count returned from vmnet_write equals zero. > + g_free(packet->vm_pkt_iov); > + g_free(packet); > + return wrote_bytes; > +} > + > +static void vmnet_send_completed(NetClientState *nc, ssize_t len) > +{ > + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); > + /* Ready to receive more packets! */ > + vmnet_client_state->qemu_ready_to_receive = true; > +} > + > +static NetClientInfo net_vmnet_macos_info = { > + .type = NET_CLIENT_DRIVER_VMNET_MACOS, > + .size = sizeof(vmnet_state_t), > + .receive_iov = vmnet_receive_iov, > + .can_receive = vmnet_can_receive, > +}; > + > +static bool _validate_ifname_is_valid_bridge_target(const char *ifname) Underscore may be dropped from the function. > +{ > + /* Iterate available bridge interfaces, ensure the provided one is valid */ > + xpc_object_t bridge_interfaces = vmnet_copy_shared_interface_list(); > + bool failed_to_match_iface_name = xpc_array_apply( > + bridge_interfaces, > + ^bool(size_t index, xpc_object_t _Nonnull value) { > + if (!strcmp(xpc_string_get_string_ptr(value), ifname)) { > + /* The interface name is valid! Stop iterating */ > + return false; > + } > + return true; > + }); > + > + if (failed_to_match_iface_name) { > + error_printf("Invalid bridge interface name provided: %s\n", ifname); > + error_printf("Valid bridge interfaces:\n"); > + xpc_array_apply( > + vmnet_copy_shared_interface_list(), > + ^bool(size_t index, xpc_object_t _Nonnull value) { > + error_printf("\t%s\n", xpc_string_get_string_ptr(value)); > + /* Keep iterating */ > + return true; > + }); > + exit(1); > + return false; > + } > + > + return true; > +} > + > +static xpc_object_t _construct_vmnet_interface_description( Underscore is not needed I think. > + const NetdevVmnetModeOptions *vmnet_opts) > +{ > + operating_modes_t mode = _vmnet_operating_mode_enum_compat( > + vmnet_opts->mode); > + > + /* Validate options */ > + if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { > + NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; > + /* If one DHCP parameter is configured, all 3 are required */ > + if (mode_opts.has_dhcp_start_address || > + mode_opts.has_dhcp_end_address || > + mode_opts.has_dhcp_subnet_mask) { > + if (!(mode_opts.has_dhcp_start_address && > + mode_opts.has_dhcp_end_address && > + mode_opts.has_dhcp_subnet_mask)) { > + error_printf("Incomplete DHCP configuration provided\n"); > + exit(1); > + } > + } > + } else if (mode == VMNET_BRIDGED_MODE) { I think we want to enable bridging mode only on macOS 10.15 and above where vmnet_copy_shared_interface_list() is supported. > + /* Nothing to validate */ > + } else { > + error_printf("Unknown vmnet mode %d\n", mode); > + exit(1); > + } > + > + xpc_object_t interface_desc = xpc_dictionary_create(NULL, NULL, 0); > + xpc_dictionary_set_uint64( > + interface_desc, > + vmnet_operation_mode_key, > + mode > + ); > + > + if (mode == VMNET_BRIDGED_MODE) { > + /* > + * Configure the provided physical interface to act > + * as a bridge with QEMU > + */ > + NetdevVmnetModeOptionsBridged mode_opts = vmnet_opts->u.bridged; > + /* Bridge with en0 by default */ > + const char *physical_ifname = mode_opts.has_ifname ? mode_opts.ifname : > + "en0"; I think a default interface is not needed here, it's better to require an explicit inteface to bridge with. Some people prefer wired, others wireless. Ocasionally some do both :) More comments later! Thanks, Roman > + _validate_ifname_is_valid_bridge_target(physical_ifname); > + xpc_dictionary_set_string(interface_desc, > + vmnet_shared_interface_name_key, > + physical_ifname); > + } else if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { > + /* Pass the DHCP configuration to vmnet, if the user provided one */ > + NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; > + if (mode_opts.has_dhcp_start_address) { > + /* All DHCP arguments are available, as per the checks above */ > + xpc_dictionary_set_string(interface_desc, > + vmnet_start_address_key, > + mode_opts.dhcp_start_address); > + xpc_dictionary_set_string(interface_desc, > + vmnet_end_address_key, > + mode_opts.dhcp_end_address); > + xpc_dictionary_set_string(interface_desc, > + vmnet_subnet_mask_key, > + mode_opts.dhcp_subnet_mask); > + } > + } > + > + return interface_desc; > +} > + > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > + NetClientState *peer, Error **errp) > +{ > + assert(netdev->type == NET_CLIENT_DRIVER_VMNET_MACOS); > + > + NetdevVmnetModeOptions *vmnet_opts = netdev->u.vmnet_macos.options; > + xpc_object_t iface_desc = _construct_vmnet_interface_description(vmnet_opts); > + > + NetClientState *nc = qemu_new_net_client(&net_vmnet_macos_info, peer, > + "vmnet", name); > + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); > + > + dispatch_queue_t vmnet_dispatch_queue = dispatch_queue_create( > + "org.qemu.vmnet.iface_queue", > + DISPATCH_QUEUE_SERIAL > + ); > + > + __block vmnet_return_t vmnet_start_status = 0; > + __block uint64_t vmnet_iface_mtu = 0; > + __block uint64_t vmnet_max_packet_size = 0; > + __block const char *vmnet_mac_address = NULL; > + /* > + * We can't refer to an array type directly within a block, > + * so hold a pointer instead. > + */ > + uuid_string_t vmnet_iface_uuid = {0}; > + __block uuid_string_t *vmnet_iface_uuid_ptr = &vmnet_iface_uuid; > + /* These are only provided in VMNET_HOST_MODE and VMNET_SHARED_MODE */ > + bool vmnet_provides_dhcp_info = ( > + vmnet_opts->mode == VMNET_OPERATING_MODE_HOST || > + vmnet_opts->mode == VMNET_OPERATING_MODE_SHARED); > + __block const char *vmnet_subnet_mask = NULL; > + __block const char *vmnet_dhcp_range_start = NULL; > + __block const char *vmnet_dhcp_range_end = NULL; > + > + /* Create the vmnet interface */ > + dispatch_semaphore_t vmnet_iface_sem = dispatch_semaphore_create(0); > + interface_ref vmnet_iface_ref = vmnet_start_interface( > + iface_desc, > + vmnet_dispatch_queue, > + ^(vmnet_return_t status, xpc_object_t _Nullable interface_param) { > + vmnet_start_status = status; > + if (vmnet_start_status != VMNET_SUCCESS || !interface_param) { > + /* Early return if the interface couldn't be started */ > + dispatch_semaphore_signal(vmnet_iface_sem); > + return; > + } > + > + /* > + * Read the configuration that vmnet provided us. > + * The provided dictionary is owned by XPC and may be freed > + * shortly after this block's execution. > + * So, copy data buffers now. > + */ > + vmnet_iface_mtu = xpc_dictionary_get_uint64( > + interface_param, > + vmnet_mtu_key > + ); > + vmnet_max_packet_size = xpc_dictionary_get_uint64( > + interface_param, > + vmnet_max_packet_size_key > + ); > + vmnet_mac_address = strdup(xpc_dictionary_get_string( > + interface_param, > + vmnet_mac_address_key > + )); > + > + const uint8_t *iface_uuid = xpc_dictionary_get_uuid( > + interface_param, > + vmnet_interface_id_key > + ); > + uuid_unparse_upper(iface_uuid, *vmnet_iface_uuid_ptr); > + > + /* If we're in a mode that provides DHCP info, read it out now */ > + if (vmnet_provides_dhcp_info) { > + vmnet_dhcp_range_start = strdup(xpc_dictionary_get_string( > + interface_param, > + vmnet_start_address_key > + )); > + vmnet_dhcp_range_end = strdup(xpc_dictionary_get_string( > + interface_param, > + vmnet_end_address_key > + )); > + vmnet_subnet_mask = strdup(xpc_dictionary_get_string( > + interface_param, > + vmnet_subnet_mask_key > + )); > + } > + dispatch_semaphore_signal(vmnet_iface_sem); > + }); > + > + /* And block until we receive a response from vmnet */ > + dispatch_semaphore_wait(vmnet_iface_sem, DISPATCH_TIME_FOREVER); > + > + /* Did we manage to start the interface? */ > + if (vmnet_start_status != VMNET_SUCCESS || !vmnet_iface_ref) { > + error_printf("Failed to start interface: %s\n", > + _vmnet_status_repr(vmnet_start_status)); > + if (vmnet_start_status == VMNET_FAILURE) { > + error_printf("Hint: vmnet requires running with root access\n"); > + } > + return -1; > + } > + > + info_report("Started vmnet interface with configuration:"); > + info_report("MTU: %llu", vmnet_iface_mtu); > + info_report("Max packet size: %llu", vmnet_max_packet_size); > + info_report("MAC: %s", vmnet_mac_address); > + if (vmnet_provides_dhcp_info) { > + info_report("DHCP IPv4 start: %s", vmnet_dhcp_range_start); > + info_report("DHCP IPv4 end: %s", vmnet_dhcp_range_end); > + info_report("IPv4 subnet mask: %s", vmnet_subnet_mask); > + } > + info_report("UUID: %s", vmnet_iface_uuid); > + > + /* The interface is up! Set a block to run when packets are received */ > + vmnet_client_state->vmnet_iface_ref = vmnet_iface_ref; > + vmnet_return_t event_cb_stat = vmnet_interface_set_event_callback( > + vmnet_iface_ref, > + VMNET_INTERFACE_PACKETS_AVAILABLE, > + vmnet_dispatch_queue, > + ^(interface_event_t event_mask, xpc_object_t _Nonnull event) { > + if (event_mask != VMNET_INTERFACE_PACKETS_AVAILABLE) { > + error_printf("Unknown vmnet interface event 0x%08x\n", event_mask); > + return; > + } > + > + /* If we're unable to handle more packets now, drop this packet */ > + if (!vmnet_client_state->qemu_ready_to_receive) { > + return; > + } > + > + /* > + * TODO(Phillip Tennen <phillip@axleos.com>): There may be more than > + * one packet available. > + * As an optimization, we could read > + * vmnet_estimated_packets_available_key packets now. > + */ > + char *packet_buf = g_malloc0(vmnet_max_packet_size); > + struct iovec *iov = g_new0(struct iovec, 1); > + iov->iov_base = packet_buf; > + iov->iov_len = vmnet_max_packet_size; > + > + int pktcnt = 1; > + struct vmpktdesc *v = g_new0(struct vmpktdesc, pktcnt); > + v->vm_pkt_size = vmnet_max_packet_size; > + v->vm_pkt_iov = iov; > + v->vm_pkt_iovcnt = 1; > + v->vm_flags = 0; > + > + vmnet_return_t result = vmnet_read(vmnet_iface_ref, v, &pktcnt); > + if (result != VMNET_SUCCESS) { > + error_printf("Failed to read packet from host: %s\n", > + _vmnet_status_repr(result)); > + } > + > + /* Ensure we read exactly one packet */ > + assert(pktcnt == 1); > + > + /* Dispatch this block to a global queue instead of the main queue, > + * which is only created when the program has a Cocoa event loop. > + * If QEMU is started with -nographic, no Cocoa event loop will be > + * created and thus the main queue will be unavailable. > + */ > + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, > + 0), > + ^{ > + qemu_mutex_lock_iothread(); > + > + /* > + * Deliver the packet to the guest > + * If the delivery succeeded synchronously, this returns the length > + * of the sent packet. > + */ > + if (qemu_send_packet_async(nc, iov->iov_base, > + v->vm_pkt_size, > + vmnet_send_completed) == 0) { > + vmnet_client_state->qemu_ready_to_receive = false; > + } > + > + /* > + * It's safe to free the packet buffers. > + * Even if delivery needs to wait, qemu_net_queue_append copies > + * the packet buffer. > + */ > + g_free(v); > + g_free(iov); > + g_free(packet_buf); > + > + qemu_mutex_unlock_iothread(); > + }); > + }); > + > + /* Did we manage to set an event callback? */ > + if (event_cb_stat != VMNET_SUCCESS) { > + error_printf("Failed to set up a callback to receive packets: %s\n", > + _vmnet_status_repr(vmnet_start_status)); > + exit(1); > + } > + > + /* We're now ready to receive packets */ > + vmnet_client_state->qemu_ready_to_receive = true; > + vmnet_client_state->link_up = true; > + > + /* Include DHCP info if we're in a relevant mode */ > + if (vmnet_provides_dhcp_info) { > + snprintf(nc->info_str, sizeof(nc->info_str), > + "dhcp_start=%s,dhcp_end=%s,mask=%s", > + vmnet_dhcp_range_start, vmnet_dhcp_range_end, > + vmnet_subnet_mask); > + } else { > + snprintf(nc->info_str, sizeof(nc->info_str), > + "mac=%s", vmnet_mac_address); > + } > + > + return 0; > +} > diff --git a/qapi/net.json b/qapi/net.json > index c31748c87f..e4d4143243 100644 > --- a/qapi/net.json > +++ b/qapi/net.json > @@ -450,6 +450,115 @@ > '*vhostdev': 'str', > '*queues': 'int' } } > > +## > +# @VmnetOperatingMode: > +# > +# The operating modes in which a vmnet netdev can run > +# Only available on macOS > +# > +# @host: the guest may communicate with the host > +# and other guest network interfaces > +# > +# @shared: the guest may reach the Internet through a NAT, > +# and may communicate with the host and other guest > +# network interfaces > +# > +# @bridged: the guest's traffic is bridged with a > +# physical network interface of the host > +# > +# Since: 6.0 > +## > +{ 'enum': 'VmnetOperatingMode', > + 'data': [ 'host', 'shared', 'bridged' ], > + 'if': 'defined(CONFIG_DARWIN)' } > + > +## > +# @NetdevVmnetModeOptionsBridged: > +# > +# Options for the vmnet-macos netdev > +# that are only available in 'bridged' mode > +# Only available on macOS > +# > +# @ifname: the physical network interface to bridge with > +# (defaults to en0 if not specified) > +# > +# Since: 6.0 > +## > +{ 'struct': 'NetdevVmnetModeOptionsBridged', > + 'data': { '*ifname': 'str' }, > + 'if': 'defined(CONFIG_DARWIN)' } > + > +## > +# @NetdevVmnetModeOptionsHostOrShared: > +# > +# Options for the vmnet-macos netdev > +# that are only available in 'host' or 'shared' mode > +# Only available on macOS > +# > +# @dhcp-start-address: the gateway address to use for the interface. > +# The range to dhcp_end_address is placed in the DHCP pool. > +# (only valid with mode=host|shared) > +# (must be specified with dhcp-end-address and > +# dhcp-subnet-mask) > +# (allocated automatically if unset) > +# > +# @dhcp-end-address: the DHCP IPv4 range end address to use for the interface. > +# (only valid with mode=host|shared) > +# (must be specified with dhcp-start-address and > +# dhcp-subnet-mask) > +# (allocated automatically if unset) > +# > +# @dhcp-subnet-mask: the IPv4 subnet mask (string) to use on the interface. > +# (only valid with mode=host|shared) > +# (must be specified with dhcp-start-address and > +# dhcp-end-address) > +# (allocated automatically if unset) > +# > +# Since: 6.0 > +## > +{ 'struct': 'NetdevVmnetModeOptionsHostOrShared', > + 'data': { > + '*dhcp-start-address': 'str' , > + '*dhcp-end-address': 'str', > + '*dhcp-subnet-mask': 'str' }, > + 'if': 'defined(CONFIG_DARWIN)' } > + > +## > +# @NetdevVmnetModeOptions: > +# > +# Options specific to different operating modes of a vmnet netdev > +# Only available on macOS > +# > +# @mode: the operating mode vmnet should run in > +# > +# Since: 6.0 > +## > +{ 'union': 'NetdevVmnetModeOptions', > + 'base': { 'mode': 'VmnetOperatingMode' }, > + 'discriminator': 'mode', > + 'data': { > + 'bridged': 'NetdevVmnetModeOptionsBridged', > + 'host': 'NetdevVmnetModeOptionsHostOrShared', > + 'shared': 'NetdevVmnetModeOptionsHostOrShared' }, > + 'if': 'defined(CONFIG_DARWIN)' } > + > +## > +# @NetdevVmnetOptions: > +# > +# vmnet network backend > +# Only available on macOS > +# > +# @options: a structure specifying the mode and mode-specific options > +# (once QAPI supports a union type as a branch to another union type, > +# this structure can be changed to a union, and the contents of > +# NetdevVmnetModeOptions moved here) > +# > +# Since: 6.0 > +## > +{ 'struct': 'NetdevVmnetOptions', > + 'data': {'options': 'NetdevVmnetModeOptions' }, > + 'if': 'defined(CONFIG_DARWIN)' } > + > ## > # @NetClientDriver: > # > @@ -458,10 +567,13 @@ > # Since: 2.7 > # > # @vhost-vdpa since 5.1 > +# > +# @vmnet-macos since 6.0 (only available on macOS) > ## > { '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-macos', 'if': 'defined(CONFIG_DARWIN)' } ] } > > ## > # @Netdev: > @@ -475,6 +587,8 @@ > # Since: 1.2 > # > # 'l2tpv3' - since 2.1 > +# > +# 'vmnet-macos' since 6.0 (only available on macOS) > ## > { 'union': 'Netdev', > 'base': { 'id': 'str', 'type': 'NetClientDriver' }, > @@ -490,7 +604,9 @@ > 'hubport': 'NetdevHubPortOptions', > 'netmap': 'NetdevNetmapOptions', > 'vhost-user': 'NetdevVhostUserOptions', > - 'vhost-vdpa': 'NetdevVhostVDPAOptions' } } > + 'vhost-vdpa': 'NetdevVhostVDPAOptions', > + 'vmnet-macos': { 'type': 'NetdevVmnetOptions', > + 'if': 'defined(CONFIG_DARWIN)' } } } > > ## > # @NetFilterDirection: > diff --git a/qemu-options.hx b/qemu-options.hx > index 9172d51659..ec6b40b079 100644 > --- a/qemu-options.hx > +++ b/qemu-options.hx > @@ -2483,6 +2483,15 @@ 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_DARWIN > + "-netdev vmnet-macos,id=str,mode=bridged[,ifname=ifname]\n" > + " configure a macOS-provided vmnet network in \"physical interface bridge\" mode\n" > + " the physical interface to bridge with defaults to en0 if unspecified\n" > + "-netdev vmnet-macos,id=str,mode=host|shared\n" > + " [,dhcp_start_address=addr,dhcp_end_address=addr,dhcp_subnet_mask=mask]\n" > + " configure a macOS-provided vmnet network in \"host\" or \"shared\" mode\n" > + " the DHCP configuration will be set automatically if unspecified\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) > -- > 2.24.3 (Apple Git-128) > > ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] net/macos: implement vmnet-based netdev 2021-02-23 23:24 ` Roman Bolshakov @ 2021-03-03 11:24 ` Phillip Tennen 2021-04-26 14:05 ` Alessio Dionisi 0 siblings, 1 reply; 5+ messages in thread From: Phillip Tennen @ 2021-03-03 11:24 UTC (permalink / raw) To: Roman Bolshakov Cc: Thomas Huth, Stefan Hajnoczi, jasowang, qemu-devel qemu-devel, Markus Armbruster, Phillip Tennen, Howard Spoelstra [-- Attachment #1: Type: text/plain, Size: 35244 bytes --] Thanks very much for your help and feedback! Apologies for my delay in following up. I'll submit a new version that implements the feedback you've provided here, as well as the QAPI schema changes @Markus Armbruster <armbru@redhat.com> (thanks to you as well for your time and review!) pointed out. Phillip On Wed, Feb 24, 2021 at 12:25 AM Roman Bolshakov <r.bolshakov@yadro.com> wrote: > On Thu, Feb 18, 2021 at 02:49:47PM +0100, phillip.ennen@gmail.com wrote: > > From: Phillip Tennen <phillip@axleos.com> > > > > This patch implements a new netdev device, reachable via -netdev > > vmnet-macos, that’s backed by macOS’s vmnet framework. > > > > The vmnet framework provides native bridging support, and its usage in > > this patch is intended as a replacement for attempts to use a tap device > > via the tuntaposx kernel extension. Notably, the tap/tuntaposx approach > > never would have worked in the first place, as QEMU interacts with the > > tap device via poll(), and macOS does not support polling device files. > > > > vmnet requires either a special entitlement, granted via a provisioning > > profile, or root access. Otherwise attempts to create the virtual > > interface will fail with a “generic error” status code. QEMU may not > > currently be signed with an entitlement granted in a provisioning > > profile, as this would necessitate pre-signed binary build distribution, > > rather than source-code distribution. As such, using this netdev > > currently requires that qemu be run with root access. I’ve opened a > > feedback report with Apple to allow the use of the relevant entitlement > > with this use case: > > https://openradar.appspot.com/radar?id=5007417364447232 > > > > vmnet offers three operating modes, all of which are supported by this > > patch via the “mode=host|shared|bridge” option: > > > > * "Host" mode: Allows the vmnet interface to communicate with other > > * vmnet > > interfaces that are in host mode and also with the native host. > > * "Shared" mode: Allows traffic originating from the vmnet interface to > > reach the Internet through a NAT. The vmnet interface can also > > communicate with the native host. > > * "Bridged" mode: Bridges the vmnet interface with a physical network > > interface. > > > > Each of these modes also provide some extra configuration that’s > > supported by this patch: > > > > * "Bridged" mode: The user may specify the physical interface to bridge > > with. Defaults to en0. > > * "Host" mode / "Shared" mode: The user may specify the DHCP range and > > subnet. Allocated by vmnet if not provided. > > > > vmnet also offers some extra configuration options that are not > > supported by this patch: > > > > * Enable isolation from other VMs using vmnet > > * Port forwarding rules > > * Enabling TCP segmentation offload > > * Only applicable in "shared" mode: specifying the NAT IPv6 prefix > > * Only available in "host" mode: specifying the IP address for the VM > > within an isolated network > > > > Note that this patch requires macOS 10.15 as a minimum, as this is when > > bridging support was implemented in vmnet.framework. > > > > Signed-off-by: Phillip Tennen <phillip@axleos.com> > > --- > > configure | 2 +- > > net/clients.h | 6 + > > net/meson.build | 1 + > > net/net.c | 3 + > > net/vmnet-macos.c | 447 ++++++++++++++++++++++++++++++++++++++++++++++ > > qapi/net.json | 120 ++++++++++++- > > qemu-options.hx | 9 + > > 7 files changed, 585 insertions(+), 3 deletions(-) > > create mode 100644 net/vmnet-macos.c > > > > Hi Phillip, > > Thanks for working on this! > > Note that the patch doesn't apply to current master and there's a lot of > warnings wrt trailing whitespaces: > > git am v4-net-macos-implement-vmnet-based-netdev.patch > Applying: net/macos: implement vmnet-based netdev > .git/rebase-apply/patch:462: trailing whitespace. > * If QEMU is started with -nographic, no Cocoa event loop will be > .git/rebase-apply/patch:465: trailing whitespace. > > dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, > .git/rebase-apply/patch:466: trailing whitespace. > 0), > .git/rebase-apply/patch:532: trailing whitespace. > # @host: the guest may communicate with the host > .git/rebase-apply/patch:535: trailing whitespace. > # @shared: the guest may reach the Internet through a NAT, > error: patch failed: configure:778 > error: configure: patch does not apply > Patch failed at 0001 net/macos: implement vmnet-based netdev > hint: Use 'git am --show-current-patch' to see the failed patch > When you have resolved this problem, run "git am --continue". > If you prefer to skip this patch, run "git am --skip" instead. > To restore the original branch and stop patching, run "git am --abort". > > Also it would be helpful to provide a changelog under commit message > delimiter ("---") for reach new version of the patch to provide an > overview of what has been changed between the versions. > > > diff --git a/configure b/configure > > index 4afd22bdf5..f449198db1 100755 > > --- a/configure > > +++ b/configure > > @@ -778,7 +778,7 @@ Darwin) > > fi > > audio_drv_list="coreaudio try-sdl" > > audio_possible_drivers="coreaudio sdl" > > - QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit > $QEMU_LDFLAGS" > > + QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit -framework > vmnet $QEMU_LDFLAGS" > > I'm not sure this is right approach. Instead, we need a new > configuration option for the feature + proper discovery. Something like > this should work: > > > https://github.com/roolebo/qemu/commit/e6c52d6bedb92f16defb5782b696853824b14bd9 > > > # Disable attempts to use ObjectiveC features in os/object.h since > they > > # won't work when we're compiling with gcc as a C compiler. > > QEMU_CFLAGS="-DOS_OBJECT_USE_OBJC=0 $QEMU_CFLAGS" > > diff --git a/net/clients.h b/net/clients.h > > index 92f9b59aed..463a9b2f67 100644 > > --- a/net/clients.h > > +++ b/net/clients.h > > @@ -63,4 +63,10 @@ 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_DARWIN > > Respectively, it would be wrapped with #ifdef CONFIG_VMNET instead of > more generic CONFIG_DARWIN. > > > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > > + NetClientState *peer, Error **errp); > > +#endif > > + > > #endif /* QEMU_NET_CLIENTS_H */ > > diff --git a/net/meson.build b/net/meson.build > > index 1076b0a7ab..8c7c32f775 100644 > > --- a/net/meson.build > > +++ b/net/meson.build > > @@ -37,5 +37,6 @@ endif > > softmmu_ss.add(when: 'CONFIG_POSIX', if_true: files(tap_posix)) > > softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('tap-win32.c')) > > softmmu_ss.add(when: 'CONFIG_VHOST_NET_VDPA', if_true: > files('vhost-vdpa.c')) > > +softmmu_ss.add(when: 'CONFIG_DARWIN', if_true: files('vmnet-macos.c')) > > > > subdir('can') > > diff --git a/net/net.c b/net/net.c > > index c1cd9c75f6..e68a410a89 100644 > > --- a/net/net.c > > +++ b/net/net.c > > @@ -977,6 +977,9 @@ static int (* const > net_client_init_fun[NET_CLIENT_DRIVER__MAX])( > > #ifdef CONFIG_L2TPV3 > > [NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3, > > #endif > > +#ifdef CONFIG_DARWIN > > CONFIG_VMNET should be used here as well. > > > + [NET_CLIENT_DRIVER_VMNET_MACOS] = net_init_vmnet_macos, > > +#endif > > }; > > > > > > diff --git a/net/vmnet-macos.c b/net/vmnet-macos.c > > new file mode 100644 > > index 0000000000..1a762751dd > > --- /dev/null > > +++ b/net/vmnet-macos.c > > @@ -0,0 +1,447 @@ > > +/* > > + * vmnet.framework backed netdev for macOS 10.15+ hosts > > + * > > + * 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/error-report.h" > > +#include "qapi/qapi-types-net.h" > > +#include "net/net.h" > > +/* macOS vmnet framework header */ > > +#include <vmnet/vmnet.h> > > + > > +typedef struct vmnet_state { > > + NetClientState nc; > > + interface_ref vmnet_iface_ref; > > + /* Switched on after vmnet informs us that the interface has > started */ > > + bool link_up; > > + /* > > + * If qemu_send_packet_async returns 0, this is switched off until > our > > + * delivery callback is invoked > > + */ > > + bool qemu_ready_to_receive; > > +} vmnet_state_t; > > + > > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > > + NetClientState *peer, Error **errp); > > + > > +static const char *_vmnet_status_repr(vmnet_return_t status) > > Underscore may be dropped. > > > +{ > > + switch (status) { > > + case VMNET_SUCCESS: > > + return "success"; > > + case VMNET_FAILURE: > > + return "generic failure"; > > + case VMNET_MEM_FAILURE: > > + return "out of memory"; > > + case VMNET_INVALID_ARGUMENT: > > + return "invalid argument"; > > + case VMNET_SETUP_INCOMPLETE: > > + return "setup is incomplete"; > > + case VMNET_INVALID_ACCESS: > > + return "insufficient permissions"; > > + case VMNET_PACKET_TOO_BIG: > > + return "packet size exceeds MTU"; > > + case VMNET_BUFFER_EXHAUSTED: > > + return "kernel buffers temporarily exhausted"; > > + case VMNET_TOO_MANY_PACKETS: > > + return "number of packets exceeds system limit"; > > + /* This error code was introduced in macOS 11.0 */ > > +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 > > + case VMNET_SHARING_SERVICE_BUSY: > > + return "sharing service busy"; > > +#endif > > + default: > > + return "unknown status code"; > > + } > > +} > > + > > +static operating_modes_t _vmnet_operating_mode_enum_compat( > > + VmnetOperatingMode mode) > > Underscore may be dropped. > > > +{ > > + switch (mode) { > > + case VMNET_OPERATING_MODE_HOST: > > + return VMNET_HOST_MODE; > > + case VMNET_OPERATING_MODE_SHARED: > > + return VMNET_SHARED_MODE; > > + case VMNET_OPERATING_MODE_BRIDGED: > > + return VMNET_BRIDGED_MODE; > > + default: > > + /* Should never happen as the modes are parsed before we get > here */ > > + assert(false); > > + } > > +} > > + > > +static bool vmnet_can_receive(NetClientState *nc) > > +{ > > + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); > > + return s->link_up; > > I'm not sure this is correct. > Did you mean s->qemu_ready_to_receive? > > > +} > > + > > +static ssize_t vmnet_receive_iov(NetClientState *nc, > > + const struct iovec *iovs, > > + int iovcnt) > > +{ > > + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); > > + > > + /* Combine the provided iovs into a single vmnet packet */ > > + struct vmpktdesc *packet = g_new0(struct vmpktdesc, 1); > > packet_count could be used instead of 1. > > > + packet->vm_pkt_iov = g_new0(struct iovec, iovcnt); > > + memcpy(packet->vm_pkt_iov, iovs, sizeof(struct iovec) * iovcnt); > > + packet->vm_pkt_iovcnt = iovcnt; > > Should we use iov_copy() instead? > > > + packet->vm_flags = 0; > > The line is redundant with g_new0. > > > + > > + /* Figure out the packet size by iterating the iov's */ > > + for (int i = 0; i < iovcnt; i++) { > > + const struct iovec *iov = iovs + i; > > + packet->vm_pkt_size += iov->iov_len; > > + } > > I wonder if we should add a check if packet->vm_pkt_size is beyond > vmnet_max_packet_size_key? > > Also I'm not entirely sure that we should at most transmit only one > packet, a sort of coalescing might be helpful (Apple claims up ot 200 > packets per one vmnet_write) but I'm not an expert of net part of QEMU. > Stefan may provide more info on that. > > > + > > + /* Finally, write the packet to the vmnet interface */ > > + int packet_count = 1; > > + vmnet_return_t result = vmnet_write(s->vmnet_iface_ref, packet, > > + &packet_count); > > + if (result != VMNET_SUCCESS || packet_count != 1) { > > + error_printf("Failed to send packet to host: %s\n", > > + _vmnet_status_repr(result)); > > + } > > + ssize_t wrote_bytes = packet->vm_pkt_size; > > That's going to mismatch with actual number of bytes written if > packet_count returned from vmnet_write equals zero. > > > + g_free(packet->vm_pkt_iov); > > + g_free(packet); > > + return wrote_bytes; > > +} > > + > > +static void vmnet_send_completed(NetClientState *nc, ssize_t len) > > +{ > > + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, > nc); > > + /* Ready to receive more packets! */ > > + vmnet_client_state->qemu_ready_to_receive = true; > > +} > > + > > +static NetClientInfo net_vmnet_macos_info = { > > + .type = NET_CLIENT_DRIVER_VMNET_MACOS, > > + .size = sizeof(vmnet_state_t), > > + .receive_iov = vmnet_receive_iov, > > + .can_receive = vmnet_can_receive, > > +}; > > + > > +static bool _validate_ifname_is_valid_bridge_target(const char *ifname) > > Underscore may be dropped from the function. > > > +{ > > + /* Iterate available bridge interfaces, ensure the provided one is > valid */ > > + xpc_object_t bridge_interfaces = vmnet_copy_shared_interface_list(); > > + bool failed_to_match_iface_name = xpc_array_apply( > > + bridge_interfaces, > > + ^bool(size_t index, xpc_object_t _Nonnull value) { > > + if (!strcmp(xpc_string_get_string_ptr(value), ifname)) { > > + /* The interface name is valid! Stop iterating */ > > + return false; > > + } > > + return true; > > + }); > > + > > + if (failed_to_match_iface_name) { > > + error_printf("Invalid bridge interface name provided: %s\n", > ifname); > > + error_printf("Valid bridge interfaces:\n"); > > + xpc_array_apply( > > + vmnet_copy_shared_interface_list(), > > + ^bool(size_t index, xpc_object_t _Nonnull value) { > > + error_printf("\t%s\n", xpc_string_get_string_ptr(value)); > > + /* Keep iterating */ > > + return true; > > + }); > > + exit(1); > > + return false; > > + } > > + > > + return true; > > +} > > + > > +static xpc_object_t _construct_vmnet_interface_description( > > Underscore is not needed I think. > > > + const NetdevVmnetModeOptions *vmnet_opts) > > +{ > > + operating_modes_t mode = _vmnet_operating_mode_enum_compat( > > + vmnet_opts->mode); > > + > > + /* Validate options */ > > + if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { > > + NetdevVmnetModeOptionsHostOrShared mode_opts = > vmnet_opts->u.host; > > + /* If one DHCP parameter is configured, all 3 are required */ > > + if (mode_opts.has_dhcp_start_address || > > + mode_opts.has_dhcp_end_address || > > + mode_opts.has_dhcp_subnet_mask) { > > + if (!(mode_opts.has_dhcp_start_address && > > + mode_opts.has_dhcp_end_address && > > + mode_opts.has_dhcp_subnet_mask)) { > > + error_printf("Incomplete DHCP configuration > provided\n"); > > + exit(1); > > + } > > + } > > + } else if (mode == VMNET_BRIDGED_MODE) { > > I think we want to enable bridging mode only on macOS 10.15 and above > where vmnet_copy_shared_interface_list() is supported. > > > > + /* Nothing to validate */ > > + } else { > > + error_printf("Unknown vmnet mode %d\n", mode); > > + exit(1); > > + } > > + > > + xpc_object_t interface_desc = xpc_dictionary_create(NULL, NULL, 0); > > + xpc_dictionary_set_uint64( > > + interface_desc, > > + vmnet_operation_mode_key, > > + mode > > + ); > > + > > + if (mode == VMNET_BRIDGED_MODE) { > > + /* > > + * Configure the provided physical interface to act > > + * as a bridge with QEMU > > + */ > > + NetdevVmnetModeOptionsBridged mode_opts = vmnet_opts->u.bridged; > > + /* Bridge with en0 by default */ > > + const char *physical_ifname = mode_opts.has_ifname ? > mode_opts.ifname : > > + "en0"; > > I think a default interface is not needed here, it's better to require > an explicit inteface to bridge with. Some people prefer wired, others > wireless. Ocasionally some do both :) > > More comments later! > > Thanks, > Roman > > > + _validate_ifname_is_valid_bridge_target(physical_ifname); > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_shared_interface_name_key, > > + physical_ifname); > > + } else if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { > > + /* Pass the DHCP configuration to vmnet, if the user provided > one */ > > + NetdevVmnetModeOptionsHostOrShared mode_opts = > vmnet_opts->u.host; > > + if (mode_opts.has_dhcp_start_address) { > > + /* All DHCP arguments are available, as per the checks > above */ > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_start_address_key, > > + mode_opts.dhcp_start_address); > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_end_address_key, > > + mode_opts.dhcp_end_address); > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_subnet_mask_key, > > + mode_opts.dhcp_subnet_mask); > > + } > > + } > > + > > + return interface_desc; > > +} > > + > > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > > + NetClientState *peer, Error **errp) > > +{ > > + assert(netdev->type == NET_CLIENT_DRIVER_VMNET_MACOS); > > + > > + NetdevVmnetModeOptions *vmnet_opts = netdev->u.vmnet_macos.options; > > + xpc_object_t iface_desc = > _construct_vmnet_interface_description(vmnet_opts); > > + > > + NetClientState *nc = qemu_new_net_client(&net_vmnet_macos_info, > peer, > > + "vmnet", name); > > + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, > nc); > > + > > + dispatch_queue_t vmnet_dispatch_queue = dispatch_queue_create( > > + "org.qemu.vmnet.iface_queue", > > + DISPATCH_QUEUE_SERIAL > > + ); > > + > > + __block vmnet_return_t vmnet_start_status = 0; > > + __block uint64_t vmnet_iface_mtu = 0; > > + __block uint64_t vmnet_max_packet_size = 0; > > + __block const char *vmnet_mac_address = NULL; > > + /* > > + * We can't refer to an array type directly within a block, > > + * so hold a pointer instead. > > + */ > > + uuid_string_t vmnet_iface_uuid = {0}; > > + __block uuid_string_t *vmnet_iface_uuid_ptr = &vmnet_iface_uuid; > > + /* These are only provided in VMNET_HOST_MODE and VMNET_SHARED_MODE > */ > > + bool vmnet_provides_dhcp_info = ( > > + vmnet_opts->mode == VMNET_OPERATING_MODE_HOST || > > + vmnet_opts->mode == VMNET_OPERATING_MODE_SHARED); > > + __block const char *vmnet_subnet_mask = NULL; > > + __block const char *vmnet_dhcp_range_start = NULL; > > + __block const char *vmnet_dhcp_range_end = NULL; > > + > > + /* Create the vmnet interface */ > > + dispatch_semaphore_t vmnet_iface_sem = dispatch_semaphore_create(0); > > + interface_ref vmnet_iface_ref = vmnet_start_interface( > > + iface_desc, > > + vmnet_dispatch_queue, > > + ^(vmnet_return_t status, xpc_object_t _Nullable > interface_param) { > > + vmnet_start_status = status; > > + if (vmnet_start_status != VMNET_SUCCESS || !interface_param) { > > + /* Early return if the interface couldn't be started */ > > + dispatch_semaphore_signal(vmnet_iface_sem); > > + return; > > + } > > + > > + /* > > + * Read the configuration that vmnet provided us. > > + * The provided dictionary is owned by XPC and may be freed > > + * shortly after this block's execution. > > + * So, copy data buffers now. > > + */ > > + vmnet_iface_mtu = xpc_dictionary_get_uint64( > > + interface_param, > > + vmnet_mtu_key > > + ); > > + vmnet_max_packet_size = xpc_dictionary_get_uint64( > > + interface_param, > > + vmnet_max_packet_size_key > > + ); > > + vmnet_mac_address = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_mac_address_key > > + )); > > + > > + const uint8_t *iface_uuid = xpc_dictionary_get_uuid( > > + interface_param, > > + vmnet_interface_id_key > > + ); > > + uuid_unparse_upper(iface_uuid, *vmnet_iface_uuid_ptr); > > + > > + /* If we're in a mode that provides DHCP info, read it out now > */ > > + if (vmnet_provides_dhcp_info) { > > + vmnet_dhcp_range_start = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_start_address_key > > + )); > > + vmnet_dhcp_range_end = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_end_address_key > > + )); > > + vmnet_subnet_mask = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_subnet_mask_key > > + )); > > + } > > + dispatch_semaphore_signal(vmnet_iface_sem); > > + }); > > + > > + /* And block until we receive a response from vmnet */ > > + dispatch_semaphore_wait(vmnet_iface_sem, DISPATCH_TIME_FOREVER); > > + > > + /* Did we manage to start the interface? */ > > + if (vmnet_start_status != VMNET_SUCCESS || !vmnet_iface_ref) { > > + error_printf("Failed to start interface: %s\n", > > + _vmnet_status_repr(vmnet_start_status)); > > + if (vmnet_start_status == VMNET_FAILURE) { > > + error_printf("Hint: vmnet requires running with root > access\n"); > > + } > > + return -1; > > + } > > + > > + info_report("Started vmnet interface with configuration:"); > > + info_report("MTU: %llu", vmnet_iface_mtu); > > + info_report("Max packet size: %llu", vmnet_max_packet_size); > > + info_report("MAC: %s", vmnet_mac_address); > > + if (vmnet_provides_dhcp_info) { > > + info_report("DHCP IPv4 start: %s", vmnet_dhcp_range_start); > > + info_report("DHCP IPv4 end: %s", vmnet_dhcp_range_end); > > + info_report("IPv4 subnet mask: %s", vmnet_subnet_mask); > > + } > > + info_report("UUID: %s", vmnet_iface_uuid); > > + > > + /* The interface is up! Set a block to run when packets are > received */ > > + vmnet_client_state->vmnet_iface_ref = vmnet_iface_ref; > > + vmnet_return_t event_cb_stat = vmnet_interface_set_event_callback( > > + vmnet_iface_ref, > > + VMNET_INTERFACE_PACKETS_AVAILABLE, > > + vmnet_dispatch_queue, > > + ^(interface_event_t event_mask, xpc_object_t _Nonnull event) { > > + if (event_mask != VMNET_INTERFACE_PACKETS_AVAILABLE) { > > + error_printf("Unknown vmnet interface event 0x%08x\n", > event_mask); > > + return; > > + } > > + > > + /* If we're unable to handle more packets now, drop this packet > */ > > + if (!vmnet_client_state->qemu_ready_to_receive) { > > + return; > > + } > > + > > + /* > > + * TODO(Phillip Tennen <phillip@axleos.com>): There may be > more than > > + * one packet available. > > + * As an optimization, we could read > > + * vmnet_estimated_packets_available_key packets now. > > + */ > > + char *packet_buf = g_malloc0(vmnet_max_packet_size); > > + struct iovec *iov = g_new0(struct iovec, 1); > > + iov->iov_base = packet_buf; > > + iov->iov_len = vmnet_max_packet_size; > > + > > + int pktcnt = 1; > > + struct vmpktdesc *v = g_new0(struct vmpktdesc, pktcnt); > > + v->vm_pkt_size = vmnet_max_packet_size; > > + v->vm_pkt_iov = iov; > > + v->vm_pkt_iovcnt = 1; > > + v->vm_flags = 0; > > + > > + vmnet_return_t result = vmnet_read(vmnet_iface_ref, v, &pktcnt); > > + if (result != VMNET_SUCCESS) { > > + error_printf("Failed to read packet from host: %s\n", > > + _vmnet_status_repr(result)); > > + } > > + > > + /* Ensure we read exactly one packet */ > > + assert(pktcnt == 1); > > + > > + /* Dispatch this block to a global queue instead of the main > queue, > > + * which is only created when the program has a Cocoa event > loop. > > + * If QEMU is started with -nographic, no Cocoa event loop will > be > > + * created and thus the main queue will be unavailable. > > + */ > > + > dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, > > + 0), > > + ^{ > > + qemu_mutex_lock_iothread(); > > + > > + /* > > + * Deliver the packet to the guest > > + * If the delivery succeeded synchronously, this returns > the length > > + * of the sent packet. > > + */ > > + if (qemu_send_packet_async(nc, iov->iov_base, > > + v->vm_pkt_size, > > + vmnet_send_completed) == 0) { > > + vmnet_client_state->qemu_ready_to_receive = false; > > + } > > + > > + /* > > + * It's safe to free the packet buffers. > > + * Even if delivery needs to wait, qemu_net_queue_append > copies > > + * the packet buffer. > > + */ > > + g_free(v); > > + g_free(iov); > > + g_free(packet_buf); > > + > > + qemu_mutex_unlock_iothread(); > > + }); > > + }); > > + > > + /* Did we manage to set an event callback? */ > > + if (event_cb_stat != VMNET_SUCCESS) { > > + error_printf("Failed to set up a callback to receive packets: > %s\n", > > + _vmnet_status_repr(vmnet_start_status)); > > + exit(1); > > + } > > + > > + /* We're now ready to receive packets */ > > + vmnet_client_state->qemu_ready_to_receive = true; > > + vmnet_client_state->link_up = true; > > + > > + /* Include DHCP info if we're in a relevant mode */ > > + if (vmnet_provides_dhcp_info) { > > + snprintf(nc->info_str, sizeof(nc->info_str), > > + "dhcp_start=%s,dhcp_end=%s,mask=%s", > > + vmnet_dhcp_range_start, vmnet_dhcp_range_end, > > + vmnet_subnet_mask); > > + } else { > > + snprintf(nc->info_str, sizeof(nc->info_str), > > + "mac=%s", vmnet_mac_address); > > + } > > + > > + return 0; > > +} > > diff --git a/qapi/net.json b/qapi/net.json > > index c31748c87f..e4d4143243 100644 > > --- a/qapi/net.json > > +++ b/qapi/net.json > > @@ -450,6 +450,115 @@ > > '*vhostdev': 'str', > > '*queues': 'int' } } > > > > +## > > +# @VmnetOperatingMode: > > +# > > +# The operating modes in which a vmnet netdev can run > > +# Only available on macOS > > +# > > +# @host: the guest may communicate with the host > > +# and other guest network interfaces > > +# > > +# @shared: the guest may reach the Internet through a NAT, > > +# and may communicate with the host and other guest > > +# network interfaces > > +# > > +# @bridged: the guest's traffic is bridged with a > > +# physical network interface of the host > > +# > > +# Since: 6.0 > > +## > > +{ 'enum': 'VmnetOperatingMode', > > + 'data': [ 'host', 'shared', 'bridged' ], > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetModeOptionsBridged: > > +# > > +# Options for the vmnet-macos netdev > > +# that are only available in 'bridged' mode > > +# Only available on macOS > > +# > > +# @ifname: the physical network interface to bridge with > > +# (defaults to en0 if not specified) > > +# > > +# Since: 6.0 > > +## > > +{ 'struct': 'NetdevVmnetModeOptionsBridged', > > + 'data': { '*ifname': 'str' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetModeOptionsHostOrShared: > > +# > > +# Options for the vmnet-macos netdev > > +# that are only available in 'host' or 'shared' mode > > +# Only available on macOS > > +# > > +# @dhcp-start-address: the gateway address to use for the interface. > > +# The range to dhcp_end_address is placed in the > DHCP pool. > > +# (only valid with mode=host|shared) > > +# (must be specified with dhcp-end-address and > > +# dhcp-subnet-mask) > > +# (allocated automatically if unset) > > +# > > +# @dhcp-end-address: the DHCP IPv4 range end address to use for the > interface. > > +# (only valid with mode=host|shared) > > +# (must be specified with dhcp-start-address and > > +# dhcp-subnet-mask) > > +# (allocated automatically if unset) > > +# > > +# @dhcp-subnet-mask: the IPv4 subnet mask (string) to use on the > interface. > > +# (only valid with mode=host|shared) > > +# (must be specified with dhcp-start-address and > > +# dhcp-end-address) > > +# (allocated automatically if unset) > > +# > > +# Since: 6.0 > > +## > > +{ 'struct': 'NetdevVmnetModeOptionsHostOrShared', > > + 'data': { > > + '*dhcp-start-address': 'str' , > > + '*dhcp-end-address': 'str', > > + '*dhcp-subnet-mask': 'str' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetModeOptions: > > +# > > +# Options specific to different operating modes of a vmnet netdev > > +# Only available on macOS > > +# > > +# @mode: the operating mode vmnet should run in > > +# > > +# Since: 6.0 > > +## > > +{ 'union': 'NetdevVmnetModeOptions', > > + 'base': { 'mode': 'VmnetOperatingMode' }, > > + 'discriminator': 'mode', > > + 'data': { > > + 'bridged': 'NetdevVmnetModeOptionsBridged', > > + 'host': 'NetdevVmnetModeOptionsHostOrShared', > > + 'shared': 'NetdevVmnetModeOptionsHostOrShared' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetOptions: > > +# > > +# vmnet network backend > > +# Only available on macOS > > +# > > +# @options: a structure specifying the mode and mode-specific options > > +# (once QAPI supports a union type as a branch to another > union type, > > +# this structure can be changed to a union, and the contents > of > > +# NetdevVmnetModeOptions moved here) > > +# > > +# Since: 6.0 > > +## > > +{ 'struct': 'NetdevVmnetOptions', > > + 'data': {'options': 'NetdevVmnetModeOptions' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > ## > > # @NetClientDriver: > > # > > @@ -458,10 +567,13 @@ > > # Since: 2.7 > > # > > # @vhost-vdpa since 5.1 > > +# > > +# @vmnet-macos since 6.0 (only available on macOS) > > ## > > { '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-macos', 'if': 'defined(CONFIG_DARWIN)' } ] > } > > > > ## > > # @Netdev: > > @@ -475,6 +587,8 @@ > > # Since: 1.2 > > # > > # 'l2tpv3' - since 2.1 > > +# > > +# 'vmnet-macos' since 6.0 (only available on macOS) > > ## > > { 'union': 'Netdev', > > 'base': { 'id': 'str', 'type': 'NetClientDriver' }, > > @@ -490,7 +604,9 @@ > > 'hubport': 'NetdevHubPortOptions', > > 'netmap': 'NetdevNetmapOptions', > > 'vhost-user': 'NetdevVhostUserOptions', > > - 'vhost-vdpa': 'NetdevVhostVDPAOptions' } } > > + 'vhost-vdpa': 'NetdevVhostVDPAOptions', > > + 'vmnet-macos': { 'type': 'NetdevVmnetOptions', > > + 'if': 'defined(CONFIG_DARWIN)' } } } > > > > ## > > # @NetFilterDirection: > > diff --git a/qemu-options.hx b/qemu-options.hx > > index 9172d51659..ec6b40b079 100644 > > --- a/qemu-options.hx > > +++ b/qemu-options.hx > > @@ -2483,6 +2483,15 @@ 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_DARWIN > > + "-netdev vmnet-macos,id=str,mode=bridged[,ifname=ifname]\n" > > + " configure a macOS-provided vmnet network in \"physical > interface bridge\" mode\n" > > + " the physical interface to bridge with defaults to en0 if > unspecified\n" > > + "-netdev vmnet-macos,id=str,mode=host|shared\n" > > + " > [,dhcp_start_address=addr,dhcp_end_address=addr,dhcp_subnet_mask=mask]\n" > > + " configure a macOS-provided vmnet network in \"host\" or > \"shared\" mode\n" > > + " the DHCP configuration will be set automatically if > unspecified\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) > > -- > > 2.24.3 (Apple Git-128) > > > > > [-- Attachment #2: Type: text/html, Size: 44237 bytes --] ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] net/macos: implement vmnet-based netdev 2021-03-03 11:24 ` Phillip Tennen @ 2021-04-26 14:05 ` Alessio Dionisi 0 siblings, 0 replies; 5+ messages in thread From: Alessio Dionisi @ 2021-04-26 14:05 UTC (permalink / raw) To: Phillip Tennen Cc: Thomas Huth, Stefan Hajnoczi, jasowang, Markus Armbruster, qemu-devel qemu-devel, Roman Bolshakov, Phillip Tennen, Howard Spoelstra [-- Attachment #1: Type: text/plain, Size: 35816 bytes --] Hello, I'm replying to this thread to let you know that I am available to help out on this patch as I have been working on the same feature for a few weeks. AD > On 3 Mar 2021, at 12:24, Phillip Tennen <phillip.ennen@gmail.com <mailto:phillip.ennen@gmail.com>> wrote: > > Thanks very much for your help and feedback! > > Apologies for my delay in following up. I'll submit a new version that implements the feedback you've provided here, as well as the QAPI schema changes @Markus Armbruster <mailto:armbru@redhat.com> (thanks to you as well for your time and review!) pointed out. > > Phillip > > On Wed, Feb 24, 2021 at 12:25 AM Roman Bolshakov <r.bolshakov@yadro.com <mailto:r.bolshakov@yadro.com>> wrote: > On Thu, Feb 18, 2021 at 02:49:47PM +0100, phillip.ennen@gmail.com <mailto:phillip.ennen@gmail.com> wrote: > > From: Phillip Tennen <phillip@axleos.com <mailto:phillip@axleos.com>> > > > > This patch implements a new netdev device, reachable via -netdev > > vmnet-macos, that’s backed by macOS’s vmnet framework. > > > > The vmnet framework provides native bridging support, and its usage in > > this patch is intended as a replacement for attempts to use a tap device > > via the tuntaposx kernel extension. Notably, the tap/tuntaposx approach > > never would have worked in the first place, as QEMU interacts with the > > tap device via poll(), and macOS does not support polling device files. > > > > vmnet requires either a special entitlement, granted via a provisioning > > profile, or root access. Otherwise attempts to create the virtual > > interface will fail with a “generic error” status code. QEMU may not > > currently be signed with an entitlement granted in a provisioning > > profile, as this would necessitate pre-signed binary build distribution, > > rather than source-code distribution. As such, using this netdev > > currently requires that qemu be run with root access. I’ve opened a > > feedback report with Apple to allow the use of the relevant entitlement > > with this use case: > > https://openradar.appspot.com/radar?id=5007417364447232 <https://openradar.appspot.com/radar?id=5007417364447232> > > > > vmnet offers three operating modes, all of which are supported by this > > patch via the “mode=host|shared|bridge” option: > > > > * "Host" mode: Allows the vmnet interface to communicate with other > > * vmnet > > interfaces that are in host mode and also with the native host. > > * "Shared" mode: Allows traffic originating from the vmnet interface to > > reach the Internet through a NAT. The vmnet interface can also > > communicate with the native host. > > * "Bridged" mode: Bridges the vmnet interface with a physical network > > interface. > > > > Each of these modes also provide some extra configuration that’s > > supported by this patch: > > > > * "Bridged" mode: The user may specify the physical interface to bridge > > with. Defaults to en0. > > * "Host" mode / "Shared" mode: The user may specify the DHCP range and > > subnet. Allocated by vmnet if not provided. > > > > vmnet also offers some extra configuration options that are not > > supported by this patch: > > > > * Enable isolation from other VMs using vmnet > > * Port forwarding rules > > * Enabling TCP segmentation offload > > * Only applicable in "shared" mode: specifying the NAT IPv6 prefix > > * Only available in "host" mode: specifying the IP address for the VM > > within an isolated network > > > > Note that this patch requires macOS 10.15 as a minimum, as this is when > > bridging support was implemented in vmnet.framework. > > > > Signed-off-by: Phillip Tennen <phillip@axleos.com <mailto:phillip@axleos.com>> > > --- > > configure | 2 +- > > net/clients.h | 6 + > > net/meson.build | 1 + > > net/net.c | 3 + > > net/vmnet-macos.c | 447 ++++++++++++++++++++++++++++++++++++++++++++++ > > qapi/net.json | 120 ++++++++++++- > > qemu-options.hx | 9 + > > 7 files changed, 585 insertions(+), 3 deletions(-) > > create mode 100644 net/vmnet-macos.c > > > > Hi Phillip, > > Thanks for working on this! > > Note that the patch doesn't apply to current master and there's a lot of > warnings wrt trailing whitespaces: > > git am v4-net-macos-implement-vmnet-based-netdev.patch > Applying: net/macos: implement vmnet-based netdev > .git/rebase-apply/patch:462: trailing whitespace. > * If QEMU is started with -nographic, no Cocoa event loop will be > .git/rebase-apply/patch:465: trailing whitespace. > dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, > .git/rebase-apply/patch:466: trailing whitespace. > 0), > .git/rebase-apply/patch:532: trailing whitespace. > # @host: the guest may communicate with the host > .git/rebase-apply/patch:535: trailing whitespace. > # @shared: the guest may reach the Internet through a NAT, > error: patch failed: configure:778 > error: configure: patch does not apply > Patch failed at 0001 net/macos: implement vmnet-based netdev > hint: Use 'git am --show-current-patch' to see the failed patch > When you have resolved this problem, run "git am --continue". > If you prefer to skip this patch, run "git am --skip" instead. > To restore the original branch and stop patching, run "git am --abort". > > Also it would be helpful to provide a changelog under commit message > delimiter ("---") for reach new version of the patch to provide an > overview of what has been changed between the versions. > > > diff --git a/configure b/configure > > index 4afd22bdf5..f449198db1 100755 > > --- a/configure > > +++ b/configure > > @@ -778,7 +778,7 @@ Darwin) > > fi > > audio_drv_list="coreaudio try-sdl" > > audio_possible_drivers="coreaudio sdl" > > - QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit $QEMU_LDFLAGS" > > + QEMU_LDFLAGS="-framework CoreFoundation -framework IOKit -framework vmnet $QEMU_LDFLAGS" > > I'm not sure this is right approach. Instead, we need a new > configuration option for the feature + proper discovery. Something like > this should work: > > https://github.com/roolebo/qemu/commit/e6c52d6bedb92f16defb5782b696853824b14bd9 <https://github.com/roolebo/qemu/commit/e6c52d6bedb92f16defb5782b696853824b14bd9> > > > # Disable attempts to use ObjectiveC features in os/object.h since they > > # won't work when we're compiling with gcc as a C compiler. > > QEMU_CFLAGS="-DOS_OBJECT_USE_OBJC=0 $QEMU_CFLAGS" > > diff --git a/net/clients.h b/net/clients.h > > index 92f9b59aed..463a9b2f67 100644 > > --- a/net/clients.h > > +++ b/net/clients.h > > @@ -63,4 +63,10 @@ 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_DARWIN > > Respectively, it would be wrapped with #ifdef CONFIG_VMNET instead of > more generic CONFIG_DARWIN. > > > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > > + NetClientState *peer, Error **errp); > > +#endif > > + > > #endif /* QEMU_NET_CLIENTS_H */ > > diff --git a/net/meson.build b/net/meson.build > > index 1076b0a7ab..8c7c32f775 100644 > > --- a/net/meson.build > > +++ b/net/meson.build > > @@ -37,5 +37,6 @@ endif > > softmmu_ss.add(when: 'CONFIG_POSIX', if_true: files(tap_posix)) > > softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('tap-win32.c')) > > softmmu_ss.add(when: 'CONFIG_VHOST_NET_VDPA', if_true: files('vhost-vdpa.c')) > > +softmmu_ss.add(when: 'CONFIG_DARWIN', if_true: files('vmnet-macos.c')) > > > > subdir('can') > > diff --git a/net/net.c b/net/net.c > > index c1cd9c75f6..e68a410a89 100644 > > --- a/net/net.c > > +++ b/net/net.c > > @@ -977,6 +977,9 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( > > #ifdef CONFIG_L2TPV3 > > [NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3, > > #endif > > +#ifdef CONFIG_DARWIN > > CONFIG_VMNET should be used here as well. > > > + [NET_CLIENT_DRIVER_VMNET_MACOS] = net_init_vmnet_macos, > > +#endif > > }; > > > > > > diff --git a/net/vmnet-macos.c b/net/vmnet-macos.c > > new file mode 100644 > > index 0000000000..1a762751dd > > --- /dev/null > > +++ b/net/vmnet-macos.c > > @@ -0,0 +1,447 @@ > > +/* > > + * vmnet.framework backed netdev for macOS 10.15+ hosts > > + * > > + * Copyright (c) 2021 Phillip Tennen <phillip@axleos.com <mailto: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/error-report.h" > > +#include "qapi/qapi-types-net.h" > > +#include "net/net.h" > > +/* macOS vmnet framework header */ > > +#include <vmnet/vmnet.h> > > + > > +typedef struct vmnet_state { > > + NetClientState nc; > > + interface_ref vmnet_iface_ref; > > + /* Switched on after vmnet informs us that the interface has started */ > > + bool link_up; > > + /* > > + * If qemu_send_packet_async returns 0, this is switched off until our > > + * delivery callback is invoked > > + */ > > + bool qemu_ready_to_receive; > > +} vmnet_state_t; > > + > > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > > + NetClientState *peer, Error **errp); > > + > > +static const char *_vmnet_status_repr(vmnet_return_t status) > > Underscore may be dropped. > > > +{ > > + switch (status) { > > + case VMNET_SUCCESS: > > + return "success"; > > + case VMNET_FAILURE: > > + return "generic failure"; > > + case VMNET_MEM_FAILURE: > > + return "out of memory"; > > + case VMNET_INVALID_ARGUMENT: > > + return "invalid argument"; > > + case VMNET_SETUP_INCOMPLETE: > > + return "setup is incomplete"; > > + case VMNET_INVALID_ACCESS: > > + return "insufficient permissions"; > > + case VMNET_PACKET_TOO_BIG: > > + return "packet size exceeds MTU"; > > + case VMNET_BUFFER_EXHAUSTED: > > + return "kernel buffers temporarily exhausted"; > > + case VMNET_TOO_MANY_PACKETS: > > + return "number of packets exceeds system limit"; > > + /* This error code was introduced in macOS 11.0 */ > > +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 > > + case VMNET_SHARING_SERVICE_BUSY: > > + return "sharing service busy"; > > +#endif > > + default: > > + return "unknown status code"; > > + } > > +} > > + > > +static operating_modes_t _vmnet_operating_mode_enum_compat( > > + VmnetOperatingMode mode) > > Underscore may be dropped. > > > +{ > > + switch (mode) { > > + case VMNET_OPERATING_MODE_HOST: > > + return VMNET_HOST_MODE; > > + case VMNET_OPERATING_MODE_SHARED: > > + return VMNET_SHARED_MODE; > > + case VMNET_OPERATING_MODE_BRIDGED: > > + return VMNET_BRIDGED_MODE; > > + default: > > + /* Should never happen as the modes are parsed before we get here */ > > + assert(false); > > + } > > +} > > + > > +static bool vmnet_can_receive(NetClientState *nc) > > +{ > > + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); > > + return s->link_up; > > I'm not sure this is correct. > Did you mean s->qemu_ready_to_receive? > > > +} > > + > > +static ssize_t vmnet_receive_iov(NetClientState *nc, > > + const struct iovec *iovs, > > + int iovcnt) > > +{ > > + vmnet_state_t *s = DO_UPCAST(vmnet_state_t, nc, nc); > > + > > + /* Combine the provided iovs into a single vmnet packet */ > > + struct vmpktdesc *packet = g_new0(struct vmpktdesc, 1); > > packet_count could be used instead of 1. > > > + packet->vm_pkt_iov = g_new0(struct iovec, iovcnt); > > + memcpy(packet->vm_pkt_iov, iovs, sizeof(struct iovec) * iovcnt); > > + packet->vm_pkt_iovcnt = iovcnt; > > Should we use iov_copy() instead? > > > + packet->vm_flags = 0; > > The line is redundant with g_new0. > > > + > > + /* Figure out the packet size by iterating the iov's */ > > + for (int i = 0; i < iovcnt; i++) { > > + const struct iovec *iov = iovs + i; > > + packet->vm_pkt_size += iov->iov_len; > > + } > > I wonder if we should add a check if packet->vm_pkt_size is beyond > vmnet_max_packet_size_key? > > Also I'm not entirely sure that we should at most transmit only one > packet, a sort of coalescing might be helpful (Apple claims up ot 200 > packets per one vmnet_write) but I'm not an expert of net part of QEMU. > Stefan may provide more info on that. > > > + > > + /* Finally, write the packet to the vmnet interface */ > > + int packet_count = 1; > > + vmnet_return_t result = vmnet_write(s->vmnet_iface_ref, packet, > > + &packet_count); > > + if (result != VMNET_SUCCESS || packet_count != 1) { > > + error_printf("Failed to send packet to host: %s\n", > > + _vmnet_status_repr(result)); > > + } > > + ssize_t wrote_bytes = packet->vm_pkt_size; > > That's going to mismatch with actual number of bytes written if > packet_count returned from vmnet_write equals zero. > > > + g_free(packet->vm_pkt_iov); > > + g_free(packet); > > + return wrote_bytes; > > +} > > + > > +static void vmnet_send_completed(NetClientState *nc, ssize_t len) > > +{ > > + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); > > + /* Ready to receive more packets! */ > > + vmnet_client_state->qemu_ready_to_receive = true; > > +} > > + > > +static NetClientInfo net_vmnet_macos_info = { > > + .type = NET_CLIENT_DRIVER_VMNET_MACOS, > > + .size = sizeof(vmnet_state_t), > > + .receive_iov = vmnet_receive_iov, > > + .can_receive = vmnet_can_receive, > > +}; > > + > > +static bool _validate_ifname_is_valid_bridge_target(const char *ifname) > > Underscore may be dropped from the function. > > > +{ > > + /* Iterate available bridge interfaces, ensure the provided one is valid */ > > + xpc_object_t bridge_interfaces = vmnet_copy_shared_interface_list(); > > + bool failed_to_match_iface_name = xpc_array_apply( > > + bridge_interfaces, > > + ^bool(size_t index, xpc_object_t _Nonnull value) { > > + if (!strcmp(xpc_string_get_string_ptr(value), ifname)) { > > + /* The interface name is valid! Stop iterating */ > > + return false; > > + } > > + return true; > > + }); > > + > > + if (failed_to_match_iface_name) { > > + error_printf("Invalid bridge interface name provided: %s\n", ifname); > > + error_printf("Valid bridge interfaces:\n"); > > + xpc_array_apply( > > + vmnet_copy_shared_interface_list(), > > + ^bool(size_t index, xpc_object_t _Nonnull value) { > > + error_printf("\t%s\n", xpc_string_get_string_ptr(value)); > > + /* Keep iterating */ > > + return true; > > + }); > > + exit(1); > > + return false; > > + } > > + > > + return true; > > +} > > + > > +static xpc_object_t _construct_vmnet_interface_description( > > Underscore is not needed I think. > > > + const NetdevVmnetModeOptions *vmnet_opts) > > +{ > > + operating_modes_t mode = _vmnet_operating_mode_enum_compat( > > + vmnet_opts->mode); > > + > > + /* Validate options */ > > + if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { > > + NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; > > + /* If one DHCP parameter is configured, all 3 are required */ > > + if (mode_opts.has_dhcp_start_address || > > + mode_opts.has_dhcp_end_address || > > + mode_opts.has_dhcp_subnet_mask) { > > + if (!(mode_opts.has_dhcp_start_address && > > + mode_opts.has_dhcp_end_address && > > + mode_opts.has_dhcp_subnet_mask)) { > > + error_printf("Incomplete DHCP configuration provided\n"); > > + exit(1); > > + } > > + } > > + } else if (mode == VMNET_BRIDGED_MODE) { > > I think we want to enable bridging mode only on macOS 10.15 and above > where vmnet_copy_shared_interface_list() is supported. > > > > + /* Nothing to validate */ > > + } else { > > + error_printf("Unknown vmnet mode %d\n", mode); > > + exit(1); > > + } > > + > > + xpc_object_t interface_desc = xpc_dictionary_create(NULL, NULL, 0); > > + xpc_dictionary_set_uint64( > > + interface_desc, > > + vmnet_operation_mode_key, > > + mode > > + ); > > + > > + if (mode == VMNET_BRIDGED_MODE) { > > + /* > > + * Configure the provided physical interface to act > > + * as a bridge with QEMU > > + */ > > + NetdevVmnetModeOptionsBridged mode_opts = vmnet_opts->u.bridged; > > + /* Bridge with en0 by default */ > > + const char *physical_ifname = mode_opts.has_ifname ? mode_opts.ifname : > > + "en0"; > > I think a default interface is not needed here, it's better to require > an explicit inteface to bridge with. Some people prefer wired, others > wireless. Ocasionally some do both :) > > More comments later! > > Thanks, > Roman > > > + _validate_ifname_is_valid_bridge_target(physical_ifname); > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_shared_interface_name_key, > > + physical_ifname); > > + } else if (mode == VMNET_HOST_MODE || mode == VMNET_SHARED_MODE) { > > + /* Pass the DHCP configuration to vmnet, if the user provided one */ > > + NetdevVmnetModeOptionsHostOrShared mode_opts = vmnet_opts->u.host; > > + if (mode_opts.has_dhcp_start_address) { > > + /* All DHCP arguments are available, as per the checks above */ > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_start_address_key, > > + mode_opts.dhcp_start_address); > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_end_address_key, > > + mode_opts.dhcp_end_address); > > + xpc_dictionary_set_string(interface_desc, > > + vmnet_subnet_mask_key, > > + mode_opts.dhcp_subnet_mask); > > + } > > + } > > + > > + return interface_desc; > > +} > > + > > +int net_init_vmnet_macos(const Netdev *netdev, const char *name, > > + NetClientState *peer, Error **errp) > > +{ > > + assert(netdev->type == NET_CLIENT_DRIVER_VMNET_MACOS); > > + > > + NetdevVmnetModeOptions *vmnet_opts = netdev->u.vmnet_macos.options; > > + xpc_object_t iface_desc = _construct_vmnet_interface_description(vmnet_opts); > > + > > + NetClientState *nc = qemu_new_net_client(&net_vmnet_macos_info, peer, > > + "vmnet", name); > > + vmnet_state_t *vmnet_client_state = DO_UPCAST(vmnet_state_t, nc, nc); > > + > > + dispatch_queue_t vmnet_dispatch_queue = dispatch_queue_create( > > + "org.qemu.vmnet.iface_queue", > > + DISPATCH_QUEUE_SERIAL > > + ); > > + > > + __block vmnet_return_t vmnet_start_status = 0; > > + __block uint64_t vmnet_iface_mtu = 0; > > + __block uint64_t vmnet_max_packet_size = 0; > > + __block const char *vmnet_mac_address = NULL; > > + /* > > + * We can't refer to an array type directly within a block, > > + * so hold a pointer instead. > > + */ > > + uuid_string_t vmnet_iface_uuid = {0}; > > + __block uuid_string_t *vmnet_iface_uuid_ptr = &vmnet_iface_uuid; > > + /* These are only provided in VMNET_HOST_MODE and VMNET_SHARED_MODE */ > > + bool vmnet_provides_dhcp_info = ( > > + vmnet_opts->mode == VMNET_OPERATING_MODE_HOST || > > + vmnet_opts->mode == VMNET_OPERATING_MODE_SHARED); > > + __block const char *vmnet_subnet_mask = NULL; > > + __block const char *vmnet_dhcp_range_start = NULL; > > + __block const char *vmnet_dhcp_range_end = NULL; > > + > > + /* Create the vmnet interface */ > > + dispatch_semaphore_t vmnet_iface_sem = dispatch_semaphore_create(0); > > + interface_ref vmnet_iface_ref = vmnet_start_interface( > > + iface_desc, > > + vmnet_dispatch_queue, > > + ^(vmnet_return_t status, xpc_object_t _Nullable interface_param) { > > + vmnet_start_status = status; > > + if (vmnet_start_status != VMNET_SUCCESS || !interface_param) { > > + /* Early return if the interface couldn't be started */ > > + dispatch_semaphore_signal(vmnet_iface_sem); > > + return; > > + } > > + > > + /* > > + * Read the configuration that vmnet provided us. > > + * The provided dictionary is owned by XPC and may be freed > > + * shortly after this block's execution. > > + * So, copy data buffers now. > > + */ > > + vmnet_iface_mtu = xpc_dictionary_get_uint64( > > + interface_param, > > + vmnet_mtu_key > > + ); > > + vmnet_max_packet_size = xpc_dictionary_get_uint64( > > + interface_param, > > + vmnet_max_packet_size_key > > + ); > > + vmnet_mac_address = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_mac_address_key > > + )); > > + > > + const uint8_t *iface_uuid = xpc_dictionary_get_uuid( > > + interface_param, > > + vmnet_interface_id_key > > + ); > > + uuid_unparse_upper(iface_uuid, *vmnet_iface_uuid_ptr); > > + > > + /* If we're in a mode that provides DHCP info, read it out now */ > > + if (vmnet_provides_dhcp_info) { > > + vmnet_dhcp_range_start = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_start_address_key > > + )); > > + vmnet_dhcp_range_end = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_end_address_key > > + )); > > + vmnet_subnet_mask = strdup(xpc_dictionary_get_string( > > + interface_param, > > + vmnet_subnet_mask_key > > + )); > > + } > > + dispatch_semaphore_signal(vmnet_iface_sem); > > + }); > > + > > + /* And block until we receive a response from vmnet */ > > + dispatch_semaphore_wait(vmnet_iface_sem, DISPATCH_TIME_FOREVER); > > + > > + /* Did we manage to start the interface? */ > > + if (vmnet_start_status != VMNET_SUCCESS || !vmnet_iface_ref) { > > + error_printf("Failed to start interface: %s\n", > > + _vmnet_status_repr(vmnet_start_status)); > > + if (vmnet_start_status == VMNET_FAILURE) { > > + error_printf("Hint: vmnet requires running with root access\n"); > > + } > > + return -1; > > + } > > + > > + info_report("Started vmnet interface with configuration:"); > > + info_report("MTU: %llu", vmnet_iface_mtu); > > + info_report("Max packet size: %llu", vmnet_max_packet_size); > > + info_report("MAC: %s", vmnet_mac_address); > > + if (vmnet_provides_dhcp_info) { > > + info_report("DHCP IPv4 start: %s", vmnet_dhcp_range_start); > > + info_report("DHCP IPv4 end: %s", vmnet_dhcp_range_end); > > + info_report("IPv4 subnet mask: %s", vmnet_subnet_mask); > > + } > > + info_report("UUID: %s", vmnet_iface_uuid); > > + > > + /* The interface is up! Set a block to run when packets are received */ > > + vmnet_client_state->vmnet_iface_ref = vmnet_iface_ref; > > + vmnet_return_t event_cb_stat = vmnet_interface_set_event_callback( > > + vmnet_iface_ref, > > + VMNET_INTERFACE_PACKETS_AVAILABLE, > > + vmnet_dispatch_queue, > > + ^(interface_event_t event_mask, xpc_object_t _Nonnull event) { > > + if (event_mask != VMNET_INTERFACE_PACKETS_AVAILABLE) { > > + error_printf("Unknown vmnet interface event 0x%08x\n", event_mask); > > + return; > > + } > > + > > + /* If we're unable to handle more packets now, drop this packet */ > > + if (!vmnet_client_state->qemu_ready_to_receive) { > > + return; > > + } > > + > > + /* > > + * TODO(Phillip Tennen <phillip@axleos.com <mailto:phillip@axleos.com>>): There may be more than > > + * one packet available. > > + * As an optimization, we could read > > + * vmnet_estimated_packets_available_key packets now. > > + */ > > + char *packet_buf = g_malloc0(vmnet_max_packet_size); > > + struct iovec *iov = g_new0(struct iovec, 1); > > + iov->iov_base = packet_buf; > > + iov->iov_len = vmnet_max_packet_size; > > + > > + int pktcnt = 1; > > + struct vmpktdesc *v = g_new0(struct vmpktdesc, pktcnt); > > + v->vm_pkt_size = vmnet_max_packet_size; > > + v->vm_pkt_iov = iov; > > + v->vm_pkt_iovcnt = 1; > > + v->vm_flags = 0; > > + > > + vmnet_return_t result = vmnet_read(vmnet_iface_ref, v, &pktcnt); > > + if (result != VMNET_SUCCESS) { > > + error_printf("Failed to read packet from host: %s\n", > > + _vmnet_status_repr(result)); > > + } > > + > > + /* Ensure we read exactly one packet */ > > + assert(pktcnt == 1); > > + > > + /* Dispatch this block to a global queue instead of the main queue, > > + * which is only created when the program has a Cocoa event loop. > > + * If QEMU is started with -nographic, no Cocoa event loop will be > > + * created and thus the main queue will be unavailable. > > + */ > > + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, > > + 0), > > + ^{ > > + qemu_mutex_lock_iothread(); > > + > > + /* > > + * Deliver the packet to the guest > > + * If the delivery succeeded synchronously, this returns the length > > + * of the sent packet. > > + */ > > + if (qemu_send_packet_async(nc, iov->iov_base, > > + v->vm_pkt_size, > > + vmnet_send_completed) == 0) { > > + vmnet_client_state->qemu_ready_to_receive = false; > > + } > > + > > + /* > > + * It's safe to free the packet buffers. > > + * Even if delivery needs to wait, qemu_net_queue_append copies > > + * the packet buffer. > > + */ > > + g_free(v); > > + g_free(iov); > > + g_free(packet_buf); > > + > > + qemu_mutex_unlock_iothread(); > > + }); > > + }); > > + > > + /* Did we manage to set an event callback? */ > > + if (event_cb_stat != VMNET_SUCCESS) { > > + error_printf("Failed to set up a callback to receive packets: %s\n", > > + _vmnet_status_repr(vmnet_start_status)); > > + exit(1); > > + } > > + > > + /* We're now ready to receive packets */ > > + vmnet_client_state->qemu_ready_to_receive = true; > > + vmnet_client_state->link_up = true; > > + > > + /* Include DHCP info if we're in a relevant mode */ > > + if (vmnet_provides_dhcp_info) { > > + snprintf(nc->info_str, sizeof(nc->info_str), > > + "dhcp_start=%s,dhcp_end=%s,mask=%s", > > + vmnet_dhcp_range_start, vmnet_dhcp_range_end, > > + vmnet_subnet_mask); > > + } else { > > + snprintf(nc->info_str, sizeof(nc->info_str), > > + "mac=%s", vmnet_mac_address); > > + } > > + > > + return 0; > > +} > > diff --git a/qapi/net.json b/qapi/net.json > > index c31748c87f..e4d4143243 100644 > > --- a/qapi/net.json > > +++ b/qapi/net.json > > @@ -450,6 +450,115 @@ > > '*vhostdev': 'str', > > '*queues': 'int' } } > > > > +## > > +# @VmnetOperatingMode: > > +# > > +# The operating modes in which a vmnet netdev can run > > +# Only available on macOS > > +# > > +# @host: the guest may communicate with the host > > +# and other guest network interfaces > > +# > > +# @shared: the guest may reach the Internet through a NAT, > > +# and may communicate with the host and other guest > > +# network interfaces > > +# > > +# @bridged: the guest's traffic is bridged with a > > +# physical network interface of the host > > +# > > +# Since: 6.0 > > +## > > +{ 'enum': 'VmnetOperatingMode', > > + 'data': [ 'host', 'shared', 'bridged' ], > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetModeOptionsBridged: > > +# > > +# Options for the vmnet-macos netdev > > +# that are only available in 'bridged' mode > > +# Only available on macOS > > +# > > +# @ifname: the physical network interface to bridge with > > +# (defaults to en0 if not specified) > > +# > > +# Since: 6.0 > > +## > > +{ 'struct': 'NetdevVmnetModeOptionsBridged', > > + 'data': { '*ifname': 'str' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetModeOptionsHostOrShared: > > +# > > +# Options for the vmnet-macos netdev > > +# that are only available in 'host' or 'shared' mode > > +# Only available on macOS > > +# > > +# @dhcp-start-address: the gateway address to use for the interface. > > +# The range to dhcp_end_address is placed in the DHCP pool. > > +# (only valid with mode=host|shared) > > +# (must be specified with dhcp-end-address and > > +# dhcp-subnet-mask) > > +# (allocated automatically if unset) > > +# > > +# @dhcp-end-address: the DHCP IPv4 range end address to use for the interface. > > +# (only valid with mode=host|shared) > > +# (must be specified with dhcp-start-address and > > +# dhcp-subnet-mask) > > +# (allocated automatically if unset) > > +# > > +# @dhcp-subnet-mask: the IPv4 subnet mask (string) to use on the interface. > > +# (only valid with mode=host|shared) > > +# (must be specified with dhcp-start-address and > > +# dhcp-end-address) > > +# (allocated automatically if unset) > > +# > > +# Since: 6.0 > > +## > > +{ 'struct': 'NetdevVmnetModeOptionsHostOrShared', > > + 'data': { > > + '*dhcp-start-address': 'str' , > > + '*dhcp-end-address': 'str', > > + '*dhcp-subnet-mask': 'str' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetModeOptions: > > +# > > +# Options specific to different operating modes of a vmnet netdev > > +# Only available on macOS > > +# > > +# @mode: the operating mode vmnet should run in > > +# > > +# Since: 6.0 > > +## > > +{ 'union': 'NetdevVmnetModeOptions', > > + 'base': { 'mode': 'VmnetOperatingMode' }, > > + 'discriminator': 'mode', > > + 'data': { > > + 'bridged': 'NetdevVmnetModeOptionsBridged', > > + 'host': 'NetdevVmnetModeOptionsHostOrShared', > > + 'shared': 'NetdevVmnetModeOptionsHostOrShared' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > +## > > +# @NetdevVmnetOptions: > > +# > > +# vmnet network backend > > +# Only available on macOS > > +# > > +# @options: a structure specifying the mode and mode-specific options > > +# (once QAPI supports a union type as a branch to another union type, > > +# this structure can be changed to a union, and the contents of > > +# NetdevVmnetModeOptions moved here) > > +# > > +# Since: 6.0 > > +## > > +{ 'struct': 'NetdevVmnetOptions', > > + 'data': {'options': 'NetdevVmnetModeOptions' }, > > + 'if': 'defined(CONFIG_DARWIN)' } > > + > > ## > > # @NetClientDriver: > > # > > @@ -458,10 +567,13 @@ > > # Since: 2.7 > > # > > # @vhost-vdpa since 5.1 > > +# > > +# @vmnet-macos since 6.0 (only available on macOS) > > ## > > { '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-macos', 'if': 'defined(CONFIG_DARWIN)' } ] } > > > > ## > > # @Netdev: > > @@ -475,6 +587,8 @@ > > # Since: 1.2 > > # > > # 'l2tpv3' - since 2.1 > > +# > > +# 'vmnet-macos' since 6.0 (only available on macOS) > > ## > > { 'union': 'Netdev', > > 'base': { 'id': 'str', 'type': 'NetClientDriver' }, > > @@ -490,7 +604,9 @@ > > 'hubport': 'NetdevHubPortOptions', > > 'netmap': 'NetdevNetmapOptions', > > 'vhost-user': 'NetdevVhostUserOptions', > > - 'vhost-vdpa': 'NetdevVhostVDPAOptions' } } > > + 'vhost-vdpa': 'NetdevVhostVDPAOptions', > > + 'vmnet-macos': { 'type': 'NetdevVmnetOptions', > > + 'if': 'defined(CONFIG_DARWIN)' } } } > > > > ## > > # @NetFilterDirection: > > diff --git a/qemu-options.hx b/qemu-options.hx > > index 9172d51659..ec6b40b079 100644 > > --- a/qemu-options.hx > > +++ b/qemu-options.hx > > @@ -2483,6 +2483,15 @@ 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_DARWIN > > + "-netdev vmnet-macos,id=str,mode=bridged[,ifname=ifname]\n" > > + " configure a macOS-provided vmnet network in \"physical interface bridge\" mode\n" > > + " the physical interface to bridge with defaults to en0 if unspecified\n" > > + "-netdev vmnet-macos,id=str,mode=host|shared\n" > > + " [,dhcp_start_address=addr,dhcp_end_address=addr,dhcp_subnet_mask=mask]\n" > > + " configure a macOS-provided vmnet network in \"host\" or \"shared\" mode\n" > > + " the DHCP configuration will be set automatically if unspecified\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) > > -- > > 2.24.3 (Apple Git-128) > > > > [-- Attachment #2: Type: text/html, Size: 60395 bytes --] ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2021-04-26 14:30 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2021-02-18 13:49 [PATCH v4] net/macos: implement vmnet-based netdev phillip.ennen 2021-02-20 6:39 ` Howard Spoelstra 2021-02-23 23:24 ` Roman Bolshakov 2021-03-03 11:24 ` Phillip Tennen 2021-04-26 14:05 ` Alessio Dionisi
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.