From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.5 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,HTML_MESSAGE,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,MENTIONS_GIT_HOSTING,SPF_HELO_NONE,SPF_PASS, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8351FC433E0 for ; Wed, 3 Mar 2021 11:26:34 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 5E6CA64EDF for ; Wed, 3 Mar 2021 11:26:33 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 5E6CA64EDF Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:52434 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lHPeC-0002oI-AH for qemu-devel@archiver.kernel.org; Wed, 03 Mar 2021 06:26:32 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:50842) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lHPca-0001qR-Vt for qemu-devel@nongnu.org; Wed, 03 Mar 2021 06:24:53 -0500 Received: from mail-pg1-x52f.google.com ([2607:f8b0:4864:20::52f]:45905) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lHPcT-0006Bw-Rt for qemu-devel@nongnu.org; Wed, 03 Mar 2021 06:24:52 -0500 Received: by mail-pg1-x52f.google.com with SMTP id p21so16105589pgl.12 for ; Wed, 03 Mar 2021 03:24:44 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=C8Mv8HKXCDyxPeOa+V1/Dv1dqrwydTLqsI8TIQQVpIM=; b=isWdnyULnawtm/ZgeRg4kfz+muOIq0KEg8T15ra0tWg4OMQoQZDWY9M6fRum3zkwsF fM760sFKTDwIX36DJlX2BRG2wP19s4Y/b4/Vg1QvddeyKK+g2PYVsP2IQXMs2NDR/G95 7j8PanAhDRUC7ek4c0rgAbf8K6N9CrczOtDYxcHnYpUWNjyFs1rzgaUNjQH8R8EOtsHi W32L1h+fx7TaE8EdLHcswG1fjuHRlnaNcyZANOL7A6lTQ6q0kusBo55WpH9YkUkehQju +itU2WBaKcCqAXFz4bxHF3Uju0Xi1hM6EVqonWZexjGJ9FPQoy1wxexg8EVvFevKBjQz syoA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=C8Mv8HKXCDyxPeOa+V1/Dv1dqrwydTLqsI8TIQQVpIM=; b=ntuEwq3M83n4mu7px8dOqUJc76njZ/To3VNrReAzpq9WyIL8/X1hoDNWd46i1THZH2 Ik70dA4ImHLPbQtah+DzEbEAULhDX0GqwrTeEqtXTdreON0ASf8D8Tbol3sU/YaeASyf sVlRA1raHPIZW6MZj1A+3jmTWZDYhik/1MZJAlIvJEePbC6PF62ohbFW+ACf/nZdWB0l TaP36fZ44Bp9lJ5E5Gfv2kZCUUWqzLxZYF8vI/KpD83O7p3Yna11anae9khsSEbDjE8J 70OXCWOidCYMbyr0B4o32hN3B7/U27G4rCEjZ5VxoyY6Nmll4lKt8YW8ZVJ+/0Z/KhU2 HtNw== X-Gm-Message-State: AOAM533yFlVS2GcfAAfB6hnSWBOqMWNOnJgzOS+6Ps1jMvS4ti3uMrE0 F5gT1gRSbgzrY2g3ZZPP8a3umJxfVC18uhid8VY= X-Google-Smtp-Source: ABdhPJzBNlaZNwm+H4j3gcI8WthcbNCB+bgP9U2nX6UX86FVCO7FmF1C6ASnh1s0ndmseSigG2dDP2uLNncnGJhvWlI= X-Received: by 2002:a63:511:: with SMTP id 17mr2566809pgf.173.1614770683700; Wed, 03 Mar 2021 03:24:43 -0800 (PST) MIME-Version: 1.0 References: <20210218134947.1860-1-phillip.ennen@gmail.com> In-Reply-To: From: Phillip Tennen Date: Wed, 3 Mar 2021 12:24:32 +0100 Message-ID: Subject: Re: [PATCH v4] net/macos: implement vmnet-based netdev To: Roman Bolshakov Content-Type: multipart/alternative; boundary="000000000000feea8705bca0183f" Received-SPF: pass client-ip=2607:f8b0:4864:20::52f; envelope-from=phillip.ennen@gmail.com; helo=mail-pg1-x52f.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, HTML_MESSAGE=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: Thomas Huth , Stefan Hajnoczi , jasowang@redhat.com, qemu-devel qemu-devel , Markus Armbruster , Phillip Tennen , Howard Spoelstra Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" --000000000000feea8705bca0183f Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable 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 (thanks to you as well for your time and review!) pointed out. Phillip On Wed, Feb 24, 2021 at 12:25 AM Roman Bolshakov wrote: > On Thu, Feb 18, 2021 at 02:49:47PM +0100, phillip.ennen@gmail.com wrote: > > 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 devic= e > > 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.= 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=E2=80=99ve open= ed 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=99= 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 > > --- > > 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 b= e > .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=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 -framewor= k > 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/e6c52d6bedb92f16defb5782b696853824= b14bd9 > > > # 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, > > > > 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] =3D net_init_l2tpv3, > > #endif > > +#ifdef CONFIG_DARWIN > > CONFIG_VMNET should be used here as well. > > > + [NET_CLIENT_DRIVER_VMNET_MACOS] =3D 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 > > + * > > + * 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 > > + > > +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 >=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) > > 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 =3D 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 =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_count could be used instead of 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; > > Should we use iov_copy() instead? > > > + packet->vm_flags =3D 0; > > The line is redundant with g_new0. > > > + > > + /* 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; > > + } > > 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 =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; > > 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 =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= ) > > Underscore may be dropped from the function. > > > +{ > > + /* Iterate available bridge interfaces, ensure the provided one is > valid */ > > + xpc_object_t bridge_interfaces =3D vmnet_copy_shared_interface_lis= t(); > > + 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", > 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 =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.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 =3D=3D 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 =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.brid= ged; > > + /* Bridge with en0 by default */ > > + const char *physical_ifname =3D 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 =3D=3D VMNET_HOST_MODE || mode =3D=3D VMNET_SHARED= _MODE) { > > + /* Pass the DHCP configuration to vmnet, if the user provided > one */ > > + NetdevVmnetModeOptionsHostOrShared mode_opts =3D > 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 =3D=3D NET_CLIENT_DRIVER_VMNET_MACOS); > > + > > + NetdevVmnetModeOptions *vmnet_opts =3D netdev->u.vmnet_macos.optio= ns; > > + xpc_object_t iface_desc =3D > _construct_vmnet_interface_description(vmnet_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_MOD= E > */ > > + 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_strin= g( > > + 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_callbac= k( > > + 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_mask); > > + return; > > + } > > + > > + /* If we're unable to handle more packets now, drop this packe= t > */ > > + if (!vmnet_client_state->qemu_ready_to_receive) { > > + return; > > + } > > + > > + /* > > + * TODO(Phillip Tennen ): There may be > more than > > + * 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, &pktc= nt); > > + 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 wil= l > 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) =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' } } > > > > +## > > +# @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=3Dhost|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=3Dhost|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=3Dhost|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 content= s > 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=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 > interface bridge\" mode\n" > > + " the physical interface to bridge with defaults to en0 if > unspecified\n" > > + "-netdev vmnet-macos,id=3Dstr,mode=3Dhost|shared\n" > > + " > [,dhcp_start_address=3Daddr,dhcp_end_address=3Daddr,dhcp_subnet_mask=3Dm= ask]\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=3Dstr,hubid=3Dn[,netdev=3Dnd]\n" > > " configure a hub port on the hub with ID 'n'\n", > QEMU_ARCH_ALL) > > -- > > 2.24.3 (Apple Git-128) > > > > > --000000000000feea8705bca0183f Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
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=C2=A0@Markus Armbruster=C2= =A0(thanks to you as well for your time and review!) pointed out.

Phillip

On Wed, Feb 24, 2021 at 12:25 AM Roman Bolsha= kov <r.bolshakov@yadro.com&= gt; wrote:
On Thu, Feb 18, 2021 at 02:49:47PM +01= 00, phillip.en= nen@gmail.com wrote:
> From: Phillip Tennen <phillip@axleos.com>
>
> 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 devi= ce
> via the tuntaposx kernel extension. Notably, the tap/tuntaposx approac= h
> 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 provisionin= g
> profile, or root access. Otherwise attempts to create the virtual
> interface will fail with a =E2=80=9Cgeneric error=E2=80=9D status code= . QEMU may not
> currently be signed with an entitlement granted in a provisioning
> profile, as this would necessitate pre-signed binary build distributio= n,
> rather than source-code distribution. As such, using this netdev
> currently requires that qemu be run with root access. I=E2=80=99ve ope= ned a
> feedback report with Apple to allow the use of the relevant entitlemen= t
> 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 wit= h other
> * vmnet
> interfaces that are in host mode and also with the native host.
> * "Shared" mode: Allows traffic originating from the vmnet i= nterface 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 physica= l 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 interfac= e to bridge
> with. Defaults to en0.
> * "Host" mode / "Shared" mode: The user may specif= y 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 f= or the VM
> within an isolated network
>
> Note that this patch requires macOS 10.15 as a minimum, as this is whe= n
> bridging support was implemented in vmnet.framework.
>
> Signed-off-by: Phillip Tennen <phillip@axleos.com>
> ---
>=C2=A0 configure=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|=C2=A0 =C2=A02 +- >=C2=A0 net/clients.h=C2=A0 =C2=A0 =C2=A0|=C2=A0 =C2=A06 +
>=C2=A0 net/meson.build=C2=A0 =C2=A0|=C2=A0 =C2=A01 +
>=C2=A0 net/net.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|=C2=A0 =C2=A03 +
>=C2=A0 net/vmnet-macos.c | 447 ++++++++++++++++++++++++++++++++++++++++= ++++++
>=C2=A0 qapi/net.json=C2=A0 =C2=A0 =C2=A0| 120 ++++++++++++-
>=C2=A0 qemu-options.hx=C2=A0 =C2=A0|=C2=A0 =C2=A09 +
>=C2=A0 7 files changed, 585 insertions(+), 3 deletions(-)
>=C2=A0 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 l= ot 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.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* If QEMU is started with -nographic, no = Cocoa event loop will be
.git/rebase-apply/patch:465: trailing whitespace.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 dispatch_async(dispatch_get_global_queue(DISPAT= CH_QUEUE_PRIORITY_HIGH,
.git/rebase-apply/patch:466: trailing whitespace.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A00),
.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&= quot;.

Also it would be helpful to provide a changelog under commit message
delimiter ("---")=C2=A0 for reach new version of the patch to pro= vide 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)
>=C2=A0 =C2=A0 fi
>=C2=A0 =C2=A0 audio_drv_list=3D"coreaudio try-sdl"
>=C2=A0 =C2=A0 audio_possible_drivers=3D"coreaudio sdl"
> -=C2=A0 QEMU_LDFLAGS=3D"-framework CoreFoundation -framework IOKi= t $QEMU_LDFLAGS"
> +=C2=A0 QEMU_LDFLAGS=3D"-framework CoreFoundation -framework IOKi= t -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/ro= olebo/qemu/commit/e6c52d6bedb92f16defb5782b696853824b14bd9

>=C2=A0 =C2=A0 # Disable attempts to use ObjectiveC features in os/objec= t.h since they
>=C2=A0 =C2=A0 # won't work when we're compiling with gcc as a C= compiler.
>=C2=A0 =C2=A0 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,
>=C2=A0
>=C2=A0 int net_init_vhost_vdpa(const Netdev *netdev, const char *name,<= br> >=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 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,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 NetClientState *peer, Error **errp);
> +#endif
> +
>=C2=A0 #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
>=C2=A0 softmmu_ss.add(when: 'CONFIG_POSIX', if_true: files(tap_= posix))
>=C2=A0 softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('= ;tap-win32.c'))
>=C2=A0 softmmu_ss.add(when: 'CONFIG_VHOST_NET_VDPA', if_true: f= iles('vhost-vdpa.c'))
> +softmmu_ss.add(when: 'CONFIG_DARWIN', if_true: files('vmn= et-macos.c'))
>=C2=A0
>=C2=A0 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])(
>=C2=A0 #ifdef CONFIG_L2TPV3
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [NET_CLIENT_DRIVER_L2TPV3]=C2=A0 =C2= =A0 =3D net_init_l2tpv3,
>=C2=A0 #endif
> +#ifdef CONFIG_DARWIN

CONFIG_VMNET should be used here as well.

> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 [NET_CLIENT_DRIVER_VMNET_MACOS] =3D net_i= nit_vmnet_macos,
> +#endif
>=C2=A0 };
>=C2=A0
>=C2=A0
> 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 {
> +=C2=A0 =C2=A0 NetClientState nc;
> +=C2=A0 =C2=A0 interface_ref vmnet_iface_ref;
> +=C2=A0 =C2=A0 /* Switched on after vmnet informs us that the interfac= e has started */
> +=C2=A0 =C2=A0 bool link_up;
> +=C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0* If qemu_send_packet_async returns 0, this is sw= itched off until our
> +=C2=A0 =C2=A0 =C2=A0* delivery callback is invoked
> +=C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 bool qemu_ready_to_receive;
> +} vmnet_state_t;
> +
> +int net_init_vmnet_macos(const Netdev *netdev, const char *name,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0NetClientState *peer, Error **errp);
> +
> +static const char *_vmnet_status_repr(vmnet_return_t status)

