A small companion service for connecting retro machines (real DOS PCs, Windows 9x) to networks and the internet over a plain serial cable. It is a single self-contained program — no runtime to install, nothing to set up; download the one file for your operating system and run it.
One dashboard manages IPX, DOS TCP/IP, Windows dial-up, modem games, and BBS access. Click either image for the full-size view.
The host does several jobs over that serial link, picked per connection:
| Mode | What the retro machine runs | What the host does |
|---|---|---|
IPX bridge (server) |
dalicom.exe (Dali's serial transport) |
Relays IPX to an IPXBox/DOSbox server, owning the server connection (registration, keepalive). |
Local IPX switch (switch) |
dalicom.exe |
Networks several local DOS machines directly to each other — no server, no internet. |
SLIP IP (slip) |
The DOS ETHERSL packet driver (no Heddle software) | Terminates a SLIP IP link and NATs/routes the machine onto the internet/LAN — TCP/IP networking (web, FTP, ping, mTCP apps) over the cable, with optional DHCP auto-config. |
Modem (modem) |
Any DOS/Windows comms or game software that dials a Hayes modem | Emulates a Hayes AT modem whose "phone line" is a TCP connection — so two machines, or a machine and a telnet BBS, connect over the internet with no real modem. |
| Dial-up ISP (PPP) | Windows 9x / DOS Dial-Up Networking (built in — no Heddle software needed) | Acts as the ISP: terminates PPP and gives the machine real internet, LAN access, and name resolution. A modem-mode connection that dials the reserved number ppp. |
All modes ride the same serial-cable plumbing and can run side by side (one box,
several machines). The IPX modes (server, switch) carry IPX over the cable for
DOS LAN gaming; the IP modes (slip, PPP) carry TCP/IP for internet/LAN access.
See SERIAL-PROTOCOL.md for the IPX serial wire format.
The fastest way to understand Heddle is to start from what you want to connect:
| What you want to do | Use |
|---|---|
| Join an online IPX game | server mode and dalicom.exe |
| Connect several local DOS IPX computers | switch mode and dalicom.exe |
| Connect two modem games on one host, a LAN, or across the internet | modem mode |
| Dial a telnet BBS | modem mode with Telnet handling |
| Give Windows 9x internet or LAN access | PPP dial-up ISP |
| Give DOS mTCP internet or LAN access | SLIP and ETHERSL |
The networking and game recipes provide complete paired configurations for modem games, LAN and WAN calls, "already connected" games, Windows 9x PPP, Samba/WINS, SLIP, OpenWrt TAP, and client-to-client TCP/IP routing.
Reference documentation:
- Configuration reference - every CLI option and INI key.
- Retro client setup - Windows 9x PPP, DOS IPX/SLIP, modem games, and terminal software.
- Serial hardware guide - cables, adapters, signals, and port troubleshooting.
- Operations guide - upgrades, dashboard workflow, security, diagnostics, and compatibility status.
Download the file for your system and run it from a terminal:
| Your computer | File to use |
|---|---|
| Windows | heddle-windows-amd64.exe |
| Linux PC | heddle-linux-amd64 |
| macOS (Intel) | heddle-macos-amd64 |
| macOS (Apple Silicon) | heddle-macos-arm64 |
| Raspberry Pi (64-bit OS) | heddle-pi-arm64 |
| Raspberry Pi (32-bit OS) | heddle-pi-armv7 |
| OpenWrt router | Matching architecture-specific heddle_*.ipk |
On macOS/Linux/Pi, make it executable first: chmod +x heddle-*.
For OpenWrt architecture selection and installation, see the
OpenWrt package guide.
# Windows (USB-serial shows up as COMx)
heddle-windows-amd64.exe -serial COM3 -server ipx.example.com
# Linux / Raspberry Pi (USB-serial is usually /dev/ttyUSB0)
./heddle-pi-arm64 -serial /dev/ttyUSB0 -server 192.168.1.50
# With the web dashboard (browse to http://localhost:8080)
heddle -serial COM3 -server ipx.example.com -web
When it connects you'll see a status line with the assigned IPX address and packet counters. Press Ctrl+C to stop.
Run the serial build of Dali pointed at the matching COM port and baud, e.g.:
dalicom com1 115200
then start your IPX game as you would on a LAN. A null-modem serial cable connects the DOS machine's COM port to the host's serial port (a USB-serial adapter on a modern PC/Pi).
heddle [-config <file>] [-serial <port>] [-baud <n>]
[-server <host>] [-server-port <n>] [-switch]
[-web] [-headless] [-web-addr <addr>] [-wirelog]
[-uplink <nat|tap:name>]
-configread settings from an INI file (see below). CLI flags override the file. If./heddle.iniexists it is loaded automatically.-serialthe serial port connected to the DOS machine.-baudline speed; must matchdalicom.exe(default 115200).-serverthe IPXBox/DOSbox server hostname or IP.-server-portserver UDP port (default 213; many public servers use 10000).-switchlocal IPX switch mode — network local clients directly to each other, no external server (see below).-serverignored.-webserve the browser dashboard.-headlessdisable the dashboard, which is enabled by default.-web-addrdashboard listen address (default:8080).-wireloglog every IPX packet (source/dest node:socket) for diagnostics.-uplinkIP uplink: portablenator a preconfigured Linux/OpenWrttap:<interface>.
SLIP IP mode, modem mode, the dial-up-ISP (PPP) mode, and their per-connection routing options (WAN/LAN/client↔client, DNS, WINS) are configured per profile in the INI file rather than via flags — see SLIP IP mode, Modem mode and Dial-up ISP (PPP) below.
With -switch, the host becomes a built-in IPX switch: several local DOS
machines plugged into this host (each via its own serial/USB-serial port)
network directly to each other — no IPXBox/DOSbox server, no internet
required. The host assigns each machine its IPX address and forwards packets
between them (unicast to the right machine, broadcasts to all others).
This is the simplest possible IPX LAN party: plug the machines in, run the host
with -switch, and start an IPX game on each. Example (one machine; repeat the
host with a different serial port per machine, or list several in config):
heddle -serial COM4 -switch
heddle -serial /dev/ttyUSB0 -switch -web
(For connecting to a public/online IPXBox community instead, omit -switch and
use -server.)
The IP services have two host-wide uplinks. uplink = nat is the portable
default described below. On Linux/OpenWRT, uplink = tap:heddle0 places SLIP
and PPP clients on the LAN through a preconfigured TAP port, so OpenWRT owns
DHCP, routing, NAT, and firewall policy and other LAN hosts can initiate IP
connections to the retro machine.
A mode = slip connection gives a DOS machine real TCP/IP networking — web,
FTP, ping, any mTCP app — over the serial cable,
with the host acting as its router. The DOS side runs the ETHERSL packet
driver (which speaks plain RFC 1055 SLIP carrying bare IPv4); the host terminates
the link and NATs/routes the machine onto the internet and/or your LAN. No
Heddle software runs on the DOS machine — ETHERSL plus any packet-driver TCP/IP
stack (mTCP is the usual one) is all it needs.
This is the lighter, IP sibling of the PPP dial-up path: where PPP suits Windows 9x's built-in Dial-Up Networking, SLIP suits DOS mTCP and is rock-solid on fast hardware (it negotiates nothing — the link is up the moment the port opens).
[profile "dos-internet"]
mode = slip
serial = COM5
baud = 115200
autostart = true
# routing (all optional; SAME keys/semantics as PPP — see "Dial-up ISP" below):
wan = on # NAT to the internet (default on)
lan = off # also route to the host's LAN: off | on/auto | a CIDR
peers = off # let this client reach other dial-in/SLIP clients
# WINS applies to PPP dial-up profiles; see below.Addressing — the host owns it. Heddle auto-assigns each SLIP link a private
/30 (host = gateway/DNS, the DOS machine = the other usable address) and prints
the exact mtcp.cfg lines to paste on the DOS side, e.g.:
DOS mtcp.cfg: IPADDR 192.168.30.2 NETMASK 255.255.255.252 GATEWAY 192.168.30.1 NAMESERVER 192.168.30.1 MTU 576
Or skip the typing with DHCP. Put DHCP in mtcp.cfg instead of a static
IPADDR, and the DOS machine asks for its address on connect. In NAT mode
Heddle supplies its private /30; in TAP mode OpenWRT supplies a real LAN lease.
Static and DHCP are interchangeable;
DHCP is the zero-config option.
Routing (wan / lan / peers) and the host-as-resolver DNS work as
described under Dial-up ISP (PPP) because both IP modes
share the same NAT/routing engine. WINS assignment is specific to PPP. SLIP
carries IPv4 only; for DOS IPX LAN gaming use server/switch mode with
dalicom.exe. Layer-2 protocols such as EtherDFS are not carried by SLIP.
Create a persistent TAP port, add it to br-lan, and bring it up before Heddle
starts. Exact persistence belongs in the RetroWRT image/service configuration;
the equivalent runtime commands are:
ip tuntap add dev heddle0 mode tap
ip link set dev heddle0 master br-lan
ip link set dev heddle0 upConfigure Heddle and a DHCP SLIP profile:
uplink = tap:heddle0
[profile "dos-lan"]
mode = slip
serial = /dev/ttyUSB0
baud = 115200
autostart = truePut DHCP in mtcp.cfg; OpenWRT will issue and display the lease. Heddle uses
the DOS packet driver's hardware address, so OpenWRT reservations, device
history, and firewall rules remain stable.
For static addressing, add all three fields below and use the same values in
mtcp.cfg:
slip-ip = 192.168.1.50
slip-mask = 255.255.255.0
slip-gateway = 192.168.1.1Reserve a static slip-ip outside OpenWRT's dynamic pool. An explicitly
configured TAP failure stops that SLIP connection instead of silently changing
it back to NAT. PPP-over-TAP supports the same DHCP-or-static choice and obtains
a stable OpenWrt lease before completing IPCP. Leave ppp-ip, ppp-mask,
ppp-gateway, and ppp-dns blank for DHCP, or set the first three together for
a fixed LAN address.
For automatic TAP creation, boot startup, crash respawn, and optional opkg
installation, see OpenWRT packaging. The same
procd service script can also be installed manually.
A mode = modem connection turns the host into a software Hayes modem for the
attached machine: the serial port behaves like a real modem, but the "phone line"
is a TCP connection. The machine dials with normal AT commands (or its game's
modem menu) and the host places/answers the call over the network. No real modem,
no phone line.
This enables, over the same serial cable:
- Modem head-to-head games — two machines (each on its own host serial port,
or two hosts across the internet) connect for a modem game. Verified with
Doom (
sersetup) and Shadow Warrior. - Dialing a BBS — dial a telnet address (e.g. a classic BBS on port 23) and the host transparently handles the Telnet protocol so a terminal program gets clean text. Verified with Windows HyperTerminal → a public BBS.
Dialing is driven by a phonebook ([phonebook] in the INI): a dialed
"number" maps to a host:port (or the reserved word ppp, see below). Games
with a non-standard modem handshake (some Build-engine titles) are supported via
an "already connected" path — the host pre-establishes the link at startup and
the game picks its "already connected" option, skipping the handshake.
Modem-mode profile keys (all optional): listen (accept inbound calls on a TCP
address), autoanswer, autoaccept, dial (auto-dial a peer at startup, for
"already connected"), telnet (force BBS/telnet handling), carrierc1 (see
note under Dial-up ISP).
For complete same-host, LAN, and internet examples, including which side needs a listener and router port forward, see Modem games and direct calls.
The host can be a retro machine's ISP: a Windows 9x (or DOS) machine uses its built-in Dial-Up Networking to "dial" the host, and the host terminates PPP and routes its traffic — giving the machine real internet, access to your LAN (file shares), and name resolution. Nothing extra is installed on the retro machine — just Windows' own dial-up, pointed at a Hayes modem on the COM port wired to the host. This is pure-Go and self-contained (it is not PPTP and needs no kernel TUN device or admin privileges).
Set it up as a modem profile whose phonebook has an entry mapping to the reserved
destination ppp:
[profile "dialup"]
mode = modem
serial = COM4
baud = 115200
autostart = true
# routing (all optional; defaults shown):
wan = on # NAT to the internet
lan = 192.168.1.0/24 # also route to your LAN ("on"/"auto" = auto-detect)
peers = off # let this client reach other dial-in clients
wins = auto # NAT: resolve \\NAME through host/OpenWrt DNS
# wins = 192.168.1.50 # or advertise an existing WINS server
[phonebook]
internet = ppp # dial "internet" from Windows DUNOn the Windows 9x machine: add a Standard Modem on the wired COM port, make a
Dial-Up connection that dials internet (username/password blank — no auth), and
connect. It negotiates PPP, gets an IP, and is online.
For internet-only, LAN-only, Samba/WINS, mapped-drive timing, and mutually reachable PPP/SLIP client examples, see Windows 9x dial-up ISP.
Routing modes are composable per profile — enable any combination:
wan— NAT to the internet (default on). Turn off for a LAN-only, no-internet client.lan— route to the host's real LAN so the machine can reach other LAN hosts and file shares.off(default),on/auto(auto-detect the host's subnet), or an explicit CIDR like192.168.1.0/24. In portable NAT mode, strict internet-only isolation should also be enforced by the host firewall: with no configured LAN CIDR, private destinations may follow the host's normal routing path.peers— client↔client routing: let this dial-in machine reach other dial-in machines (for TCP/IP LAN games or file sharing between two dialed-in PCs). Isolated by default; a connection is allowed only when both ends setpeers = on, so each side controls its own exposure.
DNS works in every mode: the host advertises itself as the client's DNS
server and resolves on its behalf, so name resolution works whether or not WAN is
enabled (no dependency on a public resolver, and no internet "leak" when wan is
off).
Windows file shares by name (\\SERVER) need WINS/NBNS, because Windows
normally discovers those names by broadcast and broadcasts do not cross the
point-to-point dial-up link. In self-contained NAT mode, wins = auto makes
Heddle advertise its private PPP gateway as WINS and resolve NetBIOS names
through the host/OpenWrt DNS resolver (including .lan). Only private or
link-local IPv4 results are returned. Alternatively, set wins to the IP of a
real Samba or Windows WINS server. TAP mode should use an explicit LAN WINS
server if one is available. Without WINS you can still reach shares by IP:
\\192.168.1.50\share. On Windows 95 you may need nbtstat -R and a reboot the
first time so it requests the new WINS address.
About
carrierc1: Windows Dial-Up Networking requires the modem to assert a carrier-detect (DCD) edge at connect time. The host does this automatically for PPP profiles; thecarrierc1key forces it on/off if needed. Raw modem games (the "already connected" path) use the default carrier behavior instead. A genuine full-handshake null-modem cable (DTR↔DCD wired through) is required for Win9x dial-up — a data-only (TX/RX) cable will connect a game but not Win9x DUN.
Copy heddle.ini.example to heddle.ini and edit it. The
format is a simple key = value with #/; comments; both Windows and Unix line
endings work. Global keys match the flag names; connections are defined as named
[profile "..."] sections (one per machine), so one host can run several at once.
# Global settings
web = true
web-addr = :8080
# An IPX bridge to a public server
[profile "innov8bit"]
mode = server
serial = COM4
baud = 115200
server = ipx.innov8bit.com
server-port = 10000
autostart = true
# A Windows 9x dial-up-ISP connection (see "Dial-up ISP" above)
[profile "dialup"]
mode = modem
serial = COM5
baud = 115200
autostart = true
lan = on
# A DOS SLIP IP connection (ETHERSL -> internet, see "SLIP IP mode" above)
[profile "dos-internet"]
mode = slip
serial = COM6
baud = 115200
autostart = true
wan = on
[phonebook]
internet = pppThe configuration reference documents every key, default,
validation rule, and restart requirement. The dashboard can also save/edit
profiles for you (it writes them below a # --- heddle profiles ... --- marker;
keep hand-edits above it).
With -web, the host serves a browser control panel (reachable from any device
on the LAN, e.g. http://raspberrypi.local:8080). It shows:
- IPX connections grouped by server, each as a card with live state, assigned IPX node, packet counters and rate, and per-connection controls (start / stop / reconnect / remove / rename / wire-log toggle).
- A live topology map per server: a node/edge graph of who is talking to whom, inferred from observed traffic. Your machines are highlighted; external players (other clients on the same server) appear as they send traffic, and links light up when two nodes are actively exchanging packets.
- SLIP IP links and modem/PPP connections as their own cards too — the add-connection form picks the mode (server / switch / SLIP IP / modem) and exposes the relevant fields (server address, or the SLIP "route to LAN" option, etc.), so every connection type is managed from the dashboard.
- An activity log with connection events (and full packet detail when a connection's wire log is enabled).
- An add-connection form — the host is multi-connection, so one box can bridge several DOS machines at once.
- A focused connection editor drawer that keeps the connection list visible, groups advanced modem/TAP settings, and reports save or restart failures in place.
The dashboard has no built-in authentication or TLS. Do not expose it directly to the public internet. See the operations guide for safe binding and remote-access options, and for an explanation of saved profiles versus running connections.
Because IPX is connectionless (no client roster, no disconnect event), the external-client and link information is inferred from traffic with liveness timeouts — active links light up, idle ones dim, and stale ones are pruned.
Only needed if you want to build it yourself. Requires Go (1.23+).
go build -o heddle . # build for this machine
./build-all.sh # or build-all.bat on Windows: all platforms -> dist/
go test ./... # run the unit tests
build-all cross-compiles every supported platform from one machine (pure-Go,
CGO_ENABLED=0).
internal/slip— SLIP framing/deframing (RFC 1055), the serial wire codec.internal/ipxbox— DOSbox/IPXBox UDP client (registration, ping reply, IPX).internal/ipxswitch— the built-in local IPX switch (server-less LAN play).internal/bridge— one DOS-machine ↔ IPXBox bridge instance.internal/modem— Hayes AT modem emulation over TCP (modem mode, BBS/telnet).internal/ppp— PPP server (HDLC, LCP/IPCP), userspace NAT, routing policy (WAN/LAN/client↔client), host-as-resolver DNS, WINS.internal/manager— registry of connections + inferred topology tracking.internal/config— the INI config loader.internal/web— the embedded web dashboard (HTTP + Server-Sent Events).main.go— entry point: config + flags, wires manager ↔ web.dos/— the DOS IPX serial client (dalicom.exe) and its Open Watcom build. Seedos/README.md.
Heddle's DOS client (dalicom, under dos/) is derived from Dali, Simon
Howard's DOS IPX driver (https://github.com/fragglet/dali). The IPX core files
(dos/ipx.c, dos/ipx.h, dos/ints.c, dos/ints.h, dos/dbipx.h) retain
Simon Howard's copyright alongside modifications for Heddle; see each file's
header and the NOTICE file. Dali's UDP/mTCP transport is not part of
Heddle — Heddle uses a lighter serial (SLIP-framed) transport. The Go host is an
original work.

