cd /news/developer-tools/usb-c-lan-mac-to-windows-instruction… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-46187] src=gist.github.com β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

usb_c_lan_mac_to_windows_instruction_by_claude.md

A developer used Claude Code to create a solution for connecting a Windows laptop to a MacBook via a USB-C cable as a LAN link, enabling IP-based communication for tools like TeamViewer. The solution involves running a TinyCore Linux VM in VirtualBox with a patched cdc_ncm driver to handle Apple's non-standard USB NCM device. The developer documented several pitfalls, including avoiding usbipd bind and xHCI controllers, and provided a step-by-step instruction set.

read33 min views5 publishedJun 1, 2026

Human text follows...

Hi. In this gist I'd like to share a solution for connecting a Windows laptop to a Macbook into LAN via a 5$ usb-c cable (you can ping from either to either, and, for example, connect from one to another in team viewer via LAN IP). This solution was produced by Claude Code after 2 days of trial and error. Supposedly, you should be able to make a prompt to Claude Code to use this file to set up the usb-c LAN connection and it will hopefully implement the solution much faster than it did for me.

On the Macbook side I did not have to do much, just enabled Internet Sharing with Wi-Fi as source and ticked all other checkboxes for the destinations. I also ran a couple ip-related commands there, most important one being the sudo route add -net 192.168.2.0/24 -interface bridge100

, see the instruction below.

On windows I think it was some rocket science stuff, the bot was installing linux on virtualbox, writing some driver patches, etc... Not sure how much did it leave in the end though, will need to read through that AI slop below.

You can see the results of this instruction here: https://www.youtube.com/watch?v=VtNlKl0ABmE

AI-generated text follows...

Goal: Make a USB-C cable between a MacBook and a Windows laptop act as a LAN link, so both machines can reach each other by IP β€” for TeamViewer, games, file sharing, etc.

Tested on: macOS Sequoia 15.7.2 + Windows 11, TinyCore Linux CorePure64 14.0 (kernel 6.1.2-tinycore64), VirtualBox 7.2.8.

Why it's non-trivial: Apple's USB NCM device (VID 05AC, PID 1905) is non-standard β€” it has no interrupt IN endpoint and no IAD descriptor. Windows' built-in NCM drivers (UsbNcm.sys

, UsbNcmUm.sys

) both fail with NTSTATUS 0xC0000483

. The only driver that works is Linux's cdc_ncm

, patched. The solution runs a tiny Linux VM (TinyCore) in VirtualBox that acts as a transparent router between the USB link and Windows.

These are the footguns discovered over multiple days of debugging. Read before touching anything.

1. DO NOT use usbipd bind on the Apple NCM device. usbipd-win installs WinUSB as the driver and issues a USB

SET_CONFIGURATION

reset. Apple's NCM device responds by entering a hard error state (0000:0002 Unknown USB Device (Device Descriptor Request Failed)

). Recovery requires toggling Internet Sharing on the Mac. usbipd-win's VBoxUSBMon.sys

also registers as a global USB filter at the kernel level and will break USB tethering for all other devices (phones, etc.) β€” see charging/tethering section below.1b. DO NOT use Disable-PnpDevice on the Apple NCM device (or its interfaces).

Disable-PnpDevice

issues a USB reset equivalent to usbipd's SET_CONFIGURATION

β€” the Apple NCM device enters the same hard error state and completely disappears from the Windows USB bus. Recovery requires a physical cable replug. When dealing with VBoxUSBMon stale state, only use: VM poweroff β†’ net stop VBoxUSBMon /y

β†’ net start VBoxUSBMon

β†’ VM start. Never touch the PnP device directly.2. DO NOT use xHCI USB controller in VirtualBox. xHCI causes VBoxSVC

to fail silently: the device shows as Captured

in list usbhost

but is never attached to the VM. VBox.log fills with ConsoleWrap::detachUSBDevice

errors every ~111 seconds. Use OHCI + EHCI only.

3. DO NOT plug the USB-C cable before the VM is running with modules loaded. If the cable is already plugged in when the VM starts, VBoxUSBMon may enter a bad state. Always start the VM first, wait for boot, then plug.

4. DO NOT enable proxy_arp=1 on usb0. This causes the VM to answer ALL ARP requests on the USB link, including 169.254.x.x link-local addresses, producing a flood of gratuitous ARPs and ~50% packet drops. Set proxy_arp=0

on usb0 and use an explicit pub ARP entry for 192.168.2.200

only.

5. DO NOT try to add en6 to bridge100 on macOS 15 Sequoia. en6

(the Apple USB NCM interface) has options=400<CHANNEL_IO>

. Running ifconfig bridge100 addm en6

returns EINVAL

. Internet Sharing cannot manage this interface via bridge100 on macOS 15. The v2 architecture (described below) works around this by assigning the IP directly to en6 with IS disabled.

6. DO NOT use nc -l -p 5555 -e sh in TinyCore. BusyBox

nc

does not support -e

. Use dropbear SSH instead (installed via tce-load -wi dropbear

).7. DO NOT use bare nc -l -p 9999 > /file as a file receiver via keyboard injection. BusyBox

nc

keeps stdin open after the TCP client disconnects. All subsequent keyboard-injected commands get silently swallowed into nc's stdin. Always use nc -l -p 9999 < /dev/null > /file

.8. DO NOT use rm without -f in keyboard-injected commands. TinyCore's BusyBox

rm

is aliased to rm -i

and will prompt interactively, blocking all subsequent keyboard-injected commands. Always use rm -f

.9. DO NOT set ExtraData VBoxInternal/Devices/usb-ohci/0/LUN#0/Config/LogLevel in VirtualBox 7.2.8. This causes a

VERR_CFGM_CONFIG_UNKNOWN_VALUE

crash at VM startup. Remove it if present.11. DO NOT use CHANNEL_IO flag to identify the NCM interface in the watchdog.

CHANNEL_IO

(options=400

) also appears on Wi-Fi (en0) and possibly other interfaces β€” it is NOT unique to the Apple USB NCM device. Using it as the identifier caused the watchdog to call ifconfig en0 down/up

in a loop, breaking Mac internet. Always use mtu 8178

as the NCM identifier (combined with en*

prefix to exclude anpi*

).10. KNOWN SIDE EFFECTS of this setup:

Charging: VirtualBox's exclusive USB capture may break USB-C Power Delivery negotiation on initial VM start. If the MacBook stops charging, simply replug the USB-C cable β€” charging resumes and the LAN stays up.Phone tethering: Ifusbipd-win

was ever installed on Windows, itsVBoxUSBMon.sys

