{"slug": "usb-c-lan-mac-to-windows-instruction-by-claude-md", "title": "usb_c_lan_mac_to_windows_instruction_by_claude.md", "summary": "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.", "body_md": "Human text follows...\n\nHi.\nIn this gist I'd like to share a solution for connecting a Windows laptop to a Macbook into LAN via a 5$\nusb-c cable (you can ping from either to either, and, for example, connect from one to another in\n[team viewer via LAN IP](https://www.teamviewer.com/en/global/support/knowledge-base/teamviewer-remote/remote-control/use-teamviewer-remote-in-lan/)).\nThis solution was produced by [Claude Code](https://code.claude.com/docs/en/terminal-guide) after 2 days of trial and error.\nSupposedly, 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.\n\nOn 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.\nI also ran a couple ip-related commands there, most important one being the `sudo route add -net 192.168.2.0/24 -interface bridge100`\n\n, see the instruction below.\n\nOn 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.\n\nYou can see the results of this instruction here: [https://www.youtube.com/watch?v=VtNlKl0ABmE](https://www.youtube.com/watch?v=VtNlKl0ABmE)\n\nAI-generated text follows...\n\n**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.\n\n**Tested on**: macOS Sequoia 15.7.2 + Windows 11, TinyCore Linux CorePure64 14.0 (kernel 6.1.2-tinycore64), VirtualBox 7.2.8.\n\n**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`\n\n, `UsbNcmUm.sys`\n\n) both fail with NTSTATUS `0xC0000483`\n\n. The only driver that works is Linux's `cdc_ncm`\n\n, patched. The solution runs a tiny Linux VM (TinyCore) in VirtualBox that acts as a transparent router between the USB link and Windows.\n\nThese are the footguns discovered over multiple days of debugging. Read before touching anything.\n\n**1. DO NOT use usbipd bind on the Apple NCM device.**\nusbipd-win installs WinUSB as the driver and issues a USB\n\n`SET_CONFIGURATION`\n\nreset. Apple's NCM device responds by entering a hard error state (`0000:0002 Unknown USB Device (Device Descriptor Request Failed)`\n\n). Recovery requires toggling Internet Sharing on the Mac. usbipd-win's `VBoxUSBMon.sys`\n\nalso 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).**\n\n`Disable-PnpDevice`\n\nissues a USB reset equivalent to usbipd's `SET_CONFIGURATION`\n\n— 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`\n\n→ `net start VBoxUSBMon`\n\n→ VM start. Never touch the PnP device directly.**2. DO NOT use xHCI USB controller in VirtualBox.**\nxHCI causes `VBoxSVC`\n\nto fail silently: the device shows as `Captured`\n\nin `list usbhost`\n\nbut is never attached to the VM. VBox.log fills with `ConsoleWrap::detachUSBDevice`\n\nerrors every ~111 seconds. Use OHCI + EHCI only.\n\n**3. DO NOT plug the USB-C cable before the VM is running with modules loaded.**\nIf 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.\n\n**4. DO NOT enable proxy_arp=1 on usb0.**\nThis 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`\n\non usb0 and use an explicit pub ARP entry for `192.168.2.200`\n\nonly.\n\n**5. DO NOT try to add en6 to bridge100 on macOS 15 Sequoia.**\n`en6`\n\n(the Apple USB NCM interface) has `options=400<CHANNEL_IO>`\n\n. Running `ifconfig bridge100 addm en6`\n\nreturns `EINVAL`\n\n. 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.\n\n**6. DO NOT use nc -l -p 5555 -e sh in TinyCore.**\nBusyBox\n\n`nc`\n\ndoes not support `-e`\n\n. Use dropbear SSH instead (installed via `tce-load -wi dropbear`\n\n).**7. DO NOT use bare nc -l -p 9999 > /file as a file receiver via keyboard injection.**\nBusyBox\n\n`nc`\n\nkeeps 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`\n\n.**8. DO NOT use rm without -f in keyboard-injected commands.**\nTinyCore's BusyBox\n\n`rm`\n\nis aliased to `rm -i`\n\nand will prompt interactively, blocking all subsequent keyboard-injected commands. Always use `rm -f`\n\n.**9. DO NOT set ExtraData VBoxInternal/Devices/usb-ohci/0/LUN#0/Config/LogLevel in VirtualBox 7.2.8.**\nThis causes a\n\n`VERR_CFGM_CONFIG_UNKNOWN_VALUE`\n\ncrash at VM startup. Remove it if present.**11. DO NOT use CHANNEL_IO flag to identify the NCM interface in the watchdog.**\n\n`CHANNEL_IO`\n\n(`options=400`\n\n) 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`\n\nin a loop, breaking Mac internet. Always use `mtu 8178`\n\nas the NCM identifier (combined with `en*`\n\nprefix to exclude `anpi*`\n\n).**10. KNOWN SIDE EFFECTS of this setup:**\n\n**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**: If`usbipd-win`\n\nwas ever installed on Windows, its`VBoxUSBMon.sys`\n\nmay be registered as a global USB filter and will intercept all USB devices including Android phone tethering. Uninstall usbipd-win to restore phone tethering.\n\n**12. DO NOT work on the VM console while UsbcLanWatchdog is running and USB-C ping is broken.**\nThe Windows watchdog monitors\n\n`192.168.2.1`\n\nevery 15 seconds. After enough consecutive failures it escalates to a full VM restart (level 3). If you are doing manual VM work (loading modules, editing scripts, testing) and the link is currently broken, the watchdog will restart the VM under you. Stop it first:\n\n```\nStop-ScheduledTask -TaskName \"UsbcLanWatchdog\"\n# ... do your work ...\nStart-ScheduledTask -TaskName \"UsbcLanWatchdog\"\n```\n\n**13. DO NOT use double quotes in keyboardputstring commands.**\nVirtualBox\n\n`keyboardputstring`\n\nsilently drops `\"`\n\ncharacters on this setup. Any command containing double quotes is silently mangled (the quotes vanish, breaking the command). Always use single-quoted shell forms instead:\n\n```\n# WRONG — double quotes will be stripped:\nKbSend 'printf \"PermitRootLogin yes\\n\" | sudo tee /etc/sshd_config'\n# RIGHT — single-quoted sh -c:\nKbSend \"sudo sh -c 'echo PermitRootLogin yes > /etc/sshd_config'\"\nMacBook (en6: 192.168.2.1, Internet Sharing DISABLED)\n    |\n    | USB-C cable → Apple USB NCM device (VID 05AC, PID 1905)\n    | NOTE: en6 has CHANNEL_IO flag → cannot be added to bridge100 (macOS 15)\n    |\nTinyCore Linux VM  [usb0: 192.168.2.3 (static), eth1: 192.168.56.x (DHCP)]\n    |  ip_forward + proxy_arp(eth1 only) + pub ARP for 192.168.2.200 + host route\n    |\n    | VirtualBox Host-Only adapter\n    |\nWindows (192.168.56.1 primary, + 192.168.2.200 secondary)\n```\n\nMac sees Windows at `192.168.2.200`\n\n. Windows sees Mac at `192.168.2.1`\n\n. The VM is invisible to both sides.\n\n**Key architecture change from v1**: In macOS 15, `en6`\n\n(the Apple USB NCM interface) cannot be bridged. The v1 approach of enabling Internet Sharing over bridge100 does NOT work. Instead:\n\n- Internet Sharing is\n**disabled**(NAT.Enabled=0 in nat.plist) `192.168.2.1`\n\nis assigned**directly to en6** by the watchdog daemon- The VM uses a\n**static IP** on usb0 (DHCP from Mac's bootpd requires bridge100, which doesn't work)\n\n| Address | Interface | Who assigns it | Notes |\n|---|---|---|---|\n`192.168.2.1` |\nen6 (Mac) | watchdog daemon | Mac's LAN IP, assigned directly to en6 |\n`192.168.2.3` |\nusb0 (VM) | ncm_setup.sh (static) | VM's USB side IP |\n`192.168.56.1` |\nHost-Only (Windows) | VirtualBox Host Network Manager | Windows side of Host-Only |\n`192.168.56.x` |\neth1 (VM) | VirtualBox DHCP | VM's Host-Only IP (e.g. .101 or .102) |\n`192.168.2.200` |\nHost-Only (Windows) | Manual (PowerShell) | Windows' LAN IP — use this from Mac |\n\n- MacBook running macOS Sequoia 15.x (this guide is specific to Sequoia; IS approach differs on older versions)\n- Windows laptop (the machine running VirtualBox)\n- USB-C cable connecting the two machines directly\n[VirtualBox 7.2.8+](https://www.virtualbox.org/wiki/Downloads)installed on Windows[TinyCore Linux CorePure64 14.0 ISO](http://tinycorelinux.net/14.x/x86_64/release/CorePure64-14.0.iso)- Internet access on both machines during setup\n\nIn macOS 15 Sequoia, we assign the IP directly to en6 rather than using Internet Sharing, because en6 has the `CHANNEL_IO`\n\nflag and cannot be added to bridge100.\n\nWith the USB-C cable plugged in (and Internet Sharing previously configured), run:\n\n```\nifconfig -a | grep -B2 'mtu 8178'\n```\n\nLook for an `en*`\n\ninterface (NOT `anpi*`\n\n) with `mtu 8178`\n\n. This is the Apple USB NCM interface — it should be `en6`\n\non most setups. Note the name.\n\nInternet Sharing must be OFF. Edit the nat.plist to disable it:\n\n```\n# Check current state\nplutil -p /Library/Preferences/SystemConfiguration/com.apple.nat.plist | grep -A2 NAT\n\n# Disable IS (set NAT.Enabled = 0)\nsudo plutil -replace NAT.Enabled -bool false /Library/Preferences/SystemConfiguration/com.apple.nat.plist\n\n# Verify\nplutil -p /Library/Preferences/SystemConfiguration/com.apple.nat.plist | grep Enabled\n```\n\nAlso turn off Internet Sharing in System Settings → General → Sharing (toggle off) if it was on.\n\n```\nsudo ifconfig en6 192.168.2.1 netmask 255.255.255.0\n```\n\nVerify:\n\n```\nifconfig en6 | grep inet\n# Should show: inet 192.168.2.1 netmask 0xffffff00 broadcast 192.168.2.255\n```\n\nmacOS connected routes are always `RTF_IFSCOPE`\n\n— they are invisible to general routing lookups from user processes. Without this, `ping 192.168.2.200`\n\nwill use your Wi-Fi IP as the source and go nowhere.\n\n```\nsudo route add -net 192.168.2.0/24 -interface en6\n```\n\nVerify it's working:\n\n```\nroute -n get 192.168.2.200\n# Must show: interface: en6  (NOT en0/Wi-Fi)\n```\n\nThe watchdog runs every 20 seconds and:\n\n- Finds the active NCM interface (\n`en*`\n\nwith mtu 8178 — NOT`anpi*`\n\nwhich is a sibling interface with the same mtu) - Assigns\n`192.168.2.1`\n\nto it if missing - Adds the non-scoped route if wrong or missing\n- Flushes stale ARP for\n`192.168.2.200`\n\n``` bash\nsudo tee /usr/local/bin/usbc_lan_watchdog.sh > /dev/null << 'WATCHDOG'\n#!/bin/sh\n\nfind_ncm_iface() {\n    # Find en* interface with mtu 8178 (Apple USB NCM)\n    # Must filter to en* prefix — anpi2/anpi* are sibling interfaces also with mtu 8178 but wrong\n    for iface in $(ifconfig -l); do\n        case \"$iface\" in en*) ;; *) continue ;; esac\n        mtu=$(ifconfig \"$iface\" 2>/dev/null | awk '/mtu /{print $NF}')\n        st=$(ifconfig \"$iface\" 2>/dev/null | awk '/status:/{print $2}')\n        if [ \"$mtu\" = \"8178\" ] && [ \"$st\" = \"active\" ]; then\n            echo \"$iface\"\n            return\n        fi\n    done\n}\n\nensure_ncm_ip() {\n    iface=\"$1\"\n    cur=$(ifconfig \"$iface\" 2>/dev/null | awk '/inet /{print $2}')\n    if [ \"$cur\" != \"192.168.2.1\" ]; then\n        ifconfig \"$iface\" 192.168.2.1 netmask 255.255.255.0 2>/dev/null\n    fi\n}\n\nensure_route() {\n    iface=\"$1\"\n    cur=$(route -n get 192.168.2.0 2>/dev/null | awk '/interface:/{print $2}')\n    if [ \"$cur\" != \"$iface\" ]; then\n        route delete -net 192.168.2.0/24 2>/dev/null\n        route add -net 192.168.2.0/24 -interface \"$iface\" 2>/dev/null\n    fi\n}\n\nensure_arp() {\n    # Flush stale ARP cache for Windows IP to avoid NUD FAILED state\n    arp -d 192.168.2.200 2>/dev/null\n}\n\nIFACE=$(find_ncm_iface)\nif [ -n \"$IFACE\" ]; then\n    ensure_ncm_ip \"$IFACE\"\n    ensure_route \"$IFACE\"\n    ensure_arp\nfi\nWATCHDOG\nsudo chmod +x /usr/local/bin/usbc_lan_watchdog.sh\n```\n\nInstall the launchd plist. **Important**: use `scp`\n\nor a file transfer rather than heredoc-over-SSH — XML quotes get stripped in heredoc. Write to a local file:\n\n```\nsudo tee /Library/LaunchDaemons/net.klesun.usbc_route.plist > /dev/null << 'PLIST'\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>Label</key>\n    <string>net.klesun.usbc_route</string>\n    <key>ProgramArguments</key>\n    <array>\n        <string>/bin/sh</string>\n        <string>/usr/local/bin/usbc_lan_watchdog.sh</string>\n    </array>\n    <key>StartInterval</key>\n    <integer>20</integer>\n    <key>RunAtLoad</key>\n    <true/>\n    <key>StandardOutPath</key>\n    <string>/var/log/usbc_route.log</string>\n    <key>StandardErrorPath</key>\n    <string>/var/log/usbc_route.log</string>\n</dict>\n</plist>\nPLIST\n\nsudo launchctl bootstrap system /Library/LaunchDaemons/net.klesun.usbc_route.plist\n```\n\nVerify the daemon is running:\n\n```\nsudo launchctl print system/net.klesun.usbc_route\n# Should show state = running\n```\n\nOpen **VirtualBox Manager → File → Host Network Manager** (or Tools → Network):\n\n- Click\n**Create** to add a new Host-Only adapter - Set IPv4 Address:\n`192.168.56.1`\n\n, Mask:`255.255.255.0`\n\n- Under\n**DHCP Server** tab: enable it, set:- Server Address:\n`192.168.56.100`\n\n- Lower/Upper bound:\n`192.168.56.101`\n\n–`192.168.56.200`\n\n- Server Address:\n\nCreate a new VM named **AppleNCM**:\n\n- Type:\n**Linux**, Version:** Other Linux (64-bit)** - RAM:\n**256 MB** - No virtual hard disk (TinyCore can run in RAM; for persistence add a small disk — see Persistence section)\n**Storage**: attach`CorePure64-14.0.iso`\n\nas IDE optical drive**Network**:- Adapter 1:\n**NAT**(for internet access during setup) - Adapter 2:\n**Host-Only Adapter**→ select the adapter from step 2a\n\n- Adapter 1:\n**USB**: enable** OHCI + EHCI**controllers —** DO NOT enable xHCI**(causes VBoxSVC attach failures)- Add USB Device Filter: Vendor ID\n`05ac`\n\n, Product ID`1905`\n\n- Add USB Device Filter: Vendor ID\n\nTo set USB controllers via VBoxManage (VM must be stopped):\n\n``` php\n$vbm = \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\"\n& $vbm modifyvm AppleNCM --usbohci on --usbehci on --usbxhci off\n```\n\nDo notadd any`VBoxInternal/Devices/usb-ohci/0/LUN#0/Config/LogLevel`\n\nExtraData — it causes a`VERR_CFGM_CONFIG_UNKNOWN_VALUE`\n\ncrash in VirtualBox 7.2.8.\n\nIf you deleted a previous VM or had attachment failures, VBoxSVC may retain stale \"held device\" references. Symptom: `list usbhost`\n\nshows the Apple device as `Captured`\n\nbut the VM never sees it; VBox.log shows `ConsoleWrap::detachUSBDevice`\n\nerrors starting in the first 2 minutes.\n\nFix — kill and restart VBoxSVC:\n\n``` php\n$vbm = \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\"\n& $vbm controlvm AppleNCM poweroff 2>$null\nStart-Sleep -Seconds 3\ntaskkill /IM VBoxSVC.exe /F 2>$null\nStart-Sleep -Seconds 3\n& $vbm list vms   # triggers VBoxSVC restart\n```\n\nAfter VBoxSVC restarts, verify the Apple device shows `Current State: Available`\n\nbefore starting the VM:\n\n```\n& $vbm list usbhost | Select-String -Pattern \"05ac|1905|State|UUID\" | Select-Object -First 10\n```\n\nRun **PowerShell as Administrator**:\n\n``` php\n# Find the VirtualBox Host-Only adapter\n$hoAdapter = Get-NetAdapter | Where-Object { $_.InterfaceDescription -like \"*VirtualBox Host-Only*\" }\n$hoAdapter | Select-Object Name, InterfaceIndex, InterfaceDescription\n$idx = $hoAdapter.InterfaceIndex\n\n# Add 192.168.2.200 as a secondary IP — this becomes Windows' LAN IP visible to Mac\nNew-NetIPAddress -InterfaceIndex $idx -IPAddress 192.168.2.200 -PrefixLength 24\n\n# Allow ICMP from Mac's subnet (needed for ping; TeamViewer works without this)\nNew-NetFirewallRule -DisplayName \"Allow ICMP from Mac subnet\" `\n    -Direction Inbound -Protocol ICMPv4 `\n    -RemoteAddress \"192.168.2.0/24\" -Action Allow -Profile Any\n```\n\nVerify:\n\n``` php\n$name = (Get-NetAdapter | Where-Object { $_.InterfaceDescription -like \"*VirtualBox Host-Only*\" }).Name\nnetsh interface ip show address $name\n# Should show both 192.168.56.1 and 192.168.2.200\n```\n\nApple's USB NCM has no interrupt IN endpoint, so `netif_carrier_on`\n\nis 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\")`\n\nat load time. No compiler needed — the ELF binary is assembled directly from bytes.\n\nSave the following as `make_carrier_fix.ps1`\n\nand run it:\n\n```\n# make_carrier_fix.ps1\n# Produces %TEMP%\\carrier_fix.ko — 1856 bytes, built for kernel 6.1.2-tinycore64\n$ErrorActionPreference = \"Stop\"\n\nfunction le16([uint16]$v) { [byte[]]@([byte]($v -band 0xFF),[byte](($v -shr 8) -band 0xFF)) }\nfunction 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)) }\nfunction le64([uint64]$v) {\n    [byte[]]@([byte]($v -band 0xFF),[byte](($v -shr 8) -band 0xFF),[byte](($v -shr 16) -band 0xFF),[byte](($v -shr 24) -band 0xFF),\n              [byte](($v -shr 32) -band 0xFF),[byte](($v -shr 40) -band 0xFF),[byte](($v -shr 48) -band 0xFF),[byte](($v -shr 56) -band 0xFF))\n}\nfunction sle64([int64]$v) { le64([System.BitConverter]::ToUInt64([System.BitConverter]::GetBytes($v),0)) }\nfunction asc([string]$s) { [System.Text.Encoding]::ASCII.GetBytes($s) }\n\n$buf = [System.Collections.Generic.List[byte]]::new(2000)\nfunction A([byte[]]$b) { $buf.AddRange($b) }\nfunction Z([int]$n) { $buf.AddRange([byte[]]::new($n)) }\nfunction PadTo([int]$o) { while ($buf.Count -lt $o) { $buf.Add(0) } }\n\nA ([byte[]](\n    0x7F,0x45,0x4C,0x46, 0x02,0x01,0x01,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\n    0x01,0x00, 0x3E,0x00, 0x01,0x00,0x00,0x00,\n    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\n    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\n    0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,\n    0x00,0x00,0x00,0x00,\n    0x40,0x00, 0x00,0x00, 0x00,0x00,\n    0x40,0x00, 0x09,0x00, 0x06,0x00\n))\nA ([byte[]](\n    0x55, 0x48,0x89,0xE5,\n    0x48,0x8D,0x3D, 0x00,0x00,0x00,0x00,\n    0x48,0x8D,0x35, 0x16,0x00,0x00,0x00,\n    0xE8, 0x00,0x00,0x00,0x00,\n    0x48,0x85,0xC0,\n    0x74,0x08,\n    0x48,0x89,0xC7,\n    0xE8, 0x00,0x00,0x00,0x00,\n    0x31,0xC0,\n    0x5D, 0xC3,\n    0x75,0x73,0x62,0x30,0x00,\n    0xC3\n))\nif ($buf.Count -ne 0x6E) { throw \".text end check failed\" }\nPadTo 0x70\nA (asc \"vermagic=6.1.2-tinycore64 SMP mod_unload \"); $buf.Add(0)\nA (asc \"license=GPL\"); $buf.Add(0)\nA (asc \"name=carrier_fix\"); $buf.Add(0)\nPadTo 0xB8\nZ 24; A (asc \"carrier_fix\"); $buf.Add(0); Z (640 - 36)\nif ($buf.Count -ne 0x338) { throw \".module end check failed\" }\nA (le32 0);  $buf.Add(0);    $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)\nA (le32 1);  $buf.Add(0x12); $buf.Add(0); A (le16 1); A (le64 0);    A (le64 0)\nA (le32 18); $buf.Add(0x12); $buf.Add(0); A (le16 1); A (le64 0x2D); A (le64 0)\nA (le32 35); $buf.Add(0x10); $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)\nA (le32 53); $buf.Add(0x11); $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)\nA (le32 62); $buf.Add(0x10); $buf.Add(0); A (le16 0); A (le64 0);    A (le64 0)\nif ($buf.Count -ne 0x3C8) { throw \".symtab end check failed\" }\n$buf.Add(0)\nA (asc \"carrier_fix_init\"); $buf.Add(0)\nA (asc \"carrier_fix_exit\"); $buf.Add(0)\nA (asc \"__dev_get_by_name\"); $buf.Add(0)\nA (asc \"init_net\"); $buf.Add(0)\nA (asc \"netif_carrier_on\"); $buf.Add(0)\nPadTo 0x418\n$buf.Add(0)\nA (asc \".text\"); $buf.Add(0); A (asc \".modinfo\"); $buf.Add(0)\nA (asc \".gnu.linkonce.this_module\"); $buf.Add(0)\nA (asc \".symtab\"); $buf.Add(0); A (asc \".strtab\"); $buf.Add(0)\nA (asc \".shstrtab\"); $buf.Add(0); A (asc \".rela.text\"); $buf.Add(0)\nA (asc \".rela.gnu.linkonce.this_module\"); $buf.Add(0)\nif ($buf.Count -ne 0x486) { throw \".shstrtab end check failed\" }\nPadTo 0x488\nA (le64 0x07); A (le64 ([uint64](([uint64]4 -shl 32) -bor 2))); A (sle64 -4)\nA (le64 0x13); A (le64 ([uint64](([uint64]3 -shl 32) -bor 4))); A (sle64 -4)\nA (le64 0x20); A (le64 ([uint64](([uint64]5 -shl 32) -bor 4))); A (sle64 -4)\nif ($buf.Count -ne 0x4D0) { throw \".rela.text end check failed\" }\nA (le64 0x138); A (le64 ([uint64](([uint64]1 -shl 32) -bor 1))); A (sle64 0)\nA (le64 0x270); A (le64 ([uint64](([uint64]2 -shl 32) -bor 1))); A (sle64 0)\nif ($buf.Count -ne 0x500) { throw \".rela.mod end check failed\" }\nfunction Shdr([uint32]$nm,[uint32]$ty,[uint64]$fl,[uint64]$off,[uint64]$sz,[uint32]$lk,[uint32]$inf,[uint64]$al,[uint64]$es) {\n    A (le32 $nm); A (le32 $ty); A (le64 $fl); A (le64 0)\n    A (le64 $off); A (le64 $sz); A (le32 $lk); A (le32 $inf); A (le64 $al); A (le64 $es)\n}\nShdr 0  0 0 0      0      0 0 0  0\nShdr 1  1 6 0x40   0x2E   0 0 16 0\nShdr 7  1 2 0x70   0x47   0 0 1  0\nShdr 16 1 3 0xB8   0x280  0 0 8  0\nShdr 42 2 0 0x338  0x90   5 1 8  24\nShdr 50 3 0 0x3C8  0x4F   0 0 1  0\nShdr 58 3 0 0x418  0x6E   0 0 1  0\nShdr 68 4 0 0x488  0x48   4 1 8  24\nShdr 79 4 0 0x4D0  0x30   4 3 8  24\nif ($buf.Count -ne 0x740) { throw \"final size check failed\" }\n$outPath = \"$env:TEMP\\carrier_fix.ko\"\n[System.IO.File]::WriteAllBytes($outPath, $buf.ToArray())\nWrite-Host \"OK: $($buf.Count) bytes -> $outPath\"\n```\n\nOutput: `%TEMP%\\carrier_fix.ko`\n\n(1856 bytes).\n\nIf you use a different TinyCore version: the`vermagic`\n\nstring in the script must exactly match`uname -r`\n\noutput on the VM, with a trailing space before the null. Also, the cdc_ncm.ko patch offset in Part 4e may differ.\n\n```\n& \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\" startvm AppleNCM --type headless\nStart-Sleep -Seconds 30   # wait for TinyCore to fully boot\n```\n\nTinyCore has no SSH by default and BusyBox `nc`\n\ndoes not support `-e sh`\n\n. We must install dropbear via keyboard injection, then SSH for all subsequent work.\n\n**Important**: Use the `keyboardputscancode`\n\nmethod for Enter (scan code `1c 9c`\n\n), not `keyboardputstring`\n\nwith a newline — newlines in `keyboardputstring`\n\nare unreliable. Also, the pipe character `|`\n\nin `keyboardputstring`\n\nmay be mangled on non-US keyboard layouts; use file redirects where possible.\n\n``` php\n$vbm = \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\"\nfunction KbSend([string]$cmd) {\n    & $vbm controlvm AppleNCM keyboardputstring $cmd\n    & $vbm controlvm AppleNCM keyboardputscancode 1c 9c  # Enter\n    Start-Sleep -Milliseconds 800\n}\n\n# Set DNS (NAT adapter uses 10.0.2.3 as DNS resolver)\nKbSend \"sudo sh -c 'echo nameserver 10.0.2.3 > /etc/resolv.conf'\"\nStart-Sleep -Seconds 2\n\n# Install dropbear SSH server\nKbSend \"tce-load -wi dropbear\"\nStart-Sleep -Seconds 30  # wait for download\n\n# Generate host key and start dropbear\nKbSend \"sudo mkdir -p /etc/dropbear\"\nKbSend \"sudo /usr/local/bin/dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key\"\nStart-Sleep -Seconds 5\nKbSend \"echo tc:ncmvm | sudo chpasswd\"\nKbSend \"sudo /usr/local/sbin/dropbear -p 22 -r /etc/dropbear/dropbear_rsa_host_key\"\nStart-Sleep -Seconds 3\n```\n\nNote: TinyCore's sudo has a restricted PATH that does NOT include`/usr/local/sbin/`\n\n. Always use full paths for dropbear commands:`/usr/local/sbin/dropbear`\n\n,`/usr/local/bin/dropbearkey`\n\n.\n\nGet the VM's eth1 IP:\n\n``` php\n$vmIP = (& \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\" guestproperty get AppleNCM \"/VirtualBox/GuestInfo/Net/1/V4/IP\") -replace \"Value: \",\"\"\nWrite-Host \"VM IP: $vmIP\"\n```\n\nSSH into the VM (use a separate known_hosts file — dropbear generates a new key each reboot):\n\n``` php\n$tmpKH = \"$env:TEMP\\known_hosts_ncm3\"\nssh -i C:\\Users\\safro\\.ssh\\id_rsa -o \"UserKnownHostsFile=$tmpKH\" -o StrictHostKeyChecking=no tc@$vmIP\n```\n\nAfter 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`\n\nor SSH with`StrictHostKeyChecking=no`\n\n.\n\nIPv6 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:\n\n```\necho 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6\necho 1 > /proc/sys/net/ipv6/conf/default/disable_ipv6\n```\n\nAlso: **never run tce-load -wi iptables** on this VM — it pulls\n\n`ipv6-netfilter`\n\nas a dependency and triggers the same lockup.\n\n```\nsudo modprobe usbnet\nsudo modprobe cdc_ether\n# Note: cdc_ncm is loaded in step 4e after patching; xhci_hcd is not needed with OHCI+EHCI\n```\n\nApple's USB NCM has no interrupt IN endpoint, so `dev->status`\n\nis NULL. The stock cdc_ncm driver dereferences it during `bind()`\n\nand fails with `-ENODEV`\n\n. Fix: NOP the 6-byte NULL check at file offset 0x1CEC.\n\n```\ncd /tmp\nsudo cp /lib/modules/6.1.2-tinycore64/kernel/drivers/net/usb/cdc_ncm.ko.gz .\nsudo gunzip -f cdc_ncm.ko.gz\n\n# Verify bytes before patching (offset 0x1CEC = 7404 decimal)\n# You should see something like: 48 85 c0 0f 84 ... (TEST RAX,RAX + conditional jump)\nod -A x -t x1 -j 7400 -N 16 /tmp/cdc_ncm.ko\n\n# Apply patch: write 6 NOP bytes (0x90) at offset 7404\nprintf '\\220\\220\\220\\220\\220\\220' | dd of=/tmp/cdc_ncm.ko bs=1 seek=7404 count=6 conv=notrunc\n\n# Verify: should now show 90 90 90 90 90 90 at 0x1cec\nod -A x -t x1 -j 7400 -N 16 /tmp/cdc_ncm.ko\n\n# Install the patched module persistently\ngzip -9 -c /tmp/cdc_ncm.ko > /tmp/cdc_ncm.ko.gz\nsudo cp /tmp/cdc_ncm.ko.gz /lib/modules/6.1.2-tinycore64/kernel/drivers/net/usb/cdc_ncm.ko.gz\n```\n\nLoad the patched module:\n\n```\nsudo modprobe cdc_ncm\n```\n\nIf using a different kernel: Find the patch offset by searching for the`TEST RAX,RAX`\n\n(bytes`48 85 c0`\n\n) pattern near a call to`usb_alloc_urb`\n\nin the ncm_bind function of the disassembled .ko.\n\nIn the VM SSH session, start a file receiver. **Must include < /dev/null** — otherwise BusyBox nc keeps stdin open after transfer and swallows subsequent commands:\n\n```\nnc -l -p 9999 < /dev/null > /tmp/carrier_fix.ko\n```\n\nIn a **new** Windows PowerShell window, send the file:\n\n``` php\n$bytes = [System.IO.File]::ReadAllBytes(\"$env:TEMP\\carrier_fix.ko\")\n$s = [System.Net.Sockets.TcpClient]::new(\"192.168.56.101\", 9999)\n$s.GetStream().Write($bytes, 0, $bytes.Length)\n$s.Close()\n```\n\nVerify:\n\n```\nls -la /tmp/carrier_fix.ko   # must be exactly 1856 bytes\n```\n\nNote:`scp`\n\ndoes NOT work on TinyCore — it requires`/usr/libexec/sftp-server`\n\nwhich TinyCore doesn't provide. Use the TCP transfer above.\n\n```\nmkdir -p /home/tc/ncm_modules\ncp /tmp/carrier_fix.ko /home/tc/ncm_modules/\ncp /tmp/cdc_ncm.ko /home/tc/ncm_modules/    # keep the uncompressed .ko — insmod needs it\n\ncat > /home/tc/ncm_modules/ncm_setup.sh << 'EOF'\n#!/bin/sh\nsleep 1\n# If usb0 is absent, the stock cdc_ncm bound first and failed (no interrupt endpoint).\n# Swap it out for the patched version and manually bind the interfaces.\nif ! /sbin/ifconfig usb0 > /dev/null 2>&1; then\n    /sbin/rmmod cdc_mbim 2>/dev/null; true\n    /sbin/rmmod cdc_ncm 2>/dev/null; true\n    /sbin/rmmod cdc_ether 2>/dev/null; true\n    /sbin/modprobe cdc_ether 2>/dev/null; true\n    /sbin/insmod /home/tc/ncm_modules/cdc_ncm.ko 2>/dev/null; true\n    sleep 1\n    echo 1-1:1.0 > /sys/bus/usb/drivers/cdc_ncm/bind 2>/dev/null; true\n    echo 1-1:1.1 > /sys/bus/usb/drivers/cdc_ncm/bind 2>/dev/null; true\n    sleep 1\nfi\n/sbin/rmmod carrier_fix 2>/dev/null; true\n/sbin/insmod /home/tc/ncm_modules/carrier_fix.ko 2>/dev/null; true\n/sbin/ifconfig usb0 192.168.2.3 netmask 255.255.255.0 broadcast 192.168.2.255\n/sbin/route del default gw 192.168.2.1 2>/dev/null; true\n/sbin/ifconfig usb1 0.0.0.0 2>/dev/null; true\n/sbin/sysctl -w net.ipv4.conf.usb0.proxy_arp=0 >/dev/null\n/sbin/sysctl -w net.ipv4.conf.eth1.proxy_arp=1 >/dev/null\n/sbin/sysctl -w net.ipv4.ip_forward=1 >/dev/null\n/sbin/route add -host 192.168.2.200 gw 192.168.56.1 dev eth1 2>/dev/null; true\n/sbin/arp -s 192.168.2.200 00:00:00:00:00:00 pub -i usb0 2>/dev/null; true\necho setup done\nEOF\nchmod +x /home/tc/ncm_modules/ncm_setup.sh\n```\n\nKey points about ncm_setup.sh:\n\n**usb0 absence detection**: At cold boot, the stock cdc_ncm.ko loads from`/lib/modules/`\n\nand 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`\n\n, then manually binds interface`1-1:1.0`\n\n.- Uses\n**static IP**`192.168.2.3`\n\non usb0 (not DHCP — Mac's bootpd requires bridge100 which doesn't work) - Removes the default gateway to avoid routing internet through the USB link\n- Sets\n`proxy_arp=0`\n\non usb0 (NOT 1) — general proxy_arp on usb0 causes a 169.254.x.x ARP flood - Uses a published ARP entry (\n`arp -s ... pub -i usb0`\n\n) to answer ARP for`192.168.2.200`\n\nspecifically — without this, Mac cannot ARP-resolve Windows via the USB link\n\nTinyCore executes `/opt/bootlocal.sh`\n\non every boot. Create it to auto-start sshd and the USB link watchdog:\n\n``` bash\nsudo tee /opt/bootlocal.sh > /dev/null << 'EOF'\n#!/bin/sh\nsudo /usr/local/sbin/sshd 2>/dev/null\nsh /home/tc/ncm_modules/ncm_watch2.sh &\nEOF\nsudo chmod +x /opt/bootlocal.sh\n```\n\nNote: Always write system files via`sudo tee`\n\nfrom 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`\n\n). Also never use double quotes in`keyboardputstring`\n\ncommands — they are silently dropped (Warning #13).\n\nThis script runs as a background loop. It detects when usb0 loses its IP, goes down, or the `192.168.2.200`\n\nhost route disappears (which happens at cold boot due to an eth1 DHCP race), and re-runs ncm_setup.sh:\n\n``` bash\ncat > /home/tc/ncm_modules/ncm_watch2.sh << 'EOF'\n#!/bin/sh\nLAST=0\nwhile true; do\n  NOW=$(date +%s)\n  DIFF=$((NOW - LAST))\n  if [ $DIFF -gt 5 ]; then\n    STATE=$(cat /sys/class/net/usb0/operstate 2>/dev/null)\n    IP=$(/sbin/ifconfig usb0 2>/dev/null | grep inet)\n    ROUTE=$(route -n 2>/dev/null | grep 192.168.2.200)\n    if [ \"$STATE\" != up ] || [ -z \"$IP\" ] || [ -z \"$ROUTE\" ]; then\n      echo $(date): usb0 not ready or route missing, running ncm_setup.sh\n      /home/tc/ncm_modules/ncm_setup.sh >> /tmp/ncm_setup.log 2>&1\n      LAST=$NOW\n    fi\n  fi\n  sleep 2\ndone\nEOF\nchmod +x /home/tc/ncm_modules/ncm_watch2.sh\n```\n\nThe 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`\n\nfails 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`\n\n.\n\n**VM must be running and modules loaded before plugging the cable.**\n\nPlug the USB-C cable between Mac and Windows. VBoxUSBMon captures the Apple device before Windows' driver can touch it.\n\nIf the cable was already plugged in before starting the VM, unplug and replug it now.\n\nWatch for usb0 to appear:\n\n```\nifconfig -a | grep usb\n```\n\nIf usb0 appears but stays down:\n\n```\necho 1 > /proc/sys/net/ipv6/conf/usb0/disable_ipv6\nsudo ifconfig usb0 up\nsudo /home/tc/ncm_modules/ncm_setup.sh\n```\n\nVerify the interface is fully up:\n\n```\nifconfig usb0\n# Must show: UP BROADCAST RUNNING MULTICAST\n# Must NOT show: inet6 (if it does, see troubleshooting)\n# Must show: inet 192.168.2.3 (static IP assigned by ncm_setup.sh)\n```\n\nFrom the **VM**:\n\n```\nping -c 3 192.168.2.1    # Mac\nping -c 3 192.168.2.200  # Windows\n```\n\nFrom **Windows PowerShell**:\n\n```\nping -n 3 192.168.2.1    # Mac (~5ms expected)\n```\n\nFrom **Mac Terminal**:\n\n```\nping -c 3 192.168.2.200  # Windows (~5ms expected)\n```\n\n**TeamViewer**: On Mac → Remote Control → enter `192.168.2.200`\n\n.\n\nExpected results when fully working:\n\n- Windows → Mac (192.168.2.1): ~5ms\n- VM → Mac: ~4ms\n- VM → Windows (192.168.2.200): ~1ms\n- Mac → VM (192.168.2.3): ~5ms\n- Mac → Windows (192.168.2.200): ~5ms\n\nTinyCore Linux runs entirely in RAM. By default, everything is lost on reboot.\n\nAfter each VM reboot, you must:\n\n- Reinstall SSH (Part 4b)\n- Re-disable IPv6 (Part 4c)\n- Load modules (Part 4d:\n`modprobe usbnet; modprobe cdc_ether; modprobe cdc_ncm`\n\n) - Re-transfer carrier_fix.ko and cdc_ncm.ko (Parts 4f–4g)\n- Re-run ncm_setup.sh (Part 5a)\n\nIn VirtualBox Manager, add a small (128MB+) VDI disk to the AppleNCM VM (Storage → Add Hard Disk). The disk will appear as `/dev/sda`\n\ninside TinyCore.\n\n```\n# Create a filesystem (raw disk, no partition table needed for SYSLINUX on TinyCore)\nsudo mkfs.ext2 /dev/sda\nsudo mkdir -p /mnt/sda\nsudo mount /dev/sda /mnt/sda\nsudo mkdir -p /mnt/sda/tce /mnt/sda/boot\n# Copy kernel and initrd so SYSLINUX can boot\nsudo cp /boot/vmlinuz64 /mnt/sda/boot/\nsudo cp /boot/corepure64.gz /mnt/sda/boot/\nsudo tce-load -wi syslinux\nsudo extlinux --install /mnt/sda\n```\n\nCreate `/mnt/sda/syslinux.cfg`\n\n:\n\n```\nsudo tee /mnt/sda/syslinux.cfg << 'EOF'\nDEFAULT tc\nPROMPT 0\nTIMEOUT 30\nLABEL tc\n  KERNEL /boot/vmlinuz64\n  INITRD /boot/corepure64.gz\n  APPEND loglevel=3 tce=sda restore=sda/tce\nEOF\n```\n\nCRITICAL:`restore=`\n\nMUST be`sda/tce`\n\n, NOT`sda`\n\n. TinyCore's`filetool.sh`\n\nparses this as DEVICE/FULLPATH —`restore=sda`\n\nlooks for`/mnt/sda/mydata.tgz`\n\n(wrong), while`restore=sda/tce`\n\ncorrectly finds`/mnt/sda/tce/mydata.tgz`\n\n.\n\nOnce all scripts and keys are in place, build the persistence archive:\n\n```\ncd /\nsudo tar czf /mnt/sda/tce/mydata.tgz \\\n  home/tc/ncm_modules/ \\\n  home/tc/.ssh/ \\\n  usr/local/etc/ssh/ \\\n  opt/bootlocal.sh\n```\n\nImportant: Run from`/`\n\nusing paths without a leading slash (`home/tc/...`\n\nnot`/home/tc/...`\n\n). TinyCore's restore extracts the archive relative to`/`\n\n— a leading slash produces double-root paths and nothing gets restored.\n\nRebuild this archive whenever you modify any of the persisted files.\n\n| File/Setting | Survives? | Notes |\n|---|---|---|\n`/home/tc/ncm_modules/` (scripts + .ko files) |\nYes |\nin mydata.tgz |\n`/home/tc/.ssh/authorized_keys` |\nYes |\nin mydata.tgz |\n`/usr/local/etc/ssh/` (sshd config + host keys) |\nYes |\nin mydata.tgz |\n`/opt/bootlocal.sh` |\nYes |\nin mydata.tgz; starts sshd + ncm_watch2.sh at boot |\npatched `/lib/modules/.../cdc_ncm.ko.gz` |\nNo |\nstock version reloads; ncm_setup.sh detects this and loads `/home/tc/ncm_modules/cdc_ncm.ko` instead |\n`ip_forward` , `proxy_arp` , IPv6 sysctl |\nNo |\nncm_watch2.sh re-runs ncm_setup.sh after boot to restore them |\n\nSave as `C:\\Users\\safro\\Desktop\\usbc_watchdog.ps1`\n\n. Monitors `192.168.2.1`\n\nevery 15s with three-level escalation:\n\n**ncm_setup**— re-runs`/home/tc/ncm_modules/ncm_setup.sh`\n\nvia SSH (fixes lost IP/route config)**USB replug**— VBoxManage detach (12s wait) + reattach + ncm_setup (fixes USB driver soft-lock)** VM restart**— full`poweroff`\n\n+`startvm`\n\n+ ncm_setup (fixes NCM NTB negotiation failure caused by cable jiggle — the only reliable software fix for this case)\n\n``` php\n$VBM     = \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\"\n$VM      = \"AppleNCM\"\n$SSH_KEY = \"C:\\Users\\safro\\.ssh\\id_rsa\"\n$SSH_KH  = \"$env:TEMP\\known_hosts_ncm3\"\n$MAC_IP  = \"192.168.2.1\"\n$LOG     = \"C:\\Users\\safro\\Desktop\\usbc_watchdog.log\"\n\nfunction Log($msg) {\n    $line = \"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg\"\n    Write-Host $line\n    Add-Content -Path $LOG -Value $line\n}\nfunction Test-MacReachable {\n    $result = ping -n 1 -w 2000 $MAC_IP 2>$null\n    return ($result -match \"TTL=\")\n}\nfunction Get-VmIp {\n    $v = & $VBM guestproperty get $VM \"/VirtualBox/GuestInfo/Net/1/V4/IP\" 2>$null\n    if ($v -match \"Value:\\s*(\\S+)\") { return $matches[1] }\n    return \"192.168.56.102\"\n}\nfunction Invoke-VmSetup($ip) {\n    ssh -i $SSH_KEY -o \"UserKnownHostsFile=$SSH_KH\" -o StrictHostKeyChecking=no `\n        -o ConnectTimeout=10 tc@$ip \"sudo /home/tc/ncm_modules/ncm_setup.sh 2>&1\" 2>&1\n}\nfunction Get-NcmUuid {\n    $lines = (& $VBM list usbhost 2>&1) -join \"`n\" -split \"` n\"\n    for ($i = 0; $i -lt $lines.Count; $i++) {\n        if ($lines[$i] -match \"05ac\" -and ($lines[$i] -match \"1905\" -or\n            ($i+1 -lt $lines.Count -and $lines[$i+1] -match \"1905\"))) {\n            for ($j = [Math]::Max(0, $i-6); $j -le $i; $j++) {\n                if ($lines[$j] -match \"UUID:\\s*(\\S+)\") { return $matches[1] }\n            }\n        }\n    }\n    return $null\n}\nfunction Invoke-UsbReplug {\n    $uuid = Get-NcmUuid\n    if (-not $uuid) { Log \"USB replug: Apple NCM device not found in usbhost list\"; return }\n    Log \"USB replug: detaching $uuid\"\n    & $VBM controlvm $VM usbdetach $uuid 2>&1 | Out-Null\n    Start-Sleep 12\n    Log \"USB replug: reattaching $uuid\"\n    & $VBM controlvm $VM usbattach $uuid 2>&1 | Out-Null\n    Start-Sleep 8\n}\nfunction Invoke-VmRestart {\n    Log \"VM restart: powering off $VM\"\n    & $VBM controlvm $VM poweroff 2>&1 | Out-Null\n    Start-Sleep 5\n    Log \"VM restart: starting $VM headless\"\n    & $VBM startvm $VM --type headless 2>&1 | Out-Null\n    Log \"VM restart: waiting 45s for TinyCore to boot\"\n    Start-Sleep 45\n}\n\nLog \"Watchdog started. Monitoring $MAC_IP every 15s.\"\n\n$failCount = 0\nwhile ($true) {\n    if (Test-MacReachable) {\n        $failCount = 0\n    } else {\n        $failCount++\n        Log \"Mac unreachable (fail #$failCount)\"\n\n        # Level 1: re-run ncm_setup (fixes lost IP/route config)\n        $vmIp = Get-VmIp\n        Invoke-VmSetup $vmIp | ForEach-Object { Log \"  vm: $_\" }\n        Start-Sleep 8\n\n        if (Test-MacReachable) {\n            Log \"Recovered after ncm_setup\"; $failCount = 0\n        } else {\n            # Level 2: USB detach/reattach + ncm_setup (fixes USB driver soft-lock)\n            Log \"Still unreachable — USB replug\"\n            Invoke-UsbReplug\n            $vmIp = Get-VmIp\n            Invoke-VmSetup $vmIp | ForEach-Object { Log \"  vm: $_\" }\n            Start-Sleep 10\n\n            if (Test-MacReachable) {\n                Log \"Recovered after USB replug\"; $failCount = 0\n            } else {\n                # Level 3: full VM restart (fixes NCM NTB negotiation failure from cable jiggle)\n                Log \"Still unreachable — restarting VM (NCM renegotiation)\"\n                Invoke-VmRestart\n                $vmIp = Get-VmIp\n                Invoke-VmSetup $vmIp | ForEach-Object { Log \"  vm: $_\" }\n                Start-Sleep 10\n\n                if (Test-MacReachable) {\n                    Log \"Recovered after VM restart\"\n                } else {\n                    Log \"Still unreachable after VM restart — will retry next cycle\"\n                }\n                $failCount = 0\n            }\n        }\n    }\n    Start-Sleep 15\n}\n```\n\nRegister as a scheduled task (run as Administrator):\n\n``` php\n$script  = \"C:\\Users\\safro\\Desktop\\usbc_watchdog.ps1\"\n$action  = New-ScheduledTaskAction -Execute \"powershell.exe\" `\n               -Argument \"-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `\"$script`\"\"\n$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME\n$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 0) `\n                -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1)\nRegister-ScheduledTask -TaskName \"UsbcLanWatchdog\" -Action $action -Trigger $trigger `\n    -Settings $settings -RunLevel Highest -Force\nStart-ScheduledTask -TaskName \"UsbcLanWatchdog\"\n```\n\nRegister a scheduled task so the VM starts automatically at Windows boot (60s delay lets VirtualBox services initialize first):\n\n``` php\n$vbm = \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\"\n$action = New-ScheduledTaskAction -Execute \"powershell.exe\" -Argument \"-NonInteractive -WindowStyle Hidden -Command `\"Start-Sleep -Seconds 60; & '$vbm' startvm AppleNCM --type headless`\"\"\n$trigger = New-ScheduledTaskTrigger -AtStartup\n$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) -MultipleInstances IgnoreNew\n$principal = New-ScheduledTaskPrincipal -UserId \"SYSTEM\" -LogonType ServiceAccount -RunLevel Highest\nRegister-ScheduledTask -TaskName \"StartAppleNCM_VirtualBox\" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force\n```\n\nAfter Windows reboots, the full LAN comes up automatically within ~90 seconds (60s task delay + 30s TinyCore boot). No manual steps needed.\n\n**What survives a Windows reboot automatically:**\n\n- VM start —\n`StartAppleNCM_VirtualBox`\n\nscheduled task (SYSTEM, at boot) - Windows 192.168.2.200 secondary IP — static, persists in adapter config\n- LAN watchdog —\n`UsbcLanWatchdog`\n\nscheduled task (at logon) - Mac watchdog — launchd daemon at\n`/Library/LaunchDaemons/net.klesun.usbc_route.plist`\n\n(auto-starts on Mac boot if installed per Part 1e) - VirtualBox USB filter — saved in VM config, auto-captures on VM start\n\n| Machine | Interface | IP | Role |\n|---|---|---|---|\n| Mac | en6 | `192.168.2.1` |\nMac's LAN IP, assigned directly by watchdog |\n| VM | usb0 | `192.168.2.3` |\nStatic IP assigned by ncm_setup.sh |\n| VM | eth1 | `192.168.56.x` |\nGets IP from VirtualBox DHCP (e.g. .101 or .102) |\n| Windows | Host-Only | `192.168.56.1` |\nHost-Only primary (set in VirtualBox) |\n| Windows | Host-Only | `192.168.2.200` |\nWindows' LAN IP — use this from Mac |\n\n`dmesg | tail -30`\n\n— look for USB errors or NCM bind failures- Ensure the VM USB filter has Vendor ID\n`05ac`\n\n/ Product ID`1905`\n\n- Ensure USB controller is OHCI+EHCI, NOT xHCI\n- Unplug and replug the cable (VM must be running)\n- Check\n`VBoxManage list usbhost`\n\n— device must show`Current State: Available`\n\n(not`Captured`\n\nstuck) before plugging. If stuck as Captured: kill VBoxSVC (see Part 2c) - Do NOT run\n`usbipd bind`\n\non the Apple device — it resets the device into an error state\n\n- Check carrier:\n`cat /sys/class/net/usb0/carrier`\n\n— if`0`\n\n, carrier_fix didn't work - Reload:\n`sudo rmmod carrier_fix; sudo insmod /home/tc/ncm_modules/carrier_fix.ko`\n\n- Verify the vermagic in carrier_fix.ko matches:\n`uname -r`\n\non VM must match the string embedded in the module\n\nIPv6 was active when the NCM device appeared (ICMPv6 multicast → URB error → TX permanently stuck).\n\nFix:\n\n```\nsudo rmmod cdc_ncm\necho 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6\necho 1 > /proc/sys/net/ipv6/conf/default/disable_ipv6\nsudo modprobe cdc_ncm\n```\n\nThen re-run ncm_setup.sh.\n\n**Step 1: Check Mac's source IP when pinging**\n\n```\nsudo tcpdump -i any -n icmp\n```\n\nIf you see `192.168.155.x > 192.168.2.200`\n\n(Wi-Fi IP as source instead of `192.168.2.1`\n\n), the non-scoped route is missing. Fix:\n\n```\nsudo route add -net 192.168.2.0/24 -interface en6\n```\n\nThis is the most common failure mode — all macOS connected routes carry the `RTF_IFSCOPE`\n\nflag and are invisible to general routing. Verify with:\n\n```\nroute -n get 192.168.2.200\n# Must show: interface: en6\n```\n\n**Step 2: Check en6 has the right IP**\n\n```\nifconfig en6 | grep inet\n# Must show: inet 192.168.2.1\n```\n\nIf not, run: `sudo ifconfig en6 192.168.2.1 netmask 255.255.255.0`\n\n**Step 3: Check VM routing**\n\n```\n# On VM:\nroute -n | grep 192.168.2.200    # host route via eth1 must exist\narp -n | grep 192.168.2.200      # must show 00:00:00:00:00:00 PMP (published)\ncat /proc/sys/net/ipv4/ip_forward  # must be 1\ncat /proc/sys/net/ipv4/conf/eth1/proxy_arp  # must be 1\n```\n\n**Step 4: Check Windows**\n\n```\n# Windows firewall:\nGet-NetFirewallRule -DisplayName \"Allow ICMP from Mac subnet\"\n# Windows secondary IP:\nGet-NetIPAddress | Where-Object { $_.IPAddress -eq \"192.168.2.200\" }\n```\n\nThis is caused by a USB `SET_CONFIGURATION`\n\nbeing sent while Internet Sharing is active, or by `usbipd bind`\n\n.\n\nRecovery (on Mac):\n\n- System Settings → General → Sharing → Internet Sharing: toggle\n**OFF**, wait 5 seconds, toggle** ON**again - Or via Terminal:\n\n```\nsudo launchctl unload /System/Library/LaunchDaemons/com.apple.InternetSharing.plist\nsleep 6\nsudo launchctl load /System/Library/LaunchDaemons/com.apple.InternetSharing.plist\n```\n\nWith the v2 architecture (IS disabled), this recovery step is not needed — simply unplug and replug the cable.\n\nWith 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.\n\nSymptoms: VBox.log shows repeated `ConsoleWrap::detachUSBDevice: USB device ... is not attached to this machine`\n\nerrors. Device shows as `Captured`\n\nin `list usbhost`\n\nbut VM never sees usb0.\n\nFix: Kill VBoxSVC to flush stale state (see Part 2c). Then restart VM and replug cable.\n\nWith persistence enabled (Part 7), the OpenSSH host keys survive reboots (they are stored in `/usr/local/etc/ssh/`\n\nwhich 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.\n\nFix:\n\n```\nRemove-Item -Force \"$env:TEMP\\known_hosts_ncm3\" -ErrorAction SilentlyContinue\nssh -i C:\\Users\\safro\\.ssh\\id_rsa -o \"UserKnownHostsFile=$env:TEMP\\known_hosts_ncm3\" -o StrictHostKeyChecking=no tc@192.168.56.102\n```\n\nOr always SSH with `-o StrictHostKeyChecking=no`\n\n.\n\nTinyCore gets eth1 IP from VirtualBox DHCP. It may change (e.g. from .101 to .102). Always check:\n\n```\n& \"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe\" guestproperty get AppleNCM \"/VirtualBox/GuestInfo/Net/1/V4/IP\"\n```\n\nmacOS ARP entries expire after ~20 minutes of inactivity. The VM's pub ARP entry (`arp -s 192.168.2.200 pub`\n\n) answers new ARP requests automatically. The Mac watchdog also flushes stale entries every 20 seconds.\n\nIf stuck: from VM, `ping -c 1 192.168.2.1`\n\nrefreshes the ARP path in both directions.\n\nIf `usbipd-win`\n\nwas installed at any point, its `VBoxUSBMon.sys`\n\nis registered as a global USB filter at the kernel level and intercepts all USB devices — including Android phones. This is what broke phone tethering.\n\nFix: 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.\n\nAfter a cold reboot, the VM's `/lib/modules/`\n\nstill has the stock `cdc_ncm.ko.gz`\n\n. TinyCore loads it automatically, it fails to bind the Apple NCM device (no interrupt endpoint → `-ENODEV`\n\n), and usb0 never appears.\n\n`ncm_watch2.sh`\n\n(started by `bootlocal.sh`\n\n) detects this and calls `ncm_setup.sh`\n\n, which handles it: it unloads the stock module, loads the patched `/home/tc/ncm_modules/cdc_ncm.ko`\n\n, and manually binds the interface. Within ~10 seconds of boot, usb0 should appear.\n\nIf it doesn't recover automatically:\n\n```\ntail -f /tmp/ncm_setup.log   # watch what ncm_setup.sh is doing\n# Or run manually:\nsudo /home/tc/ncm_modules/ncm_setup.sh\n```\n\nIf `ncm_watch2.sh`\n\nis not running (e.g. `bootlocal.sh`\n\nnot in mydata.tgz):\n\n```\n# Check:\nps | grep ncm\n# Start manually:\nsh /home/tc/ncm_modules/ncm_watch2.sh &\n```\n\nIf `sc.exe query vboxsup`\n\nreturns `STATE: 3 STOP_PENDING`\n\n, the VirtualBox kernel driver is deadlocked. No software workaround exists — not `net stop`\n\n, not `pnputil /restart-device`\n\n, not reinstalling VirtualBox, not `sc delete`\n\n.\n\n**Only fix**: physically restart the Windows machine.\n\n**Cause**: Running the WinUSB bridge (`NcmBridge.ps1`\n\n) while `VBoxUSBMon`\n\nhas 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.\n\nAfter a restart, if VBoxSup fails to start automatically:\n\n```\npnputil /add-driver \"C:\\Program Files\\Oracle\\VirtualBox\\drivers\\vboxsup\\VBoxSup.inf\" /install\nsc start vboxsup\n```\n\nVirtualBox'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.\n\n**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.", "url": "https://wpnews.pro/news/usb-c-lan-mac-to-windows-instruction-by-claude-md", "canonical_source": "https://gist.github.com/klesun/5d61fed2b20600cd5f9aae4e34252220", "published_at": "2026-06-01 20:43:16+00:00", "updated_at": "2026-07-01 08:18:32.208277+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "artificial-intelligence"], "entities": ["Claude Code", "MacBook", "Windows", "VirtualBox", "TinyCore Linux", "TeamViewer", "Apple", "cdc_ncm"], "alternates": {"html": "https://wpnews.pro/news/usb-c-lan-mac-to-windows-instruction-by-claude-md", "markdown": "https://wpnews.pro/news/usb-c-lan-mac-to-windows-instruction-by-claude-md.md", "text": "https://wpnews.pro/news/usb-c-lan-mac-to-windows-instruction-by-claude-md.txt", "jsonld": "https://wpnews.pro/news/usb-c-lan-mac-to-windows-instruction-by-claude-md.jsonld"}}