Skip to content

feat: add TAP networking backend for bridged A2065 emulation#1784

Merged
midwan merged 3 commits intoBlitterStudio:masterfrom
tbdye:feat/tap-backend
Feb 14, 2026
Merged

feat: add TAP networking backend for bridged A2065 emulation#1784
midwan merged 3 commits intoBlitterStudio:masterfrom
tbdye:feat/tap-backend

Conversation

@tbdye
Copy link
Contributor

@tbdye tbdye commented Feb 13, 2026

Summary

The PCAP backend works for bridged A2065 networking but has an inherent limitation: the host cannot communicate directly with the emulated machine. pcap_sendpacket() on a bridge interface forwards frames to bridge ports but doesn't loop back to the local kernel stack. This means services running on the host (FTP, SSH, telnet) cannot reach the emulated Amiga, and vice versa.

The TAP backend solves this by creating a kernel-level virtual Ethernet port that participates in the bridge natively. The kernel routes traffic between the TAP port and the local network stack, enabling full bidirectional host-to-guest communication. TAP also eliminates the need for promiscuous mode, BPF filters, and userspace GRO segmentation — the kernel delivers standard-sized frames through the bridge before they reach the TAP fd.

The Approach

Two operating modes

  • Option A (auto-create): Configure a2065=br0. Amiberry opens /dev/net/tun, creates a TAP interface named amiberryN, adds it to the specified bridge via SIOCBRADDIF, and brings it up. On exit, the interface is removed from the bridge and destroyed. Requires cap_net_admin.

  • Option B (pre-created): Configure a2065=tap0. Amiberry attaches to an existing TAP device created by the administrator (e.g. via ip tuntap add). It does not modify bridge membership or destroy the interface on exit. Does not require cap_net_admin.

Graceful fallback

If TAP creation fails (e.g. missing cap_net_admin capability), the backend falls back to PCAP transparently with a log message. No user intervention required.

Build system

TAP is controlled by the USE_UAENET_TAP cmake option (ON by default on Linux, forced OFF on Android, macOS, and FreeBSD). PCAP and TAP can be independently enabled or disabled at compile time.

Bridge MAC handling

The TAP interface MAC is intentionally not set to the emulated NIC's MAC address. When a bridge port's link-layer address matches a destination MAC, Linux creates a "permanent local" FDB entry and delivers those frames to the bridge's own network stack instead of forwarding them to the port. This silently breaks all unicast receive. Instead, the TAP keeps its kernel-assigned random MAC, and the bridge learns the Amiga MAC from outgoing traffic — the standard L2 learning path.

Enumeration cache safety

The UAE device selection system caches ndd[] pointers on first enumeration. TAP enumeration populates a static tap_nd[] array whose entries are referenced by those cached pointers. On subsequent single-device lookups (which happen during a2065_config()), the TAP enumerator searches the existing array rather than re-enumerating, which would free the strings and invalidate the cached pointers.

Testing

Tested on Debian 13 (kernel 6.12) running as a Proxmox VM with a virtio NIC (ens18) bridged to br0. Emulated A4000/040 with A2065 NIC, Roadshow TCP/IP stack.

PCAP regression tests (no regressions found)

Test Config Result
Non-bridge pcap a2065=ens18 PASS — pcap opens physical NIC directly
TAP fail → pcap fallback a2065=br0, cap_net_admin removed PASS — TAP fails gracefully, pcap fallback works
Compile-time pcap-only -DUSE_UAENET_TAP=OFF PASS — pure pcap build, no TAP code compiled

TAP integration tests

Test Result
Auto-create on bridge (a2065=br0) PASS — creates amiberry0, adds to br0, DHCP acquires IP
Ping gateway (192.168.6.1) PASS — sub-20ms replies
Ping internet (8.8.8.8) PASS — sub-20ms replies
Ping host (new capability) PASS — sub-5ms replies (impossible with PCAP on bridge)
FTP download, LAN (192.168.6.134) ~1,658 KB/s
FTP download, US mirror (us.aminet.net) ~198 KB/s
FTP download, EU mirror (ftp.aminet.net) ~61 KB/s
Re-init (Amiga reset) PASS — destroys old TAP, creates new one cleanly

