Dev Docs Architecture Overview
Netatalk Architecture Overview
Audited against Netatalk
d63155e13on 2026-10-10. This describes the current source checkout; older releases may differ.
Source tree
| Directory | Responsibility |
|---|---|
| etc/afpd | AFP listener, session workers, command handlers, directory and metadata caches |
| etc/netatalk | Controller, service supervision, Zeroconf registration |
| etc/uams | Dynamically loaded authentication modules |
| etc/spotlight | CNID, Localsearch, and Xapian search backends |
| libatalk | Shared protocol, AppleDouble, CNID, Unicode, VFS, ACL, and utility code |
| include/atalk | Shared structures and API declarations |
| bin | Utilities, including dbd, nad, and afppasswd |
| test | Unit tests, AFP command tests, integration scripts, and fuzzing |
| doc | Manual, man pages, and generated developer documentation inputs |
Build selection starts in meson.build and meson_options.txt. A source file’s presence does not mean every build includes it.
Process Model
netatalk supervises the afpd parent. The parent accepts DSI connections and forks a worker for each connection. Each worker owns its authentication state, open forks, mounted volumes, and caches. AppleTalk-enabled builds also provide AFP over ASP.
flowchart TD
controller[netatalk controller] --> parent[afpd listener and IPC relay]
client[AFP client] -->|TCP port 548 by default| parent
parent -->|fork| worker[afpd session worker]
worker --> filesystem[Filesystem and AppleDouble storage]
worker --> sqlite[Embedded SQLite CNID]
worker --> mysql[Optional external MySQL server]
worker -->|control and cache hints| parent
parent -->|hint pipe| worker
controller -.->|Localsearch volumes only| dbus[Private D-Bus session]
| Role | Entry points and source |
|---|---|
| Controller | main(), run_afpd(), timer_impl() in etc/netatalk/netatalk.c |
| AFP parent | main(), dsi_start() in etc/afpd/main.c |
| Session creation | dsi_getsession() in libatalk/dsi/dsi_getsess.c; accept/fork in libatalk/dsi/dsi_tcp.c |
| DSI worker | afp_over_dsi() in etc/afpd/afp_dsi.c |
| AFP dispatch | preauth_switch, postauth_switch, afp_switch in etc/afpd/switch.c |
| ASP worker | afp_over_asp() in etc/afpd/afp_asp.c, built with AppleTalk support |
Event loops and signals
The controller supports libevent or libev, selected by the Meson with-eventloop option (auto prefers libevent). Shared behavior resides in sigterm_impl(), sigquit_impl(), sighup_impl(), sigchld_impl(), and timer_impl(); library-specific callbacks wrap these functions.
The AFP parent uses poll() for listeners, child IPC, and the optional afpstats Unix socket. It reaps dead children before processing descriptors or flushing hints. Pending hints select a 50 ms poll timeout; otherwise the parent blocks indefinitely. This is a batching interval, not an unconditional 50 ms delivery deadline: more descriptor activity can postpone a timeout flush.
The DSI worker polls the client socket, hint pipe, and signal self-pipe. Signal handlers record requests and wake the loop; process_deferred_signals() performs session actions in main-loop context. The idle worker receives a temporary cache-access grant while the main thread waits in poll().
See AFP Daemon, Controller Daemon, and Network Protocols.
Session data and IPC
| Structure | Definition | Ownership |
|---|---|---|
AFPObj / struct afp_options |
include/atalk/globals.h | Server/session context and parsed options |
DSI |
include/atalk/dsi.h | Transport buffers, socket, timer, session flags |
server_child_t / afp_child_t |
include/atalk/server_child.h | Parent’s PID-indexed child table and reconnect state |
struct vol |
include/atalk/volume.h | Volume configuration and per-session runtime handles |
struct dir / struct path |
include/atalk/directory.h | Cached identity, path, stat, and AppleDouble state |
struct ofork |
etc/afpd/fork.h | Open AFP fork reference, shared AppleDouble handle, virtual-fork state |
struct adouble |
include/atalk/adouble.h | Fork descriptors, parsed metadata, locks, reference counts |
Each DSI worker has a bidirectional Unix socketpair for control and a parent-to-child hint pipe. The control protocol includes IPC_SESSIONTOKEN for an opaque reconnect token, in addition to login, state, volumes, client identity, reconnect, and cache-hint messages. The wire header is 14 bytes; a cache-hint payload adds 8 bytes. Definitions are in include/atalk/server_ipc.h.
Reconnect tokens are random 8-byte values, not encoded PIDs. The parent validates the token and requesting UID before transferring a socket to an existing session. See Authentication.
Caches and persistence
Worker caches accelerate different parts of a request:
| Cache | Implementation | Behavior |
|---|---|---|
| Directory/file identity and stat | etc/afpd/dircache.c | ARC by default; LRU selectable; 65,536 live entries by default |
| AppleDouble metadata | etc/afpd/ad_cache.c | Inline in struct dir; invalidated with metadata/stat changes |
| Resource fork content | etc/afpd/ad_cache.c | Separate LRU and byte budget, 32 MiB by default |
| Parent-directory descriptors | etc/afpd/pfd_cache.c | 16 slots; accelerates validation using relative stat operations |
These caches are per process. Ordinary cache hints are best effort, and filesystem validation remains necessary. multi protocol = yes adjusts coherency defaults for files changed through other services or local tools. A CNID table reset uses a separate hint that ends affected sessions so clients discard obsolete IDs.
Persistent IDs come from mandatory SQLite or optional MySQL, through libatalk/cnid/cnid.c. AppleDouble storage holds Mac metadata and resource forks; its disk format and platform storage differ from the CNID database.
See Caching Architecture, CNID Database, and Data Flow.
Optional services and execution modes
Spotlight is selected per volume through spotlight backend: cnid (default), localsearch, or xapian, subject to build availability. Only Localsearch needs the controller’s private D-Bus/indexer setup.
The normal server runs as root and changes effective identity after login. netatalk --single-user starts a restricted server as a non-root user, requires SRP and SQLite, and serves only that user’s identity. Its configuration, PID file, credential store, and CNID state require private user-owned locations.
AppleTalk services (atalkd, papd, timelord, a2boot, and macipgw) have separate service scripts. The controller does not supervise them. See AppleTalk Networking.
Audit findings and verification scope: Developer Documentation Audit.
Footnotes
Generated from the Netatalk GitHub Wiki
Last updated 2026-10-21