diff options
Diffstat (limited to 'ext/tap-mac/tuntap/src/tap/tap.cc')
-rw-r--r-- | ext/tap-mac/tuntap/src/tap/tap.cc | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/ext/tap-mac/tuntap/src/tap/tap.cc b/ext/tap-mac/tuntap/src/tap/tap.cc index 149f1e71..b348a85e 100644 --- a/ext/tap-mac/tuntap/src/tap/tap.cc +++ b/ext/tap-mac/tuntap/src/tap/tap.cc @@ -38,6 +38,8 @@ extern "C" { #include <sys/random.h> #include <sys/kern_event.h> +#include <mach/thread_policy.h> + #include <net/if_types.h> #include <net/if_arp.h> #include <net/if_dl.h> @@ -89,10 +91,25 @@ struct ifmediareq32 { #define SIOCGIFMEDIA32 _IOWR('i', 56, struct ifmediareq32) /* get net media */ #define SIOCGIFMEDIA64 _IOWR('i', 56, struct ifmediareq64) /* get net media (64-bit) */ +/* thread_policy_set is exported in Mach.kext, but commented in mach/thread_policy.h in the + * Kernel.Framework headers (why?). Add a local declaration to work around that. + */ +extern "C" { +kern_return_t thread_policy_set( + thread_t thread, + thread_policy_flavor_t flavor, + thread_policy_t policy_info, + mach_msg_type_number_t count); +} static unsigned char ETHER_BROADCAST_ADDR[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* members */ +tap_interface::tap_interface() { + bzero(attached_protos, sizeof(attached_protos)); + input_thread = THREAD_NULL; +} + bool tap_interface::initialize(unsigned short major, unsigned short unit) { @@ -166,6 +183,30 @@ tap_interface::initialize_interface() */ bpfattach(ifp, DLT_EN10MB, ifnet_hdrlen(ifp)); + /* Inject an empty packet to trigger the input thread calling demux(), which will unblock + * thread_sync_lock. This is part of a hack to avoid a kernel crash on re-attaching + * interfaces, see comment in shutdown_interface for more information. + */ + mbuf_t empty_mbuf; + mbuf_gethdr(MBUF_WAITOK, MBUF_TYPE_DATA, &empty_mbuf); + if (empty_mbuf != NULL) { + mbuf_pkthdr_setrcvif(empty_mbuf, ifp); + mbuf_pkthdr_setlen(empty_mbuf, 0); + mbuf_pkthdr_setheader(empty_mbuf, mbuf_data(empty_mbuf)); + mbuf_set_csum_performed(empty_mbuf, 0, 0); + if (ifnet_input(ifp, empty_mbuf, NULL) == 0) { + auto_lock l(&thread_sync_lock); + for (int i = 0; i < 100 && input_thread == THREAD_NULL; ++i) { + dprintf("input thread not found, waiting...\n"); + thread_sync_lock.sleep(&input_thread, 10000000); + } + } else { + mbuf_freem(empty_mbuf); + } + } + if (input_thread == THREAD_NULL) + dprintf("Failed to determine input thread!\n"); + return 0; } @@ -186,6 +227,36 @@ tap_interface::shutdown_interface() cleanup_interface(); unregister_interface(); + + /* There's a race condition in the kernel that may cause crashes when quickly re-attaching + * interfaces. The crash happens when the interface gets re-attached before the input thread + * for the interface managed to terminate, in which case an assert on the input_waiting flag + * to be clear triggers in ifnet_attach. The bug is really that there's no synchronization + * for terminating the input thread. To work around this, the following code does add the + * missing synchronization to wait for the input thread to terminate. Of course, threading + * primitives available to kexts are few, and I'm not aware of a way to wait for a thread to + * terminate. Hence, the code calls thread_policy_set (passing bogus parameters) in a loop, + * until it returns KERN_TERMINATED. Since this is all rather fragile, there's an upper + * limit on the loop iteratations we're willing to make, so this terminates eventually even + * if things change on the kernel side eventually. + */ + if (input_thread != THREAD_NULL) { + dprintf("Waiting for input thread...\n"); + kern_return_t result = 0; + for (int i = 0; i < 100; ++i) { + result = thread_policy_set(input_thread, -1, NULL, 0); + dprintf("thread_policy_set result: %d\n", result); + if (result == KERN_TERMINATED) { + dprintf("Input thread terminated.\n"); + thread_deallocate(input_thread); + input_thread = THREAD_NULL; + break; + } + + auto_lock l(&thread_sync_lock); + thread_sync_lock.sleep(&input_thread, 10000000); + } + } } errno_t @@ -263,6 +334,16 @@ tap_interface::if_demux(mbuf_t m, char *header, protocol_family_t *proto) dprintf("tap: if_demux\n"); + /* Make note of what input thread this interface is running on. This is part of a hack to + * avoid a crash on re-attaching interfaces, see comment in shutdown_interface for details. + */ + if (input_thread == THREAD_NULL) { + auto_lock l(&thread_sync_lock); + input_thread = current_thread(); + thread_reference(input_thread); + thread_sync_lock.wakeup(&input_thread); + } + /* size check */ if (mbuf_len(m) < sizeof(struct ether_header)) return ENOENT; |