Networking¶
Amix networks the way a stock AT&T System V Release 4 workstation does: a STREAMS-based TCP/IP stack with both TLI and BSD sockets, configured with static IPv4 addresses ✅. The native Ethernet card is the Commodore A2065 (a LANCE/Am7990 board), which appears as the interface aen0 ✅. There is no DHCP ✅ and no PPP 🟡; DNS resolution is disabled out of the box (the resolver falls back to /etc/hosts) and must be switched on by hand ✅/🟡. NFS works as both server and client ✅. SLIP exists but is buggy enough that you must reboot between dial-up sessions 🟡.
For driver-level mechanics of how a network card hangs off the kernel, see the STREAMS driver guide and the Hydra case study. For the broader kernel picture see the kernel architecture page.
This page condenses §11 of the research brief. Networking facts there are tagged ✅ where they come from SVR4 itself or repo source, and 🟡 where they come from amigaunix.com / community write-ups. Carry those tags as written.
The stack: SVR4 STREAMS TCP/IP¶
- Amix uses the SVR4 STREAMS framework for its protocol stack ✅. TCP/IP, the transport providers, and the network drivers are all STREAMS modules pushed onto a stream — this is the same architecture SVR4 documents in its Streams Programmer's Guide and Network Programmer's Guide (both cited by the Ditto driver paper as the references for writing such drivers) ✅.
- Two programming interfaces are available on top of it: TLI (the SVR4-native Transport Layer Interface) and BSD sockets ✅.
- A network interface driver is therefore a STREAMS character driver — a third kind of driver alongside plain block and character drivers, distinguished by carrying a
streamtabrather than ordinaryread/writeentry points ✅. See how STREAMS drivers differ.
Because the stack is statically linked into a monolithic kernel, and stock Amix has no loadable modules (one third-party exception) ✅, adding or swapping a network card driver means relinking /unix (see Adding a driver below and the kernel build page).
Interfaces and devices¶
| Card | Interface | Bus | Driver | Tag |
|---|---|---|---|---|
| A2065 (Am7990 LANCE Ethernet) | aen0 |
Zorro II | native (aen/ in the kernel source tree) |
✅ |
| Hydra AmigaNet (NE2000 / DP8390) | hya0 |
Zorro II | modern hydra-amix STREAMS/DLPI driver |
✅ |
| Z3660 (accelerator's onboard Zynq GEM) | zen0 |
Zorro III (eth window at 0x10000000, inside the identity-mapped low 1 GB) |
modern z3660eth STREAMS/DLPI driver — works on real hardware (2026-06) |
✅ |
| Ariadne I | (Gateway! Vol.2 driver) | Zorro II | community | 🟡 |
The A2065 is the card Commodore shipped and the only Ethernet board Amix supports out of the box ✅. aen0 is what ifconfig and the routing tables refer to. Both modern STREAMS/DLPI add-on drivers (Hydra → hya0, Z3660 → zen0) are real-hardware-verified — see Adding other network cards.
Note: the loopback interface (lo0) and the conventional 127.0.0.1 are SVR4 standard; only the physical-interface name aen0 is Amix-specific here.
Static IP configuration (no DHCP)¶
There is no DHCP client in Amix — every host gets a static IPv4 address ✅. The pieces are the standard SVR4 / BSD-derived files and commands:
# Bring the A2065 up with a static address and netmask.
ifconfig aen0 192.168.0.10 netmask 255.255.255.0 up
# Verify.
ifconfig aen0
Hostname and host/network databases live where SVR4 keeps them:
/etc/hosts # name → IP, and the resolver's only source until DNS is enabled
/etc/netmasks # subnet masks per network
/etc/networks # network names
/etc/netconfig # STREAMS/TLI transport provider table
The node name lives in a file, not in the kernel — check before you believe otherwise. A boot
applies the host's name at runtime from /etc/nodename ✅, and on a kernel line whose
boot-slice kernel is loaded from the boot partition, that kernel contains no hostname at all —
measured 2026-09-09: neither the old nor the new name appears anywhere in the running kernel's bytes,
yet the box reports the new one. SVR4's setuname -n patches /stand/unix, which on such a line
is not the kernel that boots, so running it neither persists a rename nor invalidates the running
kernel's checksums 🟡.
The nuance matters because the opposite is true of some images: where /stand/unix is what boots,
a setuname -n rename does live in that kernel file and must be re-applied after replacing it. Both
behaviours are real; they belong to different image lineages, and a note describing one is not a fact
about the other ✅. Establish which applies to the image in front of you before relying on either —
strings the kernel that actually boots for the name.
Renaming a host also touches /etc/inet/hosts (the entry the interface's own address resolves
through) and /etc/net/{ticlts,ticots,ticotsord}/hosts (the RPC transport names, a manual step no
first-boot script performs) ✅.
Default route — the metric is mandatory. Amix requires a metric (hop count) argument on route add default; omit it and the route is rejected ✅/🟡:
The trailing 1 is the metric/hopcount, not an option flag. This is the single most common networking gotcha on Amix and is listed in the quirks checklist.
Reading the routing table back — check /stand/unix first ✅/🟡. On the project's 2.1c bench
image, whose /stand/unix is not the kernel it boots, netstat -rn prints
read error on routehash and exits 1 (four runs over three boots), and netstat -in exits 0 with
only its header line ✅. netstat reads the kernel through /dev/kmem at addresses taken from
/stand/unix (quirk 80), so a namelist that does not match the
running kernel is the leading explanation 🟡. On that image netstat -s also counts no IP packets
while ifconfig shows the interfaces up ✅, and naming /stand/unix explicitly changes nothing ✅.
That image's /stand/unix is the retail 2.1 kernel, byte for byte, an unbound ELF relocatable, and
no file there holds the booted kernel's symbols ✅ (read offline from the image file); a run with the
booted kernel's own symbols is the next test. Boxes whose
/stand/unix is the booted kernel have shown both commands working
(quirk 26). Practical rule: a
script should not depend on netstat -r to read the routes; set them with route and keep its own
record of what it set (quirk 93).
Raw DLPI on aen0: what a DHCP client meets ✅¶
Stock Amix has no DHCP client, so one has to talk to the A2065 driver through DLPI itself. These
are the stock aen driver's and IP's answers as measured on the project's 2.1c bench image (emulated
A3000 with an A2065, IP already linked and addressed on aen0) ✅:
aenis a DLPI Style 1 provider ✅. On a fresh streamDL_INFO_ACKreports state 0 (unbound) and no address (dl_addr_offset0),DL_ETHER, a maximum SDU of 1500 and an address length of 6.- It never answers
DL_ATTACH_REQ✅: noDL_OK_ACKand noDL_ERROR_ACK. An unknown primitive is dropped silently too, and the stream stays usable. A client that attaches unconditionally hangs; attach only whendl_provider_stylesays Style 2. - The station address comes back in
DL_BIND_ACK✅, and inDL_INFO_ACKonly once the stream is bound (state 3). A second stream can bind SAP0x0800while IP's own stream is linked, and it then receives a copy of every IP frame (58 frames queued in 3 s). - An open can return a slot an earlier process left bound ✅. It arrives already bound (state 3,
the old SAP, the address present) with 53–58 frames queued before any request is sent; a fresh slot
queues none. The acks are
M_PCPROTO, so they still overtake the queued data. A client must check the state inDL_INFO_ACKand drain or skip stale frames. - Every received frame is 14 bytes longer than its IP datagram ✅ (342 bytes for a 328-byte DHCP packet): the receive path counts the Ethernet header into the copied length. Trust the IP and UDP lengths, never the frame length.
DL_UNITDATA_REQtakes a 6-byte destination atdl_dest_addr_offsetand fills the Ethernet type from the bound SAP ✅. A broadcast DHCPDISCOVER built with the client's own IP and UDP headers (source0.0.0.0) goes out, and both the broadcast OFFER and a unicast ACK come back on the DLPI stream.- A
DL_UNITDATA_REQwith a data part under 60 bytes is refused ✅:putmsgfails withERANGE(errno 34) at the stream head, and nothing is sent. A 28-byte ARP body fails, and so does one padded to 46 bytes (Ethernet's minimum payload); padded to 60 bytes it goes out. The driver'smodule_infosets its minimum packet size toAEN_MIN, 64 minus the 4-byte CRC, a figure that counts the 14-byte Ethernet header, but the stream head checks it against the data part alone. The driver lengthens a short frame itself when it transmits, so the 60-byte floor is only the stream head's. IP's own packets never meet it, and a 328-byte DHCP message is unaffected; a raw-DLPI client sending ARP or another short frame must pad its data part to 60 bytes. - IP answers an unexpected unicast ACK with ICMP port unreachable ✅ (type 3, code 3) when the
interface has an address and nothing is bound to UDP port 68. With a socket bound to
*:68it never does, and renew and rebind ACKs arrive on that socket. A DHCP client needs its port-68 socket from the moment it is bound. - A UDP send to
255.255.255.255withSO_BROADCASTleaves as the subnet's directed broadcast (x.y.z.255, Ethernet broadcast destination) ✅. A broadcast REBIND therefore reaches only the local segment; a relay agent would not see it. - A unit with no board behind it fails at once ✅:
/dev/aen1on a machine with one A2065 returnsENXIO/ENODEVimmediately. A machine with no A2065 at all was not tested 🔴.
Under Amiberry the emulated A2065 rewrites the station address on its way to the host (emulation fidelity). The driver-side view of the first four points and the short-frame refusal is in writing a STREAMS driver; the checklist item is quirk 95.
DNS is off by default — turning it on¶
By default the C library resolves names from /etc/hosts only; DNS lookups are disabled ✅. This is deliberate in the shipped configuration — the stock libsocket.so does not call a name server. Enabling DNS means swapping in the DNS-capable library on two resolution paths and providing a resolver config. This procedure is now first-hand verified on a running Amix 2.1 ✅:
# 1. gethostbyname() path (ping / telnet / ftp clients): swap libsocket -> libsockdns.
cp /usr/lib/libsocket.so /usr/lib/libsocket.so.orig
ln -f /usr/lib/libsockdns.so /usr/lib/libsocket.so
# 2. TLI / netdir path: activate the DNS-enabled transport table.
cp /etc/netconfig /etc/netconfig.TCP
cp /etc/netconfig.DNS /etc/netconfig # appends /usr/lib/resolv.so to tcp/udp/icmp/rawip
# 3. resolver config — nameservers ONLY (see the domain warning below).
cat > /etc/resolv.conf <<'EOF'
nameserver 192.168.0.1
nameserver 192.168.0.2
EOF
Notes and caveats:
/usr/lib/libsockdns.sois the DNS-enabled socket library; hard-linkinglibsocket.soonto it is what flipsgethostbyname()from hosts-only to DNS ✅. Useln -f(atomic relink) so already-running daemons keep the old inode safely./etc/netconfig.DNSis a ready-made variant of the SVR4 transport table that appends/usr/lib/resolv.soto each provider; copying it over/etc/netconfigenables DNS on the TLI/netdir_getbyname()path ✅. Both files ship with the install.in.named(BIND) is only needed if this host is serving DNS; a pure client needs just the two swaps above plus/etc/resolv.conf✅.
🛑 Leave the domain UNSET — the
/etc/domain"general weirdness". If a default domain is set, the resolver appends it to every lookup, including fully-qualified names — sowww.google.comis queried aswww.google.com.<yourdomain>. If your local zone has a wildcard, that resolves to the wrong address and never falls back (the classic symptom ispinggettingICMP Host Unreachablefor a name while the literal IP works). The domain is set atsysinitfrom/etc/domainviadomainname \cat /etc/domain`. Fix it by emptying the file (cp /dev/null /etc/domain, orecho nodomain > /etc/domain) anddomainname ""to apply now.options ndots:1` does not help — this resolver ignores it. ✅ (Documented as "general weirdness" on amigaunix.com and confirmed first-hand.)⚠️ Also fix the boot-time
ifconfigs, or enabling DNS adds ~3 minutes to every boot. After enabling DNS via the procedure above, the box stalls ~3 minutes atThe system is coming up. Please wait.on every boot. Stock Amix (DNS off) does not. Reproduced first-hand on Amix 2.1c under Amiberry, 2026-06-07 ✅.Root cause ✅:
/etc/rc2.d/S69inetbrings networking up with twoifconfigcalls that take hostnames, each forcing agethostbyname(): -ifconfig lo0 localhost up— inS69inetitself -ifconfig aen0 \uname -n` up -trailers— in/etc/inet/network-config`With DNS enabled,
gethostbyname()tries DNS first, but these run before the default route is installed (route add default …happens later, in/etc/inet/rc.inet). With no route to the nameservers, each lookup burns the full resolver retransmit schedule — ~90 s each, ~180 s total — before falling back to/etc/hosts. With DNS off (stock) the same names resolve instantly from/etc/hosts, which is why the trap appears only after you enable DNS.This is independent of the
/etc/domainweirdness above ✅ — a box with an already-empty/etc/domainstill hangs the full 180 s. They are two different problems; fix both.
The fix — give both boot-time ifconfigs a literal IP (boot 210 s → 29 s; runtime DNS is unaffected) ✅. Configuring your own interface should never depend on a name service:
# /etc/rc2.d/S69inet
- /usr/sbin/ifconfig lo0 localhost up
+ /usr/sbin/ifconfig lo0 127.0.0.1 up
# /etc/inet/network-config
- /usr/sbin/ifconfig aen0 `uname -n` up -trailers
+ /usr/sbin/ifconfig aen0 <this-host-static-ip> up -trailers
lo0 is always 127.0.0.1; aen0 takes this host's own static address (the same one already in /etc/hosts — e.g. 192.0.2.38 in the LAN setup). Only the boot path stops calling the resolver; DNS for clients, mail, etc. is untouched ✅.
🔗 Edit
S69inetin place — it is a hardlink (the same inode is also/etc/init.d/inetinit), so unlink/recreate would desync the two ✅. And never leave backup copies namedS*inside/etc/rc2.d/—rc2'sfor f in /etc/rc2.d/S*glob will run them at boot; park backups elsewhere ✅.
For a complete, reproducible LAN setup (static IP at boot, gateway, DNS, internet, reachable inbound — all surviving reboot) under Amiberry, see Putting Amix on your real LAN.
gethostbyname() takes names, not dotted quads ✅¶
The stock hosts-only libsocket gethostbyname() does not accept an IPv4 literal ✅: given
"a.b.c.d", it opens /etc/hosts, reads it, makes no other lookup and fails, while the same call
with a name listed in /etc/hosts works (measured with truss during the rsync 3.1.3 port: its
bundled getaddrinfo passed the address string through and reported "Unknown server error"). Whether
the DNS-enabled libsockdns behaves the same is untested 🔴. Porting rule: parse IPv4 literals
yourself with inet_addr() before calling gethostbyname(); the rsync port patches its
lib/getaddrinfo.c this way, and apkg does the same (package repository).
Unix-domain sockets: the path is a mode-000 FIFO, and chmod breaks it ✅¶
bind() on an AF_UNIX stream socket creates its path as a FIFO with no permission bits
(st_mode 010000, ls -l shows p---------: S_ISFIFO true, no S_IFSOCK) under any umask
(000, 022 and 077 measured) ✅. connect() to it works with any of the
usual address lengths (sizeof(struct sockaddr_un) = 110, strlen(path) + 2, + 3) ✅. After a
chmod(path, 0777) (mode 010777), every connect() fails with ENOTSOCK (errno 95) at all
three lengths ✅. The mode bits are not checked: a non-root client (uid 2, bin) connects to the
mode-000 path and gets the server's data, just as root does ✅. So it is the chmod itself that
breaks the socket, not the mode it sets, and a mode-000 socket is no access control. Measured in
emulation on the 2.1c bench image (stock userland; its kernel is described under
quirk 93) with GCC 14 test programs. Porting rule: never chmod a bound AF_UNIX path on Amix. Daemons that do (prngd chmods
its socket, so its own --kill and prngd-ctl could never reach it) need that call removed; prngd's
UnixWare DONT_CHMOD_SOCKET define was meant for exactly this but nothing in 0.9.29 read it.
Non-blocking connect(): use O_NDELAY, not O_NONBLOCK ✅¶
A TCP connect() on a socket set non-blocking with fcntl behaves differently under the two flags
(GCC 14 probe with the stock libsocket/libnsl, the 1.1 disks booted on 68030 and 68060 kernels in
emulation, 2026-11-02) ✅:
O_NONBLOCK |
O_NDELAY |
|
|---|---|---|
connect() to a remote host |
−1, errno 11 EAGAIN ("No more processes") |
−1, errno 150 EINPROGRESS |
select() for write |
writable at once | writable once connected |
getsockopt(SO_ERROR) |
0, for an open and a closed port alike | 0 when connected; for a refused port select reports readable and writable and getsockopt itself fails with EPIPE (32), raising SIGPIPE |
a second connect() |
errno 149 EALREADY |
errno 133 EISCONN |
Under O_NONBLOCK the program learns nothing about the outcome. A connect to 127.0.0.1 or the box's
own address completes at once (rc 0) under either flag. Porting rule: code written for POSIX
(Dropbear's dbclient failed every outbound connection with "No more processes") should connect under
O_NDELAY, switch to O_NONBLOCK once connected, and ignore SIGPIPE around the SO_ERROR query.
A sockmod open that fails can leave a freed queue on the STREAMS run list and panic the stock
kernel in queuerun (quirk 92).
NFS (server and client)¶
Amix includes NFS as both server and client ✅, the SVR4-standard RPC/NFS stack. The install/boot kernel is itself NFS/RPC-capable: the on-floppy bootstrap embeds a full NFS/RPC client string table (confirmed by string analysis of amix_21_boot.adf) ✅, so network booting / network install paths are wired in at the bootstrap level.
Typical usage follows ordinary SVR4 conventions:
# Server: export a directory (entries in /etc/dfs/dfstab, then):
shareall
share
# Client: mount a remote export.
mount -F nfs server:/export/home /mnt
share/shareall/dfstab and mount -F nfs are the SVR4 distributed-filesystem (DFS) interfaces; Amix carries them as part of the SVR4 base ✅. For local disk and filesystem details (UFS vs s5) see filesystems and disks.
STREAMS message-block exhaustion — the box wedges with memory to spare 🟡¶
Under sustained bench sessions a box can wedge while freemem is still healthy 🟡. The console
signature is WARNING: ldterm: (ldtermsrv) out of blocks and console_get_buffer: out of blocks; the
kernel is still alive (it echoes those warnings on each keypress) but TCP dies first and console echo
second. Crucially freemem was still ~2100 pages (≈4.3 MB) when it happened, so this is not page
exhaustion — SVR4 STREAMS mblks come from their own preallocated arena, which the entire network
stack lives on, and that arena can starve while the page pool is fine ✅ (mechanism) / 🟡 (the leak).
It tracked the number of telnet/FTP sessions opened, not the filesystem workload — pointing at a
per-connection / per-packet mblk leak on the network path (the specific driver is not yet
isolated, so this is carried 🟡). Bench workarounds: power-cycle after heavy session churn, and run
long scripts as a single session with output redirected to a file on the box rather than streamed
over many short-lived connections.
Distinguishing it from a heap/page leak is easy once you know the shape: sample freemem (via
live adb on /dev/kmem) —
if it barely moves while the box dies, the culprit is STREAMS mblks, not the heap. (This is an
open issue on the z3660eth / TCP network path; cdfs was
ruled out with these numbers.)
A 2026-07-27 soak of 69 530 cdfs mount/read/unmount cycles measured FAIL=0 in every STREAMS class
with use at baseline, so cdfs is not a source of this ✅; and a refused (rather than timed-out)
connection is the inetd throttle,
not this.
A different STREAMS failure, a kernel panic in the deferred-service routine queuerun, is
quirk 92.
TCP's 4 KB lower read queue: IP drops the fourth segment of every burst, uncounted ✅¶
On the stock kernel, a fast sender loses every segment past the third in each burst it delivers to
the box, and no counter on the box records it. The visible symptom is an inbound TCP transfer
(FTP put into the box) running at a few tens of KB/s while the outbound direction runs at full
speed. The sender shows hundreds of retransmissions per few MB, each after a ~200 ms timeout. This
stack has no SACK, so a lost segment costs a whole timeout ✅.
The discard. ipintr, IP's lower-read service procedure, drains its whole input queue in one
pass. For each datagram it first counts it (ipstat.ips_total), then asks canput() of the protocol
queue above it. When that queue is full, it calls freemsg() and moves on. No ipstat or tcpstat
field is incremented, and the STRLOG call is compiled out. In the USL SVR4 source this is
netinet/ip_input.c, and the Amix binary has the same code (ipintr+0x41c in both the stock and the
2.1c kernels). The allocation failure just above it is silent the same way ✅.
The limit. TCP's lower read queue is the one queue under IP that every TCP connection shares. Its
high-water mark comes from TCP's static module_info: tcpm_info[] gives every entry
hiwat 4096, lowat 1024. IP's own is 8192. STREAMS copies that value into the queue when IP is
linked under TCP at boot, so no mtune tunable or ndd-style setting reaches it. putq counts data
bytes, and a full segment is about 1500 of them:
Segment in one ipintr pass |
Queue bytes after it | |
|---|---|---|
| 1st | 1500 | passed up |
| 2nd | 3000 | passed up |
| 3rd | 4500 ≥ 4096 → QFULL | passed up, queue now full |
| 4th and later | — | canput fails, freed |
TCP's service procedure cannot run until ipintr returns. So whenever more than three full segments
reach IP between two runs of the STREAMS scheduler, the rest of the pass is lost ✅. The per-connection
receive window (24576 bytes, SO_RCVBUF) is a different queue, each connection's upper read
queue, and changing it does not help. Shrinking the window below 4 KB avoids the loss only by capping
throughput at about three segments per round trip.
Who hits it. Any driver that hands IP several frames at once. A polled driver does this by
construction. z3660eth drains a whole
60 Hz tick's worth of frames per pass, up to IP's 8 KB, so its pushes collapsed to 25–30 KB/s. An
interrupt-per-frame driver on a slow CPU can hit it too, whenever interrupts batch. The limit is in
the stack, not in any one driver 🟡 (only z3660eth has been measured).
Why it hides. Every counter a user can read looks healthy. The driver delivered every frame, every
IP and TCP error counter is zero, and the only sign is that netstat -s's IP "total packets received"
runs a few hundred above TCP's "total packets received" over a transfer. On a quiet LAN the honest
gap is only the broadcast floor, a few a second. A packet capture on the sender shows the 4th and
later frames of each back-to-back train lost, which reads like a receiver going blind. It is the
kernel. (Mind that netstat reads /stand/unix for its symbols; on a box booting a different
kernel, point it at the matching one.)
The fix: one long word of kernel data. Raise that module_info entry's mi_hiwat from 0x1000
to 0x4000 (16384). That covers IP's 8 KB queue plus one segment, so one ipintr pass always fits ✅.
In the stock 2.1c /stand/unix the word is at .data+0xf9c8, file offset 0xe6ba4, and reads
00001000 (followed by the lowat word 00000400). Other kernels place it elsewhere. Find it
through tcplrinit's qi_minfo, which points at tcpm_info[3], and check the old value before
writing. Patching the file takes effect at the next boot. On a running kernel the copy that counts is
the live queue's q_hiwat. Writing it through /dev/kmem, after checking that the queue's q_qinfo
is tcplrinit, gives the same effect at once.
Measured on a real Z3660 box (68060, 100 Mbit, 4 MiB FTP push, 2026-10-21) ✅:
| Push | Sender retransmissions | IP in − TCP in | |
|---|---|---|---|
| stock 4096 | 24.6 KB/s | 372 | 443 |
live q_hiwat raised to 16384 |
334 / 294 KB/s | 13 / 15 | 22 / 15 (the broadcast floor) |
| kernel built with the patch, with the driver's back-pressure | 330–372 KB/s | 0–10 | 9–17 |
The pull was unchanged at about 640 KB/s. The kernels this project builds carry the patch as a build-time data patch with an old-value check (first-party record, 2026-10-21; not yet in a public repository). The other lever, for a stock kernel left unpatched, is driver-side pacing: hand IP at most two or three full frames per scheduler pass, then re-queue. The full Z3660 numbers, including the driver and firmware fixes that come on top, are on Z3660 ethernet.
The inetd anti-looping throttle — one service refuses connections while the box is healthy ✅¶
If telnet answers Connection refused while FTP still answers, ICMP is alive, and the console shows
an undisturbed login:, the box is fine — SVR4 inetd has disabled that one service because
something opened 40 connections to it inside 60 seconds ✅. It re-enables itself within ten minutes
with no intervention, and it says so only into a log nobody is reading (below). This is the most
misleading failure shape on an Amix box: it looks like a filesystem/STREAMS wedge, yet it reproduces
from bare TCP connect/close alone, with the filesystem unmounted ✅.
The mechanism and its constants ✅¶
inetd's accept loop counts invocations per service entry and, on the 40th inside the window,
logs a message, closes and deregisters the listening socket, and arms a re-enable alarm. All three
constants are compile-time immediates in the shipped /usr/sbin/inetd (Amix 2.1c; 38 228 bytes,
md5 ddfbd40aaaa02a1935a5ef6a37879337, not stripped), read out of its disassembly ✅:
| Constant | Value | Governs |
|---|---|---|
TOOMANY |
40 invocations | the trip threshold, counted per service |
CNT_INTVL |
60 s | the counting window |
RETRYTIME |
600 s | the alarm() that re-enables the service |
These match the classic BSD inetd values 🟡 — but this build encodes them as immediates, which
is what makes the next section true. The message is
<service>/<proto> server failing (looping), service terminated at daemon.err, tagged
inetd[<pid>] (openlog("inetd", LOG_NOWAIT|LOG_PID, LOG_DAEMON)) ✅.
Three details change how you reason about it ✅:
- The window is anchored, not sliding.
se_timeis stamped when the counter is at 1 and the elapsed check runs only once the counter reaches 40, so the real rule is: the 40th connection of a window trips iff it lands within 60 s of the 1st. A trailing-60 s counter held under 40 is therefore a sound and conservative guard. - The 600 s re-enable timer is global, not per service.
retry()re-setup()s every service whose listening fd is-1, so a second service that trips while the timer is already running does not get its own 600 s. Recovery is "up to 600 s"; a recovery much shorter than ten minutes is not evidence against this mechanism. - The refusal you observe lands later than the 40th connect, because the kernel's listen backlog
keeps completing handshakes while
inetdworks through them. Measured first-refusal indexes across four trips: 40, 61, 68, 70 ✅. 40 is the floor and the only safe number to design against — never calibrate a guard against an observed refusal index.
There is no knob ✅¶
This build exposes no per-service rate cap, by any syntax or flag ✅ — established three independent ways:
- The
inetd.confwait field is a boolean.getconfigent()parses the 4th field with a single full-tokenstrcmp("wait", …)and stores 0/1. There is nose_maxmember and no suffix parsing, so the 4.4BSD/Linuxnowait.<max>syntax is simply not implemented ✅. - Live confirmation.
telnet stream tcp nowait.100 root /usr/sbin/in.telnetd in.telnetdplus aSIGHUPwas re-read without complaint, telnet kept working — and the service still tripped, at connect #68. The.100is silently ignored: a config that looks like it worked and did nothing ✅. - No command-line flag either. Argument parsing is hand-rolled and accepts exactly
-d(debug),-s(standalone, i.e. outside the SAF), and-t(log every connection); anything else printsinetd: Unknown flag -%c ignored.There is no-R rate✅.
So the only ways to move the threshold are to patch three immediates in a vendor binary, or not to trip it. Don't trip it — see the operational rule below.
It is per service — which makes the diagnosis one command ✅¶
The counter lives in the per-service servtab entry, so every nowait service in
/etc/inet/inetd.conf has its own independent counter (on the stock image: ftp, telnet, shell,
login, exec, uucp, nntp, finger). Measured in both directions on the same box ✅:
- tripping ftp (70 rapid connects to :21) → :21 refused, :23 still open
- tripping telnet (68 rapid connects to :23) → :23 refused, :21 still open
Port 21 answering while port 23 refuses ⇒ the throttle. Nothing needs fixing; the service returns by itself. It also means you can still pull evidence off the box over FTP while telnet is throttled.
Distinguish it from the two failures it imitates ✅:
| What you see | What it is |
|---|---|
One port refuses (ECONNREFUSED), another answers, ICMP alive, console healthy |
this throttle — wait up to 10 min |
Ports time out, console prints ldterm: (ldtermsrv) out of blocks |
STREAMS mblk starvation (previous section) 🟡 |
| Nothing answers and the console is dead | a genuine kernel wedge |
Why it is silent: syslogd ships deliberately disabled ✅¶
The box does report the trip — into a void. Everything the logging system needs is present and
correct on the stock image: /usr/sbin/syslogd (22 728 bytes, May 1992), a valid /etc/syslog.conf,
the eight /var/log/* targets (all present, all zero-length, never written), and /dev/log +
/dev/conslog. What is missing is the daemon ever starting — /etc/init.d/syslogd line 8 is a
vendor-hardcoded exit ✅:
sh -x /etc/init.d/syslogd start outputs exactly + exit. Note also that this SVR4 logs to
/var/log/*, not /var/adm/messages ✅ — looking for the latter finds nothing and misleads you
into "this image has no syslog".
Enabling it is one commented-out line, with no /etc/syslog.conf change: the stock
*.notice;kern.none /var/log/notice line already selects daemon.err ✅.
🔗 Edit with
cpin place, nevermv./etc/init.d/syslogdand/etc/rc2.d/S70syslogdare the same inode (3 hard links, inode 10261 on the stock image).mvreplaces the file and silently breaks therc2.dhook, so the fix works once and never again after a reboot ✅. This is the same hazard as theS69inet/inetinithardlink noted above.
# on the box, as root
cp /etc/init.d/syslogd /etc/init.d/syslogd.orig
sed '8s/^exit$/#exit/' /etc/init.d/syslogd > /tmp/sl.new
cp /tmp/sl.new /etc/init.d/syslogd # cp, NOT mv -- preserves the inode
/etc/init.d/syslogd start # or reboot; S70syslogd now runs it
Verified end to end — both real trips were then recorded ✅:
Mar 23 09:51:02 uaeamix inetd[156]: ftp/tcp server failing (looping), service terminated
Mar 23 10:02:59 uaeamix inetd[156]: telnet/tcp server failing (looping), service terminated
Measured cost, over 23 minutes of deliberate abuse (2 trips, ~170 telnet logins, 120 batched
commands): one daemon, and 293 bytes total across the whole of /var/log ✅ — roughly 80 bytes
per event, into the eight pre-existing files, with no new files or directories. The 20-minute mark
heartbeat is routed to /dev/console only by the stock config and never to a file, so its disk
cost is zero (syslogd -m 0 disables it outright) ✅. Nothing else broke across the session. To also
put it on the console, add one line — daemon.err<TAB>/dev/console. Reverse it by restoring the
exit.
Enabled, yet not running: the stale /etc/syslog.pid ✅¶
With the exit commented out, the same script can still skip starting the daemon at boot, silently.
The start branch of /etc/init.d/syslogd (Patchlevel syslogd_1.0, identical on two separately
installed 2.1c disks) decides "already running" from a pid file ✅:
pid=`cat -s /etc/syslog.pid`
...
kill -0 $pid 2> /dev/null
#
# If there wasn't a pid to kill, or the process
# did not exists, start syslogd
#
if [ -z "$pid" -o $? -ne 0 ]
then
/usr/sbin/syslogd
fi
syslogd writes its pid to /etc/syslog.pid when it starts, and nothing in the boot sequence
removes the file: after a reboot it still holds the previous boot's pid ✅. When S70syslogd runs, it
sends kill -0 to that old number. If any live process happens to own it at that moment, the test
succeeds, the script concludes syslogd is already up, and the daemon is not started. Nothing is
printed; cron, lpsched and inetd start as normal and logins work ✅. Since the daemon never
starts, the file is never rewritten, so the same stale pid is tested again on every following boot.
Early pid numbering repeats from boot to boot, so whether this bites is decided by how many
processes start before S70, not by chance ✅. On one disk, each variant booted twice, with the
previous boot's syslogd at pid 86 in the file:
| Early-boot additions | syslogd after boot |
|---|---|
| none | running (pid 86, the same number as the stale file) |
one extra sysinit entry in /etc/inittab running a short command |
running (pid 87) |
one extra /etc/rc2.d/S01… script running a short command |
running (pid 88) |
| both | not running, 6 of 6 boots; the file still holds 86 |
So an unrelated, harmless addition to the early boot can switch logging off. Recognise it by: no
syslogd in ps -ef although line 8 is commented out, /var/log/* staying zero-length, and
/etc/init.d/syslogd start working when run by hand. sh -x of the script on an affected system,
after pid 86 has gone away again, shows the logic ✅:
+ cat -s /etc/syslog.pid
pid=86
+ [ -x /usr/sbin/syslogd -a -r /etc/syslog.conf ]
+ kill -0 86
+ [ -z 86 -o 1 -ne 0 ]
+ /usr/sbin/syslogd
The kernel side is not involved: once syslogd is started, its open of /dev/log and the
ioctl(I_STR) that registers it as the console logger both succeed ✅.
Remedy 🟡 (follows from the script logic; one boot per install tested, both early-boot additions
present, syslogd running afterwards on both): remove the pid file before S70 runs — for example a
new /etc/rc2.d/S05syslogpid holding rm -f /etc/syslog.pid — or change the start test to look for
the process by name rather than trusting kill -0. If you edit /etc/init.d/syslogd itself, the
cp-in-place rule above applies.
Running, yet nothing filed: syslogd needs m4 ✅¶
A third, independent cause: syslogd runs /etc/syslog.conf through /usr/ccs/bin/m4, which only the
stock C development set ships. Without it the daemon runs, every file action is UNUSED, and
/var/log stays empty ✅ (quirk 77). And while no syslogd is
registered as the console logger, kernel messages after boot are not kept in the message buffer
either (quirk 78).
The operational rule for anything that drives an Amix box ✅¶
The throttle only ever fires on automation. Any harness that opens a fresh telnet (or FTP)
session per command reaches 40 in a minute trivially — a for f in *; do <one login>; done loop
self-destructs at the 40th file and then looks dead for ten minutes. Measured: ~30 invocations/min
never tripped across 200 invocations; ~575/min tripped within seconds ✅.
- Hold one session open for a batch of commands instead of one login per command.
- Or keep invocations under ~30 per 60 s, per service — the same guard is needed on port 21, because a per-file FTP loop trips identically at 40 files.
- Best for bulk work: run an on-box script and poll for progress at a low rate. One soak sustained ~330 filesystem cycles/min at 0.5 telnet logins/min for three hours with no trips ✅.
FTP: passive-mode downloads can arrive short ✅¶
The stock in.ftpd can deliver a passive (PASV) download short — a byte-exact prefix — and
still reply 226 Transfer complete; active (PORT) mode was clean in every bench run (2026-10-02:
passive from cdfs 18/80 short, passive from UFS 0/80, active 0/40) ✅. The server has no SIZE
command, so a client must length-gate each GET against LIST/ls -l itself. Use active mode
for anything you will checksum. Details: quirks §64.
Serial networking: SLIP is buggy, no PPP¶
- PPP is not available on Amix 🟡 — there is no PPP stack to dial out with.
- SLIP exists but is buggy 🟡. The reported workaround is to reboot between SLIP sessions: a connection that has been torn down does not cleanly reset, so a second
dial → connectin the same uptime tends to fail. Treat SLIP as a one-shot-per-boot facility. This is recorded in the quirks checklist.
If you need IP over serial in practice, the community guidance is effectively "use Ethernet (A2065 or Hydra) instead" 🟡.
Adding other network cards¶
Because the stock kernel is monolithic with no loadable modules ✅, a non-A2065 card needs a driver compiled into /unix. The modern, fully worked example is the Hydra AmigaNet driver — and as of 2026-06 it works on real hardware: ARP resolves and ICMP ping reaches both the local gateway and external IPs, which the repo calls "believed to be the first working AMIX network driver for the Hydra card" 🟡 (first-party). See the Hydra case study for the bring-up story.
hydra-amixis a STREAMS / DLPI network driver for the Hydra card (an NE2000 / DP8390 design), rev 1.2a, Zorro II, AutoConfig ID 2121/1 (0x08490001) ✅.- It registers at
cdevswslot 47 with thehyatag. Amix is SVR4.0 — there is noifconfig … plumb; you link the interface in withslink addaen /dev/hya0 hya0, thenifconfig hya0 <ip> netmask <m> up -trailers✅. - Its entry points (
hydraopen,hydrawput,hydraintr,setup_ne2000) handle DLPI primitives (DL_INFO_REQ,DL_BIND_REQ,DL_UNITDATA_REQ) and the INT2 RX/TX interrupt;hydraopenruns a three-method card detect (autocon()/bootinfo with address validation, then direct Zorro II I/O-slot and memory probes — the bootinfo table can be corrupt on 2.1p2), and it deliberately mirrors the existing A2065 LANCE driver (aen/) ✅. - It is built natively on the Amix box (
makein the driver dir,make forceto relink the kernel) with the stockcc/GNU GCC 1.40.5 ✅ or the native GCC 2.7.2.3 its repo names (the "packaged on amigaunix.com" attribution is unsourced 🔴; the only native 2.7.2.3 tree seen is on a collaborator's own machine) — no cross-compiler; its README offers driver source "intended to be compiled against a licensed AMIX 2.1p2 installation" and includes no kernel binaries ✅.
Full detail — the build line, the DLPI flow, and the LANCE-mirroring design — is on the Hydra case study. The general procedure for writing this class of driver is on Writing a STREAMS driver, and the relink/boot-partition steps are on the kernel build page.
A second real-hardware-verified example is the z3660eth driver for the Z3660 accelerator's onboard ethernet (interface zen0) — the network analogue of the A4091 SCSI work, giving Amix full bidirectional TCP/IP on a physical A4000 + Z3660 (2026-06) ✅. It is a contrasting design to hydra: instead of programming a NIC chip directly it speaks the Z3660 firmware's frame mailbox over MMIO, registers at cdevsw slot 51 (tag zen; 48 until 2026-07-30), and services RX from a polled timeout() callout — no int2_tbl/init_tbl edit. The one real-hardware blocker was an INT6 interrupt storm (the firmware raised level-6 on every received frame, but Amix has no eth INT6 handler, so ARP broadcasts hard-locked the box); the fix was to disable the firmware interrupt and keep the polled drain. Bring-up is the same slink plumbing as hydra (slink addaen /dev/zen0 zen0, then ifconfig zen0 … up -trailers). See the Z3660 ethernet driver case study for the mailbox protocol, the storm, and the build/deploy story.
Build note:
hydra-amixis built natively on the Amix box with GCC 2.7.2.3 (the "pkg on amigaunix.com" attribution is unsourced 🔴 — the stock on-box compiler is GNU GCC 1.40.5 ✅). As of 2026-06 a Linux-hosted cross-toolchain also exists —isoriano1968/gcc-cross-amix(m68k-cbm-sysv4-gcc) — so you can build driver objects on a modern host too ✅. Either way you need an installed Amix system to supply the headers and libraries; the cross-toolchain takes them as a sysroot, and its README says "Each user must provide their own AMIX sysroot from a licensed AMIX installation" (the amigaux 1.0 install media are publicly distributed). See the toolchain page.
Quick reference¶
| Task | Command / file | Tag |
|---|---|---|
| Bring A2065 up | ifconfig aen0 <ip> netmask <mask> up |
✅ |
| Bring Hydra up | slink addaen /dev/hya0 hya0, then ifconfig hya0 <ip> … up -trailers (no ifconfig plumb on SVR4.0) |
✅ |
Bring Z3660 (zen0) up |
slink addaen /dev/zen0 zen0, then ifconfig zen0 <ip> … up -trailers (cdevsw 51; real-HW) |
✅ |
| Default route (metric required) | route add default <gw> 1 |
✅/🟡 |
| Static name→IP, resolver fallback | /etc/hosts |
✅ |
| Enable DNS (1/2) | ln -f /usr/lib/libsockdns.so /usr/lib/libsocket.so + /etc/resolv.conf (nameservers only) |
✅ |
| Enable DNS (2/2) | cp /etc/netconfig.DNS /etc/netconfig (activates /usr/lib/resolv.so) |
✅ |
| Unset domain (or DNS breaks) | cp /dev/null /etc/domain; domainname "" — else it's appended to every lookup |
✅ |
| Fix slow boot after DNS | literal IPs in boot ifconfigs: lo0 127.0.0.1, aen0 <static-ip> — else +180 s/boot |
✅ |
| NFS export / mount | share / shareall; mount -F nfs host:/path /mnt |
✅ |
| SLIP | works once per boot; reboot between sessions | 🟡 |
| PPP | not available | 🟡 |
Inbound TCP crawls (push ≪ pull, IP total ≫ TCP total in netstat -s) |
stock TCP lower-read hiwat 4096: patch tcpm_info[3].mi_hiwat → 0x4000 (2.1c file offset 0xe6ba4, old value 0x1000) |
✅ |
STREAMS mblk starvation |
box wedges (ldterm: out of blocks) while freemem is fine; tracks session churn — power-cycle, single-session long jobs |
🟡 |
| Service refuses connections (one port only) | inetd anti-looping throttle: 40 conns/60 s per service, self-heals ≤ 600 s. Diagnose: port 21 open + port 23 refused ⇒ throttle |
✅ |
See the throttle (and every other daemon.*) |
Comment the exit on line 8 of /etc/init.d/syslogd (cp in place — 3 hard links); logs land in /var/log/notice |
✅ |
syslogd enabled but not running after boot |
stale /etc/syslog.pid passes the script's kill -0; rm -f /etc/syslog.pid before S70 |
✅ / fix 🟡 |
See also¶
- Writing a STREAMS driver — how a network interface attaches to the SVR4 stack.
- Hydra case study — a complete modern STREAMS/DLPI network driver (
hya0). - Z3660 ethernet driver case study — a real-hardware STREAMS/DLPI driver (
zen0) over the Z3660 firmware mailbox; the INT6 storm and polled RX. - Z3660 ethernet — the card's firmware frame path, its
z3660cfg.txtkeys, and Amix against AmigaOS throughput. - Kernel architecture — monolithic SVR4, STREAMS, no loadable modules in stock Amix.
- Filesystems and disks — UFS/s5, needed for NFS server exports.
- Quirks — DNS-off-by-default, the
routemetric, and the SLIP reboot bug in one list. - Hardware — the A2065 and other supported expansion.
- amigaunix.com networking notes — end-user network setup (the community source for the DNS/resolver procedure).
Sources¶
- The short
DL_UNITDATA_REQrefusal (first-party record, 2026-10-31) ✅ — the amixuserland/baseARP check in its DHCP client on the emulated 2.1c bench image (stock userland): a SAP0x0806stream on/dev/aen0,putmsgerrno 34 with a 28-byte and a 46-byte data part, sent with 60.amigaux/amixuserland/base/dhcpc/dhcpc.carpcheck(88954e7, onmaster). The minimum isAEN_MINin the stockaen.h, used byaen_minfoinaen.c(amigaux/amix-system). - Stock sources, public ✅ — the stock A2065 driver in
amigaux/amix-system(7cba961):usr/sys/amiga/driver/aen/aen.c. - The
aenDLPI measurements (first-party record, 2026-10-30) ✅ (🟡/🔴 where tagged) — the amixuserland/baseDLPI probe on the emulated 2.1c bench image (A3000, 68030, A2065 as/dev/aen0, IP already linked on it) against a one-client test DHCP server on the host bridge, eight runs over two boots, plus/dev/aen1open tests; the probe, the test server and the write-up are inamigaux/amixuserland/base/docs/dhcp-probe.mdanduserland/base/dhcpc/probe/(8ee48af). The silentDL_ATTACH_REQand the state left byaencloseagree with the stock driver source (aen.c:aenproto,aenopen,aenclose). - The
netstat -rnroutehash failure (first-party record, 2026-10-30) ✅/🟡 — the amixuserland/baseruns on the emulated 2.1c bench image (A3000) over three boots:netstat -rn→read error on routehash, exit 1;netstat -inheader only, exit 0;/stand/unix1475108 bytes and no/unix. The booted kernel is not/stand/unix(the k30 kernel work's comparison, 2026-10-30). The follow-up runs (first-party record, 2026-10-30) ✅:netstat -rn /stand/unixandnetstat -rn /stand/unix /dev/kmemfail alike,netstat -inin both forms prints its header only,netstat -scounts 0 IP packets received whileifconfig aen0/lo0show both up, andod -xandls -lishow/stand/unixas anET_RELhard link to/stand/2.1_unix; a run with the booted kernel's own symbols is still to come. The image-file read (first-party record, 2026-10-31) ✅: the k30 kernel work's offline read of the bench image:/stand/unixand/stand/2.1_unixone inode, byte-identical toamigaux/amix-systemkernels/2.1/unix(sha25697ae253a…), no other kernel in/stand; the booted load image 1,252,347 bytes against 1,171,216 for/stand/unixthroughelf2brel. -
The
AF_UNIXsocket path (first-party record, 2026-10-30) ✅ — the amix-packagesprngd0.9.29 port's GCC 14 test program on the emulated 2.1c bench image (mode010000afterbind(),connect()at three lengths,ENOTSOCKafterchmod) and its rerun (mode 000 under umask 000, 022 and 077; a client dropped to uid 2 withsetgid/setuidconnects and reads); its patch 02 in amix-packagesrecipes/prngd(cc61d24). -
The dotted-quad
gethostbyname()✅ — first-party record, 2026-10-30: the amix-packages rsync 3.1.3 port, atrussof the failing connect on an emulated stock 2.1c (/etc/hostsopened and read, no other lookup, the call failing) and the same call with an/etc/hostsname succeeding; the port'slib/getaddrinfo.cpatch (amix-packagese7ac916,recipes/rsync/patches/02-getaddrinfo-numeric-host.patch). - The 4 KB TCP lower-read queue ✅ — first-party record, 2026-10-21: the stock kernel's
ipintr,putq,canput,iplrputandtcplrputdisassembled from the Amix/stand/unixobjects (stock and 2.1c;ipintridentical, 0x24 apart) against the USL SVR4 source (netinet/ip_input.cipintr,netinet/ip_main.cipm_info,netinet/tcp_main.ctcpm_info,netinet/tcp_output.c,netinet/in_pcb.c,io/stream.c); the 2.1c file-offset word00001000re-read for this page; the live/dev/kmemraise and the patched-kernel rows on a real Z3660 box. The "any batching driver" generalisation is 🟡, since onlyz3660ethwas measured. - The FTP passive-mode truncation ✅ — the 2026-10-02 bench matrix (passive from
cdfs18/80 short, passive from UFS 0/80, active 0/40,226on every truncated transfer) and the earlier 68030 measurement plus the length-gate recipe inamix-kerntools/tools/amixsync.py("the truncation trap"; thisftpdhas noSIZE). The short-write mechanism is a diagnosis from behaviour 🟡. - Research brief §11 "Networking, X11, userland" (STREAMS TCP/IP;
aen0/A2065; static IP, no DHCP; DNS off by default +libsockdns.soswap,in.named,/etc/resolv.conf;route add default <gw> 1; NFS server+client; SLIP buggy; no PPP). - Research brief §2 "Hardware & requirements" (A2065 native; Hydra via
hydra-amix; Ariadne I via Gateway 🟡). - Research brief §6 (
isoriano1968/hydra-amix— STREAMS/DLPI, NE2000/DP8390,cdevswslot 47,hya0, AutoConfig0x08490001, three-method autoconfig detect, nativemake/make forcebuild with GCC 2.7.2.3 (its "from amigaunix.com" attribution is 🔴, unsourced, per the toolchain page),slinkbring-up). - Research brief §4 "Kernel architecture" (monolithic SVR4; STREAMS, TLI + BSD sockets; no loadable modules in stock Amix).
- Research brief §3 / §10 (
amix_21_boot.adfstring analysis — embedded NFS/RPC client string table), viatools/inspect-adf.sh. - Research brief §12 "Quirks checklist" (DNS off by default; SLIP reboot bug).
- Research brief §13 (cross-toolchain:
gcc-cross-amixnow provides a publicm68k-cbm-sysv4recipe; native on-box build still simplest). - Ditto, Writing Amix Device Drivers, 1990 European Amiga Developer's Conference (cites the SVR4 Streams Programmer's Guide and Network Programmer's Guide).
- github.com/isoriano1968/hydra-amix — its README's "License" section (driver source and build scripts only, no kernel binaries, "intended to be compiled against a licensed AMIX 2.1p2 installation").
- github.com/isoriano1968/gcc-cross-amix — its README (each user provides their own AMIX sysroot from a licensed AMIX installation); the amigaux.org downloads page (the publicly distributed 1.0 install media).
- amigaunix.com — networking (community-reported resolver/DNS procedure).
- The A4091-on-Amix project — networking investigation, 2026-06-07 (reproduced locally ✅): instrumented
/etc/rc2per-script timing on Amix 2.1c under Amiberry; DNS-enabled boot 210 s → 29 s after replacing the boot-timeifconfighostnames with literal IPs. Source files:/etc/rc2.d/S69inet,/etc/inet/network-config,/etc/inet/rc.ineton the running system. - The amix-z3660net project — the native
z3660ethSTREAMS/DLPI driver (zen0, cdevsw 51 — 48 until 2026-07-30) for the Z3660's onboard ethernet, validated on a real A4000 + Z3660, 2026-06-21 ✅ (ifconfig zen0,netstat -inzero-error, laptop↔boxping/ftp); the firmware-mailbox protocol, the INT6 storm, and the polled-RX design are on the Z3660 ethernet driver case study. - The STREAMS
mblkforensics (first-party record, 2026-07-12) —mblk-arena exhaustion wedges the box (ldterm: (ldtermsrv) out of blocks,console_get_buffer: out of blocks; TCP then console echo die) whilefreememstays ~2100 pages, tracking telnet/FTP session count rather than filesystem load; suspected per-connection/per-packet leak on the network path, driver not yet isolated — real A4000 + Z3660, carried 🟡. - The
inetdthrottle investigation (first-party record, 2026-07-27) ✅ — the throttle's constants read out of the shipped/usr/sbin/inetd(md5ddfbd40aaaa02a1935a5ef6a37879337):TOOMANY40 /CNT_INTVL60 s /RETRYTIME600 s as compile-time immediates, thedaemon.erropenlog()identity, the anchored (not sliding) window, the global re-enable alarm, the absence of anynowait.<max>syntax or command-line flag (disassembly plus a livenowait.100+SIGHUPtest that still tripped at #68), the per-service independence measured both directions (ftp tripped → 23 open; telnet tripped → 21 open), and thesyslogdenablement (/etc/init.d/syslogdline-8exit, the 3-hard-linkcp-in-place hazard,daemon.erralready selected by the stocksyslog.conf, 293 B of growth in 23 min of abuse). - The stale
/etc/syslog.pid✅ — first-party record, 2026-10-26: the start branch of/etc/init.d/syslogd(Patchlevelsyslogd_1.0) read on two separately installed 2.1c disks; a livesh -x /etc/init.d/syslogd starton an affected boot; atrussof/usr/sbin/syslogd(/dev/logopen andI_STRioctl both succeed); a four-way variant test of early-boot additions on one disk under emulation, two boots per variant. The pid-file-removal remedy is 🟡, one boot per install. - The wedge soak run during the
cdfsport (first-party record, 2026-07-26/27) ✅ — the rate measurements: the refusal reproduced with the filesystem loop paused; four bare-TCP trips (29.9/min → no trip in 200 invocations; ~550–575/min → trip at 40/61/77); three self-recoveries at 9m48s / 10m11s / 10m00s; FTP up throughout; and the box verified healthy while refusing (28 processes, zeroin.telnetd, STREAMSfail=0in every class, the cdfs mount still traversable). - First-party measurement on a real A4000 + Z3660, 2026-09-08/09 ✅ — the node-name mechanism: on the 040/060-port kernel line the name is applied at runtime from
/etc/nodename(astringsscan of the booting boot-slice kernel found neither the old nor the new name, while the running system reported the new one), sosetuname -n— which patches/stand/unix, the untouched stock kernel on that line — is neither required nor effective there. The contrary behaviour is real on other image lineages 🟡 and the two must not be generalised across each other. Same session:/etc/net/{ticlts,ticots,ticotsord}/hostsconfirmed as a manual rename step that no first-boot script performs. - The
syslogd/m4and console-logger rows (first-party record, 2026-10-26; emulation on both kernel lines) ✅ — see quirks 77 and 78. - The non-blocking
connect()(first-party record, 2026-11-02) ✅ — the amixuserland/packagesDropbear port'sconnectprobe (socket,fcntl,connect,select,SO_ERROR, a secondconnect) on the 1.1 disks booted on 68030 and 68060 kernels in emulation, behind its patch 04 (amigaux/amixuserland/packages/dropbear,51ba153, onmaster).