From nobody Fri Apr 19 13:59:54 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1613656279; cv=none; d=zohomail.com; s=zohoarc; b=Hqm4x7KoYc9sv407XVIbTfheqBc1PJfJqqv5bpmlOc6ZGVQ8Ki9b9li7bax3ppjy+miKUEzuUaBhIrqLFA3hDBZIwKcT8akd0H99WDqwsipLjOSGpZzmtMtmxderdF51ptfC49oirPTzuB8Q7A1taqbXMWXLHJ/oJSsUaTBWCHY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1613656279; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:To; bh=5/v0RO+LhkmWHMQuwETKu9oqoRCkZgjyFQOeca/Xv2E=; b=P/of9YmXX6A7Sb6umZWlcFnm4tqOgIs+Q3w7/Uh8Kih66xJbn1t0l0p2AcGaGyp6g65kbBkME1VULrCKdtPM/FjTthBWWUYuf/1zymLxlkV4JG4FY17fdukbgvmDqdaZzqOB0S+EV4vRO0fXRQNmKBvmxsQNqRCziJ8VHQkou1o= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail header.from= (p=none dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1613656278442789.0548403060848; Thu, 18 Feb 2021 05:51:18 -0800 (PST) Received: from localhost ([::1]:47610 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lCji8-0003xm-0A for importer@patchew.org; Thu, 18 Feb 2021 08:51:16 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:49106) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lCjgq-0003Is-30 for qemu-devel@nongnu.org; Thu, 18 Feb 2021 08:49:56 -0500 Received: from mail-wr1-x432.google.com ([2a00:1450:4864:20::432]:39058) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lCjgm-0005Gm-Ly for qemu-devel@nongnu.org; Thu, 18 Feb 2021 08:49:55 -0500 Received: by mail-wr1-x432.google.com with SMTP id v1so3058009wrd.6 for ; Thu, 18 Feb 2021 05:49:51 -0800 (PST) Received: from localhost.localdomain ([2a01:e34:ec19:cd20:c492:ab8b:f976:806a]) by smtp.gmail.com with ESMTPSA id b2sm9525005wrn.2.2021.02.18.05.49.48 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 18 Feb 2021 05:49:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=5/v0RO+LhkmWHMQuwETKu9oqoRCkZgjyFQOeca/Xv2E=; b=inNLneCrd3DuhxuerXJBDh8ZMvpLr5TY2PC/7oTsdbHp9sUS8O7TyBJejoJNgkBcCL +dnYLPyY4tRxHORd1PeqvAUaDXPGQZslYeZCvg6agiRJRBk+hAKQIJMXmTNPHUyxRMIJ Z7rAsZA+DiU2BTRgoz9xHoYQnKK0nBZuKJnd5ezsAIUSeKn3yAYIgL+l8yfSIA2OG5OD uvXCbZjEVuKLzi//1dyxpKJ8NcCHSLZzXCQZ5hOWk+zvtFfKDr/N3byjbXUE7Tk2efGB kw+aPdeIcLrEAxqtHreeh54LGBMzpXVI5ykDY88vvBNaNAbJaaZBiXjZp96pt8C3MKx6 TCaQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=5/v0RO+LhkmWHMQuwETKu9oqoRCkZgjyFQOeca/Xv2E=; b=ex4rUdG0odt+sBbU6/SNXltRiCOBcbBfwpVzfJI+Z4pKuqT4n47xBvp6xfgZweusUo s+oXEvoRZZm1kDcFMPDsCglBxh4OpsT1+PV1YzqG2o3T+w1wZcwJOH6LDMr4d1ZxidPz SLTQXQluO67iZr6bEMvvx00RSa4c2N1P7oqOO3rz/SsdAuTMS76PxhD5mXw73Ab+hiau lHc+U8uawD059AEuKf5N1BoYgyA157xdvMulFhMb8sWK+ogd4JbMlYNPXEAj/AfschCX pOin99OS433GCteOS4MV/1nNuJKdR0BmG2NG62u4acSLGqEJ+9gPVkItGvw2H2SrZGUJ f4IA== X-Gm-Message-State: AOAM530aAUTEhtMzGIHVZs+hwRbGS1Qo5xoXCzdRcjrmPaFv78pcp3GF jGa0ojjEdKRt2EknNsO9SDxuf3rufX8RROnK X-Google-Smtp-Source: ABdhPJwOYKnhWgEOsClIcO6/4sVLKLP0N8IS6XB4hNoBsRw2A3y5sOzNyS8urp4Uz4oAgmOK0QCQNg== X-Received: by 2002:adf:f0ce:: with SMTP id x14mr4512532wro.252.1613656189766; Thu, 18 Feb 2021 05:49:49 -0800 (PST) From: phillip.ennen@gmail.com To: qemu-devel@nongnu.org Subject: [PATCH v4] net/macos: implement vmnet-based netdev Date: Thu, 18 Feb 2021 14:49:47 +0100 Message-Id: <20210218134947.1860-1-phillip.ennen@gmail.com> X-Mailer: git-send-email 2.24.3 (Apple Git-128) MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::432; envelope-from=phillip.ennen@gmail.com; helo=mail-wr1-x432.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: thuth@redhat.com, stefanha@gmail.com, jasowang@redhat.com, armbru@redhat.com, phillip@axleos.com, hsp.cat7@gmail.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) From: Phillip Tennen This patch implements a new netdev device, reachable via -netdev vmnet-macos, that=E2=80=99s backed by macOS=E2=80=99s 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 =E2=80=9Cgeneric error=E2=80=9D status code. QEM= U 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=E2=80=99ve opened a feedback report with Apple to allow the use of the relevant entitlement with this use case: https://openradar.appspot.com/radar?id=3D5007417364447232 vmnet offers three operating modes, all of which are supported by this patch via the =E2=80=9Cmode=3Dhost|shared|bridge=E2=80=9D 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=E2=80=99s 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 --- 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=3D"coreaudio try-sdl" audio_possible_drivers=3D"coreaudio sdl" - QEMU_LDFLAGS=3D"-framework CoreFoundation -framework IOKit $QEMU_LDFLAGS" + QEMU_LDFLAGS=3D"-framework CoreFoundation -framework IOKit -framework vm= net $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=3D"-DOS_OBJECT_USE_OBJC=3D0 $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, =20 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')) =20 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_DRIV= ER__MAX])( #ifdef CONFIG_L2TPV3 [NET_CLIENT_DRIVER_L2TPV3] =3D net_init_l2tpv3, #endif +#ifdef CONFIG_DARWIN + [NET_CLIENT_DRIVER_VMNET_MACOS] =3D net_init_vmnet_macos, +#endif }; =20 =20 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 + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * 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 + +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 >=3D 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 =3D 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 =3D DO_UPCAST(vmnet_state_t, nc, nc); + + /* Combine the provided iovs into a single vmnet packet */ + struct vmpktdesc *packet =3D g_new0(struct vmpktdesc, 1); + packet->vm_pkt_iov =3D g_new0(struct iovec, iovcnt); + memcpy(packet->vm_pkt_iov, iovs, sizeof(struct iovec) * iovcnt); + packet->vm_pkt_iovcnt =3D iovcnt; + packet->vm_flags =3D 0; + + /* Figure out the packet size by iterating the iov's */ + for (int i =3D 0; i < iovcnt; i++) { + const struct iovec *iov =3D iovs + i; + packet->vm_pkt_size +=3D iov->iov_len; + } + + /* Finally, write the packet to the vmnet interface */ + int packet_count =3D 1; + vmnet_return_t result =3D vmnet_write(s->vmnet_iface_ref, packet, + &packet_count); + if (result !=3D VMNET_SUCCESS || packet_count !=3D 1) { + error_printf("Failed to send packet to host: %s\n", + _vmnet_status_repr(result)); + } + ssize_t wrote_bytes =3D 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 =3D DO_UPCAST(vmnet_state_t, nc, nc); + /* Ready to receive more packets! */ + vmnet_client_state->qemu_ready_to_receive =3D true; +} + +static NetClientInfo net_vmnet_macos_info =3D { + .type =3D NET_CLIENT_DRIVER_VMNET_MACOS, + .size =3D sizeof(vmnet_state_t), + .receive_iov =3D vmnet_receive_iov, + .can_receive =3D vmnet_can_receive, +}; + +static bool _validate_ifname_is_valid_bridge_target(const char *ifname) +{ + /* Iterate available bridge interfaces, ensure the provided one is val= id */ + xpc_object_t bridge_interfaces =3D vmnet_copy_shared_interface_list(); + bool failed_to_match_iface_name =3D 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", ifnam= e); + 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 =3D _vmnet_operating_mode_enum_compat( + vmnet_opts->mode); + + /* Validate options */ + if (mode =3D=3D VMNET_HOST_MODE || mode =3D=3D VMNET_SHARED_MODE) { + NetdevVmnetModeOptionsHostOrShared mode_opts =3D vmnet_opts->u.hos= t; + /* 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 =3D=3D VMNET_BRIDGED_MODE) { + /* Nothing to validate */ + } else { + error_printf("Unknown vmnet mode %d\n", mode); + exit(1); + } + + xpc_object_t interface_desc =3D xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_uint64( + interface_desc, + vmnet_operation_mode_key, + mode + ); + + if (mode =3D=3D VMNET_BRIDGED_MODE) { + /* + * Configure the provided physical interface to act + * as a bridge with QEMU + */ + NetdevVmnetModeOptionsBridged mode_opts =3D vmnet_opts->u.bridged; + /* Bridge with en0 by default */ + const char *physical_ifname =3D mode_opts.has_ifname ? mode_opts.i= fname : + "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 =3D=3D VMNET_HOST_MODE || mode =3D=3D VMNET_SHARED_MOD= E) { + /* Pass the DHCP configuration to vmnet, if the user provided one = */ + NetdevVmnetModeOptionsHostOrShared mode_opts =3D vmnet_opts->u.hos= t; + 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 =3D=3D NET_CLIENT_DRIVER_VMNET_MACOS); + + NetdevVmnetModeOptions *vmnet_opts =3D netdev->u.vmnet_macos.options; + xpc_object_t iface_desc =3D _construct_vmnet_interface_description(vmn= et_opts); + + NetClientState *nc =3D qemu_new_net_client(&net_vmnet_macos_info, peer, + "vmnet", name); + vmnet_state_t *vmnet_client_state =3D DO_UPCAST(vmnet_state_t, nc, nc); + + dispatch_queue_t vmnet_dispatch_queue =3D dispatch_queue_create( + "org.qemu.vmnet.iface_queue", + DISPATCH_QUEUE_SERIAL + ); + + __block vmnet_return_t vmnet_start_status =3D 0; + __block uint64_t vmnet_iface_mtu =3D 0; + __block uint64_t vmnet_max_packet_size =3D 0; + __block const char *vmnet_mac_address =3D NULL; + /* + * We can't refer to an array type directly within a block, + * so hold a pointer instead. + */ + uuid_string_t vmnet_iface_uuid =3D {0}; + __block uuid_string_t *vmnet_iface_uuid_ptr =3D &vmnet_iface_uuid; + /* These are only provided in VMNET_HOST_MODE and VMNET_SHARED_MODE */ + bool vmnet_provides_dhcp_info =3D ( + vmnet_opts->mode =3D=3D VMNET_OPERATING_MODE_HOST || + vmnet_opts->mode =3D=3D VMNET_OPERATING_MODE_SHARED); + __block const char *vmnet_subnet_mask =3D NULL; + __block const char *vmnet_dhcp_range_start =3D NULL; + __block const char *vmnet_dhcp_range_end =3D NULL; + + /* Create the vmnet interface */ + dispatch_semaphore_t vmnet_iface_sem =3D dispatch_semaphore_create(0); + interface_ref vmnet_iface_ref =3D vmnet_start_interface( + iface_desc, + vmnet_dispatch_queue, + ^(vmnet_return_t status, xpc_object_t _Nullable interface_param) { + vmnet_start_status =3D status; + if (vmnet_start_status !=3D 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 =3D xpc_dictionary_get_uint64( + interface_param, + vmnet_mtu_key + ); + vmnet_max_packet_size =3D xpc_dictionary_get_uint64( + interface_param, + vmnet_max_packet_size_key + ); + vmnet_mac_address =3D strdup(xpc_dictionary_get_string( + interface_param, + vmnet_mac_address_key + )); + + const uint8_t *iface_uuid =3D 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 =3D strdup(xpc_dictionary_get_string( + interface_param, + vmnet_start_address_key + )); + vmnet_dhcp_range_end =3D strdup(xpc_dictionary_get_string( + interface_param, + vmnet_end_address_key + )); + vmnet_subnet_mask =3D 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 !=3D VMNET_SUCCESS || !vmnet_iface_ref) { + error_printf("Failed to start interface: %s\n", + _vmnet_status_repr(vmnet_start_status)); + if (vmnet_start_status =3D=3D 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 =3D vmnet_iface_ref; + vmnet_return_t event_cb_stat =3D 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 !=3D VMNET_INTERFACE_PACKETS_AVAILABLE) { + error_printf("Unknown vmnet interface event 0x%08x\n", event_m= ask); + 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 ): There may be more th= an + * one packet available. + * As an optimization, we could read + * vmnet_estimated_packets_available_key packets now. + */ + char *packet_buf =3D g_malloc0(vmnet_max_packet_size); + struct iovec *iov =3D g_new0(struct iovec, 1); + iov->iov_base =3D packet_buf; + iov->iov_len =3D vmnet_max_packet_size; + + int pktcnt =3D 1; + struct vmpktdesc *v =3D g_new0(struct vmpktdesc, pktcnt); + v->vm_pkt_size =3D vmnet_max_packet_size; + v->vm_pkt_iov =3D iov; + v->vm_pkt_iovcnt =3D 1; + v->vm_flags =3D 0; + + vmnet_return_t result =3D vmnet_read(vmnet_iface_ref, v, &pktcnt); + if (result !=3D VMNET_SUCCESS) { + error_printf("Failed to read packet from host: %s\n", + _vmnet_status_repr(result)); + } + + /* Ensure we read exactly one packet */ + assert(pktcnt =3D=3D 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=20 + * created and thus the main queue will be unavailable. + */ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_H= IGH,=20 + 0),=20 + ^{ + qemu_mutex_lock_iothread(); + + /* + * Deliver the packet to the guest + * If the delivery succeeded synchronously, this returns the l= ength + * of the sent packet. + */ + if (qemu_send_packet_async(nc, iov->iov_base, + v->vm_pkt_size, + vmnet_send_completed) =3D=3D 0) { + vmnet_client_state->qemu_ready_to_receive =3D 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 !=3D 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 =3D true; + vmnet_client_state->link_up =3D 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=3D%s,dhcp_end=3D%s,mask=3D%s", + vmnet_dhcp_range_start, vmnet_dhcp_range_end, + vmnet_subnet_mask); + } else { + snprintf(nc->info_str, sizeof(nc->info_str), + "mac=3D%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' } } =20 +## +# @VmnetOperatingMode: +# +# The operating modes in which a vmnet netdev can run +# Only available on macOS +# +# @host: the guest may communicate with the host=20 +# and other guest network interfaces +# +# @shared: the guest may reach the Internet through a NAT,=20 +# and may communicate with the host and other guest=20 +# network interfaces +# +# @bridged: the guest's traffic is bridged with a=20 +# 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=20 +# (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.=20 +# The range to dhcp_end_address is placed in the DHCP= pool. +# (only valid with mode=3Dhost|shared) +# (must be specified with dhcp-end-address and=20 +# dhcp-subnet-mask) +# (allocated automatically if unset) +# +# @dhcp-end-address: the DHCP IPv4 range end address to use for the interf= ace.=20 +# (only valid with mode=3Dhost|shared) +# (must be specified with dhcp-start-address and=20 +# dhcp-subnet-mask) +# (allocated automatically if unset) +# +# @dhcp-subnet-mask: the IPv4 subnet mask (string) to use on the interface. +# (only valid with mode=3Dhost|shared) +# (must be specified with dhcp-start-address and=20 +# dhcp-end-address) +# (allocated automatically if unset) +# +# Since: 6.0 +## +{ 'struct': 'NetdevVmnetModeOptionsHostOrShared', + 'data': {=20 + '*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)' } ] } =20 ## # @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',=20 + 'if': 'defined(CONFIG_DARWIN)' } } } =20 ## # @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=3Dstr,vhostdev=3D/path/to/dev\n" " configure a vhost-vdpa network,Establish a vhost-vdpa= netdev\n" +#endif +#ifdef CONFIG_DARWIN + "-netdev vmnet-macos,id=3Dstr,mode=3Dbridged[,ifname=3Difname]\n" + " configure a macOS-provided vmnet network in \"physical inter= face bridge\" mode\n" + " the physical interface to bridge with defaults to en0 if uns= pecified\n" + "-netdev vmnet-macos,id=3Dstr,mode=3Dhost|shared\n" + " [,dhcp_start_address=3Daddr,dhcp_end_address=3Da= ddr,dhcp_subnet_mask=3Dmask]\n" + " configure a macOS-provided vmnet network in \"host\" or \"sh= ared\" mode\n" + " the DHCP configuration will be set automatically if unspecif= ied\n" #endif "-netdev hubport,id=3Dstr,hubid=3Dn[,netdev=3Dnd]\n" " configure a hub port on the hub with ID 'n'\n", QEMU_= ARCH_ALL) --=20 2.24.3 (Apple Git-128)