Dev Docs AppleTalk Networking
- AppleTalk Networking
- Overview
- AppleTalk Protocol Stack
- atalkd — AppleTalk Network Manager Daemon
- Source Files
- Internal Headers
- Architecture
- Service Dispatch Table
- Interface Data Model
- Configuration File Format
- Startup and Address Acquisition
- RTMP — Routing Table Maintenance Protocol
- NBP — Name Binding Protocol
- ZIP — Zone Information Protocol
- AEP — AppleTalk Echo Protocol
- Phase 1 / Phase 2 Compatibility
- Service Discovery via NBP
- Kernel Module — BSD DDP/AARP Stack
- ATP — AppleTalk Transaction Protocol
- ASP — AppleTalk Session Protocol
- papd — Printer Access Protocol Daemon
- timelord — Time Synchronization Service
- macipgw — MacIP Gateway
- a2boot — Apple II Network Boot Server
- Build Configuration
AppleTalk Networking
Overview
Netatalk implements the AppleTalk protocol suite, enabling file sharing, printing, time synchronization, and network booting for classic Macintosh, Apple IIgs, and Apple IIe systems. AppleTalk support is an optional build-time feature controlled by the with-appletalk meson option. When disabled, the preprocessor macro NO_DDP is defined, which guards all AppleTalk-specific headers and code paths.
The AppleTalk subsystem comprises five daemons and a BSD kernel module:
| Component | Location | Purpose |
|---|---|---|
| atalkd | etc/atalkd/ |
AppleTalk network manager daemon |
| papd | etc/papd/ |
Printer Access Protocol daemon |
| timelord | contrib/timelord/ |
Timelord time synchronization service |
| macipgw | contrib/macipgw/ |
MacIP gateway (IP over AppleTalk) |
| a2boot | contrib/a2boot/ |
Apple II network boot server |
| kernel module | sys/netatalk/ |
BSD kernel DDP/AARP implementation |
AppleTalk Protocol Stack
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph TB
subgraph "Application Layer"
AFP["AFP<br/>Apple Filing Protocol"]:::blue
PAP["PAP<br/>Printer Access Protocol"]:::blue
end
subgraph "Session Layer"
ASP["ASP<br/>AppleTalk Session Protocol"]:::cyan
end
subgraph "Transport Layer"
ATP["ATP<br/>AppleTalk Transaction Protocol"]:::green
end
subgraph "Network / Routing"
RTMP["RTMP<br/>Routing Table Maintenance"]:::yellow
NBP["NBP<br/>Name Binding Protocol"]:::yellow
ZIP["ZIP<br/>Zone Information Protocol"]:::yellow
AEP["AEP<br/>AppleTalk Echo Protocol"]:::yellow
end
subgraph "Datagram Layer"
DDP["DDP<br/>Datagram Delivery Protocol"]:::salmon
end
subgraph "Data Link Layer"
AARP["AARP<br/>AppleTalk Address<br/>Resolution Protocol"]:::purple
ET["EtherTalk<br/>802.3 / Ethernet"]:::purple
end
AFP --> ASP
PAP --> ATP
ASP --> ATP
ATP --> DDP
RTMP --> DDP
NBP --> DDP
ZIP --> DDP
AEP --> DDP
DDP --> AARP
AARP --> ET
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
DDP Type Constants
All DDP socket types are defined in include/atalk/ddp.h:
| Constant | Value | Protocol |
|---|---|---|
DDPTYPE_RTMPRD |
1 | RTMP Response/Data |
DDPTYPE_NBP |
2 | Name Binding Protocol |
DDPTYPE_ATP |
3 | AppleTalk Transaction Protocol |
DDPTYPE_AEP |
4 | AppleTalk Echo Protocol |
DDPTYPE_RTMPR |
5 | RTMP Request |
DDPTYPE_ZIP |
6 | Zone Information Protocol |
DDPTYPE_ADSP |
7 | AppleTalk Data Stream Protocol |
atalkd — AppleTalk Network Manager Daemon
The atalkd daemon is the core AppleTalk networking component. It manages interface configuration, address acquisition, routing, zone management, and service dispatch for four AppleTalk protocols.
Source Files
| File | Purpose |
|---|---|
etc/atalkd/main.c |
Daemon entry point, event loop, signal handlers, address bootstrap |
etc/atalkd/config.c |
Configuration file parsing (readconf()), writing (writeconf()), and interface discovery (getifconf()) |
etc/atalkd/rtmp.c |
RTMP packet processing (rtmp_packet()), routing table management |
etc/atalkd/nbp.c |
NBP packet processing (nbp_packet()) |
etc/atalkd/zip.c |
ZIP packet processing (zip_packet()), zone management (addzone()) |
etc/atalkd/aep.c |
AEP echo handler (aep_packet()) |
etc/atalkd/multicast.c |
EtherTalk multicast address setup (addmulti()) |
etc/atalkd/route.c |
Kernel routing table manipulation (route()) |
Internal Headers
| Header | Key Contents |
|---|---|
etc/atalkd/interface.h |
struct interface, IFACE_* flags |
etc/atalkd/rtmp.h |
struct rtmptab, struct rtmp_head, struct rtmp_tuple |
etc/atalkd/zip.h |
struct ziptab, zone table globals |
etc/atalkd/gate.h |
struct gate — gateway (router) tracking |
etc/atalkd/atserv.h |
struct atport, struct atserv — service dispatch |
Architecture
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph TB
CONF["atalkd.conf<br/>Configuration File"]:::yellow
MAIN["main()<br/>Event Loop + select()"]:::blue
TIMER["as_timer()<br/>10-second SIGALRM"]:::salmon
BOOT["bootaddr()<br/>Address Bootstrap"]:::green
SET["setaddr()<br/>Interface Setup + Bind"]:::green
subgraph "Protocol Handlers"
RTMP_H["rtmp_packet()<br/>DDP socket 1"]:::purple
NBP_H["nbp_packet()<br/>DDP socket 2"]:::purple
AEP_H["aep_packet()<br/>DDP socket 4"]:::purple
ZIP_H["zip_packet()<br/>DDP socket 6"]:::purple
end
CONF --> MAIN
MAIN --> BOOT
BOOT --> SET
MAIN -->|"recvfrom()"| RTMP_H
MAIN -->|"recvfrom()"| NBP_H
MAIN -->|"recvfrom()"| AEP_H
MAIN -->|"recvfrom()"| ZIP_H
TIMER -->|"RTMP broadcasts<br/>ZIP queries"| MAIN
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10
Service Dispatch Table
The daemon registers four protocol handlers in the atserv array, each bound to a well-known DDP socket:
static struct atserv atserv[] = {
{ "rtmp", 1, rtmp_packet }, /* DDP socket 1 — RTMP */
{ "nbp", 2, nbp_packet }, /* DDP socket 2 — NBP */
{ "echo", 4, aep_packet }, /* DDP socket 4 — AEP */
{ "zip", 6, zip_packet }, /* DDP socket 6 — ZIP */
};
Each entry’s as_port is resolved via getservbyname() at startup, falling back to the hardcoded value. The daemon opens AF_APPLETALK / SOCK_DGRAM sockets, binds them, and dispatches incoming packets through the ap_packet function pointer.
Interface Data Model
The struct interface tracks each AppleTalk network interface:
struct interface {
struct interface *i_next;
char i_name[IFNAMSIZ]; /* e.g., "eth0" */
int i_flags; /* IFACE_* bitmask */
int i_time; /* timer for configuration */
int i_group; /* isolated AppleTalk domain */
struct sockaddr_at i_addr; /* assigned AppleTalk address */
struct sockaddr_at i_caddr; /* configured address hint */
struct ziptab *i_czt; /* configured zone list (seed) */
struct rtmptab *i_rt; /* routing table for this iface */
struct gate *i_gate; /* known routers on this iface */
struct atport *i_ports; /* bound DDP sockets */
};
Key IFACE_* flags:
| Flag | Value | Meaning |
|---|---|---|
IFACE_PHASE1 |
0x001 |
AppleTalk Phase 1 (single network) |
IFACE_PHASE2 |
0x002 |
AppleTalk Phase 2 (extended networks) |
IFACE_LOOPBACK |
0x004 |
Loopback interface |
IFACE_SEED |
0x008 |
Seed router — provides authoritative config |
IFACE_ADDR |
0x010 |
Address has been set |
IFACE_CONFIG |
0x020 |
Fully configured |
IFACE_NOROUTER |
0x040 |
No router detected on interface |
IFACE_LOOP |
0x080 |
Loopback route installed |
IFACE_RSEED |
0x100 |
Router seed (implies -router switch) |
IFACE_DONTROUTE |
0x200 |
Don’t route this interface |
IFACE_ISROUTER |
0x400 |
Acting as a router |
IFACE_ERROR |
0x2000 |
Send error occurred |
Configuration File Format
Parsed by readconf() from atalkd.conf:
# interface [-seed|-router] [-dontroute] [-phase 1|2]
# [-net N[-M]] [-addr N.N] [-zone "name"]...
eth0 -phase 2 -net 8043-8044 -addr 8043.142 -zone "Engineering"
eth1 -seed -phase 2 -net 100-100 -zone "Lab"
The params table maps option names to handler functions:
| Option | Handler | Effect |
|---|---|---|
-router |
router() |
Seed + route (sets IFACE_RSEED \| IFACE_SEED \| IFACE_ISROUTER) |
-dontroute |
dontroute() |
Don’t route this interface |
-seed |
seed() |
Act as seed router |
-phase |
phase() |
Set Phase 1 or Phase 2 |
-net |
net() |
Set network range |
-addr |
addr() |
Set AppleTalk address |
-zone |
zone() |
Add zone name (requires -seed) |
Startup and Address Acquisition
main()parses options (-1,-2,-d,-f,-P,-t,-v), initializes loopback, callsreadconf()orgetifconf()bootaddr()begins address acquisition per interface:- Phase 1: calls
rtmp_request()to discover network number - Phase 2: calls
zip_getnetinfo()to query net range and zone setaddr()configures the kernel interface viaifconfig()(SIOCSIFADDR/SIOCGIFADDR), opens and binds all DDP socketsas_timer()fires every 10 seconds viaSIGALRM, handling:- Router timeouts and route poisoning (
RTMPHOPS_POISON) - ZIP queries for missing zone information
- RTMP broadcast generation (for routers)
- Stability detection — writes config when stable
RTMP — Routing Table Maintenance Protocol
RTMP manages inter-network routing. The routing table uses struct rtmptab:
struct rtmptab {
struct rtmptab *rt_next, *rt_prev; /* gateway's route list */
struct rtmptab *rt_inext, *rt_iprev; /* interface's in-use list */
unsigned short rt_firstnet, rt_lastnet; /* network range */
unsigned char rt_hops; /* hop count (max 15, poison 31) */
unsigned char rt_state; /* RTMPTAB_PERM..RTMPTAB_BAD */
unsigned char rt_flags; /* RTMPTAB_ZIPQUERY|HASZONES|EXTENDED|ROUTE */
unsigned char rt_nzq; /* ZIP query count */
struct gate *rt_gate; /* gateway (NULL for local iface) */
struct list *rt_zt; /* zone list */
const struct interface *rt_iface; /* output interface */
};
Route state progression: RTMPTAB_PERM(0) → RTMPTAB_GOOD(1) → RTMPTAB_SUSP1(2) → RTMPTAB_SUSP2(3) → RTMPTAB_BAD(4).
Key functions in etc/atalkd/rtmp.c:
| Function | Purpose |
|---|---|
rtmp_packet() |
Process incoming RTMP data and requests |
rtmp_request() |
Send RTMP request (Phase 1 bootstrapping) |
rtmp_replace() |
Find alternative route when a route is poisoned |
rtmp_free() |
Free a routing entry |
rtmp_delzonemap() |
Remove zone-to-route mappings |
looproute() |
Add/delete loopback route in kernel |
gateroute() |
Add/delete gateway route in kernel |
Wire format structures from etc/atalkd/rtmp.h:
struct rtmp_head { /* RTMP response header */
unsigned short rh_net; /* sender's network number */
unsigned char rh_nodelen; /* always 8 for extended networks */
unsigned char rh_node; /* sender's node ID */
};
struct rtmp_tuple { /* RTMP routing tuple (3 bytes) */
unsigned short rt_net; /* network number */
unsigned char rt_dist; /* distance (hops), high bit = extended */
};
NBP — Name Binding Protocol
NBP maps human-readable names (object:type@zone) to AppleTalk addresses. The daemon handles NBP operations in nbp_packet().
Wire format from include/atalk/nbp.h:
struct nbphdr {
uint32_t nh_cnt : 4, /* tuple count */
nh_op : 4, /* operation code */
nh_id : 8; /* transaction ID */
};
struct nbptuple { /* entity address portion */
uint16_t nt_net; /* network number */
uint8_t nt_node; /* node number */
uint8_t nt_port; /* socket number */
uint8_t nt_enum; /* enumerator */
};
NBP operations (include/atalk/nbp.h):
| Constant | Value | Operation |
|---|---|---|
NBPOP_BRRQ |
0x1 |
Broadcast Request |
NBPOP_LKUP |
0x2 |
Lookup |
NBPOP_LKUPREPLY |
0x3 |
Lookup Reply |
NBPOP_FWD |
0x4 |
Forward Request |
NBPOP_RGSTR |
0x7 |
Register |
NBPOP_UNRGSTR |
0x8 |
Unregister |
NBPOP_CONFIRM |
0x9 |
Confirm |
NBPOP_OK |
0xa |
Status Reply |
The Network Visible Entity (struct nbpnve) stores a resolved name:
struct nbpnve {
struct sockaddr_at nn_sat; /* address */
uint8_t nn_objlen;
char nn_obj[NBPSTRLEN]; /* object name (max 32) */
uint8_t nn_typelen;
char nn_type[NBPSTRLEN]; /* type name */
uint8_t nn_zonelen;
char nn_zone[NBPSTRLEN]; /* zone name */
};
ZIP — Zone Information Protocol
ZIP manages zone names and their mapping to network ranges. Zone data is stored in struct ziptab:
struct ziptab {
struct ziptab *zt_next, *zt_prev;
unsigned char zt_len; /* zone name length */
char *zt_name; /* zone name string */
unsigned char *zt_bcast; /* multicast address for zone */
struct list *zt_rt; /* routes in this zone */
};
Wire format from include/atalk/zip.h:
struct ziphdr {
uint8_t zh_op; /* operation */
uint8_t zh_cnt; /* count / flags / zero (context-dependent) */
};
ZIP operations (include/atalk/zip.h):
| Constant | Value | Operation |
|---|---|---|
ZIPOP_QUERY |
1 | Query zones for networks |
ZIPOP_REPLY |
2 | Reply with zone data |
ZIPOP_GNI |
5 | GetNetInfo request |
ZIPOP_GNIREPLY |
6 | GetNetInfo reply |
ZIPOP_NOTIFY |
7 | Zone change notification |
ZIPOP_EREPLY |
8 | Extended Reply |
ZIPOP_GETMYZONE |
7 | Get My Zone (ATP-based) |
ZIPOP_GETZONELIST |
8 | Get Zone List (ATP-based) |
ZIPOP_GETLOCALZONES |
9 | Get Local Zones (ATP-based) |
Key functions in etc/atalkd/zip.c:
| Function | Purpose |
|---|---|
zip_packet() |
Process ZIP packets (query, reply, GNI, notify) |
zip_getnetinfo() |
Send GetNetInfo for Phase 2 bootstrapping |
addzone() |
Add a zone to a route’s zone list |
AEP — AppleTalk Echo Protocol
The simplest protocol handler. aep_packet() receives an echo request, flips the operation byte from AEPOP_REQUEST(1) to AEPOP_REPLY(2), and sends the packet back to the sender unchanged. Defined in include/atalk/aep.h.
Phase 1 / Phase 2 Compatibility
Netatalk’s atalkd supports both AppleTalk protocol phases, controlled per-interface via the -phase option in atalkd.conf (default: Phase 2):
- Phase 1 (original LocalTalk): single network per cable, no zones, 8-bit node addressing. Used by early Macs and Apple II systems.
- Phase 2 (extended AppleTalk): multiple networks per cable via network ranges, zone names, EtherTalk encapsulation. Required for all Ethernet-attached clients.
The IFACE_PHASE1 and IFACE_PHASE2 flags in struct interface control per-interface behavior. Phase 2 routes are marked with RTMPTAB_EXTENDED. The kernel struct netrange stores the phase number and cable network range. Phase 2 uses 802.2 SNAP encapsulation defined in sys/netatalk/phase2.h.
Data Link Layers: Netatalk supports EtherTalk (AppleTalk over Ethernet, type 0x809B) with AARP (0x80F3). Multicast addresses for EtherTalk zones are configured by addmulti() during interface setup.
Service Discovery via NBP
All Netatalk AppleTalk services register themselves via NBP so classic Mac clients can discover them through the Chooser:
| Service | NBP Object | NBP Type | Registered By |
|---|---|---|---|
| AFP file server | hostname | AFPServer |
afpd (via ASP) |
| Printer | hostname | LaserWriter |
papd |
| Time server | hostname | TimeLord |
timelord |
| Apple IIgs boot | hostname | Apple //gs |
a2boot |
| Apple IIe boot | hostname | Apple //e Boot |
a2boot |
| ProDOS16 image | hostname | ProDOS16 Image |
a2boot |
Registration uses nbp_rgstr() and unregistration uses nbp_unrgstr().
Kernel Module — BSD DDP/AARP Stack
The sys/netatalk/ directory contains a BSD kernel implementation of the DDP and AARP protocols. This code targets classic BSD kernels (4.3BSD/4.4BSD) and is included in the build when AppleTalk is enabled.
Source Files
| File | Purpose |
|---|---|
sys/netatalk/ddp_input.c |
DDP packet reception (atintr(), ddp_input()) |
sys/netatalk/ddp_output.c |
DDP packet transmission (ddp_output(), ddp_route()), checksum (at_cksum()) |
sys/netatalk/ddp_usrreq.c |
Socket layer interface (ddp_usrreq()), PCB management |
sys/netatalk/aarp.c |
AARP address resolution (aarpresolve(), aarpinput(), aarpprobe()) |
sys/netatalk/at_control.c |
Interface ioctl handling (at_control()), initialization (at_ifinit()) |
sys/netatalk/at_proto.c |
Protocol switch table registration |
Kernel Headers
| Header | Key Contents |
|---|---|
sys/netatalk/at.h |
struct at_addr (s_net, s_node), struct sockaddr_at, struct netrange, port ranges, Ethernet type codes |
sys/netatalk/at_var.h |
struct at_ifaddr — per-interface AppleTalk address, AFA_* flags |
sys/netatalk/ddp_var.h |
struct ddpcb — DDP protocol control block, struct ddpstat — packet counters |
sys/netatalk/aarp.h |
struct ether_aarp, struct aarptab, AARP operation codes |
sys/netatalk/ddp.h |
DDP header format (kernel-internal) |
sys/netatalk/phase2.h |
Phase 2 SNAP/802.2 encapsulation |
AppleTalk Address Structure
From sys/netatalk/at.h:
struct at_addr {
unsigned short s_net; /* network number (0–65534) */
unsigned char s_node; /* node number (1–254, 255=broadcast) */
};
struct sockaddr_at {
/* BSD4_4: sat_len, sat_family; else: short sat_family */
unsigned char sat_port; /* DDP socket number (1–254) */
struct at_addr sat_addr; /* network address */
char sat_zero[8]; /* netrange stored here by convention */
};
struct netrange {
unsigned char nr_phase; /* 1 or 2 */
unsigned short nr_firstnet; /* first network in cable range */
unsigned short nr_lastnet; /* last network in cable range */
};
Port ranges (sys/netatalk/at.h):
| Constant | Value | Meaning |
|---|---|---|
ATPORT_FIRST |
1 | First valid port |
ATPORT_RESERVED |
128 | Start of unprivileged ports |
ATPORT_LAST |
254 | Last valid port |
DDP Protocol Control Block
struct ddpcb tracks each open DDP socket:
struct ddpcb {
struct sockaddr_at ddp_fsat, ddp_lsat; /* foreign and local addresses */
struct route ddp_route; /* cached route */
struct socket *ddp_socket; /* back pointer to socket */
struct ddpcb *ddp_prev, *ddp_next; /* global list */
struct ddpcb *ddp_pprev, *ddp_pnext; /* per-port list */
};
DDP Statistics
struct ddpstat tracks kernel-level counters:
struct ddpstat {
uint32_t ddps_short; /* short header packets received */
uint32_t ddps_long; /* long header packets received */
uint32_t ddps_nosum; /* no checksum */
uint32_t ddps_badsum; /* bad checksum */
uint32_t ddps_tooshort; /* packet too short */
uint32_t ddps_toosmall; /* not enough data */
uint32_t ddps_forward; /* packets forwarded */
uint32_t ddps_encap; /* packets encapsulated */
uint32_t ddps_cantforward; /* unreachable destination */
uint32_t ddps_nosockspace; /* no space in socket buffer */
};
AARP — AppleTalk Address Resolution Protocol
AARP maps AppleTalk addresses to Ethernet MAC addresses, analogous to ARP for IP. From sys/netatalk/aarp.h:
struct aarptab {
struct at_addr aat_ataddr; /* AppleTalk address */
unsigned char aat_enaddr[6]; /* Ethernet address */
unsigned char aat_timer; /* expiry timer */
unsigned char aat_flags; /* state flags */
struct mbuf *aat_hold; /* packet waiting for resolution */
};
AARP operations:
| Constant | Value | Meaning |
|---|---|---|
AARPOP_REQUEST |
0x01 |
Who has this AppleTalk address? |
AARPOP_RESPONSE |
0x02 |
I have that address |
AARPOP_PROBE |
0x03 |
Is this address in use? (during boot) |
Key kernel functions in sys/netatalk/aarp.c:
| Function | Purpose |
|---|---|
aarpresolve() |
Resolve AppleTalk address to Ethernet; queue packet if pending |
aarpinput() |
Process incoming AARP packets |
at_aarpinput() |
Handle AARP request/response/probe |
aarpwhohas() |
Send AARP request for an address |
aarpprobe() |
Probe for address uniqueness during startup |
aarptnew() |
Allocate new AARP table entry |
aarptimer() |
Expire stale AARP entries |
ATP — AppleTalk Transaction Protocol
ATP provides a reliable request/response layer over DDP, used by ASP, PAP, timelord, and a2boot. Defined in include/atalk/atp.h.
Packet Format
struct atphdr {
uint8_t atphd_ctrlinfo; /* function code + flags */
uint8_t atphd_bitmap; /* response bitmap or sequence number */
uint16_t atphd_tid; /* transaction ID */
};
Function codes and flags (include/atalk/atp.h):
| Constant | Value | Meaning |
|---|---|---|
ATP_TREQ |
1<<6 |
Transaction Request |
ATP_TRESP |
2<<6 |
Transaction Response |
ATP_TREL |
3<<6 |
Transaction Release |
ATP_XO |
1<<5 |
Exactly-Once mode |
ATP_EOM |
1<<4 |
End of Message |
ATP_STS |
1<<3 |
Send Transaction Status |
Protocol parameters:
| Constant | Value | Meaning |
|---|---|---|
ATP_MAXDATA |
582 | Maximum ATP data (578 + 4 user bytes) |
ATP_BUFSIZ |
587 | Maximum packet size |
ATP_HDRSIZE |
5 | Header size (includes DDP type byte) |
Handle Structure
struct atp_handle manages an open ATP session:
struct atp_handle {
int atph_socket; /* DDP socket */
struct sockaddr_at atph_saddr; /* bound address */
uint16_t atph_tid; /* last TID sent */
uint16_t atph_rtid; /* last TID received */
uint8_t atph_rxo; /* XO flag from last request */
int atph_rreltime; /* release time (seconds) */
struct atpbuf *atph_sent; /* sent XO packets */
struct atpbuf *atph_queue; /* pending packets */
int atph_reqtries; /* retry count */
int atph_reqto; /* retry timeout */
int atph_rrespcount; /* expected response count */
uint8_t atph_rbitmap; /* request bitmap */
struct atpbuf *atph_reqpkt; /* last request packet */
struct timeval atph_reqtv; /* time of last send */
struct atpbuf *atph_resppkt[8]; /* response buffers */
};
API Functions
Declared in include/atalk/atp.h:
| Function | Purpose |
|---|---|
atp_open() |
Open ATP session on a port |
atp_close() |
Close ATP session |
atp_sreq() |
Send request |
atp_rresp() |
Receive response |
atp_rreq() |
Receive request (server side) |
atp_sresp() |
Send response (server side) |
ASP — AppleTalk Session Protocol
ASP provides session management over ATP, used for AFP over AppleTalk connections. Defined in include/atalk/asp.h.
The ASP handle wraps an ATP handle with session state:
typedef struct ASP {
ATP asp_atp; /* underlying ATP handle */
struct sockaddr_at asp_sat; /* peer address */
uint8_t asp_wss; /* workstation session socket */
uint8_t asp_sid; /* session ID */
/* ... command/data buffers ... */
char cmdbuf[ASP_CMDMAXSIZ]; /* 582 bytes */
char data[ASP_DATAMAXSIZ]; /* 4656 bytes */
} *ASP;
ASP function codes (include/atalk/asp.h):
| Constant | Value | Meaning |
|---|---|---|
ASPFUNC_CLOSE |
1 | Close session |
ASPFUNC_CMD |
2 | Command (carries AFP request) |
ASPFUNC_STAT |
3 | Get status |
ASPFUNC_OPEN |
4 | Open session |
ASPFUNC_TICKLE |
5 | Keep-alive tickle |
ASPFUNC_WRITE |
6 | Write data |
ASPFUNC_WRTCONT |
7 | Write continue |
ASPFUNC_ATTN |
8 | Attention |
AFP-over-ASP integration is in etc/afpd/afp_asp.c.
papd — Printer Access Protocol Daemon
The papd daemon provides AppleTalk-based printer sharing using the PAP protocol layered on ATP.
Source Files
| File | Purpose |
|---|---|
etc/papd/main.c |
Daemon entry point, ATP event loop, NBP registration |
etc/papd/session.c |
PAP session management |
etc/papd/lp.c |
Unix spooler integration |
etc/papd/print_cups.c |
CUPS backend integration |
etc/papd/queries.c |
PostScript query handling |
etc/papd/ppd.c |
PPD (PostScript Printer Description) parsing |
etc/papd/comment.c |
DSC comment processing |
etc/papd/auth.c |
PAP authentication |
etc/papd/uam.c |
UAM loading for PAP |
PAP Protocol
PAP constants from include/atalk/pap.h:
| Constant | Value | Operation |
|---|---|---|
PAP_OPEN |
1 | Open connection |
PAP_OPENREPLY |
2 | Open reply |
PAP_READ |
3 | Read (request data) |
PAP_DATA |
4 | Data transfer |
PAP_TICKLE |
5 | Keep-alive |
PAP_CLOSE |
6 | Close connection |
PAP_CLOSEREPLY |
7 | Close reply |
PAP_SENDSTATUS |
8 | Request printer status |
PAP_STATUS |
9 | Status response |
Data limits: PAP_MAXDATA = 512 bytes per packet, PAP_MAXQUANTUM = 8 packets per transaction.
Printer Structure
From etc/papd/printer.h:
struct printer {
char *p_name; /* NBP object name */
char *p_type; /* NBP type (default: "LaserWriter") */
char *p_zone; /* NBP zone (default: "*") */
char *p_ppdfile; /* PPD file path */
char p_status[255]; /* current status string */
int p_flags; /* P_SPOOLED|P_PIPED|P_CUPS|... */
struct at_addr p_addr; /* AppleTalk address */
ATP p_atp; /* ATP handle for this printer */
struct printer *p_next; /* linked list */
/* ... spooling fields ... */
};
Printer flags (etc/papd/printer.h):
| Flag | Value | Meaning |
|---|---|---|
P_PIPED |
1<<0 |
Pipe print data to a command |
P_SPOOLED |
1<<1 |
Spool via lp subsystem |
P_REGISTERED |
1<<2 |
NBP name registered |
P_CUPS |
1<<8 |
CUPS integration enabled |
P_CUPS_PPD |
1<<9 |
Using CUPS auto-generated PPD |
P_CUPS_AUTOADDED |
1<<10 |
Auto-discovered from CUPS |
Operation
- Reads
papd.conffor printer definitions - Opens an ATP socket per printer via
atp_open() - Registers each printer via
nbp_rgstr()(typically ashostname:LaserWriter@*) - Accepts PAP open requests, forks a child per connection
- Receives PostScript data, spools via CUPS or
lp - On shutdown, calls
nbp_unrgstr()for each registered printer
timelord — Time Synchronization Service
timelord provides time synchronization for classic Macintosh systems using the Timelord protocol (reverse-engineered from the CAP distribution).
Protocol
Uses ATP for request/response. The protocol constants (contrib/timelord/timelord.c):
| Constant | Value | Meaning |
|---|---|---|
TL_GETTIME |
0 | Request current time |
TL_OK |
12 | Successful response |
TL_BAD |
10 | Error response |
EPOCH |
0x7C25B080 |
Mac epoch offset (seconds from Jan 1 1904 to Jan 1 1970) |
Operation
- Opens ATP socket via
atp_open(ATADDR_ANYPORT, ...) - Registers as
hostname:TimeLord@*vianbp_rgstr() - Waits for
TL_GETTIMErequests viaatp_rreq() - Responds with Unix time +
EPOCHoffset (optionally adjusted for local timezone with-lflag) - Sends response via
atp_sresp()
macipgw — MacIP Gateway
macipgw bridges IP traffic over AppleTalk networks, allowing classic Macs without Ethernet TCP/IP to access IP networks through a TUN/TAP tunnel.
Source Files
| File | Purpose |
|---|---|
contrib/macipgw/main.c |
Daemon entry point, event loop, configuration |
contrib/macipgw/macip.c |
MacIP protocol implementation |
contrib/macipgw/tunnel_linux.c |
Linux TUN device support |
contrib/macipgw/tunnel_bsd.c |
BSD TUN device support |
contrib/macipgw/atp_input.c |
ATP packet processing |
contrib/macipgw/nbp_lkup_async.c |
Asynchronous NBP lookups |
contrib/macipgw/util.c |
Utility functions |
contrib/macipgw/common.h |
macip_options structure |
Architecture
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph LR
MAC["Classic Mac<br/>(MacIP client)"]:::yellow
GW["macipgw<br/>Gateway Daemon"]:::blue
TUN["TUN/TAP<br/>Device"]:::green
NET["IP Network"]:::purple
MAC -->|"DDP / MacIP<br/>packets"| GW
GW -->|"macip_input()"| TUN
TUN -->|"tunnel_input()"| GW
GW -->|"DDP / MacIP<br/>packets"| MAC
TUN <-->|"IP packets"| NET
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
Configuration
The macip_options structure holds settings loaded from macipgw.conf via iniparser:
typedef struct {
char *network; /* MacIP network address */
char *netmask; /* MacIP netmask */
char *nameserver; /* DNS server for MacIP clients */
char *zone; /* AppleTalk zone */
char *unprivileged_user; /* drop privileges to this user */
} macip_options;
Operation
- Reads
macipgw.confor command-line options for network, netmask, nameserver, zone - Opens a DDP socket and a TUN/TAP device
- Event loop uses
select()on both file descriptors: - DDP packet →
macip_input()→ extract IP → write to TUN - TUN packet →
tunnel_input()→ encapsulate in DDP → send to Mac macip_idle()handles periodic maintenance
Platform support: Linux (tunnel_linux.c), FreeBSD/NetBSD/OpenBSD (tunnel_bsd.c).
a2boot — Apple II Network Boot Server
a2boot serves boot blocks and ProDOS images to Apple IIgs and Apple IIe computers over ATP.
NBP Registrations
The daemon registers three services:
| NBP Type | Request Code | File Served |
|---|---|---|
Apple //gs |
0x01000000 |
ProDOS16 Boot Blocks |
Apple //e Boot |
0x02000000 |
Apple IIe Boot Blocks |
ProDOS16 Image |
0x03000000 |
ProDOS16 Image |
Operation
- Opens ATP socket on port 3 via
atp_open(3, ...) - Registers the three NBP service types
- Receives boot block requests via
atp_rreq() - Extracts block offset from request:
fileoff = ((req & 0x00ff0000) >> 7) | ((req & 0x0000ff00) << 9) - Reads 512 bytes from the boot image file via
a2bootreq() - Sends data back via
atp_sresp() - Returns
TL_OK('\0') for success orTL_EOF('\1') at end of file
Build Configuration
AppleTalk support is controlled in meson.build:
enable_appletalk = get_option('with-appletalk')
if not enable_appletalk
have_appletalk = false
cdata.set('NO_DDP', 1) # Guards all AppleTalk headers
else
have_appletalk = true
endif
When have_appletalk is true, the following subdirectories are built:
| Meson Guard | Components Built |
|---|---|
etc/meson.build |
atalkd, papd (when have_appletalk) |
contrib/meson.build |
a2boot, timelord, macipgw (macipgw on FreeBSD/Linux/NetBSD/OpenBSD only) |
sys/meson.build |
Kernel module headers (sys/netatalk/) |
libatalk/meson.build |
ASP library, ATP library, NBP library, PAP library |
etc/afpd/meson.build |
afp_asp.c added to afpd sources |
bin/meson.build |
aecho and other AppleTalk CLI tools |
config/meson.build |
atalkd.conf, macipgw.conf, papd.conf config files |
distrib/initscripts/meson.build |
Init scripts for atalkd, papd, timelord, a2boot, macipgw |
Meson Build Options
The meson_options.txt file defines the toggle:
option('with-appletalk', type: 'boolean', value: false,
description: 'Enable AppleTalk support')
When CUPS is also enabled alongside AppleTalk, a PAP print backend for CUPS is optionally installed (meson.build):
if get_option('with-cups') and get_option('with-appletalk')
spooldir = get_option('localstatedir') / 'spool' / 'netatalk'
endif
Per-Daemon Build Definitions
atalkd (etc/atalkd/meson.build):
- Sources: aep.c, config.c, main.c, multicast.c, nbp.c, route.c, rtmp.c, zip.c
- Links: libatalk
- Defines: _PATH_ATALKDCONF (path to atalkd.conf)
papd (etc/papd/meson.build):
- Sources: auth.c, comment.c, file.c, headers.c, lp.c, magics.c, main.c, ppd.c, print_cups.c, printcap.c, queries.c, session.c, uam.c
- Links: libatalk
- Dependencies: iniparser, optionally iconv, cups
- Defines: _PATH_PAPDCONF, _PATH_PAPDPRINTCAP, _PATH_PAPDSPOOLDIR
timelord (contrib/timelord/meson.build):
- Sources: timelord.c
- Links: libatalk
a2boot (contrib/a2boot/meson.build):
- Sources: a2boot.c
- Links: libatalk
- Defines: _PATH_A_GS_BLOCKS, _PATH_A_2E_BLOCKS, _PATH_P16_IMAGE
macipgw (contrib/macipgw/meson.build):
- Sources: atp_input.c, macip.c, main.c, nbp_lkup_async.c, util.c + tunnel_linux.c (Linux) or tunnel_bsd.c (BSD)
- Links: libatalk
- Dependencies: iniparser, optionally iconv
- Defines: _PATH_MACIPGWCONF
Footnotes
This is a mirror of the Netatalk GitHub Wiki
Last updated 2026-04-06