An open-source TCP/IP stack and
bsdsocket.libraryv4.1 runtime for classic Commodore AmigaOS (68k), built on a pinned, hardened copy of lwIP 2.2.0 running as a dedicated AmigaOS Exec task (NO_SYS=1).
1.2.0-rc5 is a pre-release. tolunnet is a hobby project, written by one person for the pleasure of keeping 40-year-old machines useful. It is verified in an emulator (WinUAE, Workbench 3.0, A1200 and plain 68000 profiles) and by host unit tests, but it has not yet been tried on real hardware, and the Installer / two-disk Gotek path has never run on a real Amiga. That is where you come in.
- Try it on a PiStorm, an accelerated Amiga, a CF/HD setup or through a Gotek, and tell us what happens — working or not.
- Follow the checklist in docs/MANUAL-TEST-rc5.md (about 20 minutes) and report back with photos if you can.
- Report via GitHub Issues: your Amiga model, CPU/accelerator, Kickstart/Workbench version, network card or SANA-II driver, what you did and what you saw. Screenshots of the Setup wizard or the Preferences window are very welcome.
- Always keep a backup of
S:User-Startupand your current TCP/IP stack. The installer writes an undo script (S:tolunnet-undo), and the default Installer mode is pretend (nothing is written until you choose to). The undo never copies a backup over a liveS:User-Startup: it leaves the tolunnet block there (harmless onceC:tolunnetis gone; remove it by hand) and keeps the pre-install copy asS:User-Startup.tolunnet-old.
| Setup wizard | Address page | Test & Finish | Preferences |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Icons (Setup, Preferences, tolunnet):
![]()
All wizard pages in PAL and NTSC are further down in the Setup Wizard section.
Classic AmigaOS has long lacked an actively maintained, production-grade, 100% open-source TCP/IP stack. Roadshow is proprietary commercial software. Miami and Miami Deluxe are discontinued abandonware. AmiTCP's last open-source release dates back to version 3.0b in 1994.
tolunnet bridges this gap: modern, fully documented, rigorously verified, and distributed under the GNU General Public License v3.0 (GPL-3.0-or-later). It provides a drop-in replacement bsdsocket.library for classic 68k Amiga systems (from a bone-stock Amiga 500 up to accelerated Amiga 4000 and PiStorm systems), complete with standard network diagnostic tools, a modern Setup wizard, and Workbench Preferences.
- Author: İsmail Öztürk
- Licence: GPL-3.0-or-later — see LICENSE.
- lwIP Core Licence: BSD-3-Clause — see THIRD_PARTY_LICENSES.md.
Compatibility Notice: Applications communicate with tolunnet strictly via the standard AmigaOS library interface:
OpenLibrary("bsdsocket.library", 4). Callingbsdsocket.libraryfunctions from any third-party application (freeware, shareware, or commercial) does not subject that application to the GPL.
bsdsocket.libraryv4.1 runtime: 139 total LVO slots (-30 … -858; 121 SFD functions + reserved) generated from Roadshow SDKsfd/bsdsocket_lib.sfdviascripts/gen_lvo_table.py; 70 BUILT (vector → IPC → daemon handler, or handled in-library), 51 honest stubs returning exact Roadshow error semantics (ENOSYS,ENXIO,NULL,NO_RECOVERY,FALSE), 0 BROKEN (marshaled without a daemon handler). A vector-by-vector status table (src/lib/lib_compat_table.gen.md) is generated locally bymake python-checks.
- Roadshow 1.8 & AmiTCP V4 API: socket primitives (
socket,bind,listen,accept,connect,shutdown,getsockname,getpeername,send/recv/sendmsg/recvmsg),SocketBaseTagList(-294),WaitSelect(timer-driven, SIGIO delivery),IoctlSocket,GetSocketEvents,Dup2Socket, netdb (gethostbyname,getservbyname/byport,getprotobyname/bynumber,getnetbyname/byaddr,getaddrinfo/getnameinfo),inet_*helpers,gethostname,gethostidandvsyslog. - Exec message-port IPC: client calls reach the daemon through native
PutMsg/WaitPort/ReplyMsg. The fast path reuses one message per library base; a per-callMEMF_PUBLICmessage is allocated only in IPC-timeout mode. - Universal 68k code (
-m68000 -msoft-float): one binary set for 68000 … 68060, no FPU required. - 68000 alignment defence: 4-byte memory pool alignment (
MEM_ALIGNMENT 4) andETH_PAD_SIZE 2keep IP/TCP headers aligned.-Werror=cast-alignin the m68k build catches explicit less-aligned casts at build time;make align-checkadds a host-Wcast-align=strictpass (files the host cannot parse are re-checked with the cross compiler, and an unchecked file fails the gate) and a lint for(void *)cast hops and typed dereferences of client pointers, which neither compiler sees. - SANA-II Rev 7 driver interface: register-convention buffer-management hooks (
A0/A1/D0), multiple outstanding reads,S2_ONEVENTlink tracking (active only withTX_QUEUE=0; the TX pool turns it off) and an optional pipelined TX request pool (TX_QUEUE; the default0is synchronousDoIO). Works with standard Ethernet and wireless SANA-II drivers such asa2065.device,ariadne.device,cnet.deviceandwifipi.device. - PRNG seeding: the lwIP random source is seeded at start-up from
GetSysTime(µs resolution) and free Chip/Fast RAM, then from the adapter MAC address once the interface is open. - Text configuration:
DEVS:tolunnet.config(KEY=VALUE), described under Configuration.ENV:tolunnet.prefsoverrides it when newer. Changes take effect aftertolunnet RECONFIGorTolunnetControl RECONFIG. usergroup.library: resident library (LIBS:usergroup.library, 39 public LVOs) for user/group identity and credentials (getuid,geteuid,getpwuid,getpwnam,getgrnam,getgroups,crypt, …).- It has a built-in in-memory database: users
root,amiga,nobody; groupswheel,staff,nobody. - The first
passwd/groupfile found inAmiTCP:db/,DEVS:Internet/orDEVS:tolunnet/overrides that database. crypt()uses an internal FNV hash, not Unix DES; existing AmiTCP passwd files are not compatible.- Partial implementations are listed under Known limitations.
- It has a built-in in-memory database: users
- AutoIP (RFC 3927): optional link-local
169.254.x.x/16address fallback with Address Conflict Detection (lwIP DHCP/AutoIP cooperation, about 3–4 s). Off by default;AUTOIP=YESenables it. - mDNS responder (RFC 6762): optional Zeroconf responder that answers
<hostname>.localon224.0.0.251:5353and publishes a_workstation._tcpDNS-SD service (model=Amiga,os=AmigaOS,stack=tolunnet). Off by default;MDNS=YESenables it. - Workbench GUI:
TolunnetSetup: first-run wizard whose layout is computed from font metrics (TextLength(),tf_YSize). The bench verifies it on 640×200 NTSC and 640×256 PAL. The same code supports RTG screens, but the bench image has no RTG drivers to test them.TolunnetPrefs: GadTools preferences editor with non-blocking daemon Start/Stop, ToolTypes (TOOLPRI,PUBSCREEN) and WBStartup support.
The first-run wizard (TolunnetSetup) guides network configuration with automatic hardware detection and responsive layout adaptation across PAL and NTSC screens:
- Welcome & replacement: detects existing TCP/IP stacks (Roadshow, Miami, AmiTCP) and backs them up cleanly before proceeding.

