← Back to blog
TL;DR: You can connect a Mac to a Windows or Linux machine over a single USB-C cable and get 5+ Gbps networking. The entire internet says this can't be done. It can. I wrote a kernel module to prove it.
I wanted to connect my desktop tower to my Mac. Both have USB-C ports. Simple, right?
If you Google "USB-C networking Mac to PC," you'll find a graveyard of unanswered Stack Overflow questions, Reddit threads ending in "just use Ethernet," and forum posts from 2019 saying it's impossible. AI Overviews only increase the emphasis on this.

Spoiler: it's not impossible. It's just that nobody had written the driver yet.
But as a tinkerer, I felt, how hard can it actually be. We now have USB 4, and to get there we had USB 3, USB 3.1, USB 3.2 and the dozen weird ones in between. My Desktop seems to think it has a Gen 2 x 2. What's the harm in trying?
And so, I plugged in the cable. Windows saw something—Device Manager showed a device, but it was broken. Code 10, failed to start. No driver, no details.

Okay, let's dig deeper. I installed the absolutely incredible USB Tree View to get a real look at what the Mac was presenting.

There's a lot here. The Mac is presenting itself as a USB gadget—specifically something called CDC NCM (Network Control Model). It's essentially offering to be a network adapter.
USB CDC NCM is a standard for network-over-USB. It's how Android phones do USB tethering. It's how embedded devices expose network interfaces. Linux has supported it for years via the cdc_ncm kernel module.
When a device presents as NCM, the host should:
The Mac is presenting as NCM. So why doesn't it work? The built-in UsbNcm.sys driver in Windows tries to initialize it and fails. No error details, just "nope."
I booted into Linux to get better diagnostics, and to be able to mess with things faster.
$ lsusb
Bus 004 Device 002: ID 05ac:1905 Apple, Inc. Mac
The Mac shows up. Good. Now let's see if the driver binds:
$ dmesg | grep cdc
cdc_ncm 4-2:1.0: bind() failure
There it is. The driver sees the device, tries to bind, and fails. But why?
I dumped the full USB descriptors with lsusb -v and started digging. This is where having Claude as a collaborator became invaluable; I understand a little about drivers and low-level systems, but parsing USB descriptor dumps against spec documents and finding the root cause would have taken me months. Claude, spotted the issue in minutes.
Here's what a usual USB CDC NCM looks like:
Control interface: 1 interrupt endpoint (for link status notifications)
Data interface: 2 bulk endpoints (IN/OUT for actual data)
Here's what the Mac presents:
Control interface: 0 endpoints
Data interface: 2 bulk endpoints
The interrupt endpoint is missing. Linux's cdc_ncm driver checks for it during initialization, doesn't find it, and refuses to bind. This is likely the same issue on windows.
The interrupt endpoint isn't necessary for the data path. It's only used for link status notifications (cable plugged/unplugged, speed changes). You can poll for that instead. In our scenario we are connecting a Mac's thunderbolt enabled port to a usb 3.1 gen 2x2. The link speed will never change.
Fortunately, we don't need to rewrite the NCM stack—we just need to tell Linux: "If it's an Apple device, skip the interrupt endpoint check and poll for link status instead." I built it as an out-of-tree kernel module.
The core logic:
// Detect Apple's NCM implementation
static const struct usb_device_id apple_ncm_quirk[] = {
{ USB_DEVICE(0x05ac, 0x1905) }, // Apple Mac
{ }
};
// In bind(), skip interrupt endpoint validation for Apple devices
if (usb_match_id(intf, apple_ncm_quirk)) {
ctx->flags |= CDC_NCM_FLAG_NO_INTERRUPT;
// Initialize polling fallback instead
}
The polling implementation uses schedule_delayed_work() for now at 500ms intervals—plenty fast for link status, zero impact on throughput since the bulk endpoints handle all the actual data. Though I wonder if polling is even required for future.
$ sudo rmmod cdc_ncm
$ sudo insmod ./cdc_ncm.ko
# Plug in Mac via USB-C
$ ip link
5: enx660a69900d8e: <BROADCAST,MULTICAST,UP> mtu 1500 ...
A network interface appears. Assign IPs on both sides, and you've got connectivity.
iperf3 shows ~5 Gbps throughput over a USB4 cable. That's faster than most people's home networks, over a single cable that also carries power.

Why only 5? It seems that I need to do more work to make the link speed leverage the full SuperSpeed Plus (10), but that's likely for a next post.
I want to be clear about how this came together. I'm not a kernel developer by trade, I'm a product architect. It's been a decade since I have done serious driver or low level development. Working with Claude compressed what would have been weeks of work into days:
lsusb -v output and getting the missing endpoint identified in minutes, not hours of spec-readingThe AI didn't write the driver for me — I still had to understand every line, test it, debug it, and own it. But it removed the friction of ramping up on unfamiliar territory. That's the real value.
The same fix could theoretically be applied to Windows by forking Microsoft's open-source NCM driver and adding the Apple quirk. Well... I did it. Check out the Windows driver port where Grok helped me navigate Windows driver development.