may be registered as a global USB filter and will intercept all USB devices including Android phone tethering. Uninstall usbipd-win to restore phone tethering.

12. DO NOT work on the VM console while UsbcLanWatchdog is running and USB-C ping is broken. The Windows watchdog monitors

192.168.2.1

every 15 seconds. After enough consecutive failures it escalates to a full VM restart (level 3). If you are doing manual VM work ( modules, editing scripts, testing) and the link is currently broken, the watchdog will restart the VM under you. Stop it first:

Stop-ScheduledTask -TaskName "UsbcLanWatchdog"
Start-ScheduledTask -TaskName "UsbcLanWatchdog"

13. DO NOT use double quotes in keyboardputstring commands. VirtualBox

keyboardputstring

silently drops "

characters on this setup. Any command containing double quotes is silently mangled (the quotes vanish, breaking the command). Always use single-quoted shell forms instead:

KbSend 'printf "PermitRootLogin yes\n" | sudo tee /etc/sshd_config'
KbSend "sudo sh -c 'echo PermitRootLogin yes > /etc/sshd_config'"
MacBook (en6: 192.168.2.1, Internet Sharing DISABLED)
    |
    | USB-C cable β†’ Apple USB NCM device (VID 05AC, PID 1905)
    | NOTE: en6 has CHANNEL_IO flag β†’ cannot be added to bridge100 (macOS 15)
    |
TinyCore Linux VM  [usb0: 192.168.2.3 (static), eth1: 192.168.56.x (DHCP)]
    |  ip_forward + proxy_arp(eth1 only) + pub ARP for 192.168.2.200 + host route
    |
    | VirtualBox Host-Only adapter
    |
Windows (192.168.56.1 primary, + 192.168.2.200 secondary)

Mac sees Windows at 192.168.2.200

. Windows sees Mac at 192.168.2.1

. The VM is invisible to both sides.

Key architecture change from v1: In macOS 15, en6

(the Apple USB NCM interface) cannot be bridged. The v1 approach of enabling Internet Sharing over bridge100 does NOT work. Instead:

  • Internet Sharing is disabled(NAT.Enabled=0 in nat.plist) 192.168.2.1

is assigneddirectly to en6 by the watchdog daemon- The VM uses a static IP on usb0 (DHCP from Mac's bootpd requires bridge100, which doesn't work)

Address Interface Who assigns it Notes
192.168.2.1
en6 (Mac) watchdog daemon Mac's LAN IP, assigned directly to en6
192.168.2.3
usb0 (VM) ncm_setup.sh (static) VM's USB side IP
192.168.56.1
Host-Only (Windows) VirtualBox Host Network Manager Windows side of Host-Only
192.168.56.x
eth1 (VM) VirtualBox DHCP VM's Host-Only IP (e.g. .101 or .102)
192.168.2.200
Host-Only (Windows) Manual (PowerShell) Windows' LAN IP β€” use this from Mac
  • MacBook running macOS Sequoia 15.x (this guide is specific to Sequoia; IS approach differs on older versions)
  • Windows laptop (the machine running VirtualBox)
  • USB-C cable connecting the two machines directly VirtualBox 7.2.8+installed on WindowsTinyCore Linux CorePure64 14.0 ISO- Internet access on both machines during setup

In macOS 15 Sequoia, we assign the IP directly to en6 rather than using Internet Sharing, because en6 has the CHANNEL_IO

flag and cannot be added to bridge100.

With the USB-C cable plugged in (and Internet Sharing previously configured), run:

ifconfig -a | grep -B2 'mtu 8178'

Look for an en*

interface (NOT anpi*

) with mtu 8178

. This is the Apple USB NCM interface β€” it should be en6

on most setups. Note the name.

Internet Sharing must be OFF. Edit the nat.plist to disable it:

plutil -p /Library/Preferences/SystemConfiguration/com.apple.nat.plist | grep -A2 NAT

sudo plutil -replace NAT.Enabled -bool false /Library/Preferences/SystemConfiguration/com.apple.nat.plist

plutil -p /Library/Preferences/SystemConfiguration/com.apple.nat.plist | grep Enabled

Also turn off Internet Sharing in System Settings β†’ General β†’ Sharing (toggle off) if it was on.

sudo ifconfig en6 192.168.2.1 netmask 255.255.255.0

Verify:

ifconfig en6 | grep inet

macOS connected routes are always RTF_IFSCOPE

β€” they are invisible to general routing lookups from user processes. Without this, ping 192.168.2.200

will use your Wi-Fi IP as the source and go nowhere.

sudo route add -net 192.168.2.0/24 -interface en6

Verify it's working:

route -n get 192.168.2.200

The watchdog runs every 20 seconds and:

  • Finds the active NCM interface ( en*

with mtu 8178 β€” NOTanpi*

which is a sibling interface with the same mtu) - Assigns 192.168.2.1

to it if missing - Adds the non-scoped route if wrong or missing

  • Flushes stale ARP for 192.168.2.200
sudo tee /usr/local/bin/usbc_lan_watchdog.sh > /dev/null << 'WATCHDOG'
#!/bin/sh

find_ncm_iface() {
    for iface in $(ifconfig -l); do
        case "$iface" in en*) ;; *) continue ;; esac
        mtu=$(ifconfig "$iface" 2>/dev/null | awk '/mtu /{print $NF}')
        st=$(ifconfig "$iface" 2>/dev/null | awk '/status:/{print $2}')
        if [ "$mtu" = "8178" ] && [ "$st" = "active" ]; then
            echo "$iface"
            return
        fi
    done
}

ensure_ncm_ip() {
    iface="$1"
    cur=$(ifconfig "$iface" 2>/dev/null | awk '/inet /{print $2}')
    if [ "$cur" != "192.168.2.1" ]; then
        ifconfig "$iface" 192.168.2.1 netmask 255.255.255.0 2>/dev/null
    fi
}

ensure_route() {
    iface="$1"
    cur=$(route -n get 192.168.2.0 2>/dev/null | awk '/interface:/{print $2}')
    if [ "$cur" != "$iface" ]; then
        route delete -net 192.168.2.0/24 2>/dev/null
        route add -net 192.168.2.0/24 -interface "$iface" 2>/dev/null
    fi
}

ensure_arp() {
    arp -d 192.168.2.200 2>/dev/null
}

IFACE=$(find_ncm_iface)
if [ -n "$IFACE" ]; then
    ensure_ncm_ip "$IFACE"
    ensure_route "$IFACE"
    ensure_arp
fi
WATCHDOG
sudo chmod +x /usr/local/bin/usbc_lan_watchdog.sh

