Amiga BBS software from the early 1990s expects a Hayes modem on a serial port. It sends AT
commands, waits for RING, answers with ATA, and watches the carrier signal to know when a
caller has gone. To take telnet calls, something has to play that modem while the calls
actually arrive over TCP.
This is how I did it for Autumn’s Harvest Inn, a CNet PRO 3.05d board running in WinUAE. It now answers four telnet lines on one port. The story of getting there is in a post; this page is the setup.
Pick the bridge
I tried three routes.
The emulator’s serial-over-TCP. WinUAE can connect the emulated serial port to a TCP socket, with a Hayes emulator on the Windows side. In my setup I could give C-Net the signal that let it send (CTS) or the carrier that told it a call existed, but never both. It never got past “Answered, Hanging up”.
telnetd.device. Several BBS guides, including ABBS’s, recommend it. It’s commercial, and I
couldn’t find a legitimate place to get it. The copies that circulate are passed around as
abandonware, so I left it alone.
SerialTCP (GPL-2.0). It runs on the Amiga:
serialtcp.device looks like serial.device with a modem attached, and its daemon, SerialTCPd,
takes the TCP calls. The BBS opens serialtcp.device in place of the serial port and never knows
the difference. This is the one that worked.
What you need
- A TCP stack for the Amiga. In WinUAE that’s
bsdsocket_emu=truein the config, which lends the Amiga Windows’ own stack. Amiberry has the same BSDSocket emulation. On real hardware it would be Roadshow or another stack with a network card. - SerialTCP itself. I built it from source with the Docker image its
build.shnames (amigadev/crosstools:m68k-amigaos). The C-Net setup below also needs the connect-delay patch, and I use the escape-progress fix for screens with runs of+characters. Both were submitted September 22, 2026, and were still unmerged when I last checked, on October 18, 2026, so check the pull requests before building. Download PR #2 as a patch and PR #1 as a patch, apply them to the source before building, and follow the repository’s build instructions. An unpatched upstream build does not haveconnect-delay. - A telnet client that speaks telnet, such as SyncTERM. A raw TCP connection alone does not test the telnet option negotiation.
When cross-building under Git Bash on Windows, prefix the Docker invocation with
MSYS_NO_PATHCONV=1. Otherwise Git Bash can rewrite the volume mount and /work arguments before
Docker receives them. This setting belongs to the Git Bash build command, not the Amiga config.
Install it
Copy SerialTCPd, SerialTCPStatus, and SerialTCPStat to C:, serialtcp.device to DEVS:,
and the sample config to S:serialtcp.conf.
The settings that mattered for my board:
nodes 4 ; four virtual modems: serialtcp.device units 0-3
listen 2325 ; one TCP port for all of them
telnet on
auto-answer 0 ; never answer by itself; the BBS sends ATA
connect-delay 5000 ; milliseconds; requires the patch linked above
rings 8
when-busy busy ; a fifth caller gets the busy message
log T:serialtcp.log
All the lines share one TCP port. SerialTCP hands each caller to the first free unit, so callers
don’t need to know which node they’re on. when-busy can also be queue (hold callers until a
line frees up) or ask (let them choose). listen-node gives one unit its own port if you want
a private line, and outbound-only keeps a unit free for dialling out.
Start the daemon before the BBS. It backgrounds itself before it’s ready, and the device can’t open until it is, so wait for its port:
C:SerialTCPd
SYS:RexxC/WaitForPort serialtcp.daemon
run <>NIL: CNet:Control
Point the BBS at it
In the BBS’s port or node setup, change the device from serial.device to serialtcp.device,
with one unit per node: node 1 on unit 0, node 2 on unit 1, and so on.
Use an init string that turns echo off and auto-answer off. For C-Net I used ATE0S0=0, which was
also what the board’s original modem init had set. Leave the answer string as ATA and the ring
string as RING.
The timing problem
This one took the longest to find. SerialTCP answered ATA with CONNECT immediately, and C-Net
still hung up at its 50-second answer timeout.
C-Net 3 clears its input buffer just after it sends ATA. A real modem spends several seconds
negotiating before it reports CONNECT, so the clear happens first. SerialTCP already had the
TCP connection and replied at once, so its CONNECT was thrown away with the cleared input.
I added a connect-delay setting to SerialTCP as a local patch. It holds the call after ATA and
reports CONNECT when the delay runs out. I use 5,000 milliseconds. It defaults to zero, so
nothing changes unless it’s set. If your BBS answers and then times out waiting, this is the first
thing I’d look at. The patch is available in the pull request linked above, but had not been
merged into upstream as of October 18, 2026.
Test it
syncterm telnet://localhost:2325
For the lines, I opened five connections at once from a small script. Four reached the login
prompt and the fifth got the busy message, which is what nodes 4 and when-busy busy should
do.
On the Amiga, SerialTCPStatus lists every unit and its state. A unit shown as closed means no
BBS node has it open. SerialTCP never offers a call to a closed unit, so callers get the busy
message even though the BBS looks like it’s running. When that happened to me, the BBS’s own
node wasn’t set up on the device yet.
When the BBS still won’t answer
SerialTCP’s log records calls, but not the modem conversation itself. For a second board, ABBS
3.3, I added a trace option (another local patch) that logs every AT command the BBS sends,
every reply, and the serial requests it makes while the line is idle.
The trace showed ABBS reading every byte, including each RING, and then sending AT and its
init string again instead of ATA. That board doesn’t answer telnet yet. I’m noting it because
the trace moved the question from “is the virtual modem broken?” to “what does this BBS expect?”,
which is a question for its author.
Testing a Docker copy of the board also turned up a real SerialTCP bug: long runs of +
characters in ASCII artwork made its escape-sequence check stall on each one, so those screens
drew very slowly. The escape-progress patch linked above fixes that.
Transfer checks
XModem uploads and downloads worked in the September 21, 2026 Docker trial. That used an earlier guest configuration; it does not establish transfer support in the stock OS 3.1 Docker guest that replaced it. On September 24, 2026, Zmodem uploads and downloads worked on the WinUAE board: a 118,572-byte ZIP sent from SyncTERM and downloaded again matched the original byte for byte.
October 21 update: A separate Docker copy of the Inn on a second machine at home took calls from this PC through a relay on October 20. That tests access to that copy, not a call from another machine to the WinUAE board described in this guide. The Docker copy is still access-restricted and isn’t open to the public.
What I haven’t tested
- A caller from a separate machine on the network to this board. A later OS 3.2 rehearsal answered through the host’s LAN interfaces on port 2345, and an existing program-scoped Windows firewall rule already covered WinUAE. That does not establish this board’s port 2325 from a separate client. Check the existing firewall rules before adding one, then test the target board from another machine.
- Real hardware. The A1200 plan is SerialTCP on Roadshow with a PCMCIA Ethernet card.