netatalk.io

Dev Docs AppleTalk Networking

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

  1. main() parses options (-1, -2, -d, -f, -P, -t, -v), initializes loopback, calls readconf() or getifconf()
  2. bootaddr() begins address acquisition per interface:
  3. Phase 1: calls rtmp_request() to discover network number
  4. Phase 2: calls zip_getnetinfo() to query net range and zone
  5. setaddr() configures the kernel interface via ifconfig() (SIOCSIFADDR/SIOCGIFADDR), opens and binds all DDP sockets
  6. as_timer() fires every 10 seconds via SIGALRM, handling:
  7. Router timeouts and route poisoning (RTMPHOPS_POISON)
  8. ZIP queries for missing zone information
  9. RTMP broadcast generation (for routers)
  10. 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):

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

  1. Reads papd.conf for printer definitions
  2. Opens an ATP socket per printer via atp_open()
  3. Registers each printer via nbp_rgstr() (typically as hostname:LaserWriter@*)
  4. Accepts PAP open requests, forks a child per connection
  5. Receives PostScript data, spools via CUPS or lp
  6. 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

  1. Opens ATP socket via atp_open(ATADDR_ANYPORT, ...)
  2. Registers as hostname:TimeLord@* via nbp_rgstr()
  3. Waits for TL_GETTIME requests via atp_rreq()
  4. Responds with Unix time + EPOCH offset (optionally adjusted for local timezone with -l flag)
  5. 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

  1. Reads macipgw.conf or command-line options for network, netmask, nameserver, zone
  2. Opens a DDP socket and a TUN/TAP device
  3. Event loop uses select() on both file descriptors:
  4. DDP packet → macip_input() → extract IP → write to TUN
  5. TUN packet → tunnel_input() → encapsulate in DDP → send to Mac
  6. 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

  1. Opens ATP socket on port 3 via atp_open(3, ...)
  2. Registers the three NBP service types
  3. Receives boot block requests via atp_rreq()
  4. Extracts block offset from request: fileoff = ((req & 0x00ff0000) >> 7) | ((req & 0x0000ff00) << 9)
  5. Reads 512 bytes from the boot image file via a2bootreq()
  6. Sends data back via atp_sresp()
  7. Returns TL_OK('\0') for success or TL_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