|
@@ -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;
|