a2065: user-mode NAT backend and LANCE model hardening#220
Merged
Conversation
Give the A2065 Ethernet board real host connectivity via a slirp-style user-mode NAT, and harden the Am7990 LANCE model so a real SANA-II a2065.device driver works against it end to end. NAT backend (src/net/nat/, behind the default-on net-nat feature): a virtual gateway that NATs the guest's outbound IPv4 onto ordinary host sockets with no privileges, identically on Linux, macOS, and Windows. The guest sees the QEMU/slirp segment (10.0.2.15/24, gateway 10.0.2.2, DNS 10.0.2.3, built-in BOOTP/DHCP). smoltcp terminates ARP and the guest's TCP on the gateway and each flow is spliced onto a host socket; DNS resolves through the host's own resolver, and UDP NAT, ICMP echo, and DHCP are handled at frame level. A dedicated a2065-nat thread owns all host sockets and crosses frames to the emulated NIC over bounded drop-on-full channels, so the deterministic core never blocks on the network. Select it with [a2065] net = "nat" or --a2065-net nat. LANCE hardening (src/a2065.rs): received frames now carry the FCS trailer counted in MCNT (drivers read the payload as MCNT - 4), RX and TX frames chain across descriptors (STP..ENP), the init-block MODE word gates the engines (DTX/DRX) and drives the LOOP internal-loopback self-test, and an RX ring overrun sets MISS. STATE_VERSION 32 -> 33 for the new MODE field and the NetConfig::Nat variant. Validated end to end against AmigaOS a2065.device running AmiTCP: the guest resolves DNS, opens TCP connections, and loads a live web page over the emulated board. Docs updated in docs/zorro.md, the configuration guide, the peripherals internals, and the example config.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds real host networking for the emulated A2065 Ethernet board via a slirp-style, user-mode NAT backend (smoltcp + host sockets), and hardens the Am7990 LANCE model so real SANA-II a2065.device drivers can operate end-to-end (FCS/MCNT behavior, descriptor chaining, MODE gating, and MISS handling). This expands Copperline’s peripheral fidelity and makes the A2065 practically usable without requiring host privileges or tap/bridge setup.
Changes:
- Introduces a new
net-natbackend (src/net/nat/) and wires it intoNetConfig::Nat, config parsing, andmake_backend(feature-gated; enabled by default). - Updates the A2065 LANCE model to support real-driver behaviors (RX FCS/MCNT, RX/TX descriptor chaining, MODE bits incl. LOOP self-test, RX MISS).
- Adds CLI/config/docs surface for selecting
nat(--a2065-net,[a2065] net = "nat"), and bumps save-state version.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/zorro.rs | Updates Zorro board metadata docs/error message to include nat backend. |
| src/savestate.rs | Bumps STATE_VERSION to 33 and documents the compatibility change. |
| src/net/nat/mod.rs | NAT backend thread + channel plumbing and lifecycle management. |
| src/net/nat/engine.rs | Frame classifier + smoltcp interface integration + output queueing. |
| src/net/nat/frames.rs | Helpers to build UDP frames and ICMP echo replies; host IP mapping. |
| src/net/nat/tcp.rs | TCP flow NAT with per-flow smoltcp socket and host socket splicing. |
| src/net/nat/udp.rs | UDP per-flow NAT using connected nonblocking host sockets. |
| src/net/nat/dns.rs | DNS forwarder at 10.0.2.3 using host resolver via worker threads. |
| src/net/nat/dhcp.rs | Stateless BOOTP/DHCP responder for the slirp segment. |
| src/net/mod.rs | Adds NetConfig::Nat, feature-gated nat module, and backend selection logic. |
| src/main.rs | Adds --a2065-net flag parsing and help text. |
| src/config.rs | Adds config override for A2065 net backend and updates error messaging. |
| src/a2065.rs | LANCE model hardening: MODE bits, chaining, FCS/MCNT, MISS, loopback behavior. |
| docs/zorro.md | Documents nat backend, configuration, and limitations. |
| docs/internals/peripherals.md | Updates peripheral internals docs for LANCE behavior + NAT design. |
| docs/guide/configuration.md | Documents [a2065] net and the new --a2065-net flag. |
| copperline.example.toml | Updates example config comments to recommend nat. |
| Cargo.toml | Adds net-nat feature (default-on) and introduces optional smoltcp dep. |
| Cargo.lock | Locks new dependency graph for smoltcp and transitive crates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+87
to
+106
| let spawned = std::thread::Builder::new() | ||
| .name("a2065-nat-dns".into()) | ||
| .spawn(move || { | ||
| let addr = resolve_a(&q.name); | ||
| let rcode = if addr.is_some() { | ||
| RCODE_OK | ||
| } else { | ||
| RCODE_NXDOMAIN | ||
| }; | ||
| log::debug!( | ||
| "nat dns: answer name={:?} addr={addr:?} rcode={rcode}", | ||
| q.name | ||
| ); | ||
| let answer = addr.map(|a| (QTYPE_A, a.octets().to_vec())); | ||
| let _ = tx.send((guest_port, q.response(answer, rcode))); | ||
| outstanding.fetch_sub(1, Ordering::Relaxed); | ||
| }); | ||
| if spawned.is_err() { | ||
| self.outstanding.fetch_sub(1, Ordering::Relaxed); | ||
| } |
Comment on lines
+73
to
+82
| let thread = std::thread::Builder::new() | ||
| .name("a2065-nat".into()) | ||
| .spawn(move || run_engine(in_rx, out_tx)) | ||
| .map_err(|e| log::warn!("NAT thread failed to start: {e}; NIC left isolated")) | ||
| .ok(); | ||
| Self { | ||
| to_engine: Some(in_tx), | ||
| from_engine: out_rx, | ||
| thread, | ||
| } |
Two error-path fixes from PR review: - NatBackend keeps no send half when the engine thread fails to spawn, so send() skips the per-frame allocation and the board behaves like a cleanly isolated NIC instead of allocating then failing Disconnected on every frame. - The DNS forwarder replies SERVFAIL when a resolver worker thread fails to spawn, so the guest resolver retries promptly rather than waiting out a DNS timeout.
Regenerated from Cargo.lock after adding the smoltcp dependency for the NAT backend. The Flathub build runs offline, so smoltcp and its transitive crates (byteorder, heapless, hash32, managed, defmt) must be listed as vendored sources; without them the offline cargo build fails with "no matching package named smoltcp".
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
Gives the A2065 Ethernet board real host connectivity through a slirp-style user-mode NAT, and hardens the Am7990 LANCE model so a real AmigaOS SANA-II
a2065.devicedriver works against it end to end. Previously the board only had a loopback backend.NAT backend
New
src/net/nat/behind the default-onnet-natcargo feature (dep:smoltcp). A virtual gateway NATs the guest's outbound IPv4 onto ordinary host sockets with no privileges, drivers, or setup, identically on Linux, macOS, and Windows. Select with[a2065] net = "nat"or--a2065-net nat.The guest sees the QEMU/slirp segment:
10.0.2.15(static or built-in BOOTP/DHCP)255.255.255.010.0.2.210.0.2.3smoltcpterminates ARP and the guest's TCP on the gateway; each TCP flow is spliced onto a nonblocking host socket.getaddrinfo), so VPN/DoH/resolv.conf all just work with no per-OS discovery.smoltcp::wireparsers.a2065-natthread owns every host socket and crosses frames to the emulated NIC over bounded, drop-on-full channels, so the deterministic core never blocks on the network.10.0.2.2reach the host's127.0.0.1.LANCE hardening (
src/a2065.rs)The model had never been driven by a real SANA-II driver. Fixed so it can be:
MCNT - 4(the Linuxa2065driver does exactly this). Without it every RX frame was truncated by 4 bytes.STATE_VERSION32 → 33 for the newmodefield and theNetConfig::Natvariant.Testing
a2065.devicerunning AmiTCP overnet = "nat": the guest resolves DNS, opens TCP connections, and loads a live web page over the emulated board.cargo test,cargo clippy(default and--no-default-features),cargo fmt --check, and the wasm32 web-crate check all pass. Thenet-natfeature is excluded on wasm32.Docs
Updated
docs/zorro.md,docs/guide/configuration.md,docs/internals/peripherals.md, andcopperline.example.toml.🤖 Generated with Claude Code
https://claude.ai/code/session_01U1dK61eNHj3RaCQBQ3GveX