Skip to content

PCAP (bridged) networking: release-ready capture, cross-platform elevation, and in-core NFS for Pcap#45

Merged
techomancer merged 12 commits into
techomancer:mainfrom
danifunker:add-pcap-builds
Jun 22, 2026
Merged

PCAP (bridged) networking: release-ready capture, cross-platform elevation, and in-core NFS for Pcap#45
techomancer merged 12 commits into
techomancer:mainfrom
danifunker:add-pcap-builds

Conversation

@danifunker

Copy link
Copy Markdown
Contributor

Summary

This branch makes PCAP (bridged) networking a shippable feature and brings the
in-core NFS server online in PCAP mode. In bridged mode the SEEQ Ethernet
controller is wired onto a real host NIC via libpcap, so the Indy joins your real
LAN directly (instead of the software NAT gateway). Around that core it adds
cross-platform capture-permission handling, installer plumbing, busy-LAN
hardening, and a phantom-host NFS server — plus a couple of build/GUI niceties that
rode along.

PCAP is gated to non-App-Store builds (the sandbox can't open a raw capture); App
Store / bundled builds compile without it and the UI never shows it.

PCAP bridged networking

  • Bridge the guest's raw Ethernet frames onto a real host interface (libpcap),
    presenting the Indy as a real L2 host on your LAN.
  • Change the bridged host NIC live on a running machine — no guest reboot
    (the capture is reopened in place).
  • Busy-LAN RX hardening: promiscuous capture on a chatty LAN floods the RX ring
    with frames the guest rejects and starves it. Fixed by widening the ring (32 →
    256), draining up to RX_DRAIN_CAP frames per iteration, and pushing a kernel BPF
    filter so only the guest's own unicast + broadcast/multicast frames are delivered.
  • A single backend-aware network status badge (one indicator for NAT or PCAP).

Capture permissions & installers

  • macOS: a one-time admin ChmodBPF LaunchDaemon (Wireshark's model) makes
    /dev/bpf* group-readable and adds the user to the access_bpf group. The GUI
    detects a permission failure and offers to install it (then quit & reopen).
  • Linux: setcap cap_net_raw,cap_net_admin applied by the deb/rpm postinst;
    pkexec re-exec fallback for AppImage/portable. UNTESTED
  • Windows: detects a missing Npcap driver and opens its download page —
    never auto-downloads.
  • Installer respects "install for all users" ({autopf}/{autodesktop}) and
    runs the CHD folder-permission preflight on all macOS builds.

In-core NFS over PCAP

  • NfsVirtualHost presents the existing in-core NFS server as a phantom L2 host
    on the bridged LAN: it answers ARP + portmap/2049/1234 UDP for one virtual IP and
    injects replies straight back to the guest — still zero host sockets.
  • Made it actually usable end-to-end:
    • GUI "NFS server IP" field (PCAP mode) — nfs_pcap_ip was plumbed everywhere
      except the GUI, so the responder was never built and NFS silently did nothing;
      plus a "Use <ip>" free-address suggestion.
    • Inbound IP-fragment reassembly in handle_udp (keyed by (src, id, proto),
      5 s age-out, mirroring the NAT engine's handle_ip) so large guest→host NFS
      writes work. A buffered-but-incomplete fragment is consumed rather than bridged
      onto the wire.
    • The Help → "Mount the shared folder" window now shows the assigned PCAP IP
      (it previously derived a NAT/detected gateway that doesn't exist in bridged mode).

Also included

  • r5k feature passthrough — an R5000 CPU build variant.
  • Memory tab shows the build's CPU.
  • docs/pcap-release-plan.md — the release plan for shipping PCAP builds.

Testing

  • New src/net.rs nfs_pcap_tests (gated cfg(all(test, feature = "pcap"))):
    fragmented request reassembles + the intermediate fragment is consumed;
    unfragmented still works.
  • Compiles cleanly with and without --features pcap; full --features pcap
    suite 282 passed, 0 failed.
  • PCAP bridging (TX + RX) verified on macOS.

Notes / limitations

  • PCAP requires elevated capture privileges (per-OS flows above).
  • The NFS server IP must be on the guest's LAN subnet; changes apply on the next
    Start (the virtual host is built at engine construction; no live re-IP).
  • NFS-over-PCAP is unit-tested and mirrors the proven NAT reassembly path, verified on MacOS

🤖 Generated with Claude Code

danifunker and others added 12 commits June 21, 2026 23:06
Surface PCAP capture-open failures to the GUI and add the per-OS flows that
grant raw-capture permission for bridged networking, plus the on-branch
packaging bits. Release-pipeline wiring (release.yml variants) lives on main
and is tracked separately in docs/pcap-release-plan.md.

Core/GUI (A1):
- PcapStatus on NatControl; PcapEngine classifies open errors
  (Active/PermissionDenied/DeviceError); Machine::net_pcap_status() ->
  handle.rs sampler -> EmulatorHandle::pcap_status().
- "Enable packet capture" button in the Network tab + an auto-prompt modal on
  PermissionDenied, both dispatching to new iris-gui/src/capture_access.rs.

macOS (A3/A4/A5): Wireshark-style ChmodBPF — LaunchDaemon + script under
installer/macos/chmod-bpf/, embedded via include_str! and installed via one
osascript admin prompt (group access_bpf, launchctl bootstrap); apple-events
entitlement.

Linux (A2/A6): pkexec whole-process re-exec (AppImage-aware, uid via
/proc/self, no libc dep) as the portable fallback; setcap-in-postinst as the
package default (installer/linux/postinst + rpm post_install_script); rpm
requires libpcap.

Windows (A7): installer/iris-gui.iss [Code] gated #ifdef Pcap — detect Npcap,
offer to download+run it from npcap.com (never bundled), per-user/no-admin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reworked the Windows Npcap handling to never silently download or bundle the
driver. Detect → open npcap.com for the user → they install it → re-check.

installer/iris-gui.iss (still #ifdef Pcap-gated; standard installer unchanged):
- Dropped DownloadTemporaryFile/CreateDownloadPage and the Exec of any
  downloaded file, plus the old [Tasks] opt-in and NpcapVersion define.
- A custom wizard page (shown only when Npcap is missing; ShouldSkipPage hides
  it otherwise) explains the requirement and has an "Open the Npcap download
  page" button → ShellExec to https://npcap.com/#download.
- NextButtonClick re-checks NpcapInstalled(); if still missing, a Yes/No prompt
  lets the user stay to install + re-check, or continue without it.

iris-gui/src/capture_access.rs (Windows runtime): enable() now checks for Npcap;
if present, advises Administrator + relaunch; if missing, opens npcap.com in the
browser (never downloads) and asks the user to install + relaunch.

docs/pcap-release-plan.md: A7 revised; noted B1-B3 are implemented on the
pcap-release-pipeline branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PCAP bridged mode no longer shows NAT-only details, and the NET indicator now
works in PCAP mode.

- config_ui: hide the NAT subnet settings and port forwards when mode = pcap
  (they only apply to the software gateway); reword the PCAP warning. The NFS
  mount-command hint is now mode-aware — NAT shows the gateway IP, PCAP shows
  the in-process NFS server's virtual LAN IP.
- net_pcap: count guest-originated IPv4 frames so the GUI NET indicator lights
  in PCAP mode (the NAT engine isn't running to bump the counter).
- config: add [network] nfs_pcap_ip (the virtual NFS IP for bridged mode).
- net_pcap: nfs_ip_candidates() — pick a free NFS IP (default .213 on a /24,
  scan upward, hard-cap at 95% of the usable host range), unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NFS-in-PCAP: in bridged mode the NAT engine isn't running, so the in-process
NFS server is presented as its own L2 host on the real LAN at a configured
virtual IP. PcapEngine intercepts each guest TX frame; ARP-for / portmap / NFS /
mountd to that IP are answered locally (reusing the NAT engine's framing,
checksum, portmap and NfsServer::handle paths) and the replies are injected back
on the RX ring — still zero host sockets. Anything else bridges to the wire.

- net::NfsVirtualHost (feature=pcap): ARP responder + UDP demux for the virtual
  NFS host. No inbound IP-fragment reassembly yet (large NFS WRITEs deferred);
  outbound replies auto-fragment.
- config/net/hpc3: thread [network] nfs_pcap_ip → GatewayConfig → PcapEngine,
  which stands up the responder when an export + virtual IP are both set.

GUI: collapse the two networking widgets (the "NET" dot and the separate blue
"net: PCAP → auto" label) into one backend-aware badge — dot colour = liveness,
label = NAT/PCAP, tooltip backend-specific. Drops the U+2192 arrow (tofu in
egui's font) and hides the NAT-only "check" button in PCAP mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reopen the PcapEngine's capture on a different host interface while the machine
runs, mirroring the existing live NAT subnet / port-forward changes. The guest
is untouched (same MAC/IP) — only the host-side capture handle is swapped.

- NatControl: pending_pcap_iface + apply_pcap_iface + request_pcap_interface().
- PcapEngine: reopen the capture when the flag is set, each loop; keep the
  previous interface if the new one fails to open (a typo won't drop networking).
- Machine::set_pcap_interface → Cmd::SetPcapInterface → worker handler.
- Networking tab: the interface "commits" on a deliberate pick (dropdown) or the
  manual field losing focus — not per keystroke, so typing "bridge100" doesn't
  thrash. On commit, reopen the live capture and latch the new iface into the
  status badge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The notarized (non-MAS) build skipped the folder-writability check entirely, so
a CHD in a macOS-TCC-protected folder (Documents/Desktop/Downloads/iCloud/
external/network volume) let the on-exit fold fail silently at quit instead of
asking for access like the sandbox build does.

- chd_dirs_needing_grant: gate on target_os = "macos" rather than
  feature = "appstore", so the dir_writable probe runs and surfaces the grant
  modal up front on notarized builds too.
- The grant modal adapts to the build: the sandbox build keeps the directory
  security-scoped grant (folder pick); the notarized build — where a folder pick
  can't override TCC — instead points the user at System Settings → Privacy &
  Security → Full Disk Access, with a Re-check button (a relaunch may be needed
  for Full Disk Access to take effect).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DefaultDirName was hardcoded to {localappdata}\Programs\IRIS, so choosing
"install for all users" (which PrivilegesRequiredOverridesAllowed=dialog allows,
elevating to admin) still dropped the files in the per-user AppData folder
instead of C:\Program Files\IRIS. Switch to the {auto*} constants, which follow
the install mode the user picks: all-users -> C:\Program Files\IRIS, per-user ->
%LocalAppData%\Programs\IRIS. Same fix for the desktop shortcut
({userdesktop} -> {autodesktop}).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The emulated CPU (R4400 vs R5000, ± secondary cache) is a compile-time choice —
the cache model differs deeply between them, so it can't be switched at runtime.
Surface it read-only so users know which CPU their build is.

- build_features::CPU derives "MIPS R4400" / "MIPS R5000" / "… (external L2)" /
  "… (Triton on-die L2)" from the r5k / r5ksc / r5ksc_triton features.
- Memory tab gains a "Processor" section above RAM showing the CPU, with a note
  that it's build-determined (download a different build to change it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lets the GUI be built with an R5000 core (cargo build -p iris-gui --features
r5k → iris/r5k). The Memory tab's Processor line then reads "MIPS R5000". This
is the build-time hook the release pipeline's R5000 variant uses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diagnosed via RX-STATS instrumentation: in promiscuous PCAP mode the RX ring
(32 deep) pinned full of LAN chatter the guest's address_filter rejects (~99.9%
of frames), while the enet thread drained only one frame per loop iteration.
The guest's real replies were dropped at the BPF capture (ring full) or queued
behind ~30 discard-me frames -> ~80% loss and ~1-2s RTT. refused=0 ruled out
descriptor/interrupt starvation; this is platform-independent (libpcap/Npcap).

Fix:
- seeq8003: enet thread drains leading filtered frames each iteration (up to
  RX_DRAIN_CAP=256) instead of one, then delivers one for-guest frame; new
  RxPumpResult::Filtered distinguishes chatter from an empty ring. Ring 32->256.
- net_pcap: install a kernel BPF filter (ether dst <guest> or broadcast or
  multicast) once the guest MAC is learned, so the host's own unicast chatter
  is dropped before it reaches the ring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two gaps kept the in-PCAP NFS server (NfsVirtualHost) from being usable:

1. GUI never set nfs_pcap_ip. config_ui.rs only *read* it for the mount
   hint, so in PCAP mode the virtual NFS host was never built and NFS
   silently did nothing. Add an 'NFS server IP' field (PCAP mode only) plus
   a 'Use <ip>' suggestion via the existing net_pcap::nfs_ip_candidates().
   The responder is a phantom L2 host (answers ARP + portmap/2049/1234 for
   one IP), so it only needs an address on the guest's subnet — no
   netmask/gateway. Takes effect on next Start.

2. NfsVirtualHost dropped inbound IP fragments, so large guest->host NFS
   WRITEs (wsize > MTU) failed. handle_udp now reassembles them (frag_reasm
   keyed by (src,id,proto), 5s age-out), mirroring the NAT engine's
   handle_ip. A buffered-but-incomplete fragment returns Some(vec![]) (not
   None) so PcapEngine treats it as consumed and does not bridge it onto the
   real wire. Adds nfs_pcap_tests (cfg(all(test,pcap))): fragmented request
   reassembles + intermediate frag consumed; unfragmented still works.

Compiles with and without --features pcap; full pcap suite 282 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Help → "Mount the shared folder" window derived the mount address
purely from NAT state — the live-adopted gateway (net_guest_gateway),
else the configured NAT-subnet gateway. In PCAP mode that's wrong: there
is no NAT gateway, and the NFS server is a virtual L2 host at the IP you
assigned. Make the address mode-aware (PCAP → cfg.network.nfs_pcap_ip;
NAT → unchanged) and fix the adjacent "from the NAT gateway" wording so
it reads correctly in bridged mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danifunker danifunker marked this pull request as ready for review June 22, 2026 03:20
@danifunker danifunker changed the title Add pcap builds PCAP (bridged) networking: release-ready capture, cross-platform elevation, and in-core NFS for Pcap Jun 22, 2026
@techomancer techomancer merged commit 3378f77 into techomancer:main Jun 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants