# usb_c_lan_mac_to_windows_instruction_by_claude.md

> Source: <https://gist.github.com/klesun/5d61fed2b20600cd5f9aae4e34252220>
> Published: 2026-06-01 20:43:16+00:00

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](https://www.teamviewer.com/en/global/support/knowledge-base/teamviewer-remote/remote-control/use-teamviewer-remote-in-lan/)).
This solution was produced by [Claude Code](https://code.claude.com/docs/en/terminal-guide) 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](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**: If`usbipd-win`

was ever installed on Windows, its`VBoxUSBMon.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 (loading 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"
# ... do your work ...
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:

```
# WRONG — double quotes will be stripped:
KbSend 'printf "PermitRootLogin yes\n" | sudo tee /etc/sshd_config'
# RIGHT — single-quoted sh -c:
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 assigned**directly 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+](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

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:

```
# Check current state
plutil -p /Library/Preferences/SystemConfiguration/com.apple.nat.plist | grep -A2 NAT

# Disable IS (set NAT.Enabled = 0)
sudo plutil -replace NAT.Enabled -bool false /Library/Preferences/SystemConfiguration/com.apple.nat.plist

# Verify
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
# Should show: inet 192.168.2.1 netmask 0xffffff00 broadcast 192.168.2.255
```

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
# Must show: interface: en6  (NOT en0/Wi-Fi)
```

The watchdog runs every 20 seconds and:

- Finds the active NCM interface (
`en*`

with mtu 8178 — NOT`anpi*`

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`

``` bash
sudo tee /usr/local/bin/usbc_lan_watchdog.sh > /dev/null << 'WATCHDOG'
#!/bin/sh

find_ncm_iface() {
    # Find en* interface with mtu 8178 (Apple USB NCM)
    # Must filter to en* prefix — anpi2/anpi* are sibling interfaces also with mtu 8178 but wrong
    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() {
    # Flush stale ARP cache for Windows IP to avoid NUD FAILED state
    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
# Should show state = running
```

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**: attach`CorePure64-14.0.iso`

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

- Adapter 1:
**USB**: enable** OHCI + EHCI**controllers —** DO NOT enable xHCI**(causes VBoxSVC attach failures)- Add USB Device Filter: Vendor ID
`05ac`

, Product ID`1905`

- Add USB Device Filter: Vendor ID

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

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

Do notadd any`VBoxInternal/Devices/usb-ohci/0/LUN#0/Config/LogLevel`

ExtraData — it causes a`VERR_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:

``` php
$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**:

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

# Add 192.168.2.200 as a secondary IP — this becomes Windows' LAN IP visible to Mac
New-NetIPAddress -InterfaceIndex $idx -IPAddress 192.168.2.200 -PrefixLength 24

# Allow ICMP from Mac's subnet (needed for ping; TeamViewer works without this)
New-NetFirewallRule -DisplayName "Allow ICMP from Mac subnet" `
    -Direction Inbound -Protocol ICMPv4 `
    -RemoteAddress "192.168.2.0/24" -Action Allow -Profile Any
```

Verify:

``` php
$name = (Get-NetAdapter | Where-Object { $_.InterfaceDescription -like "*VirtualBox Host-Only*" }).Name
netsh interface ip show address $name
# Should show both 192.168.56.1 and 192.168.2.200
```

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:

```
# make_carrier_fix.ps1
# Produces %TEMP%\carrier_fix.ko — 1856 bytes, built for kernel 6.1.2-tinycore64
$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: the`vermagic`

string in the script must exactly match`uname -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.

``` php
$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
}

# Set DNS (NAT adapter uses 10.0.2.3 as DNS resolver)
KbSend "sudo sh -c 'echo nameserver 10.0.2.3 > /etc/resolv.conf'"
Start-Sleep -Seconds 2

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

# Generate host key and start dropbear
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:

``` php
$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):

``` php
$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 with`StrictHostKeyChecking=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
# Note: cdc_ncm is loaded in step 4e after patching; xhci_hcd is not needed with OHCI+EHCI
```

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

# Verify bytes before patching (offset 0x1CEC = 7404 decimal)
# You should see something like: 48 85 c0 0f 84 ... (TEST RAX,RAX + conditional jump)
od -A x -t x1 -j 7400 -N 16 /tmp/cdc_ncm.ko

# Apply patch: write 6 NOP bytes (0x90) at offset 7404
printf '\220\220\220\220\220\220' | dd of=/tmp/cdc_ncm.ko bs=1 seek=7404 count=6 conv=notrunc

# Verify: should now show 90 90 90 90 90 90 at 0x1cec
od -A x -t x1 -j 7400 -N 16 /tmp/cdc_ncm.ko

# Install the patched module persistently
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 the`TEST RAX,RAX`

(bytes`48 85 c0`

) pattern near a call to`usb_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:

``` php
$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 usb0 is absent, the stock cdc_ncm bound first and failed (no interrupt endpoint).
# Swap it out for the patched version and manually bind the interfaces.
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 interface`1-1:1.0`

.- Uses
**static IP**`192.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 for`192.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:

``` bash
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 via`sudo 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 in`keyboardputstring`

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:

``` bash
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
# Must show: UP BROADCAST RUNNING MULTICAST
# Must NOT show: inet6 (if it does, see troubleshooting)
# Must show: inet 192.168.2.3 (static IP assigned by ncm_setup.sh)
```

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.

```
# Create a filesystem (raw disk, no partition table needed for SYSLINUX on 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
# Copy kernel and initrd so SYSLINUX can 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 be`sda/tce`

, NOT`sda`

. TinyCore's`filetool.sh`

parses this as DEVICE/FULLPATH —`restore=sda`

looks for`/mnt/sda/mydata.tgz`

(wrong), while`restore=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**— full`poweroff`

+`startvm`

+ ncm_setup (fixes NCM NTB negotiation failure caused by cable jiggle — the only reliable software fix for this case)

``` php
$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)"

        # Level 1: re-run ncm_setup (fixes lost IP/route config)
        $vmIp = Get-VmIp
        Invoke-VmSetup $vmIp | ForEach-Object { Log "  vm: $_" }
        Start-Sleep 8

        if (Test-MacReachable) {
            Log "Recovered after ncm_setup"; $failCount = 0
        } else {
            # Level 2: USB detach/reattach + ncm_setup (fixes USB driver soft-lock)
            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 {
                # Level 3: full VM restart (fixes NCM NTB negotiation failure from cable jiggle)
                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):

``` php
$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):

``` php
$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 ID`1905`

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

— device must show`Current State: Available`

(not`Captured`

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`

— if`0`

, 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
# Must show: interface: en6
```

**Step 2: Check en6 has the right IP**

```
ifconfig en6 | grep inet
# Must show: inet 192.168.2.1
```

If not, run: `sudo ifconfig en6 192.168.2.1 netmask 255.255.255.0`

**Step 3: Check VM routing**

```
# On VM:
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**

```
# Windows firewall:
Get-NetFirewallRule -DisplayName "Allow ICMP from Mac subnet"
# Windows secondary IP:
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
# Or run manually:
sudo /home/tc/ncm_modules/ncm_setup.sh
```

If `ncm_watch2.sh`

is not running (e.g. `bootlocal.sh`

not in mydata.tgz):

```
# Check:
ps | grep ncm
# Start manually:
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.