Performance comparison (TAP vs PCAP)

Test PCAP TAP Notes
LAN FTP ~885 KB/s ~1,658 KB/s 1.87x — no GRO segmentation overhead
Remote FTP (EU) ~60 KB/s ~61 KB/s RTT-bound by Roadshow's TCP window
Host-to-guest ping N/A (broken) <5ms New capability with TAP

Generated with Claude Code

@tbdye tbdye requested a review from midwan as a code owner February 13, 2026 20:14
Prepare for the TAP networking backend by widening the preprocessor
scope of shared symbols. Code that both pcap and TAP backends will
use (struct uaenet_data, queue management, open/close/trigger API,
utility functions) moves from #ifdef WITH_UAENET_PCAP to the combined
guard #if defined(WITH_UAENET_PCAP) || defined(WITH_UAENET_TAP).

pcap-specific code (GRO segmentation, pcap worker thread, pcap
enumeration, pcap_open_live/BPF filter, pcap_sendpacket calls)
remains under #ifdef WITH_UAENET_PCAP.

Zero functional changes when WITH_UAENET_PCAP is defined. Verified
by rebuilding, installing, and running PCAP networking tests on the
emulated Amiga.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@midwan
Copy link
Collaborator

midwan commented Feb 13, 2026

Cleanup code duplication in uaenet_tap_create() — The error paths at lines ~928-932, ~971-976, ~984-986, ~1007-1010 all repeat the same cleanup sequence (close(fd); ud->tap_fd = -1; ud->owns_tap = false; ...). A goto cleanup pattern would reduce this repetition, though the current approach is perfectly readable.

Also, the uaenet_tap_enumerate_free() forward declaration inside the .cpp file (line ~769) is unnecessary since the function is defined later in the same file and only called from within.

@tbdye
Copy link
Contributor Author

tbdye commented Feb 13, 2026

Cleanup code duplication in uaenet_tap_create() — The error paths at lines ~928-932, ~971-976, ~984-986, ~1007-1010 all repeat the same cleanup sequence (close(fd); ud->tap_fd = -1; ud->owns_tap = false; ...). A goto cleanup pattern would reduce this repetition, though the current approach is perfectly readable.

Also, the uaenet_tap_enumerate_free() forward declaration inside the .cpp file (line ~769) is unnecessary since the function is defined later in the same file and only called from within.

Good catches.

Regarding the forward declaration, I'll move uaenet_tap_enumerate_free() above uaenet_tap_enumerate() and delete the forward declaration. That's cleaner.

Add a TAP backend alongside the existing PCAP backend for the A2065
network card emulation. TAP operates at L2 through the kernel's bridge,
avoiding PCAP's promiscuous-mode requirement and enabling direct
host-to-guest communication — something PCAP on a bridge cannot do.

Two operating modes:
- Option A (auto-create): user configures a2065=br0, Amiberry creates a
  TAP device (amiberry0), adds it to the bridge, and tears it down on
  exit. Requires CAP_NET_ADMIN.
- Option B (pre-created): user configures a2065=tap0 for a TAP device
  that was set up externally, avoiding STP forwarding delays. Only
  requires CAP_NET_RAW.

The TAP backend is preferred when available and falls back to PCAP on
failure or when CAP_NET_ADMIN is missing. Build-time control via
-DUSE_UAENET_TAP=OFF; disabled automatically on non-Linux platforms.

Performance: LAN FTP throughput ~1.6 MB/s (vs ~885 KB/s with PCAP).
Host-to-guest ping <5ms (previously impossible with PCAP on bridge).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tbdye tbdye requested a review from midwan February 13, 2026 21:51
@midwan midwan merged commit 0bf4c9c into BlitterStudio:master Feb 14, 2026
14 checks passed
@tbdye tbdye deleted the feat/tap-backend branch February 14, 2026 13:44
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