PCAP (bridged) networking: release-ready capture, cross-platform elevation, and in-core NFS for Pcap#45
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
presenting the Indy as a real L2 host on your LAN.
(the capture is reopened in place).
with frames the guest rejects and starves it. Fixed by widening the ring (32 →
256), draining up to
RX_DRAIN_CAPframes per iteration, and pushing a kernel BPFfilter so only the guest's own unicast + broadcast/multicast frames are delivered.
Capture permissions & installers
/dev/bpf*group-readable and adds the user to theaccess_bpfgroup. The GUIdetects a permission failure and offers to install it (then quit & reopen).
setcap cap_net_raw,cap_net_adminapplied by the deb/rpm postinst;pkexecre-exec fallback for AppImage/portable. UNTESTEDnever auto-downloads.
{autopf}/{autodesktop}) andruns the CHD folder-permission preflight on all macOS builds.
In-core NFS over PCAP
NfsVirtualHostpresents the existing in-core NFS server as a phantom L2 hoston 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.
nfs_pcap_ipwas plumbed everywhereexcept the GUI, so the responder was never built and NFS silently did nothing;
plus a "Use <ip>" free-address suggestion.
handle_udp(keyed by(src, id, proto),5 s age-out, mirroring the NAT engine's
handle_ip) so large guest→host NFSwrites work. A buffered-but-incomplete fragment is consumed rather than bridged
onto the wire.
(it previously derived a NAT/detected gateway that doesn't exist in bridged mode).
Also included
r5kfeature passthrough — an R5000 CPU build variant.docs/pcap-release-plan.md— the release plan for shipping PCAP builds.Testing
src/net.rsnfs_pcap_tests(gatedcfg(all(test, feature = "pcap"))):fragmented request reassembles + the intermediate fragment is consumed;
unfragmented still works.
--features pcap; full--features pcapsuite 282 passed, 0 failed.
Notes / limitations
Start (the virtual host is built at engine construction; no live re-IP).
🤖 Generated with Claude Code