Underscore may be dropped.

> +{
> +=C2=A0 =C2=A0 switch (status) {
> +=C2=A0 =C2=A0 case VMNET_SUCCESS:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "success";
> +=C2=A0 =C2=A0 case VMNET_FAILURE:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "generic failure";
> +=C2=A0 =C2=A0 case VMNET_MEM_FAILURE:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "out of memory";
> +=C2=A0 =C2=A0 case VMNET_INVALID_ARGUMENT:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "invalid argument";
> +=C2=A0 =C2=A0 case VMNET_SETUP_INCOMPLETE:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "setup is incomplete"; > +=C2=A0 =C2=A0 case VMNET_INVALID_ACCESS:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "insufficient permissions&quo= t;;
> +=C2=A0 =C2=A0 case VMNET_PACKET_TOO_BIG:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "packet size exceeds MTU"= ;;
> +=C2=A0 =C2=A0 case VMNET_BUFFER_EXHAUSTED:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "kernel buffers temporarily e= xhausted";
> +=C2=A0 =C2=A0 case VMNET_TOO_MANY_PACKETS:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "number of packets exceeds sy= stem limit";
> +=C2=A0 =C2=A0 /* This error code was introduced in macOS 11.0 */
> +#if __MAC_OS_X_VERSION_MAX_ALLOWED >=3D 110000
> +=C2=A0 =C2=A0 case VMNET_SHARING_SERVICE_BUSY:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "sharing service busy";<= br> > +#endif
> +=C2=A0 =C2=A0 default:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "unknown status code"; > +=C2=A0 =C2=A0 }
> +}
> +
> +static operating_modes_t _vmnet_operating_mode_enum_compat(
> +=C2=A0 =C2=A0 VmnetOperatingMode mode)

Underscore may be dropped.

> +{
> +=C2=A0 =C2=A0 switch (mode) {
> +=C2=A0 =C2=A0 case VMNET_OPERATING_MODE_HOST:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return VMNET_HOST_MODE;
> +=C2=A0 =C2=A0 case VMNET_OPERATING_MODE_SHARED:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return VMNET_SHARED_MODE;
> +=C2=A0 =C2=A0 case VMNET_OPERATING_MODE_BRIDGED:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return VMNET_BRIDGED_MODE;
> +=C2=A0 =C2=A0 default:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Should never happen as the modes are p= arsed before we get here */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 assert(false);
> +=C2=A0 =C2=A0 }
> +}
> +
> +static bool vmnet_can_receive(NetClientState *nc)
> +{
> +=C2=A0 =C2=A0 vmnet_state_t *s =3D DO_UPCAST(vmnet_state_t, nc, nc);<= br> > +=C2=A0 =C2=A0 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,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0const struct iovec *iovs,<= br> > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0int iovcnt)
> +{
> +=C2=A0 =C2=A0 vmnet_state_t *s =3D DO_UPCAST(vmnet_state_t, nc, nc);<= br> > +
> +=C2=A0 =C2=A0 /* Combine the provided iovs into a single vmnet packet= */
> +=C2=A0 =C2=A0 struct vmpktdesc *packet =3D g_new0(struct vmpktdesc, 1= );

packet_count could be used instead of 1.

> +=C2=A0 =C2=A0 packet->vm_pkt_iov =3D g_new0(struct iovec, iovcnt);=
> +=C2=A0 =C2=A0 memcpy(packet->vm_pkt_iov, iovs, sizeof(struct iovec= ) * iovcnt);
> +=C2=A0 =C2=A0 packet->vm_pkt_iovcnt =3D iovcnt;

Should we use iov_copy() instead?

> +=C2=A0 =C2=A0 packet->vm_flags =3D 0;

The line is redundant with g_new0.

> +
> +=C2=A0 =C2=A0 /* Figure out the packet size by iterating the iov'= s */
> +=C2=A0 =C2=A0 for (int i =3D 0; i < iovcnt; i++) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 const struct iovec *iov =3D iovs + i;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 packet->vm_pkt_size +=3D iov->iov_l= en;
> +=C2=A0 =C2=A0 }

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.