Install the launchd plist. Important: use scp

or a file transfer rather than heredoc-over-SSH β€” XML quotes get stripped in heredoc. Write to a local file:

sudo tee /Library/LaunchDaemons/net.klesun.usbc_route.plist > /dev/null << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>net.klesun.usbc_route</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>/usr/local/bin/usbc_lan_watchdog.sh</string>
    </array>
    <key>StartInterval</key>
    <integer>20</integer>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/var/log/usbc_route.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/usbc_route.log</string>
</dict>
</plist>
PLIST

sudo launchctl bootstrap system /Library/LaunchDaemons/net.klesun.usbc_route.plist

Verify the daemon is running:

sudo launchctl print system/net.klesun.usbc_route

Open VirtualBox Manager β†’ File β†’ Host Network Manager (or Tools β†’ Network):

  • Click Create to add a new Host-Only adapter - Set IPv4 Address: 192.168.56.1

, Mask:255.255.255.0

  • Under DHCP Server tab: enable it, set:- Server Address: 192.168.56.100

  • Lower/Upper bound: 192.168.56.101

–192.168.56.200

  • Server Address:

Create a new VM named AppleNCM:

  • Type: Linux, Version:** Other Linux (64-bit)** - RAM: 256 MB - No virtual hard disk (TinyCore can run in RAM; for persistence add a small disk β€” see Persistence section) Storage: attachCorePure64-14.0.iso

as IDE optical driveNetwork:- Adapter 1: NAT(for internet access during setup) - Adapter 2: Host-Only Adapter→ select the adapter from step 2a

  • Adapter 1: USB: enable** OHCI + EHCIcontrollers β€” DO NOT enable xHCI**(causes VBoxSVC attach failures)- Add USB Device Filter: Vendor ID 05ac

, Product ID1905

  • Add USB Device Filter: Vendor ID

To set USB controllers via VBoxManage (VM must be stopped):

$vbm = "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
& $vbm modifyvm AppleNCM --usbohci on --usbehci on --usbxhci off

Do notadd anyVBoxInternal/Devices/usb-ohci/0/LUN#0/Config/LogLevel

ExtraData β€” it causes aVERR_CFGM_CONFIG_UNKNOWN_VALUE

crash in VirtualBox 7.2.8.

If you deleted a previous VM or had attachment failures, VBoxSVC may retain stale "held device" references. Symptom: list usbhost

shows the Apple device as Captured

but the VM never sees it; VBox.log shows ConsoleWrap::detachUSBDevice

errors starting in the first 2 minutes.

Fix β€” kill and restart VBoxSVC:

$vbm = "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
& $vbm controlvm AppleNCM poweroff 2>$null
Start-Sleep -Seconds 3
taskkill /IM VBoxSVC.exe /F 2>$null
Start-Sleep -Seconds 3
& $vbm list vms   # triggers VBoxSVC restart

After VBoxSVC restarts, verify the Apple device shows Current State: Available

before starting the VM:

& $vbm list usbhost | Select-String -Pattern "05ac|1905|State|UUID" | Select-Object -First 10

Run PowerShell as Administrator:

$hoAdapter = Get-NetAdapter | Where-Object { $_.InterfaceDescription -like "*VirtualBox Host-Only*" }
$hoAdapter | Select-Object Name, InterfaceIndex, InterfaceDescription
$idx = $hoAdapter.InterfaceIndex

New-NetIPAddress -InterfaceIndex $idx -IPAddress 192.168.2.200 -PrefixLength 24

New-NetFirewallRule -DisplayName "Allow ICMP from Mac subnet" `
    -Direction Inbound -Protocol ICMPv4 `
    -RemoteAddress "192.168.2.0/24" -Action Allow -Profile Any

Verify:

$name = (Get-NetAdapter | Where-Object { $_.InterfaceDescription -like "*VirtualBox Host-Only*" }).Name
netsh interface ip show address $name

Apple's USB NCM has no interrupt IN endpoint, so netif_carrier_on

is never called β€” the interface stays in "no carrier" state and Linux won't bring it up. This kernel module fixes that by calling netif_carrier_on("usb0")

at load time. No compiler needed β€” the ELF binary is assembled directly from bytes.

Save the following as make_carrier_fix.ps1

and run it:

$ErrorActionPreference = "Stop"

function le16([uint16]$v) { [byte[]]@([byte]($v -band 0xFF),[byte](($v -shr 8) -band 0xFF)) }
function le32([uint32]$v) { [byte[]]@([byte]($v -band 0xFF),[byte](($v -shr 8) -band 0xFF),[byte](($v -shr 16) -band 0xFF),[byte](($v -shr 24) -band 0xFF)) }
function le64([uint64]$v) {
    [byte[]]@([byte]($v -band 0xFF),[byte](($v -shr 8) -band 0xFF),[byte](($v -shr 16) -band 0xFF),[byte](($v -shr 24) -band 0xFF),
              [byte](($v -shr 32) -band 0xFF),[byte](($v -shr 40) -band 0xFF),[byte](($v -shr 48) -band 0xFF),[byte](($v -shr 56) -band 0xFF))
}
function sle64([int64]$v) { le64([System.BitConverter]::ToUInt64([System.BitConverter]::GetBytes($v),0)) }
function asc([string]$s) { [System.Text.Encoding]::ASCII.GetBytes($s) }

$buf = [System.Collections.Generic.List[byte]]::new(2000)
function A([byte[]]$b) { $buf.AddRange($b) }
function Z([int]$n) { $buf.AddRange([byte[]]::new($n)) }
function PadTo([int]$o) { while ($buf.Count -lt $o) { $buf.Add(0) } }

