From 46b94550ec5266b7d79423ec0021def8fdba24da Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Sat, 25 Jan 2025 22:35:25 -0400 Subject: [PATCH 1/8] 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. --- CMakeLists.txt | 3 + cmake/dep-link.cmake | 5 ++ sim_ether.c | 158 +++++++++++++++++++++++++++++++++++++++++++ sim_ether.h | 1 + 4 files changed, 167 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 120da3960..c206d4f00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/cmake/dep-link.cmake b/cmake/dep-link.cmake index 90c7707e0..512744cba 100644 --- a/cmake/dep-link.cmake +++ b/cmake/dep-link.cmake @@ -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. diff --git a/sim_ether.c b/sim_ether.c index 7faabbd3f..bf3355c57 100644 --- a/sim_ether.c +++ b/sim_ether.c @@ -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 #endif +#ifdef HAVE_VMNET_NETWORK +#include +#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; diff --git a/sim_ether.h b/sim_ether.h index e3161a9a4..fe2555329 100644 --- a/sim_ether.h +++ b/sim_ether.h @@ -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 */ From d6f6fef5d5fc73e11c5e19135dede3816c896c32 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Sat, 25 Jan 2025 23:56:19 -0400 Subject: [PATCH 2/8] Allow for shared/bridged mode, show in SHOW ETHERNET Show the allowed bridged network devices SHOW ETHERNET, as well as the shared/host networking modes. It also shows shared/host/device name for bridge in i.e. "HELP XS" output as well. Shared, bridged, and host modes have other options for configuration; i.e. isolation, IP ranges, etc. Some of these are not well documented, so should look into these. Bridged mode needs macOS 10.15. --- sim_ether.c | 55 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/sim_ether.c b/sim_ether.c index bf3355c57..eb4104ee3 100644 --- a/sim_ether.c +++ b/sim_ether.c @@ -379,6 +379,11 @@ #include #endif +// Declare earlier than other implementations +#ifdef HAVE_VMNET_NETWORK +#include +#endif + #define MAX(a,b) (((a) > (b)) ? (a) : (b)) /* Internal routine - forward declaration */ @@ -1201,11 +1206,36 @@ if (used < max) { #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"); + sprintf(list[used].name, "%s", "vmnet:shared"); + sprintf(list[used].desc, "%s", "Integrated NAT (vmnet.framework) support"); + list[used].eth_api = ETH_API_VMN; + ++used; +} +if (used < max) { + sprintf(list[used].name, "%s", "vmnet:host"); + sprintf(list[used].desc, "%s", "Integrated host-only network (vmnet.framework) support"); list[used].eth_api = ETH_API_VMN; ++used; } +// vmnet.framework has an allowed list of devices for bridging +// handy for user if we list them since ifconfig is noisy +__block int used_block; +{ + xpc_object_t bridge_list = vmnet_copy_shared_interface_list(); + + xpc_array_apply(bridge_list, ^bool(size_t index, xpc_object_t value) { + if (used_block < max) { + sprintf(list[used_block].name, "%s%s", "vmnet:", xpc_string_get_string_ptr(value)); + sprintf(list[used_block].desc, "%s", "Integrated bridged network (vmnet.framework) support"); + list[used_block].eth_api = ETH_API_VMN; + ++used_block; + } + return true; + }); + used = used_block; + + xpc_release(bridge_list); +} #endif if (used < max) { @@ -1256,10 +1286,6 @@ extern "C" { #include #endif -#ifdef HAVE_VMNET_NETWORK -#include -#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 */ @@ -2301,12 +2327,20 @@ memset(errbuf, 0, PCAP_ERRBUF_SIZE); // Because vmnet operates via callbacks, set up a semaphore to block on dispatch_semaphore_t cb_finished; + const char *devname = savname + sizeof("vmnet:") - 1; + 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); + if (0 == strcmp(devname, "shared")) { + xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_SHARED_MODE); + } else if (strlen(devname) == 0 || 0 == strcmp(devname, "host")) { + xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_HOST_MODE); + } else if (strlen(devname) > 0) { // Bridged, this is the device name + xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_BRIDGED_MODE); + xpc_dictionary_set_string(if_desc, vmnet_shared_interface_name_key, devname); + } vmn_queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); @@ -2839,6 +2873,11 @@ fprintf (st, " eth2 vde:device{:switch-port-number} (Integrated VDE su fprintf (st, " eth3 nat:{optional-nat-parameters} (Integrated NAT (SLiRP) support)\n"); #endif fprintf (st, " eth4 udp:sourceport:remotehost:remoteport (Integrated UDP bridge support)\n"); +#if defined(HAVE_VMNET_NETWORK) +fprintf (st, " eth6 vmnet:shared (Integrated NAT (vmnet.framework) support)\n"); +fprintf (st, " eth7 vmnet:host (Integrated host-only (vmnet.framework) support)\n"); +fprintf (st, " eth8 vmnet:device-name (Integrated bridged (vmnet.framework) support)\n"); +#endif fprintf (st, " sim> ATTACH %s eth0\n\n", dptr->name); fprintf (st, "or equivalently:\n\n"); fprintf (st, " sim> ATTACH %s en0\n\n", dptr->name); From b9df48974dd7d1c9b3449efe571cb270eaa8409c Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Sun, 26 Jan 2025 15:11:54 -0400 Subject: [PATCH 3/8] Check for macOS 10.15 to used bridge mode, make usage clear Bridged devices require macOS 10.15 to work correctly. Check if we're on macOS 10.15 at runtime (so a newer SDK can be used to make simh binaries that target older macOS), and at compile time (so an older SDK can be used to build simh). Also make it so vmnet: for host only is deprecated; you must explicitly provide either host, shared, or a bridged device name. --- sim_ether.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/sim_ether.c b/sim_ether.c index eb4104ee3..5ff5db5a4 100644 --- a/sim_ether.c +++ b/sim_ether.c @@ -1219,8 +1219,10 @@ if (used < max) { } // vmnet.framework has an allowed list of devices for bridging // handy for user if we list them since ifconfig is noisy -__block int used_block; -{ +#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500) +if (__builtin_available(macOS 10.15, *)) { + // avoid putting a __block marker on used on other platforms + __block int used_block; xpc_object_t bridge_list = vmnet_copy_shared_interface_list(); xpc_array_apply(bridge_list, ^bool(size_t index, xpc_object_t value) { @@ -1237,6 +1239,7 @@ __block int used_block; xpc_release(bridge_list); } #endif +#endif if (used < max) { sprintf(list[used].name, "%s", "udp:sourceport:remotehost:remoteport"); @@ -2317,8 +2320,8 @@ 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)) { +#if defined(HAVE_VMNET_NETWORK) xpc_object_t if_desc; dispatch_queue_t vmn_queue; interface_ref vmn_interface; @@ -2330,16 +2333,28 @@ memset(errbuf, 0, PCAP_ERRBUF_SIZE); const char *devname = savname + sizeof("vmnet:") - 1; 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 if (0 == strcmp(devname, "shared")) { xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_SHARED_MODE); - } else if (strlen(devname) == 0 || 0 == strcmp(devname, "host")) { + } else if (0 == strcmp(devname, "host")) { xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_HOST_MODE); +#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500) } else if (strlen(devname) > 0) { // Bridged, this is the device name - xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_BRIDGED_MODE); - xpc_dictionary_set_string(if_desc, vmnet_shared_interface_name_key, devname); + if (__builtin_available(macOS 10.15, *)) { + xpc_dictionary_set_uint64(if_desc, vmnet_operation_mode_key, VMNET_BRIDGED_MODE); + xpc_dictionary_set_string(if_desc, vmnet_shared_interface_name_key, devname); + } else { + xpc_release(if_desc); + return sim_messagef (SCPE_OPENERR, "Eth: You must be using macOS 10.15 or newer for bridged devices\n"); + } +#endif + } else { + if (__builtin_available(macOS 10.15, *)) { + xpc_release(if_desc); + return sim_messagef (SCPE_OPENERR, "Eth: You must pick either shared, host, or an allowed bridge device\n"); + } else { + xpc_release(if_desc); + return sim_messagef (SCPE_OPENERR, "Eth: You must pick either shared or host\n"); + } } vmn_queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); @@ -2362,8 +2377,10 @@ memset(errbuf, 0, PCAP_ERRBUF_SIZE); *eth_api = ETH_API_VMN; *handle = (void *)vmn_interface; /* Flag used to indicated open */ return SCPE_OK; - } +#else + return sim_messagef (SCPE_OPENERR, "Eth: No support for vmnet devices\n"); #endif + } if (0 == strncmp("tap:", savname, 4)) { int tun = -1; /* TUN/TAP Socket */ From 3c901048a762b0a75c980a0233273501e04ddca3 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Sun, 26 Jan 2025 15:51:17 -0400 Subject: [PATCH 4/8] Defensive checks for vmnet.framework Require targeting macOS 10.10, in case the user builds with an older SDK or sets the target version to an older release. This shouldn't be a problem with modern macOS SDKs, but users using or targeting older macOS should have a better message available rather than a compile message. The check is ugly, but it should work. A similar check is done for block support; this should only be a problem with users using GCC instead of clang, which is default on macOS. Also add a message for vmnet support when printing the build config. --- cmake/dep-link.cmake | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/cmake/dep-link.cmake b/cmake/dep-link.cmake index 512744cba..0dec7bcf6 100644 --- a/cmake/dep-link.cmake +++ b/cmake/dep-link.cmake @@ -352,8 +352,33 @@ if (WITH_NETWORK) endif (WITH_SLIRP) if (WITH_VMNET AND APPLE) + # CMAKE_OSX_DEPLOYMENT_TARGET is attractive, but not set by default. + # See what we're using, either by default or what the user has set. + check_c_source_compiles( + " + #include + #if TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED < 101000 + #error macOS too old + #endif + int main(int argc, char **argv) { return 0; } + " TARGETING_MACOS_10_10) + if (NOT TARGETING_MACOS_10_10) + message(FATAL_ERROR "vmnet.framework requires targeting macOS 10.10 or newer") + endif() + + # vmnet requires blocks for its callback parameter, even in vanilla C. + # This is only supported in clang, not by GCC. It's default in clang, + # but we should be clear about it. Do a feature instead of compiler + # check anyways though, in case GCC does add it eventually. + check_c_compiler_flag(-fblocks HAVE_C_BLOCKS) + if (NOT HAVE_C_BLOCKS) + message(FATAL_ERROR "vmnet.framework requires blocks support in the C compiler") + endif() + target_compile_options(simh_network INTERFACE -fblocks) + target_link_libraries(simh_network INTERFACE "-framework vmnet") target_compile_definitions(simh_network INTERFACE HAVE_VMNET_NETWORK) + list(APPEND NETWORK_PKG_STATUS "macOS vmnet.framework") endif(WITH_VMNET AND APPLE) ## Finally, set the network runtime From ae1243307f6110e7d3c6a68396f82d41df84cfcf Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Sun, 26 Jan 2025 16:13:23 -0400 Subject: [PATCH 5/8] Move vmnet below the definition of MAX to avoid redefinition --- sim_ether.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sim_ether.c b/sim_ether.c index 5ff5db5a4..3832c696c 100644 --- a/sim_ether.c +++ b/sim_ether.c @@ -379,13 +379,13 @@ #include #endif +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + // Declare earlier than other implementations #ifdef HAVE_VMNET_NETWORK #include #endif -#define MAX(a,b) (((a) > (b)) ? (a) : (b)) - /* Internal routine - forward declaration */ static int _eth_get_system_id (char *buf, size_t buf_size); static void eth_get_nic_hw_addr(ETH_DEV* dev, const char *devname, int set_on); From c892afdb7b5fe880835f0552bebb86caf5ad11e8 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Sun, 26 Jan 2025 16:16:59 -0400 Subject: [PATCH 6/8] vmnet.framework with legacy makefiles I have not put as many checks as the CMake version has. Those may get unwieldy. --- makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/makefile b/makefile index 79755eb24..f22c3bc2c 100644 --- a/makefile +++ b/makefile @@ -1059,6 +1059,13 @@ ifeq (${WIN32},) #*nix Environments (&& cygwin) NETWORK_CCDEFS += -DUSE_NETWORK endif endif + # XXX: Check for the target version of macOS, check for -fblocks + ifeq (Darwin,$(OSTYPE)) + # Provide support for macOS vmnet.framework (requires 10.10) + NETWORK_CCDEFS += -fblocks -DHAVE_VMNET_NETWORK + NETWORK_LAN_FEATURES += vmnet.framework + NETWORK_LDFLAGS += -framework vmnet + endif ifeq (slirp,$(shell if ${TEST} -e slirp_glue/sim_slirp.c; then echo slirp; fi)) NETWORK_CCDEFS += -Islirp -Islirp_glue -Islirp_glue/qemu -DHAVE_SLIRP_NETWORK -DUSE_SIMH_SLIRP_DEBUG slirp/*.c slirp_glue/*.c NETWORK_LAN_FEATURES += NAT(SLiRP) From 9b198a1ca866f1397620e9542ed192508fcbdec8 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Sun, 26 Jan 2025 16:44:37 -0400 Subject: [PATCH 7/8] Initial pass at documenting vmnet usage --- 0readme_ethernet.txt | 73 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/0readme_ethernet.txt b/0readme_ethernet.txt index 62357a7ca..0d8910c77 100644 --- a/0readme_ethernet.txt +++ b/0readme_ethernet.txt @@ -213,6 +213,79 @@ Note: As mentioned above, NAT networking is specifically capable of providing packets. +------------------------------------------------------------------------------- +Another alternative to pcap and tun/tap on macOS is using the vmnet framework. +The vmnet framework provides simh a way to provide emulated systems networking, +without requiring any additional software. Because tun/tap support requires +third-party kernel extensions (which is heavily discouraged by Apple), this +provides an equivalent that doesn't require reducing OS security. + +vmnet provides Layer 2 networking, so things like DECnet and MOP will work +properly, even across multiple systems. It can be used to either bridge to a +physical network, or provide a virtual network for systems, including NAT. + +simh must be running as root. (Running without root is possible if simh is a +notarized application. You're very unlikely to be building simh like this, so +de facto it needs root.) vmnet.framework requires macOS 10.10; macOS 10.15 is +required for bridged networking. + +When attaching an ethernet device, you have three options available: + + - "vmnet:host": This provides "host-only" networking. It allows the system to + talk to the host Mac, as well as other emulated systems on the host-only VM + network. + - "vmnet:shared": This extends the host-only network to add NAT, using the + host as a gateway. + - "vmnet:": This bridges vmnet to a physical network. You must + specify a valid network device to use. + + You can use SHOW ETHERNET in SCP to see what devices are allowed to be + bridged; these are likely your Ethernet and Wi-Fi devices. + + sim> show eth + ETH devices: + eth0 vmnet:en0 + eth1 vmnet:en7 + eth2 udp:sourceport:remotehost:remoteport + sim> attach xq vmnet:en0 + + You can verify with i.e. "ifconfig" to see if this is the correct interface + to bridge to. + +When using the host and shared modes, the host system create an IPv4 and v6 +subnet for the emulated systems. macOS will provide DHCP and other discovery +protocols on the virtual network as well. To see what IP and subnet the host +has claimed, you can use "ifconfig" to display it; macOS will bind its IP to +the bridgeN device, with the simh instance having its own vmenetN device joined +to that bridgeN device: + +vmenet0: flags=8963 mtu 1500 + options=60 + ether a6:94:63:7e:f3:04 + media: autoselect + status: active +bridge100: flags=8a63 mtu 1500 + options=3 + ether b2:f1:d8:65:66:64 + inet 192.168.67.1 netmask 0xffffff00 broadcast 192.168.67.255 + inet6 fe80::b0f1:d8ff:fe65:6664%bridge100 prefixlen 64 scopeid 0x23 + inet6 fd91:d62b:63cd:ec3d:4ad:c40c:d0b8:5826 prefixlen 64 autoconf secured + Configuration: + id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0 + maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200 + root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0 + ipfilter disabled flags 0x0 + member: vmenet0 flags=3 + ifmaxaddr 0 port 34 priority 0 path cost 0 + nd6 options=201 + media: autoselect + status: active + +Currently, settings like the subnet ranges nor port forwarding are controllable +via simh. For finer grained control over how systems are addressed, you can use +a bridged network instead of a host-only or shared network. These settings are +provided by macOS, so a future version of simh may be able to adopt these. + ------------------------------------------------------------------------------- Windows notes: From 8bb71241ddb596dbb8f88da1b5ae7ce55ae1882c Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Mon, 27 Jan 2025 17:30:05 -0400 Subject: [PATCH 8/8] Keep track of generated vmnet MAC addr The vmnet interface created for a simh instance has its own MAC address. It doesn't seem too useful to for simh itself (you can just use whatever MAC address and send/receive frames with that), but it does make referencing it in other places (i.e. SHOW ETHERNET vs. ifconfig) easier. The generated MAC address is provided at interface creation time; while we could look it up earlier like with pcap, this seems more efficient. --- sim_ether.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sim_ether.c b/sim_ether.c index 3832c696c..089ced2e0 100644 --- a/sim_ether.c +++ b/sim_ether.c @@ -1805,6 +1805,10 @@ return tool; static void eth_get_nic_hw_addr(ETH_DEV* dev, const char *devname, int set_on) { + // we set this at interface creation time in open_port for vmnet + if (dev->eth_api == ETH_API_VMN) { + return; + } memset(&dev->host_nic_phy_hw_addr, 0, sizeof(dev->host_nic_phy_hw_addr)); dev->have_host_nic_phy_addr = 0; if (dev->eth_api != ETH_API_PCAP) @@ -2361,9 +2365,27 @@ memset(errbuf, 0, PCAP_ERRBUF_SIZE); cb_finished = dispatch_semaphore_create(0); + // Set the MAC address + __block ETH_DEV *eth_dev = (ETH_DEV*)opaque; + vmn_interface = vmnet_start_interface(if_desc, vmn_queue, ^(vmnet_return_t status, xpc_object_t params){ vmn_status = status; + if (vmn_status == VMNET_SUCCESS) { + // Scan like eth_scan_mac but simplified (only one format) + const char *mac_string = xpc_dictionary_get_string(params, vmnet_mac_address_key); + int a[6]; + if (6 == sscanf(mac_string, "%x:%x:%x:%x:%x:%x", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5])) { + eth_dev->have_host_nic_phy_addr = 1; + eth_dev->host_nic_phy_hw_addr[0] = a[0]; + eth_dev->host_nic_phy_hw_addr[1] = a[1]; + eth_dev->host_nic_phy_hw_addr[2] = a[2]; + eth_dev->host_nic_phy_hw_addr[3] = a[3]; + eth_dev->host_nic_phy_hw_addr[4] = a[4]; + eth_dev->host_nic_phy_hw_addr[5] = a[5]; + } + } + dispatch_semaphore_signal(cb_finished); }); dispatch_semaphore_wait(cb_finished, DISPATCH_TIME_FOREVER);