> +
> +=C2=A0 =C2=A0 /* Finally, write the packet to the vmnet interface */<= br> > +=C2=A0 =C2=A0 int packet_count =3D 1;
> +=C2=A0 =C2=A0 vmnet_return_t result =3D vmnet_write(s->vmnet_iface= _ref, packet,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &= ;packet_count);
> +=C2=A0 =C2=A0 if (result !=3D VMNET_SUCCESS || packet_count !=3D 1) {=
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Failed to send packet = to host: %s\n",
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 _vmnet_status_repr(result))= ;
> +=C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 ssize_t wrote_bytes =3D packet->vm_pkt_size;

That's going to mismatch with actual number of bytes written if
packet_count returned from vmnet_write equals zero.

> +=C2=A0 =C2=A0 g_free(packet->vm_pkt_iov);
> +=C2=A0 =C2=A0 g_free(packet);
> +=C2=A0 =C2=A0 return wrote_bytes;
> +}
> +
> +static void vmnet_send_completed(NetClientState *nc, ssize_t len)
> +{
> +=C2=A0 =C2=A0 vmnet_state_t *vmnet_client_state =3D DO_UPCAST(vmnet_s= tate_t, nc, nc);
> +=C2=A0 =C2=A0 /* Ready to receive more packets! */
> +=C2=A0 =C2=A0 vmnet_client_state->qemu_ready_to_receive =3D true;<= br> > +}
> +
> +static NetClientInfo net_vmnet_macos_info =3D {
> +=C2=A0 =C2=A0 .type =3D NET_CLIENT_DRIVER_VMNET_MACOS,
> +=C2=A0 =C2=A0 .size =3D sizeof(vmnet_state_t),
> +=C2=A0 =C2=A0 .receive_iov =3D vmnet_receive_iov,
> +=C2=A0 =C2=A0 .can_receive =3D vmnet_can_receive,
> +};
> +
> +static bool _validate_ifname_is_valid_bridge_target(const char *ifnam= e)

Underscore may be dropped from the function.

> +{
> +=C2=A0 =C2=A0 /* Iterate available bridge interfaces, ensure the prov= ided one is valid */
> +=C2=A0 =C2=A0 xpc_object_t bridge_interfaces =3D vmnet_copy_shared_in= terface_list();
> +=C2=A0 =C2=A0 bool failed_to_match_iface_name =3D xpc_array_apply( > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 bridge_interfaces,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 ^bool(size_t index, xpc_object_t=C2=A0 _N= onnull value) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!strcmp(xpc_string_get_string_ptr(val= ue), ifname)) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* The interface name is va= lid! Stop iterating */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return false;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return true;
> +=C2=A0 =C2=A0 });
> +
> +=C2=A0 =C2=A0 if (failed_to_match_iface_name) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Invalid bridge interfa= ce name provided: %s\n", ifname);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Valid bridge interface= s:\n");
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 xpc_array_apply(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_copy_shared_interface= _list(),
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ^bool(size_t index, xpc_obj= ect_t=C2=A0 _Nonnull value) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("\t%s\n&q= uot;, xpc_string_get_string_ptr(value));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Keep iterating */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return true;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 });
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 exit(1);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return false;
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 return true;
> +}
> +
> +static xpc_object_t _construct_vmnet_interface_description(

Underscore is not needed I think.

> +=C2=A0 =C2=A0 const NetdevVmnetModeOptions *vmnet_opts)
> +{
> +=C2=A0 =C2=A0 operating_modes_t mode =3D _vmnet_operating_mode_enum_c= ompat(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_opts->mode);
> +
> +=C2=A0 =C2=A0 /* Validate options */
> +=C2=A0 =C2=A0 if (mode =3D=3D VMNET_HOST_MODE || mode =3D=3D VMNET_SH= ARED_MODE) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 NetdevVmnetModeOptionsHostOrShared mode_o= pts =3D vmnet_opts->u.host;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* If one DHCP parameter is configured, a= ll 3 are required */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (mode_opts.has_dhcp_start_address || > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 mode_opts.has_dhcp_end_addr= ess ||
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 mode_opts.has_dhcp_subnet_m= ask) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!(mode_opts.has_dhcp_st= art_address &&
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 mode_o= pts.has_dhcp_end_address &&
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 mode_o= pts.has_dhcp_subnet_mask)) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf(= "Incomplete DHCP configuration provided\n");
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 exit(1);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 } else if (mode =3D=3D 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.


> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Nothing to validate */
> +=C2=A0 =C2=A0 } else {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Unknown vmnet mode %d\= n", mode);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 exit(1);
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 xpc_object_t interface_desc =3D xpc_dictionary_create(N= ULL, NULL, 0);
> +=C2=A0 =C2=A0 xpc_dictionary_set_uint64(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_desc,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_operation_mode_key,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 mode
> +=C2=A0 =C2=A0 );
> +
> +=C2=A0 =C2=A0 if (mode =3D=3D VMNET_BRIDGED_MODE) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* Configure the provided physical i= nterface to act
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* as a bridge with QEMU
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 NetdevVmnetModeOptionsBridged mode_opts = =3D vmnet_opts->u.bridged;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Bridge with en0 by default */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 const char *physical_ifname =3D mode_opts= .has_ifname ? mode_opts.ifname :
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0&q= uot;en0";

I think a default interface is not needed here, it's better to require<= br> an explicit inteface to bridge with. Some people prefer wired, others
wireless. Ocasionally some do both :)