A ([byte[]](
    0x7F,0x45,0x4C,0x46, 0x02,0x01,0x01,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x01,0x00, 0x3E,0x00, 0x01,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,
    0x40,0x00, 0x00,0x00, 0x00,0x00,
    0x40,0x00, 0x09,0x00, 0x06,0x00
))
A ([byte[]](
    0x55, 0x48,0x89,0xE5,
    0x48,0x8D,0x3D, 0x00,0x00,0x00,0x00,
    0x48,0x8D,0x35, 0x16,0x00,0x00,0x00,
    0xE8, 0x00,0x00,0x00,0x00,
    0x48,0x85,0xC0,
    0x74,0x08,
    0x48,0x89,0xC7,
    0xE8, 0x00,0x00,0x00,0x00,
    0x31,0xC0,
    0x5D, 0xC3,
    0x75,0x73,0x62,0x30,0x00,
    0xC3
))
if ($buf.Count -ne 0x6E) { throw ".text end check failed" }
PadTo 0x70
A (asc "vermagic=6.1.2-tinycore64 SMP mod_unload "); $buf.Add(0)
A (asc "license=GPL"); $buf.Add(0)
A (asc "name=carrier_fix"); $buf.Add(0)
PadTo 0xB8
Z 24; A (asc "carrier_fix"); $buf.Add(0); Z (640 - 36)
if ($buf.Count -ne 0x338) { throw ".module end check failed" }
A (le32 0);  $buf.Add(0);    $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)
A (le32 1);  $buf.Add(0x12); $buf.Add(0); A (le16 1); A (le64 0);    A (le64 0)
A (le32 18); $buf.Add(0x12); $buf.Add(0); A (le16 1); A (le64 0x2D); A (le64 0)
A (le32 35); $buf.Add(0x10); $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)
A (le32 53); $buf.Add(0x11); $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)
A (le32 62); $buf.Add(0x10); $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)
if ($buf.Count -ne 0x3C8) { throw ".symtab end check failed" }
$buf.Add(0)
A (asc "carrier_fix_init"); $buf.Add(0)
A (asc "carrier_fix_exit"); $buf.Add(0)
A (asc "__dev_get_by_name"); $buf.Add(0)
A (asc "init_net"); $buf.Add(0)
A (asc "netif_carrier_on"); $buf.Add(0)
PadTo 0x418
$buf.Add(0)
A (asc ".text"); $buf.Add(0); A (asc ".modinfo"); $buf.Add(0)
A (asc ".gnu.linkonce.this_module"); $buf.Add(0)
A (asc ".symtab"); $buf.Add(0); A (asc ".strtab"); $buf.Add(0)
A (asc ".shstrtab"); $buf.Add(0); A (asc ".rela.text"); $buf.Add(0)
A (asc ".rela.gnu.linkonce.this_module"); $buf.Add(0)
if ($buf.Count -ne 0x486) { throw ".shstrtab end check failed" }
PadTo 0x488
A (le64 0x07); A (le64 ([uint64](([uint64]4 -shl 32) -bor 2))); A (sle64 -4)
A (le64 0x13); A (le64 ([uint64](([uint64]3 -shl 32) -bor 4))); A (sle64 -4)
A (le64 0x20); A (le64 ([uint64](([uint64]5 -shl 32) -bor 4))); A (sle64 -4)
if ($buf.Count -ne 0x4D0) { throw ".rela.text end check failed" }
A (le64 0x138); A (le64 ([uint64](([uint64]1 -shl 32) -bor 1))); A (sle64 0)
A (le64 0x270); A (le64 ([uint64](([uint64]2 -shl 32) -bor 1))); A (sle64 0)
if ($buf.Count -ne 0x500) { throw ".rela.mod end check failed" }
function Shdr([uint32]$nm,[uint32]$ty,[uint64]$fl,[uint64]$off,[uint64]$sz,[uint32]$lk,[uint32]$inf,[uint64]$al,[uint64]$es) {
    A (le32 $nm); A (le32 $ty); A (le64 $fl); A (le64 0)
    A (le64 $off); A (le64 $sz); A (le32 $lk); A (le32 $inf); A (le64 $al); A (le64 $es)
}
Shdr 0  0 0 0      0      0 0 0  0
Shdr 1  1 6 0x40   0x2E   0 0 16 0
Shdr 7  1 2 0x70   0x47   0 0 1  0
Shdr 16 1 3 0xB8   0x280  0 0 8  0
Shdr 42 2 0 0x338  0x90   5 1 8  24
Shdr 50 3 0 0x3C8  0x4F   0 0 1  0
Shdr 58 3 0 0x418  0x6E   0 0 1  0
Shdr 68 4 0 0x488  0x48   4 1 8  24
Shdr 79 4 0 0x4D0  0x30   4 3 8  24
if ($buf.Count -ne 0x740) { throw "final size check failed" }
$outPath = "$env:TEMP\carrier_fix.ko"
[System.IO.File]::WriteAllBytes($outPath, $buf.ToArray())
Write-Host "OK: $($buf.Count) bytes -> $outPath"

Output: %TEMP%\carrier_fix.ko

(1856 bytes).

If you use a different TinyCore version: thevermagic

string in the script must exactly matchuname -r

output on the VM, with a trailing space before the null. Also, the cdc_ncm.ko patch offset in Part 4e may differ.

& "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" startvm AppleNCM --type headless
Start-Sleep -Seconds 30   # wait for TinyCore to fully boot

TinyCore has no SSH by default and BusyBox nc

does not support -e sh

. We must install dropbear via keyboard injection, then SSH for all subsequent work.

Important: Use the keyboardputscancode

method for Enter (scan code 1c 9c

), not keyboardputstring

with a newline β€” newlines in keyboardputstring

are unreliable. Also, the pipe character |

in keyboardputstring

may be mangled on non-US keyboard layouts; use file redirects where possible.

$vbm = "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
function KbSend([string]$cmd) {
    & $vbm controlvm AppleNCM keyboardputstring $cmd
    & $vbm controlvm AppleNCM keyboardputscancode 1c 9c  # Enter
    Start-Sleep -Milliseconds 800
}

KbSend "sudo sh -c 'echo nameserver 10.0.2.3 > /etc/resolv.conf'"
Start-Sleep -Seconds 2

KbSend "tce-load -wi dropbear"
Start-Sleep -Seconds 30  # wait for download

KbSend "sudo mkdir -p /etc/dropbear"
KbSend "sudo /usr/local/bin/dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key"
Start-Sleep -Seconds 5
KbSend "echo tc:ncmvm | sudo chpasswd"
KbSend "sudo /usr/local/sbin/dropbear -p 22 -r /etc/dropbear/dropbear_rsa_host_key"
Start-Sleep -Seconds 3

Note: TinyCore's sudo has a restricted PATH that does NOT include/usr/local/sbin/

. Always use full paths for dropbear commands:/usr/local/sbin/dropbear

,/usr/local/bin/dropbearkey

.

Get the VM's eth1 IP:

$vmIP = (& "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" guestproperty get AppleNCM "/VirtualBox/GuestInfo/Net/1/V4/IP") -replace "Value: ",""
Write-Host "VM IP: $vmIP"

SSH into the VM (use a separate known_hosts file β€” dropbear generates a new key each reboot):

$tmpKH = "$env:TEMP\known_hosts_ncm3"
ssh -i C:\Users\safro\.ssh\id_rsa -o "UserKnownHostsFile=$tmpKH" -o StrictHostKeyChecking=no tc@$vmIP

