1
0
mirror of https://github.com/open-simh/simh.git synced 2026-01-11 23:53:30 +00:00

Initial support for vmnet.framework

On macOS, tap devices for L2 networking are not supported out of the
box. While a kext can be added to provide tap support, the kext
experience is not very good; Apple has strongly recommended against
their usage.

As a replacement that's documented and recommended, Apple introduced the
vmnet framework, intended for emulators and virtualization software
explicitly. This API requires macOS 10.10, with bridged network support
coming in macOS 10.15.

This introduces basic support for vmnet.framework in SIMH. I've tested
it by booting an emulated MicroVAX 3800 from an emulated InfoServer 150,
where it was able to reach OpenVMS 7.3 standalone BACKUP.
This commit is contained in:
Calvin Buckley 2025-01-25 22:35:25 -04:00 committed by Paul Koning
parent ad3e744951
commit 361ef76dbc
4 changed files with 167 additions and 0 deletions

View File

@ -199,6 +199,9 @@ option(WITH_VDE
option(WITH_TAP
"Enable (=1)/disable (=0) TAP/TUN device network support. (def: enabled)"
TRUE)
option(WITH_VMNET
"Enable (=1)/disable (=0) macOS vmnet.framework network support. (def: enabled)"
TRUE)
option(WITH_VIDEO
"Enable (=1)/disable (=0) simulator display and graphics support (def: enabled)"
TRUE)

View File

@ -351,6 +351,11 @@ if (WITH_NETWORK)
list(APPEND NETWORK_PKG_STATUS "NAT(SLiRP)")
endif (WITH_SLIRP)
if (WITH_VMNET AND APPLE)
target_link_libraries(simh_network INTERFACE "-framework vmnet")
target_compile_definitions(simh_network INTERFACE HAVE_VMNET_NETWORK)
endif(WITH_VMNET AND APPLE)
## Finally, set the network runtime
if (NOT network_runtime)
## Default to USE_SHARED... USE_NETWORK is deprecated.

View File

@ -1005,6 +1005,9 @@ const char *eth_capabilities(void)
#endif
#if defined (HAVE_SLIRP_NETWORK)
":NAT"
#endif
#if defined (HAVE_VMNET_NETWORK)
":VMNET"
#endif
":UDP";
}
@ -1196,6 +1199,14 @@ if (used < max) {
++used;
}
#endif
#ifdef HAVE_VMNET_NETWORK
if (used < max) {
sprintf(list[used].name, "%s", "vmnet:{optional-parameters}");
sprintf(list[used].desc, "%s", "Integrated vmnet.framework support");
list[used].eth_api = ETH_API_VMN;
++used;
}
#endif
if (used < max) {
sprintf(list[used].name, "%s", "udp:sourceport:remotehost:remoteport");
@ -1245,6 +1256,10 @@ extern "C" {
#include <dlfcn.h>
#endif
#ifdef HAVE_VMNET_NETWORK
#include <vmnet/vmnet.h>
#endif
#if defined(USE_SHARED) && (defined(_WIN32) || defined(SIM_HAVE_DLOPEN))
/* Dynamic DLL loading technique and modified source comes from
Etherial/WireShark capture_pcap.c */
@ -2060,6 +2075,37 @@ while (dev->handle) {
}
break;
#endif /* HAVE_VDE_NETWORK */
#ifdef HAVE_VMNET_NETWORK
case ETH_API_VMN:
{
vmnet_return_t ret;
int count = 1;
struct pcap_pkthdr header;
struct vmpktdesc pkt_desc;
struct iovec iov;
// XXX: Should be MTU returned from vmnet startup?
u_char buf[ETH_MAX_JUMBO_FRAME];
iov.iov_base = buf;
iov.iov_len = ETH_MAX_JUMBO_FRAME;
pkt_desc.vm_pkt_size = ETH_MAX_JUMBO_FRAME;
pkt_desc.vm_pkt_iov = &iov;
pkt_desc.vm_pkt_iovcnt = 1;
pkt_desc.vm_flags = 0;
ret = vmnet_read((interface_ref)dev->handle, &pkt_desc, &count);
if (ret == VMNET_SUCCESS && count > 0) {
status = 1;
header.caplen = header.len = pkt_desc.vm_pkt_size;
_eth_callback((u_char *)dev, &header, buf);
} else {
status = ret == VMNET_SUCCESS ? 0 : -1;
}
}
break;
#endif
#ifdef HAVE_SLIRP_NETWORK
case ETH_API_NAT:
sim_slirp_dispatch ((SLIRP*)dev->handle);
@ -2244,6 +2290,47 @@ if (bufsz < ETH_MAX_JUMBO_FRAME)
/* attempt to connect device */
memset(errbuf, 0, PCAP_ERRBUF_SIZE);
#if defined(HAVE_VMNET_NETWORK)
if (0 == strncmp("vmnet:", savname, 6)) {
xpc_object_t if_desc;
dispatch_queue_t vmn_queue;
interface_ref vmn_interface;
// __block is used so it can be captured in the callback
__block vmnet_return_t vmn_status;
// Because vmnet operates via callbacks, set up a semaphore to block on
dispatch_semaphore_t cb_finished;
if_desc = xpc_dictionary_create(NULL, NULL, 0);
// VMNET_HOST_MODE: Host and other guests
// VMNET_SHARED_MODE: NAT
// VMNET_BRIDGED_MODE: Requires macOS 10.15
// XXX: Support other modes.
xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_HOST_MODE);
vmn_queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
cb_finished = dispatch_semaphore_create(0);
vmn_interface = vmnet_start_interface(if_desc, vmn_queue, ^(vmnet_return_t status, xpc_object_t params){
vmn_status = status;
dispatch_semaphore_signal(cb_finished);
});
dispatch_semaphore_wait(cb_finished, DISPATCH_TIME_FOREVER);
dispatch_release(cb_finished);
xpc_release(if_desc);
if (vmn_status != VMNET_SUCCESS) {
return sim_messagef (SCPE_OPENERR, "Eth: Failed to create vmnet (vmnet_return_t: %d)\n", vmn_status);
}
*eth_api = ETH_API_VMN;
*handle = (void *)vmn_interface; /* Flag used to indicated open */
return SCPE_OK;
}
#endif
if (0 == strncmp("tap:", savname, 4)) {
int tun = -1; /* TUN/TAP Socket */
int on = 1;
@ -2646,6 +2733,15 @@ switch (eth_api) {
case ETH_API_NAT:
sim_slirp_close((SLIRP*)pcap);
break;
#endif
#ifdef HAVE_VMNET_NETWORK
case ETH_API_VMN:
{
dispatch_queue_t stop_queue;
stop_queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
vmnet_stop_interface((interface_ref)pcap, stop_queue, ^(vmnet_return_t status){});
}
break;
#endif
case ETH_API_UDP:
sim_close_sock(pcap_fd);
@ -2960,6 +3056,9 @@ switch (dev->eth_api) {
case ETH_API_NAT:
netname = "nat";
break;
case ETH_API_VMN:
netname = "vmnet";
break;
}
sprintf(msg, "%s(%s): ", where, netname);
switch (dev->eth_api) {
@ -2967,6 +3066,12 @@ switch (dev->eth_api) {
case ETH_API_PCAP:
sim_printf ("%s%s\n", msg, pcap_geterr ((pcap_t*)dev->handle));
break;
#endif
#if defined(HAVE_VMNET_NETWORK)
case ETH_API_VMN:
/* XXX: vmnet errors aren't global */
sim_printf ("%s\n", msg);
break;
#endif
default:
sim_err_sock (INVALID_SOCKET, msg);
@ -3088,6 +3193,27 @@ if ((packet->len >= ETH_MIN_PACKET) && (packet->len <= ETH_MAX_PACKET)) {
else
status = 1;
break;
#endif
#ifdef HAVE_VMNET_NETWORK
case ETH_API_VMN:
{
vmnet_return_t ret;
int count = 1;
struct vmpktdesc pkt_desc;
struct iovec iov;
iov.iov_base = packet->msg;
iov.iov_len = packet->len;
pkt_desc.vm_pkt_size = packet->len;
pkt_desc.vm_pkt_iov = &iov;
pkt_desc.vm_pkt_iovcnt = 1;
pkt_desc.vm_flags = 0;
ret = vmnet_write((interface_ref)dev->handle, &pkt_desc, &count);
status = (ret == VMNET_SUCCESS && count > 0) ? 0 : 1;
}
break;
#endif
case ETH_API_UDP:
status = (((int32)packet->len == sim_write_sock (dev->fd_handle, (char *)packet->msg, (int32)packet->len)) ? 0 : -1);
@ -3712,6 +3838,7 @@ switch (dev->eth_api) {
case ETH_API_VDE:
case ETH_API_UDP:
case ETH_API_NAT:
case ETH_API_VMN:
bpf_used = 0;
to_me = 0;
eth_packet_trace (dev, data, header->len, "received");
@ -3908,6 +4035,37 @@ do {
}
break;
#endif /* HAVE_VDE_NETWORK */
#ifdef HAVE_VMNET_NETWORK
case ETH_API_VMN:
{
vmnet_return_t ret;
int count = 1;
struct pcap_pkthdr header;
struct vmpktdesc pkt_desc;
struct iovec iov;
// XXX: Should be MTU returned from vmnet startup?
u_char buf[ETH_MAX_JUMBO_FRAME];
iov.iov_base = buf;
iov.iov_len = ETH_MAX_JUMBO_FRAME;
pkt_desc.vm_pkt_size = ETH_MAX_JUMBO_FRAME;
pkt_desc.vm_pkt_iov = &iov;
pkt_desc.vm_pkt_iovcnt = 1;
pkt_desc.vm_flags = 0;
ret = vmnet_read((interface_ref)dev->handle, &pkt_desc, &count);
if (ret == VMNET_SUCCESS && count > 0) {
status = 1;
header.caplen = header.len = pkt_desc.vm_pkt_size;
_eth_callback((u_char *)dev, &header, buf);
} else {
status = ret == VMNET_SUCCESS ? 0 : -1;
}
}
break;
#endif
case ETH_API_UDP:
if (1) {
struct pcap_pkthdr header;

View File

@ -265,6 +265,7 @@ struct eth_device {
#define ETH_API_VDE 3 /* VDE API in use */
#define ETH_API_UDP 4 /* UDP API in use */
#define ETH_API_NAT 5 /* NAT (SLiRP) API in use */
#define ETH_API_VMN 6 /* Apple vmnet.framework in use */
ETH_PCALLBACK read_callback; /* read callback function */
ETH_PCALLBACK write_callback; /* write callback function */
ETH_PACK* read_packet; /* read packet */