More comments later!

Thanks,
Roman

> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 _validate_ifname_is_valid_bridge_target(p= hysical_ifname);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 xpc_dictionary_set_string(interface_desc,=
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_shared_interface_na= me_key,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 physical_ifname);
> +=C2=A0 =C2=A0 } else if (mode =3D=3D VMNET_HOST_MODE || mode =3D=3D V= MNET_SHARED_MODE) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Pass the DHCP configuration to vmnet, = if the user provided one */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 NetdevVmnetModeOptionsHostOrShared mode_o= pts =3D vmnet_opts->u.host;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (mode_opts.has_dhcp_start_address) { > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* All DHCP arguments are a= vailable, as per the checks above */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 xpc_dictionary_set_string(i= nterface_desc,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_start= _address_key,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 mode_opts.d= hcp_start_address);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 xpc_dictionary_set_string(i= nterface_desc,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_end_a= ddress_key,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 mode_opts.d= hcp_end_address);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 xpc_dictionary_set_string(i= nterface_desc,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_subne= t_mask_key,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 mode_opts.d= hcp_subnet_mask);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 return interface_desc;
> +}
> +
> +int net_init_vmnet_macos(const Netdev *netdev, const char *name,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 NetClientState *peer, Error **errp)
> +{
> +=C2=A0 =C2=A0 assert(netdev->type =3D=3D NET_CLIENT_DRIVER_VMNET_M= ACOS);
> +
> +=C2=A0 =C2=A0 NetdevVmnetModeOptions *vmnet_opts =3D netdev->u.vmn= et_macos.options;
> +=C2=A0 =C2=A0 xpc_object_t iface_desc =3D _construct_vmnet_interface_= description(vmnet_opts);
> +
> +=C2=A0 =C2=A0 NetClientState *nc =3D qemu_new_net_client(&net_vmn= et_macos_info, peer,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0"vmnet", name);
> +=C2=A0 =C2=A0 vmnet_state_t *vmnet_client_state =3D DO_UPCAST(vmnet_s= tate_t, nc, nc);
> +
> +=C2=A0 =C2=A0 dispatch_queue_t vmnet_dispatch_queue =3D dispatch_queu= e_create(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 "org.qemu.vmnet.iface_queue", > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 DISPATCH_QUEUE_SERIAL
> +=C2=A0 =C2=A0 );
> +
> +=C2=A0 =C2=A0 __block vmnet_return_t vmnet_start_status =3D 0;
> +=C2=A0 =C2=A0 __block uint64_t vmnet_iface_mtu =3D 0;
> +=C2=A0 =C2=A0 __block uint64_t vmnet_max_packet_size =3D 0;
> +=C2=A0 =C2=A0 __block const char *vmnet_mac_address =3D NULL;
> +=C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0* We can't refer to an array type directly wi= thin a block,
> +=C2=A0 =C2=A0 =C2=A0* so hold a pointer instead.
> +=C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 uuid_string_t vmnet_iface_uuid =3D {0};
> +=C2=A0 =C2=A0 __block uuid_string_t *vmnet_iface_uuid_ptr =3D &vm= net_iface_uuid;
> +=C2=A0 =C2=A0 /* These are only provided in VMNET_HOST_MODE and VMNET= _SHARED_MODE */
> +=C2=A0 =C2=A0 bool vmnet_provides_dhcp_info =3D (
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_opts->mode =3D=3D VMNET_OPERATIN= G_MODE_HOST ||
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_opts->mode =3D=3D VMNET_OPERATIN= G_MODE_SHARED);
> +=C2=A0 =C2=A0 __block const char *vmnet_subnet_mask =3D NULL;
> +=C2=A0 =C2=A0 __block const char *vmnet_dhcp_range_start =3D NULL; > +=C2=A0 =C2=A0 __block const char *vmnet_dhcp_range_end =3D NULL;
> +
> +=C2=A0 =C2=A0 /* Create the vmnet interface */
> +=C2=A0 =C2=A0 dispatch_semaphore_t vmnet_iface_sem =3D dispatch_semap= hore_create(0);
> +=C2=A0 =C2=A0 interface_ref vmnet_iface_ref =3D vmnet_start_interface= (
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 iface_desc,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_dispatch_queue,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 ^(vmnet_return_t status, xpc_object_t=C2= =A0 _Nullable interface_param) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_start_status =3D status;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (vmnet_start_status !=3D VMNET_SUCCESS= || !interface_param) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Early return if the inte= rface couldn't be started */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 dispatch_semaphore_signal(v= mnet_iface_sem);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* Read the configuration that vmnet= provided us.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* The provided dictionary is owned = by XPC and may be freed
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* shortly after this block's ex= ecution.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* So, copy data buffers now.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_iface_mtu =3D xpc_dictionary_get_ui= nt64(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_param,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_mtu_key
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 );
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_max_packet_size =3D xpc_dictionary_= get_uint64(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_param,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_max_packet_size_key > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 );
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_mac_address =3D strdup(xpc_dictiona= ry_get_string(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_param,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_mac_address_key
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 ));
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 const uint8_t *iface_uuid =3D xpc_diction= ary_get_uuid(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_param,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_interface_id_key
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 );
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 uuid_unparse_upper(iface_uuid, *vmnet_ifa= ce_uuid_ptr);
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* If we're in a mode that provides D= HCP info, read it out now */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (vmnet_provides_dhcp_info) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_dhcp_range_start =3D = strdup(xpc_dictionary_get_string(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_par= am,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_start_a= ddress_key
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_dhcp_range_end =3D st= rdup(xpc_dictionary_get_string(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_par= am,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_end_add= ress_key
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_subnet_mask =3D strdu= p(xpc_dictionary_get_string(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 interface_par= am,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_subnet_= mask_key
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 dispatch_semaphore_signal(vmnet_iface_sem= );
> +=C2=A0 =C2=A0 });
> +
> +=C2=A0 =C2=A0 /* And block until we receive a response from vmnet */<= br> > +=C2=A0 =C2=A0 dispatch_semaphore_wait(vmnet_iface_sem, DISPATCH_TIME_= FOREVER);
> +
> +=C2=A0 =C2=A0 /* Did we manage to start the interface? */
> +=C2=A0 =C2=A0 if (vmnet_start_status !=3D VMNET_SUCCESS || !vmnet_ifa= ce_ref) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Failed to start interf= ace: %s\n",
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 _vmnet_status_repr(vmnet_st= art_status));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (vmnet_start_status =3D=3D VMNET_FAILU= RE) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Hint: vm= net requires running with root access\n");
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return -1;
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 info_report("Started vmnet interface with configur= ation:");
> +=C2=A0 =C2=A0 info_report("MTU:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 %llu", vmnet_iface_mtu);
> +=C2=A0 =C2=A0 info_report("Max packet size:=C2=A0 %llu", vm= net_max_packet_size);
> +=C2=A0 =C2=A0 info_report("MAC:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 %s", vmnet_mac_address);
> +=C2=A0 =C2=A0 if (vmnet_provides_dhcp_info) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 info_report("DHCP IPv4 start:=C2=A0 = %s", vmnet_dhcp_range_start);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 info_report("DHCP IPv4 end:=C2=A0 = =C2=A0 %s", vmnet_dhcp_range_end);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 info_report("IPv4 subnet mask: %s&qu= ot;, vmnet_subnet_mask);
> +=C2=A0 =C2=A0 }
> +=C2=A0 =C2=A0 info_report("UUID:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0%s", vmnet_iface_uuid);
> +
> +=C2=A0 =C2=A0 /* The interface is up! Set a block to run when packets= are received */
> +=C2=A0 =C2=A0 vmnet_client_state->vmnet_iface_ref =3D vmnet_iface_= ref;
> +=C2=A0 =C2=A0 vmnet_return_t event_cb_stat =3D vmnet_interface_set_ev= ent_callback(
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_iface_ref,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 VMNET_INTERFACE_PACKETS_AVAILABLE,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_dispatch_queue,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 ^(interface_event_t event_mask, xpc_objec= t_t=C2=A0 _Nonnull event) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (event_mask !=3D VMNET_INTERFACE_PACKE= TS_AVAILABLE) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Unknown = vmnet interface event 0x%08x\n", event_mask);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* If we're unable to handle more pac= kets now, drop this packet */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!vmnet_client_state->qemu_ready_to= _receive) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* TODO(Phillip Tennen <phillip@axleos.com>= ): There may be more than
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* one packet available.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* As an optimization, we could read=
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* vmnet_estimated_packets_available= _key packets now.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 char *packet_buf =3D g_malloc0(vmnet_max_= packet_size);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 struct iovec *iov =3D g_new0(struct iovec= , 1);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 iov->iov_base =3D packet_buf;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 iov->iov_len =3D vmnet_max_packet_size= ;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 int pktcnt =3D 1;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 struct vmpktdesc *v =3D g_new0(struct vmp= ktdesc, pktcnt);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 v->vm_pkt_size =3D vmnet_max_packet_si= ze;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 v->vm_pkt_iov =3D iov;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 v->vm_pkt_iovcnt =3D 1;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 v->vm_flags =3D 0;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_return_t result =3D vmnet_read(vmne= t_iface_ref, v, &pktcnt);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (result !=3D VMNET_SUCCESS) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Failed t= o read packet from host: %s\n",
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 _vmnet_status= _repr(result));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Ensure we read exactly one packet */ > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 assert(pktcnt =3D=3D 1);
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Dispatch this block to a global queue = instead of the main queue,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* which is only created when the pr= ogram has a Cocoa event loop.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* If QEMU is started with -nographi= c, no Cocoa event loop will be
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* created and thus the main queue w= ill be unavailable.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 dispatch_async(dispatch_get_global_queue(= DISPATCH_QUEUE_PRIORITY_HIGH,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A00),
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0^{
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_mutex_lock_iothread();=
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* Deliver the packet = to the guest
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* If the delivery suc= ceeded synchronously, this returns the length
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* of the sent packet.=
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (qemu_send_packet_async(= nc, iov->iov_base,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0v->= ;vm_pkt_size,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0vmnet= _send_completed) =3D=3D 0) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 vmnet_client_= state->qemu_ready_to_receive =3D false;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* It's safe to fr= ee the packet buffers.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* Even if delivery ne= eds to wait, qemu_net_queue_append copies
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* the packet buffer.<= br> > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 g_free(v);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 g_free(iov);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 g_free(packet_buf);
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_mutex_unlock_iothread(= );
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 });
> +=C2=A0 =C2=A0 });
> +
> +=C2=A0 =C2=A0 /* Did we manage to set an event callback? */
> +=C2=A0 =C2=A0 if (event_cb_stat !=3D VMNET_SUCCESS) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_printf("Failed to set up a cal= lback to receive packets: %s\n",
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 _vmnet_status_repr(vmnet_st= art_status));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 exit(1);
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 /* We're now ready to receive packets */
> +=C2=A0 =C2=A0 vmnet_client_state->qemu_ready_to_receive =3D true;<= br> > +=C2=A0 =C2=A0 vmnet_client_state->link_up =3D true;
> +
> +=C2=A0 =C2=A0 /* Include DHCP info if we're in a relevant mode */=
> +=C2=A0 =C2=A0 if (vmnet_provides_dhcp_info) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 snprintf(nc->info_str, sizeof(nc->i= nfo_str),
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"d= hcp_start=3D%s,dhcp_end=3D%s,mask=3D%s",
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0vmnet_d= hcp_range_start, vmnet_dhcp_range_end,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0vmnet_s= ubnet_mask);
> +=C2=A0 =C2=A0 } else {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 snprintf(nc->info_str, sizeof(nc->i= nfo_str),
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"m= ac=3D%s", vmnet_mac_address);
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 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 @@
>=C2=A0 =C2=A0 =C2=A0 '*vhostdev':=C2=A0 =C2=A0 =C2=A0'str&#= 39;,
>=C2=A0 =C2=A0 =C2=A0 '*queues':=C2=A0 =C2=A0 =C2=A0 =C2=A0'= int' } }
>=C2=A0
> +##
> +# @VmnetOperatingMode:
> +#
> +# The operating modes in which a vmnet netdev can run
> +# Only available on macOS
> +#
> +# @host: the guest may communicate with the host
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 and other guest network interfaces
> +#
> +# @shared: the guest may reach the Internet through a NAT,
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 and may communicate with the host= and other guest
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 network interfaces
> +#
> +# @bridged: the guest's traffic is bridged with a
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0physical network interface = of the host
> +#
> +# Since: 6.0
> +##
> +{ 'enum': 'VmnetOperatingMode',
> +=C2=A0 'data': [ 'host', 'shared', 'bridg= ed' ],
> +=C2=A0 '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
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (defaults to en0 if not specified= )
> +#
> +# Since: 6.0
> +##
> +{ 'struct': 'NetdevVmnetModeOptionsBridged',
> +=C2=A0 'data': { '*ifname':=C2=A0 'str' }, > +=C2=A0 'if': 'defined(CONFIG_DARWIN)' }
> +
> +##
> +# @NetdevVmnetModeOptionsHostOrShared:
> +#
> +# Options for the vmnet-macos netdev
> +# that are only available in 'host' or 'shared' mode<= br> > +# Only available on macOS
> +#
> +# @dhcp-start-address: the gateway address to use for the interface. =
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 The range to dhcp_end_address is placed in the DHCP pool.
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 (only valid with mode=3Dhost|shared)
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 (must be specified with dhcp-end-address and
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0dhcp-subnet-mask)
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 (allocated automatically if unset)
> +#
> +# @dhcp-end-address: the DHCP IPv4 range end address to use for the i= nterface.
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 (only valid with mode=3Dhost|shared)
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 (must be specified with dhcp-start-address and
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0dhcp-subnet-mask)
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 (allocated automatically if unset)
> +#
> +# @dhcp-subnet-mask: the IPv4 subnet mask (string) to use on the inte= rface.
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 (only valid with mode=3Dhost|shared)
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 (must be specified with dhcp-start-address and
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0dhcp-end-address)
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 (allocated automatically if unset)
> +#
> +# Since: 6.0
> +##
> +{ 'struct': 'NetdevVmnetModeOptionsHostOrShared',
> +=C2=A0 'data': {
> +=C2=A0 =C2=A0 '*dhcp-start-address': 'str' ,
> +=C2=A0 =C2=A0 '*dhcp-end-address':=C2=A0 =C2=A0'str',=
> +=C2=A0 =C2=A0 '*dhcp-subnet-mask':=C2=A0 =C2=A0'str' = },
> +=C2=A0 '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',
> +=C2=A0 'base': { 'mode': 'VmnetOperatingMode'= },
> +=C2=A0 'discriminator': 'mode',
> +=C2=A0 'data': {
> +=C2=A0 =C2=A0 'bridged':=C2=A0 =C2=A0 =C2=A0 'NetdevVmnet= ModeOptionsBridged',
> +=C2=A0 =C2=A0 'host':=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'N= etdevVmnetModeOptionsHostOrShared',
> +=C2=A0 =C2=A0 'shared':=C2=A0 =C2=A0 =C2=A0 =C2=A0'Netdev= VmnetModeOptionsHostOrShared' },
> +=C2=A0 'if': 'defined(CONFIG_DARWIN)' }
> +
> +##
> +# @NetdevVmnetOptions:
> +#
> +# vmnet network backend
> +# Only available on macOS
> +#
> +# @options: a structure specifying the mode and mode-specific options=
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(once QAPI supports a union= type as a branch to another union type,
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 this structure can be chan= ged to a union, and the contents of
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 NetdevVmnetModeOptions mov= ed here)
> +#
> +# Since: 6.0
> +##
> +{ 'struct': 'NetdevVmnetOptions',
> +=C2=A0 'data': {'options': 'NetdevVmnetModeOption= s' },
> +=C2=A0 'if': 'defined(CONFIG_DARWIN)' }
> +
>=C2=A0 ##
>=C2=A0 # @NetClientDriver:
>=C2=A0 #
> @@ -458,10 +567,13 @@
>=C2=A0 # Since: 2.7
>=C2=A0 #
>=C2=A0 #=C2=A0 =C2=A0 =C2=A0 =C2=A0 @vhost-vdpa since 5.1
> +#
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 @vmnet-macos since 6.0 (only available o= n macOS)
>=C2=A0 ##
>=C2=A0 { 'enum': 'NetClientDriver',
>=C2=A0 =C2=A0 'data': [ 'none', 'nic', 'use= r', 'tap', 'l2tpv3', 'socket', 'vde', > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'bridge', 'hubp= ort', 'netmap', 'vhost-user', 'vhost-vdpa' ] }<= br> > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'bridge', 'hubp= ort', 'netmap', 'vhost-user', 'vhost-vdpa',
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 { 'name': 'vmne= t-macos', 'if': 'defined(CONFIG_DARWIN)' } ] }
>=C2=A0
>=C2=A0 ##
>=C2=A0 # @Netdev:
> @@ -475,6 +587,8 @@
>=C2=A0 # Since: 1.2
>=C2=A0 #
>=C2=A0 #=C2=A0 =C2=A0 =C2=A0 =C2=A0 'l2tpv3' - since 2.1
> +#
> +#=C2=A0 =C2=A0 =C2=A0 =C2=A0 'vmnet-macos' since 6.0 (only av= ailable on macOS)
>=C2=A0 ##
>=C2=A0 { 'union': 'Netdev',
>=C2=A0 =C2=A0 'base': { 'id': 'str', 'type&= #39;: 'NetClientDriver' },
> @@ -490,7 +604,9 @@
>=C2=A0 =C2=A0 =C2=A0 'hubport':=C2=A0 'NetdevHubPortOptions= ',
>=C2=A0 =C2=A0 =C2=A0 'netmap':=C2=A0 =C2=A0'NetdevNetmapOpt= ions',
>=C2=A0 =C2=A0 =C2=A0 'vhost-user': 'NetdevVhostUserOptions&= #39;,
> -=C2=A0 =C2=A0 'vhost-vdpa': 'NetdevVhostVDPAOptions' = } }
> +=C2=A0 =C2=A0 'vhost-vdpa': 'NetdevVhostVDPAOptions',=
> +=C2=A0 =C2=A0 'vmnet-macos': { 'type': 'NetdevVmn= etOptions',
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0'if': 'defined(CONFIG_DARWIN)' } } }
>=C2=A0
>=C2=A0 ##
>=C2=A0 # @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_ne= tdev,
>=C2=A0 #ifdef __linux__
>=C2=A0 =C2=A0 =C2=A0 "-netdev vhost-vdpa,id=3Dstr,vhostdev=3D/path= /to/dev\n"
>=C2=A0 =C2=A0 =C2=A0 "=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 configure a vhost-vdpa network,Establish a vhost-vdpa netdev\= n"
> +#endif
> +#ifdef CONFIG_DARWIN
> +=C2=A0 =C2=A0 "-netdev vmnet-macos,id=3Dstr,mode=3Dbridged[,ifna= me=3Difname]\n"
> +=C2=A0 =C2=A0 "=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0configure a mac= OS-provided vmnet network in \"physical interface bridge\" mode\n= "
> +=C2=A0 =C2=A0 "=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0the physical in= terface to bridge with defaults to en0 if unspecified\n"
> +=C2=A0 =C2=A0 "-netdev vmnet-macos,id=3Dstr,mode=3Dhost|shared\n= "
> +=C2=A0 =C2=A0 "=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0[,dhcp_start_address=3Daddr,dhcp_end_address=3Da= ddr,dhcp_subnet_mask=3Dmask]\n"
> +=C2=A0 =C2=A0 "=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0configure a mac= OS-provided vmnet network in \"host\" or \"shared\" mod= e\n"
> +=C2=A0 =C2=A0 "=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0the DHCP config= uration will be set automatically if unspecified\n"
>=C2=A0 #endif
>=C2=A0 =C2=A0 =C2=A0 "-netdev hubport,id=3Dstr,hubid=3Dn[,netdev= =3Dnd]\n"
>=C2=A0 =C2=A0 =C2=A0 "=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 configure a hub port on the hub with ID 'n'\n", = QEMU_ARCH_ALL)
> --
> 2.24.3 (Apple Git-128)
>
>
--000000000000feea8705bca0183f--