After each VM reboot: dropbear is lost (TinyCore runs in RAM). Reinstall via keyboard injection (steps above). The known_hosts file will also have a stale key β€” delete$env:TEMP\known_hosts_ncm3

or SSH withStrictHostKeyChecking=no

.

IPv6 being active on usb0 when the NCM device appears causes a cdc_ncm TX lockup (ICMPv6 multicast β†’ URB error β†’ TX permanently stuck). Disable it now, before plugging the cable:

echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6
echo 1 > /proc/sys/net/ipv6/conf/default/disable_ipv6

Also: never run tce-load -wi iptables on this VM β€” it pulls

ipv6-netfilter

as a dependency and triggers the same lockup.

sudo modprobe usbnet
sudo modprobe cdc_ether

Apple's USB NCM has no interrupt IN endpoint, so dev->status

is NULL. The stock cdc_ncm driver dereferences it during bind()

and fails with -ENODEV

. Fix: NOP the 6-byte NULL check at file offset 0x1CEC.

cd /tmp
sudo cp /lib/modules/6.1.2-tinycore64/kernel/drivers/net/usb/cdc_ncm.ko.gz .
sudo gunzip -f cdc_ncm.ko.gz

od -A x -t x1 -j 7400 -N 16 /tmp/cdc_ncm.ko

printf '\220\220\220\220\220\220' | dd of=/tmp/cdc_ncm.ko bs=1 seek=7404 count=6 conv=notrunc

od -A x -t x1 -j 7400 -N 16 /tmp/cdc_ncm.ko

gzip -9 -c /tmp/cdc_ncm.ko > /tmp/cdc_ncm.ko.gz
sudo cp /tmp/cdc_ncm.ko.gz /lib/modules/6.1.2-tinycore64/kernel/drivers/net/usb/cdc_ncm.ko.gz

Load the patched module:

sudo modprobe cdc_ncm

If using a different kernel: Find the patch offset by searching for theTEST RAX,RAX

(bytes48 85 c0

) pattern near a call tousb_alloc_urb

in the ncm_bind function of the disassembled .ko.

In the VM SSH session, start a file receiver. Must include < /dev/null β€” otherwise BusyBox nc keeps stdin open after transfer and swallows subsequent commands:

nc -l -p 9999 < /dev/null > /tmp/carrier_fix.ko

In a new Windows PowerShell window, send the file:

$bytes = [System.IO.File]::ReadAllBytes("$env:TEMP\carrier_fix.ko")
$s = [System.Net.Sockets.TcpClient]::new("192.168.56.101", 9999)
$s.GetStream().Write($bytes, 0, $bytes.Length)
$s.Close()

Verify:

ls -la /tmp/carrier_fix.ko   # must be exactly 1856 bytes

Note:scp

does NOT work on TinyCore β€” it requires/usr/libexec/sftp-server

which TinyCore doesn't provide. Use the TCP transfer above.

mkdir -p /home/tc/ncm_modules
cp /tmp/carrier_fix.ko /home/tc/ncm_modules/
cp /tmp/cdc_ncm.ko /home/tc/ncm_modules/    # keep the uncompressed .ko β€” insmod needs it

cat > /home/tc/ncm_modules/ncm_setup.sh << 'EOF'
#!/bin/sh
sleep 1
if ! /sbin/ifconfig usb0 > /dev/null 2>&1; then
    /sbin/rmmod cdc_mbim 2>/dev/null; true
    /sbin/rmmod cdc_ncm 2>/dev/null; true
    /sbin/rmmod cdc_ether 2>/dev/null; true
    /sbin/modprobe cdc_ether 2>/dev/null; true
    /sbin/insmod /home/tc/ncm_modules/cdc_ncm.ko 2>/dev/null; true
    sleep 1
    echo 1-1:1.0 > /sys/bus/usb/drivers/cdc_ncm/bind 2>/dev/null; true
    echo 1-1:1.1 > /sys/bus/usb/drivers/cdc_ncm/bind 2>/dev/null; true
    sleep 1
fi
/sbin/rmmod carrier_fix 2>/dev/null; true
/sbin/insmod /home/tc/ncm_modules/carrier_fix.ko 2>/dev/null; true
/sbin/ifconfig usb0 192.168.2.3 netmask 255.255.255.0 broadcast 192.168.2.255
/sbin/route del default gw 192.168.2.1 2>/dev/null; true
/sbin/ifconfig usb1 0.0.0.0 2>/dev/null; true
/sbin/sysctl -w net.ipv4.conf.usb0.proxy_arp=0 >/dev/null
/sbin/sysctl -w net.ipv4.conf.eth1.proxy_arp=1 >/dev/null
/sbin/sysctl -w net.ipv4.ip_forward=1 >/dev/null
/sbin/route add -host 192.168.2.200 gw 192.168.56.1 dev eth1 2>/dev/null; true
/sbin/arp -s 192.168.2.200 00:00:00:00:00:00 pub -i usb0 2>/dev/null; true
echo setup done
EOF
chmod +x /home/tc/ncm_modules/ncm_setup.sh

Key points about ncm_setup.sh:

usb0 absence detection: At cold boot, the stock cdc_ncm.ko loads from/lib/modules/

and fails to bind (Apple NCM has no interrupt endpoint), leaving usb0 absent. The script detects this and swaps in the patched version from/home/tc/ncm_modules/cdc_ncm.ko

, then manually binds interface1-1:1.0

.- Uses static IP192.168.2.3

on usb0 (not DHCP β€” Mac's bootpd requires bridge100 which doesn't work) - Removes the default gateway to avoid routing internet through the USB link

  • Sets proxy_arp=0

on usb0 (NOT 1) β€” general proxy_arp on usb0 causes a 169.254.x.x ARP flood - Uses a published ARP entry ( arp -s ... pub -i usb0

) to answer ARP for192.168.2.200

specifically β€” without this, Mac cannot ARP-resolve Windows via the USB link

TinyCore executes /opt/bootlocal.sh

on every boot. Create it to auto-start sshd and the USB link watchdog:

sudo tee /opt/bootlocal.sh > /dev/null << 'EOF'
#!/bin/sh
sudo /usr/local/sbin/sshd 2>/dev/null
sh /home/tc/ncm_modules/ncm_watch2.sh &
EOF
sudo chmod +x /opt/bootlocal.sh

Note: Always write system files viasudo tee

from an SSH session β€” never via PowerShell keyboard injection. PowerShell writes UTF-8 BOM + CRLF line endings which break shell script shebangs (#!/bin/sh: not found

). Also never use double quotes inkeyboardputstring

