Hello,

I'm replying to this thread to let you know that I am available to help out on 
this patch as I have been working on the same feature for a few weeks.

AD

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

Reply via email to