Skip to content

a2065: user-mode NAT backend and LANCE model hardening#220

Merged
LinuxJedi merged 3 commits into
mainfrom
feature/a2065-user-mode-nat
Jul 18, 2026
Merged

a2065: user-mode NAT backend and LANCE model hardening#220
LinuxJedi merged 3 commits into
mainfrom
feature/a2065-user-mode-nat

Conversation

@LinuxJedi

Copy link
Copy Markdown
Owner

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.device driver works against it end to end. Previously the board only had a loopback backend.

NAT backend

New src/net/nat/ behind the default-on net-nat cargo 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:

Setting Value
IP 10.0.2.15 (static or built-in BOOTP/DHCP)
Netmask 255.255.255.0
Gateway 10.0.2.2
DNS 10.0.2.3
  • smoltcp terminates ARP and the guest's TCP on the gateway; each TCP flow is spliced onto a nonblocking host socket.
  • DNS is answered through the host's own resolver (getaddrinfo), so VPN/DoH/resolv.conf all just work with no per-OS discovery.
  • UDP NAT (per-flow host sockets), ICMP echo, and BOOTP/DHCP are handled at frame level with the smoltcp::wire parsers.
  • A dedicated a2065-nat thread 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.
  • Outbound IPv4 only; no inbound/port-forwards, no IPv6, no IP fragmentation. ICMP echo is answered locally by the gateway (a successful ping proves the NAT is up, not that the target is reachable) — as with classic slirp. TCP/UDP to 10.0.2.2 reach the host's 127.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:

  • RX FCS/MCNT: received frames store the 4-byte FCS trailer and MCNT counts it — drivers read the payload as MCNT - 4 (the Linux a2065 driver does exactly this). Without it every RX frame was truncated by 4 bytes.
  • RX and TX buffer chaining: frames spanning multiple descriptors (STP..ENP) now work in both directions; drivers configure many small buffers in the 32 KiB board RAM, so a 1500-byte frame must chain.
  • MODE word: the init-block MODE gates the engines (DTX/DRX) and drives the LOOP internal-loopback self-test some drivers run at power-up.
  • MISS on RX ring overrun.
  • STATE_VERSION 32 → 33 for the new mode field and the NetConfig::Nat variant.

Testing

  • New unit tests for the LANCE chaining/FCS/MODE/MISS behavior and for the NAT protocols (DHCP, DNS, UDP, ICMP, ARP, and a full TCP splice driven by a second smoltcp stack as the guest harness); all CI-safe (localhost only).
  • Validated end to end against AmigaOS a2065.device running AmiTCP over net = "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. The net-nat feature is excluded on wasm32.

Docs

Updated docs/zorro.md, docs/guide/configuration.md, docs/internals/peripherals.md, and copperline.example.toml.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U1dK61eNHj3RaCQBQ3GveX

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.
Copilot AI review requested due to automatic review settings July 18, 2026 19:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-nat backend (src/net/nat/) and wires it into NetConfig::Nat, config parsing, and make_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 thread src/net/nat/dns.rs
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 thread src/net/nat/mod.rs
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".
@LinuxJedi
LinuxJedi merged commit f0df73f into main Jul 18, 2026
11 checks passed
@LinuxJedi
LinuxJedi deleted the feature/a2065-user-mode-nat branch July 18, 2026 20:35
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