OPEN SOURCE · 2026 · MIT LICENSE

appletalk/

An AppleTalk protocol stack in Rust that puts vintage Macs, emulators, and modern Linux machines on the same network — and routes them onto GlobalTalk, the hobbyist AppleTalk internet.

lang
Rust 2024
links
EtherTalk · LocalTalk · LToUDP
protocols
DDP · AARP · RTMP · ZIP · NBP · ATP · AEP · AURP
size
14.4k lines · 329 tests
runs on
Linux (raw sockets)
license
MIT

## why

AppleTalk is the networking stack that shipped with every Mac from 1985 into the 2000s. A surprising amount of it is still in use: collectors run vintage Macs, Mini vMac and Snow emulate them, and GlobalTalk stitches hobbyists' networks into one AppleTalk internet over UDP tunnels.

This project speaks all of it from a Linux box. It can watch a network and decode what it hears, join the network as a node and ask it questions, bridge emulated Macs onto a real LocalTalk or EtherTalk segment, and act as a full router with AURP tunnels to other networks. Every packet layout was checked against Inside AppleTalk, 2nd edition.

fig. 1 — the router's data path (the sniffer, node, and bridge share the same capture and parsers)
EtherTalkraw Ethernet (pnet) LToUDPUDP multicast LocalTalkTashTalk serial board wire/ parsersfail closed → hex dump router step()(input, now) → actions AURP peersUDP 387 · GlobalTalk local portsback out each link

## design

  • Parsers fail closed. Each protocol lives in its own file under src/wire/ as a pure parser with no I/O. If a parser doesn't fully recognize a packet it returns nothing and the packet is shown as a hex dump, because a wrong decode is worse than no decode.
  • The router is a pure function. Its core is step(input, now) -> Vec<Action>: routing and zone tables, route aging, and split horizon are all tested without a network, a clock, or a thread.
  • The bridge translates, it doesn't just repeat. It answers address-resolution requests on behalf of LocalTalk nodes, answers LocalTalk's node-ID probes on behalf of Ethernet nodes, and rewrites short DDP headers into extended form, so the two link types look like one network.
  • Untrusted peers can't exhaust memory. Open AURP peering is capped at 256 entries and idle strangers are evicted after 90 seconds: a stranger's packet must not buy permanent memory.

## try it

$ appletalk zones
68k Mac Club *

$ appletalk nodes
Mini:AFPServer@* at 6800.150:249
raspberrypi:AFPServer@* at 6800.3:128

$ appletalk ping -c 2 6800.3
8 bytes from 6800.3: seq=0 time=1.42 ms
8 bytes from 6800.3: seq=1 time=0.98 ms

## how it was built

I developed it with Claude Code, working from written specs and implementation plans that live in the repo. Tests build packets from byte literals and check both the parsed fields and the human-readable output.

The router is tested end to end on real hardware: a LocalTalk Mac behind a TashTalk board took an address from the router, listed every GlobalTalk zone in the Chooser, and mounted a file share from a remote network across an AURP tunnel.

← cd ../projects