- Hardware detection: scans installed SANA-II device drivers and configures unit selection.

- Wireless networks: scans available Wi-Fi networks (SSID, signal strength, security) and stores passphrases.

- Address mode (DHCP): automated dynamic host configuration and hostname assignment.

- Connection test & finish: verifies network operations and saves configuration with User-Startup integration.

- Preferences:
TolunnetPrefsedits the same configuration outside the wizard.
NTSC (640×200) screenshots — same pages on the 68000 bench leg
Page 1 "Replace legacy stacks": empty legacy-stack list, the "Replace with tolunnet (recommended)" checkbox, status "Ready."
Page 2 "Network hardware": empty "Adapter:" list with Rescan and Test adapter buttons, status "Ready."
Page 3 "Wireless network": empty "Networks:" list, Scan APs button, SSID/Passphrase fields with a Show checkbox, status "Ready."
Page 4 "IP address": Mode "Automatic (DHCP)", empty DNS 2/MTU fields, host "amiga", status "Ready." — no invented gateway on screen.
Page 5 "Test & finish": the check rows (Start stack OK, IP address OK 10.0.2.15, Ping gateway OK 10.0.2.2, DNS lookup SKIPPED, HTTP HEAD SKIPPED) and the honest status line "Tests complete - 3 passed, 0 failed, 2 skipped".
TolunnetPrefs over the Workbench: ethernet.device unit 0, DHCP mode, MTU 1500, DNS 1 127.0.0.1 (the bench's resolver), Save/Use/Stop/Setup/Undo/Ping/Cancel buttons.
All 12 screenshots are PAL/NTSC captures from the newest green bench; the
-static variants are excluded on purpose: they show the known invented
Manual-mode defaults (see Known limitations in STATUS.md). Total size of
docs/screenshots/: about 44 KB.
Real outputs, copied VERBATIM from the newest green bench log
(docs/bench-logs/20261001-035223-v1.2.0-rc4-453-ga5045cb/a1200/conformance.log)
(emulated A1200, WinUAE slirp network; addresses are the bench's):
# tc_net_checks_ok: address 1 - address 10.0.2.15
# tc_net_checks_ok: ping 1 - ping 127.0.0.1 replied
# tc_net_checks_ok: dns 1 - resolved tolunbench.test to 10.0.2.55 via 10.0.2.2
# tc_net_checks_ok: tcp 1 - tcp 10.0.2.2:15880 connected
# tc_cmd_arp: gateway ARP resolves here: yes
The conformance log records the checks as one-line results; it carries no
raw ifconfig transcript. The GetNetStatus line is the first row above
(address 10.0.2.15); the wizard Test page (screenshot above) shows the
same values on screen.
The release archive ships the following programs, in C/ unless noted. The installer (Install_Tolunnet) installs the complete command set, companion tools and libraries to SYS:C/, SYS:Prefs/ and SYS:Libs/.
| Command | ReadArgs template / purpose |
|---|---|
tolunnet |
Stack daemon and bsdsocket.library provider. START/S,STOP/S,STATUS/S,RECONFIG/S,STATS/S,RAW/S,WATCH/N,DEVICE,UNIT/N,IP,NETMASK,GATEWAY. With no arguments it reads DEVS:tolunnet.config. |
TolunnetControl |
COMMAND/A: START, STOP, RESTART, STATUS, RECONFIG, STATS, VERSION. |
TolunnetSetup (SYS:Prefs/) |
First-run network wizard: hardware detection, DHCP/static setup, connection test. |
TolunnetPrefs (SYS:Prefs/) |
Preferences editor with stack Start/Stop. |
TolunnetStatus, ifconfig, netstat |
One read-only binary that takes no arguments. The name it is started under picks the output: stack status, interface summary, or socket/route summary. |
ShowNetStatus |
INTERFACES/S,ROUTES/S,DNS/S,SOCKETS/S,FULL/S |
GetNetStatus |
ONLINE/S,ADDRESS/S,GATEWAY/S,DNS/S: status query for scripts, answered through the return code. |
TolunnetPing, ping |
HOST/A,COUNT/N,SIZE/N,INTERVAL/N,TTL/N,TIMEOUT/N,QUIET/S,UDP/S: ICMP echo with min/avg/max/mdev. |
TolunnetGet, wget, curl |
URL/A,PORT/N,PATH,TO/K,QUIET/S: HTTP/1.1 client with 301/302/303/307/308 redirects, chunked transfer and Range resume. |
route |
SHOW/S,ADD/S,DEST/K,NETMASK/K,GATEWAY/K,DELETE/S,DEFAULT/S |
AddNetRoute, DeleteNetRoute |
DEST/A,MASK/K,GATEWAY/K and DEST/A,MASK/K (Roadshow-style names). |
AddNetInterface |
FILE (Roadshow-style interface control). |
ConfigureNetInterface |
NAME/M,ADDRESS/K,NETMASK/K,GATEWAY/K,DHCP/K |
Online, Offline |
NAME |
CheckNetConfig |
FILE: syntax check of a tolunnet config file. |
NetShutdown |
FORCE/S: orderly stack shutdown. |
arp |
SHOW/S: shows the ARP cache via SIOCGARP. |
hostname |
HOSTNAME,SAVE/S |
nslookup |
NAME/A,SERVER: forward (A) and reverse (PTR) lookups. |
traceroute |
HOST/A,MAXHOPS/N,QUERIES/N,WAIT/N,NUMERIC/S: UDP probe routing diagnosis with per-hop IP_TTL and ICMP replies. |
whois |
QUERY/A,SERVER (default server whois.iana.org). |
telnet |
HOST/A,PORT/N: raw TCP terminal. Telnet option negotiation (IAC/SB) is filtered out, not negotiated. |
nc |
HOST/A,PORT/N,UDP/S,LISTEN/S,TIMEOUT/N |
ftp |
HOST,PORT/N,USER,PASS,SCRIPT,QUIET/S,PASVANY/S: passive-mode FTP client. The PASV address must match the control connection's peer unless PASVANY is given. |
tftp |
HOST/A,GET/S,PUT/S,FILE/A,LOCAL: RFC 1350, octet mode. |
sntp |
HOST,SET/S,OFFSET/N |
iperf |
CLIENT/K,SERVER/S,PORT/N,SECONDS/N: simple TCP throughput sink/source with its own format, default port 5201. Not wire-compatible with iperf2 or iperf3. |
TestSocket |
Minimal socket smoke test. |
usergroup.library (SYS:Libs/) |
See Key Architecture & Features. |
DEVS:tolunnet.config holds one KEY=VALUE per line. TolunnetSetup and TolunnetPrefs write it.
| Key | Meaning |
|---|---|
DEVICE, UNIT |
SANA-II driver and unit |
DHCP |
YES/NO |
IP, NETMASK, GATEWAY |
static addressing (aliases IP_ADDR, MASK, GW) |
DNS, DNS2 |
resolvers (aliases DNS1, NAMESERVER) |
HOSTNAME, MTU |
host name, interface MTU |
AUTOIP, MDNS |
optional link-local fallback and mDNS responder (default NO; enable with AUTOIP=YES, MDNS=YES) |
TX_QUEUE |
TX request pool depth (0 = synchronous, the release default) |
LOG, LOGLEVEL, DEBUG, SYSLOG |
logging |
PRIORITY, SELECTORS, STATS, S2EVENTS, DATABASE_ORDER |
daemon tuning |
DNS_PORT, DNS_PENDING, DNS_RETRIES |
resolver tuning |
FONT |
font for TolunnetSetup (default: the screen font) |
Minimal example:
DEVICE=ethernet.device
UNIT=0
DHCP=YES
DNS=1.1.1.1
LOG=NIL:AmigaOS has no memory protection, so one unaligned access or use-after-free ends in a Guru Meditation. Every change therefore goes through three layers of testing. STATUS.md has the current results.
Every tests/host/test_*.c program (the Makefile globs them; ci/check_release_consistency.py keeps the list below in sync) is built with the host compiler under AddressSanitizer and UndefinedBehaviorSanitizer (-Werror). They compile against the real project headers, with a mock lwIP/Exec layer underneath:
test_config, test_constants, test_dns_pending, test_errstr, test_fdset, test_http, test_ifreader, test_inet_addr, test_ipc, test_ipc_dispatch, test_log_format, test_lvo_table, test_manifest_match, test_nslookup_parse, test_queues, test_route, test_sbtc, test_slot_table, test_sockaddr, test_sockopt, test_stats, test_usergroup, test_wizard_config, test_wrap.
After the tests, make test-host runs python-checks: the LVO table generators must be up to date, icon formats are validated, and a gate checks the Forbid()/Disable() regions and a lint bans (T *)(void *) hops and typed dereferences of client pointers outside a reviewed waiver list (scripts/check-cast-known.txt).
A headless WinUAE harness boots a clean Workbench 3.0 hard-disk image on Kickstart 3.1, in two profiles:
- a1200: 68EC020, AGA, PAL.
- 68000: A600-class 68000, ECS, NTSC, Fast RAM.
Each profile runs twice: with TX_QUEUE=4 (the pipelined TX pool) and with TX_QUEUE=0 (the release default: synchronous DoIO, S2_ONEVENT link tracking on). Every result row names its TX_QUEUE.
Each run does two cycles in the same OS session, without rebooting:
- Cycle 1:
- Start the daemon and obtain a DHCP lease.
- Run the
SocketConformancesuite: TCP/UDP/raw ICMP, non-blockingWaitSelect, socket events, a call into each of the 139 LVO slots, and every bundled command. - Run the third-party
bsdsocktestsuite, then stop the daemon. Its score gates the run: fewer than 126 passed, more than 2 failed, or no result fails it.
- Cycle 2: relaunch the daemon, which re-opens the SANA-II device and gets a fresh lease, then repeat the tests.
A run only counts if the tree was clean (dirty: NO). There is no MuForce/Enforcer pass: the tool image is not part of the bench, every run records SKIP, and setting MUFORCE_ADF aborts the run.
Hobbyist Amigas are used for a few hours at a time, so the soak models many short sessions rather than long uptime. It runs 12 cycles of 10 minutes on the a1200 profile (SOAK_TX_QUEUE, default 4; 0 soaks the release-default TX path):
- Each cycle pings the slirp gateway
10.0.2.2(through SANA-II), downloads from the bench HTTP service (ci/netsvc.py), takes anAvailsnapshot, then runsTolunnetControl STOP/START. Every command's return code is checked and logged as anok/not okline. - The run fails on a Guru or freeze, any
not okline, feweroklines than planned, fewer than 13 lwIP initialisations, Chip RAM drift, or Fast RAM drift (measured from the pre-session baseline) above the per-cycle budget.
The owner's target machine is an Amiga 500 with a PiStorm and wifipi.device. Real-hardware retests are done by hand on that machine.
Cross-compile with m68k-amigaos-gcc on Linux or WSL:
git clone https://github.com/tolon/tolunnet.git
cd tolunnet
# Binaries, LhA archive and ADF image (version from include/version.h)
make package CROSS=/path/to/m68k-amigaos/bin/m68k-amigaos-
# Host unit tests (ASan/UBSan) + generator/lint checks
make test-host
# Host-gcc strict cast-alignment gate over src/
make align-checkOutputs in build/:
build/tolunnet-<version>.lha: the full release, with binaries, icons, installer,README.guideand licences.build/tolunnet-<version>-disk1.adf/-disk2.adf: the Gotek/FlashFloppy two-disk set (FFS, 880 KB each), built from the stripped release tree with the explicit manifest inscripts/adf_manifest.txt. Merging both disks into one drawer reproduces the package tree byte for byte; disk 1 carriesInstall_From_Floppiesto bootstrap the install. Thebuild/release-assets/folder bundles the LHA, both ADFs andSHA256SUMS.txt.
Requirements: AmigaOS 3.0+, 68000 or higher, about 1.2 MB free for the unpacked tree.
- Download
tolunnet-<version>-disk1.adfandtolunnet-<version>-disk2.adffrom the GitHub release page and put them on the USB stick. - Boot the Amiga, open a shell, put disk 1 in DF0 and run:
Execute tolunnet1:Install_From_Floppies Work:tolunnetThe script printstolunnet two-disk install: copying disk 1 ..., thenDisk 1 copied. Insert disk 2 (tolunnet2) now- select disk 2 on the Gotek; DOS asks for thetolunnet2volume itself if it is not mounted. After disk 2 it printsBoth disks copied. - The full package is now in
Work:tolunnet; start the Installer from there:CD Work:tolunnetthenInstaller Install_Tolunnet. Add theNORUNswitch to skip the automatic Installer start. This script is shell-only: double-clicking it does not work. Full walkthrough: docs/MANUAL-TEST-rc5.md. Emulator-test status: UNTESTED - the real Installer opens requesters (script error / welcome) that a headless bench cannot answer, so its pretend run has never completed in the emulator (see the notes in STATUS.md). Please try it on real hardware and report.
- Extract
tolunnet-<version>.lhatoRAM:or any drawer. - Double-click
Install_Tolunnet(or runInstaller Install_Tolunnetfrom a shell). The script asks nothing about hardware:- It copies the complete command set to
SYS:C/,usergroup.librarytoSYS:Libs/, and the two preference tools (with icons) toSYS:Prefs/. - It detects existing TCP/IP stacks (Roadshow, Miami, AmiTCP), backs up what it replaces (an existing
usergroup.libraryincluded) and emitsS:tolunnet-undo. An existingLIBS:bsdsocket.libraryis parked withRenameas.pre-tolunnet(or.pre-tn-newerwhen an older park already exists). - It writes
DEVS:tolunnet.configand adds the config-driven startup block toS:User-Startup, guarded asIf EXISTS C:tolunnet…EndIf. S:tolunnet-undofirst runsS:tolunnet-undo-stacks(the wizard's changes to other stacks), restores the replaced files andusergroup.library, and renames a parkedbsdsocket.libraryback only when no live one exists. It leaves a liveS:User-Startupas it is and renamesS:User-Startup.tolunnet-bakto.tolunnet-old; with no liveS:User-Startupthe backup is renamed back into place.
- It copies the complete command set to
- It then offers to start
SYS:Prefs/TolunnetSetup- the wizard asks for the SANA-II device and unit and configures DHCP or a static address (you can refuse and run it later).
- Copy files from the archive:
- the
C/programs you want, toSYS:C/; Libs/usergroup.librarytoLIBS:;TolunnetPrefsandTolunnetSetupwith their.infofiles toSYS:Prefs/.
- the
- Run
TolunnetSetuponce, or createDEVS:tolunnet.configby hand (see Configuration). - Start the stack from
S:User-Startup:Stack 32768 Run <NIL: >NIL: C:tolunnet
Release state, test results and known limitations are tracked in STATUS.md.
- tolunnet is Copyright (c) 2026 Ismail Ozturk (tolon), licensed under the GNU General Public License v3.0 or later. See LICENSE.
- lwIP (BSD-3-Clause) and the other third-party material, including the Roadshow SDK headers and the BSD network headers, are listed in THIRD_PARTY_LICENSES.md with their notices.