commands β€” they are silently dropped (Warning #13).

This script runs as a background loop. It detects when usb0 loses its IP, goes down, or the 192.168.2.200

host route disappears (which happens at cold boot due to an eth1 DHCP race), and re-runs ncm_setup.sh:

cat > /home/tc/ncm_modules/ncm_watch2.sh << 'EOF'
#!/bin/sh
LAST=0
while true; do
  NOW=$(date +%s)
  DIFF=$((NOW - LAST))
  if [ $DIFF -gt 5 ]; then
    STATE=$(cat /sys/class/net/usb0/operstate 2>/dev/null)
    IP=$(/sbin/ifconfig usb0 2>/dev/null | grep inet)
    ROUTE=$(route -n 2>/dev/null | grep 192.168.2.200)
    if [ "$STATE" != up ] || [ -z "$IP" ] || [ -z "$ROUTE" ]; then
      echo $(date): usb0 not ready or route missing, running ncm_setup.sh
      /home/tc/ncm_modules/ncm_setup.sh >> /tmp/ncm_setup.log 2>&1
      LAST=$NOW
    fi
  fi
  sleep 2
done
EOF
chmod +x /home/tc/ncm_modules/ncm_watch2.sh

The ROUTE check is critical: at cold boot, ncm_setup.sh's route add -host 192.168.2.200 gw 192.168.56.1 dev eth1

fails silently if eth1's DHCP hasn't completed yet. ncm_watch2.sh catches this on the next tick once eth1 is ready. Check the log with tail -f /tmp/ncm_setup.log

.

VM must be running and modules loaded before plugging the cable.

Plug the USB-C cable between Mac and Windows. VBoxUSBMon captures the Apple device before Windows' driver can touch it.

If the cable was already plugged in before starting the VM, unplug and replug it now.

Watch for usb0 to appear:

ifconfig -a | grep usb

If usb0 appears but stays down:

echo 1 > /proc/sys/net/ipv6/conf/usb0/disable_ipv6
sudo ifconfig usb0 up
sudo /home/tc/ncm_modules/ncm_setup.sh

Verify the interface is fully up:

ifconfig usb0

From the VM:

ping -c 3 192.168.2.1    # Mac
ping -c 3 192.168.2.200  # Windows

From Windows PowerShell:

ping -n 3 192.168.2.1    # Mac (~5ms expected)

From Mac Terminal:

ping -c 3 192.168.2.200  # Windows (~5ms expected)

TeamViewer: On Mac β†’ Remote Control β†’ enter 192.168.2.200

.

Expected results when fully working:

  • Windows β†’ Mac (192.168.2.1): ~5ms
  • VM β†’ Mac: ~4ms
  • VM β†’ Windows (192.168.2.200): ~1ms
  • Mac β†’ VM (192.168.2.3): ~5ms
  • Mac β†’ Windows (192.168.2.200): ~5ms

TinyCore Linux runs entirely in RAM. By default, everything is lost on reboot.

After each VM reboot, you must:

  • Reinstall SSH (Part 4b)
  • Re-disable IPv6 (Part 4c)
  • Load modules (Part 4d: modprobe usbnet; modprobe cdc_ether; modprobe cdc_ncm

) - Re-transfer carrier_fix.ko and cdc_ncm.ko (Parts 4f–4g)

  • Re-run ncm_setup.sh (Part 5a)

In VirtualBox Manager, add a small (128MB+) VDI disk to the AppleNCM VM (Storage β†’ Add Hard Disk). The disk will appear as /dev/sda

inside TinyCore.

sudo mkfs.ext2 /dev/sda
sudo mkdir -p /mnt/sda
sudo mount /dev/sda /mnt/sda
sudo mkdir -p /mnt/sda/tce /mnt/sda/boot
sudo cp /boot/vmlinuz64 /mnt/sda/boot/
sudo cp /boot/corepure64.gz /mnt/sda/boot/
sudo tce-load -wi syslinux
sudo extlinux --install /mnt/sda

Create /mnt/sda/syslinux.cfg

:

sudo tee /mnt/sda/syslinux.cfg << 'EOF'
DEFAULT tc
PROMPT 0
TIMEOUT 30
LABEL tc
  KERNEL /boot/vmlinuz64
  INITRD /boot/corepure64.gz
  APPEND loglevel=3 tce=sda restore=sda/tce
EOF

CRITICAL:restore=

MUST besda/tce

, NOTsda

. TinyCore'sfiletool.sh

parses this as DEVICE/FULLPATH β€”restore=sda

looks for/mnt/sda/mydata.tgz

(wrong), whilerestore=sda/tce

correctly finds/mnt/sda/tce/mydata.tgz

.

Once all scripts and keys are in place, build the persistence archive:

cd /
sudo tar czf /mnt/sda/tce/mydata.tgz \
  home/tc/ncm_modules/ \
  home/tc/.ssh/ \
  usr/local/etc/ssh/ \
  opt/bootlocal.sh

Important: Run from/

using paths without a leading slash (home/tc/...

not/home/tc/...

). TinyCore's restore extracts the archive relative to/

β€” a leading slash produces double-root paths and nothing gets restored.

Rebuild this archive whenever you modify any of the persisted files.

File/Setting Survives? Notes
/home/tc/ncm_modules/ (scripts + .ko files)
Yes
in mydata.tgz
/home/tc/.ssh/authorized_keys
Yes
in mydata.tgz
/usr/local/etc/ssh/ (sshd config + host keys)
Yes
in mydata.tgz
/opt/bootlocal.sh
Yes
in mydata.tgz; starts sshd + ncm_watch2.sh at boot
patched /lib/modules/.../cdc_ncm.ko.gz
No
stock version reloads; ncm_setup.sh detects this and loads /home/tc/ncm_modules/cdc_ncm.ko instead
ip_forward , proxy_arp , IPv6 sysctl
No
ncm_watch2.sh re-runs ncm_setup.sh after boot to restore them

Save as C:\Users\safro\Desktop\usbc_watchdog.ps1

. Monitors 192.168.2.1

every 15s with three-level escalation:

ncm_setupβ€” re-runs/home/tc/ncm_modules/ncm_setup.sh

via SSH (fixes lost IP/route config)USB replugβ€” VBoxManage detach (12s wait) + reattach + ncm_setup (fixes USB driver soft-lock)** VM restart**β€” fullpoweroff

+startvm

  • ncm_setup (fixes NCM NTB negotiation failure caused by cable jiggle β€” the only reliable software fix for this case)
$VBM     = "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
$VM      = "AppleNCM"
$SSH_KEY = "C:\Users\safro\.ssh\id_rsa"
$SSH_KH  = "$env:TEMP\known_hosts_ncm3"
$MAC_IP  = "192.168.2.1"
$LOG     = "C:\Users\safro\Desktop\usbc_watchdog.log"

function Log($msg) {
    $line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg"
    Write-Host $line
    Add-Content -Path $LOG -Value $line
}
function Test-MacReachable {
    $result = ping -n 1 -w 2000 $MAC_IP 2>$null
    return ($result -match "TTL=")
}
function Get-VmIp {
    $v = & $VBM guestproperty get $VM "/VirtualBox/GuestInfo/Net/1/V4/IP" 2>$null
    if ($v -match "Value:\s*(\S+)") { return $matches[1] }
    return "192.168.56.102"
}
function Invoke-VmSetup($ip) {
    ssh -i $SSH_KEY -o "UserKnownHostsFile=$SSH_KH" -o StrictHostKeyChecking=no `
        -o ConnectTimeout=10 tc@$ip "sudo /home/tc/ncm_modules/ncm_setup.sh 2>&1" 2>&1
}
function Get-NcmUuid {
    $lines = (& $VBM list usbhost 2>&1) -join "`n" -split "` n"
    for ($i = 0; $i -lt $lines.Count; $i++) {
        if ($lines[$i] -match "05ac" -and ($lines[$i] -match "1905" -or
            ($i+1 -lt $lines.Count -and $lines[$i+1] -match "1905"))) {
            for ($j = [Math]::Max(0, $i-6); $j -le $i; $j++) {
                if ($lines[$j] -match "UUID:\s*(\S+)") { return $matches[1] }
            }
        }
    }
    return $null
}
function Invoke-UsbReplug {
    $uuid = Get-NcmUuid
    if (-not $uuid) { Log "USB replug: Apple NCM device not found in usbhost list"; return }
    Log "USB replug: detaching $uuid"
    & $VBM controlvm $VM usbdetach $uuid 2>&1 | Out-Null
    Start-Sleep 12
    Log "USB replug: reattaching $uuid"
    & $VBM controlvm $VM usbattach $uuid 2>&1 | Out-Null
    Start-Sleep 8
}
function Invoke-VmRestart {
    Log "VM restart: powering off $VM"
    & $VBM controlvm $VM poweroff 2>&1 | Out-Null
    Start-Sleep 5
    Log "VM restart: starting $VM headless"
    & $VBM startvm $VM --type headless 2>&1 | Out-Null
    Log "VM restart: waiting 45s for TinyCore to boot"
    Start-Sleep 45
}

Log "Watchdog started. Monitoring $MAC_IP every 15s."

$failCount = 0
while ($true) {
    if (Test-MacReachable) {
        $failCount = 0
    } else {
        $failCount++
        Log "Mac unreachable (fail #$failCount)"

        $vmIp = Get-VmIp
        Invoke-VmSetup $vmIp | ForEach-Object { Log "  vm: $_" }
        Start-Sleep 8

        if (Test-MacReachable) {
            Log "Recovered after ncm_setup"; $failCount = 0
        } else {
            Log "Still unreachable β€” USB replug"
            Invoke-UsbReplug
            $vmIp = Get-VmIp
            Invoke-VmSetup $vmIp | ForEach-Object { Log "  vm: $_" }
            Start-Sleep 10

            if (Test-MacReachable) {
                Log "Recovered after USB replug"; $failCount = 0
            } else {
                Log "Still unreachable β€” restarting VM (NCM renegotiation)"
                Invoke-VmRestart
                $vmIp = Get-VmIp
                Invoke-VmSetup $vmIp | ForEach-Object { Log "  vm: $_" }
                Start-Sleep 10

                if (Test-MacReachable) {
                    Log "Recovered after VM restart"
                } else {
                    Log "Still unreachable after VM restart β€” will retry next cycle"
                }
                $failCount = 0
            }
        }
    }
    Start-Sleep 15
}

Register as a scheduled task (run as Administrator):

$script  = "C:\Users\safro\Desktop\usbc_watchdog.ps1"
$action  = New-ScheduledTaskAction -Execute "powershell.exe" `
               -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$script`""
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 0) `
                -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName "UsbcLanWatchdog" -Action $action -Trigger $trigger `
    -Settings $settings -RunLevel Highest -Force
Start-ScheduledTask -TaskName "UsbcLanWatchdog"

Register a scheduled task so the VM starts automatically at Windows boot (60s delay lets VirtualBox services initialize first):

$vbm = "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NonInteractive -WindowStyle Hidden -Command `"Start-Sleep -Seconds 60; & '$vbm' startvm AppleNCM --type headless`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) -MultipleInstances IgnoreNew
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "StartAppleNCM_VirtualBox" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force

After Windows reboots, the full LAN comes up automatically within ~90 seconds (60s task delay + 30s TinyCore boot). No manual steps needed.

What survives a Windows reboot automatically:

  • VM start β€” StartAppleNCM_VirtualBox

scheduled task (SYSTEM, at boot) - Windows 192.168.2.200 secondary IP β€” static, persists in adapter config

  • LAN watchdog β€” UsbcLanWatchdog

scheduled task (at logon) - Mac watchdog β€” launchd daemon at /Library/LaunchDaemons/net.klesun.usbc_route.plist

(auto-starts on Mac boot if installed per Part 1e) - VirtualBox USB filter β€” saved in VM config, auto-captures on VM start

Machine Interface IP Role
Mac en6 192.168.2.1
Mac's LAN IP, assigned directly by watchdog
VM usb0 192.168.2.3
Static IP assigned by ncm_setup.sh
VM eth1 192.168.56.x
Gets IP from VirtualBox DHCP (e.g. .101 or .102)
Windows Host-Only 192.168.56.1
Host-Only primary (set in VirtualBox)
Windows Host-Only 192.168.2.200
Windows' LAN IP β€” use this from Mac

dmesg | tail -30

β€” look for USB errors or NCM bind failures- Ensure the VM USB filter has Vendor ID 05ac

/ Product ID1905

  • Ensure USB controller is OHCI+EHCI, NOT xHCI
  • Unplug and replug the cable (VM must be running)
  • Check VBoxManage list usbhost

β€” device must showCurrent State: Available

(notCaptured

stuck) before plugging. If stuck as Captured: kill VBoxSVC (see Part 2c) - Do NOT run usbipd bind

on the Apple device β€” it resets the device into an error state

  • Check carrier: cat /sys/class/net/usb0/carrier

β€” if0

, carrier_fix didn't work - Reload: sudo rmmod carrier_fix; sudo insmod /home/tc/ncm_modules/carrier_fix.ko

  • Verify the vermagic in carrier_fix.ko matches: uname -r

on VM must match the string embedded in the module

IPv6 was active when the NCM device appeared (ICMPv6 multicast β†’ URB error β†’ TX permanently stuck).

Fix:

sudo rmmod cdc_ncm
echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6
echo 1 > /proc/sys/net/ipv6/conf/default/disable_ipv6
sudo modprobe cdc_ncm

Then re-run ncm_setup.sh.

Step 1: Check Mac's source IP when pinging

sudo tcpdump -i any -n icmp

If you see 192.168.155.x > 192.168.2.200

(Wi-Fi IP as source instead of 192.168.2.1

), the non-scoped route is missing. Fix:

sudo route add -net 192.168.2.0/24 -interface en6

This is the most common failure mode β€” all macOS connected routes carry the RTF_IFSCOPE

flag and are invisible to general routing. Verify with:

route -n get 192.168.2.200

Step 2: Check en6 has the right IP

ifconfig en6 | grep inet

If not, run: sudo ifconfig en6 192.168.2.1 netmask 255.255.255.0

Step 3: Check VM routing

route -n | grep 192.168.2.200    # host route via eth1 must exist
arp -n | grep 192.168.2.200      # must show 00:00:00:00:00:00 PMP (published)
cat /proc/sys/net/ipv4/ip_forward  # must be 1
cat /proc/sys/net/ipv4/conf/eth1/proxy_arp  # must be 1

Step 4: Check Windows

Get-NetFirewallRule -DisplayName "Allow ICMP from Mac subnet"
Get-NetIPAddress | Where-Object { $_.IPAddress -eq "192.168.2.200" }

This is caused by a USB SET_CONFIGURATION

being sent while Internet Sharing is active, or by usbipd bind

.

Recovery (on Mac):

  • System Settings β†’ General β†’ Sharing β†’ Internet Sharing: toggle OFF, wait 5 seconds, toggle** ON**again - Or via Terminal:
sudo launchctl unload /System/Library/LaunchDaemons/com.apple.InternetSharing.plist
sleep 6
sudo launchctl load /System/Library/LaunchDaemons/com.apple.InternetSharing.plist

With the v2 architecture (IS disabled), this recovery step is not needed β€” simply unplug and replug the cable.

With the v2 architecture (IS disabled, direct en6 IP), disabling Wi-Fi on Mac does NOT drop the USB-C LAN β€” the setup is Wi-Fi independent. If connectivity drops when toggling Wi-Fi, the v2 architecture was not fully set up.

Symptoms: VBox.log shows repeated ConsoleWrap::detachUSBDevice: USB device ... is not attached to this machine

errors. Device shows as Captured

in list usbhost

but VM never sees usb0.

Fix: Kill VBoxSVC to flush stale state (see Part 2c). Then restart VM and replug cable.

With persistence enabled (Part 7), the OpenSSH host keys survive reboots (they are stored in /usr/local/etc/ssh/

which is in mydata.tgz). This error only occurs if mydata.tgz was rebuilt without the host keys, or if using the initial dropbear setup before persistence was configured.

Fix:

Remove-Item -Force "$env:TEMP\known_hosts_ncm3" -ErrorAction SilentlyContinue
ssh -i C:\Users\safro\.ssh\id_rsa -o "UserKnownHostsFile=$env:TEMP\known_hosts_ncm3" -o StrictHostKeyChecking=no tc@192.168.56.102

Or always SSH with -o StrictHostKeyChecking=no

.

TinyCore gets eth1 IP from VirtualBox DHCP. It may change (e.g. from .101 to .102). Always check:

& "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" guestproperty get AppleNCM "/VirtualBox/GuestInfo/Net/1/V4/IP"

macOS ARP entries expire after ~20 minutes of inactivity. The VM's pub ARP entry (arp -s 192.168.2.200 pub

) answers new ARP requests automatically. The Mac watchdog also flushes stale entries every 20 seconds.

If stuck: from VM, ping -c 1 192.168.2.1

refreshes the ARP path in both directions.

If usbipd-win

was installed at any point, its VBoxUSBMon.sys

is registered as a global USB filter at the kernel level and intercepts all USB devices β€” including Android phones. This is what broke phone tethering.

Fix: Uninstall usbipd-win. Then check Device Manager for any phones showing as "Unknown Device" β€” unplug and replug them to re-enumerate with the correct driver.

After a cold reboot, the VM's /lib/modules/

still has the stock cdc_ncm.ko.gz

. TinyCore loads it automatically, it fails to bind the Apple NCM device (no interrupt endpoint β†’ -ENODEV

), and usb0 never appears.

ncm_watch2.sh

(started by bootlocal.sh

) detects this and calls ncm_setup.sh

, which handles it: it unloads the stock module, loads the patched /home/tc/ncm_modules/cdc_ncm.ko

, and manually binds the interface. Within ~10 seconds of boot, usb0 should appear.

If it doesn't recover automatically:

tail -f /tmp/ncm_setup.log   # watch what ncm_setup.sh is doing
sudo /home/tc/ncm_modules/ncm_setup.sh

If ncm_watch2.sh

is not running (e.g. bootlocal.sh

not in mydata.tgz):

ps | grep ncm
sh /home/tc/ncm_modules/ncm_watch2.sh &

If sc.exe query vboxsup

returns STATE: 3 STOP_PENDING

, the VirtualBox kernel driver is deadlocked. No software workaround exists β€” not net stop

, not pnputil /restart-device

, not reinstalling VirtualBox, not sc delete

.

Only fix: physically restart the Windows machine.

Cause: Running the WinUSB bridge (NcmBridge.ps1

) while VBoxUSBMon

has a USB filter on the Apple NCM device creates a driver conflict. When VBoxUSBMon tries to stop while WinUSB holds the device open, the kernel driver deadlocks. Never run any direct WinUSB access to the Apple NCM device while the VM is running.

After a restart, if VBoxSup fails to start automatically:

pnputil /add-driver "C:\Program Files\Oracle\VirtualBox\drivers\vboxsup\VBoxSup.inf" /install
sc start vboxsup

VirtualBox's USB filter takes exclusive control of the USB-C device at the hardware level, which resets the USB-C Power Delivery negotiation. The Mac stops receiving power from the Windows laptop's USB-C port.

Fix: Simply replug the USB-C cable. Power Delivery re-negotiates on reconnect and charging resumes while the LAN continues to work. The VM's USB filter re-captures the device automatically via the VirtualBox USB filter rule.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/usb-c-lan-mac-to-win…] indexed:0 read:33min 2026-06-01 Β· β€”