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=true in 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.sh names (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 have connect-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.