netatalk.io

Dev Docs Netatalk Configuration

Netatalk Configuration System

1. Configuration Overview

Netatalk uses a centralized INI-style configuration file (afp.conf) to control all aspects of the AFP file server: global daemon settings, authentication, network binding, logging, cache tuning, charset handling, and per-volume options. The configuration system is built around the third-party iniparser library, wrapped with Netatalk-specific helpers that implement section inheritance, variable substitution, and type-safe option extraction.

Key Source Files

Component File
Default config template config/afp.conf.in
Main config parsing libatalk/util/netatalk_conf.c
Command-line options etc/afpd/afp_options.c
DSI/network initialization etc/afpd/afp_config.c
Global structures & flags include/atalk/globals.h
Public config API include/atalk/netatalk_conf.h
Volume structures & flags include/atalk/volume.h
INI parser compatibility include/atalk/iniparser_util.h
Directory cache header etc/afpd/dircache.h

2. Configuration File Format

The configuration file uses standard INI format with three section types:

; Comments use semicolons or hash marks
# This is also a comment

[Global]
; Server-wide settings — parsed by afp_config_parse()
hostname = My Server
log level = default:note

[Homes]
; Special section for auto-generated user home volumes
basedir regex = /home

[MyVolume]
; Named volume sections
path = /srv/share

Section constants defined in include/atalk/iniparser_util.h:

Constant Value Purpose
INISEC_GLOBAL "global" [Global] section name (case-insensitive)
INISEC_HOMES "homes" [Homes] section name (case-insensitive)
Any other name A volume definition

Template installed at build time from config/afp.conf.in:

[Global]
; Global server settings

[Homes]
basedir regex = @homedir@

; [my volume]
; path = /path/to/volume
; volume name = My AFP Volume

3. Configuration Architecture

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph TD
    A["afp.conf<br/>INI config file"]:::orange
    B["dictionary *config<br/>iniparser in-memory dict"]:::purple
    C["afp_config_parse()<br/>netatalk_conf.c<br/>Parse [Global] options"]:::blue
    D["struct afp_options<br/>globals.h<br/>All global settings"]:::green
    E["configinit()<br/>afp_config.c<br/>DSI/network + FCE setup"]:::cyan
    F["load_volumes()<br/>netatalk_conf.c<br/>Parse volume sections"]:::blue
    G["readvolfile()<br/>netatalk_conf.c<br/>Iterate INI sections"]:::purple
    H["creatvol()<br/>netatalk_conf.c<br/>Create struct vol"]:::purple
    I["struct vol<br/>volume.h<br/>Per-volume settings"]:::green

    A -->|"iniparser_load()"| B
    B --> C
    C --> D
    D --> E
    B --> F
    F --> G
    G --> H
    H --> I

    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 orange fill:#ff9f43,stroke:#333,rx:10,ry:10
    classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10
    classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10

4. INI Parser Integration

Netatalk uses the external iniparser library (included as a Meson subproject dependency). The integration layer in include/atalk/iniparser_util.h provides:

Compatibility Layer

Handles API differences between iniparser versions:

// Conditional include for system vs bundled iniparser
#ifdef HAVE_INIPARSER_INIPARSER_H
#include <iniparser/iniparser.h>
#else
#include <iniparser.h>
#endif

// Handle const-correctness differences between iniparser versions
#ifdef HAVE_INIPARSER_CONST_DICTIONARY
#define INIPARSER_DICTIONARY const dictionary
#else
#define INIPARSER_DICTIONARY dictionary
#endif

Helper Macros

Macro Purpose
INIPARSER_GETSTR() Get string from section:key — returns pointer into dict
INIPARSER_GETSTRDUP() Get heap-duplicated string from section:key
CONFIG_ARG_FREE() Free and NULL a config string pointer

Option Retrieval Functions

Internal to netatalk_conf.c, four typed helpers implement section fallback (volume → preset → default):

Function Return Type Description
getoption_str() const char * Returns pointer into iniparser dict
getoption_strdup() char * Returns heap-allocated copy
getoption_bool() int Boolean via iniparser_getboolean()
getoption_int() int Integer via iniparser_getint()

Each follows the pattern: try vol:opt, then defsec:opt (preset section), then return defval.

The vdgoption_bool() variant adds a third fallback to [Global] before returning the default — used for options that can be set globally and overridden per-volume (e.g., force xattr with sticky bit).


5. Configuration Loading Flow

Phase 1: afp_config_parse()

Called once at startup from the master afpd process. Defined in netatalk_conf.c function afp_config_parse().

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
    A["afp_config_parse(obj, processname)"]:::yellow
    B["Set defaults:<br/>configfile, sigconffile,<br/>uuidconf, flags"]:::cream
    C["iniparser_load(configfile)<br/>as root"]:::orange
    D["Parse logging early:<br/>log level, log file"]:::green
    E["setuplog() — init logging"]:::green
    F["Parse [Global] boolean flags<br/>→ options->flags bitmask"]:::blue
    G["Parse [Global] string options<br/>hostname, uamlist, etc."]:::blue
    H["Parse [Global] integer options<br/>connections, tickleval, etc."]:::blue
    I["Parse cache configuration<br/>dircache size/mode/rfork"]:::purple
    J["Parse charset options<br/>unix/vol/mac charset"]:::cyan
    K["readextmap() —<br/>type/creator mappings"]:::cyan
    L["Validate & clamp values"]:::salmon

    A --> B --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L

    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 cream fill:#f8f4e8,stroke:#333,rx:10,ry:10
    classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10
    classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10
    classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10

Key steps in order:

  1. Set config file path from command line or default _PATH_CONFDIR "afp.conf"
  2. Load INI file with iniparser_load() as root
  3. Parse logging first for early error reporting
  4. Parse all [Global] boolean flags into options->flags bitmask
  5. Parse string options (hostname, UAM list, charsets, etc.)
  6. Parse integer options (connections, timeout, buffer sizes, etc.)
  7. Parse cache configuration (dircache size, mode, rfork budget)
  8. Parse charset options (unix, vol, mac charset)
  9. Load extension mappings via readextmap()
  10. Validate and clamp values (tickleval ≥ 1, dsireadbuf ≥ 6, volnamelen 8–255)

Phase 2: load_volumes()

Called from both the master (for Zeroconf announcements) and forked children (for actual volume serving). Defined in netatalk_conf.c function load_volumes().

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
    A["load_volumes(obj, flags)"]:::yellow
    B{"Config file<br/>changed?"}:::cream
    C["Re-read INI file<br/>iniparser_load()"]:::orange
    D["readvolfile(obj, pwent)<br/>Iterate INI sections"]:::blue
    E{"Section ==<br/>[Homes]?"}:::cream
    F["Match basedir regex<br/>Build home path<br/>Substitute $u in name"]:::purple
    G["Read path, volume name,<br/>vol preset from section"]:::blue
    H["creatvol(obj, pwd,<br/>section, name, path, preset)"]:::green
    I["Prune deleted volumes<br/>from linked list"]:::salmon
    J["Rewrite afp_voluuid.conf"]:::cyan

    A --> B
    B -->|"No (skip)"| DONE["Return"]:::cream
    B -->|"Yes"| C --> D
    D --> E
    E -->|"Yes"| F --> H
    E -->|"No"| G --> H
    H --> I --> J

    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 cream fill:#f8f4e8,stroke:#333,rx:10,ry:10
    classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10
    classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10
    classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10

Load flags (from include/atalk/volume.h lv_flags_t):

Flag Value Purpose
LV_DEFAULT 0 Load in user/session context (honors authorization)
LV_ALL 1 Skip access checks (master process)
LV_FORCE 2 Reload even if config file timestamp unchanged

Phase 3: creatvol()

Defined in netatalk_conf.c function creatvol(), creates a struct vol from INI options:

  1. Check if volume already exists (reloading: clear v_deleted flag and return)
  2. Check access lists (valid users, invalid users, hosts allow, hosts deny)
  3. Parse per-volume options: password, veto files, charsets, paths, permissions
  4. Parse EA mode, casefold settings, boolean volume flags into v_flags bitmask
  5. Handle charset conversion for volume name (UTF-8-MAC and Mac charset)
  6. Assign volume ID, check EA support, initialize VFS
  7. Get or generate UUID for Time Machine Zeroconf record
  8. Insert into global Volumes linked list

Option Precedence

For volume options, the resolution order is:

Volume Section → vol preset Section → [Global] (for vdg options) → Default Value

The vol preset mechanism allows shared defaults:

[Global]
vol preset = my_defaults

[my_defaults]
ea = sys
unix priv = yes

[Volume1]
path = /data1
; Inherits ea=sys and unix priv=yes from my_defaults

6. Variable Substitution

The volxlate() function in netatalk_conf.c expands variables in volume paths and names:

Variable Expansion Context Required
$b Basename of path Path available
$c Client IP:port or AppleTalk address AFP session
$d Volume path on server Path available
$f User full name (GECOS field) Login context
$g User primary group name Login context
$h Server hostname Always
$i Client IP (no port) AFP session
$s Server name (or hostname) Always
$u Username Login context
$v Volume name (or path basename) Path/volname
$$ Literal $ Always

7. Global Section Options

All options parsed in afp_config_parse() in netatalk_conf.c and stored in struct afp_options defined in globals.h.

Boolean Flag Options

Each sets a bit in options->flags. Flag constants defined in globals.h.

Config Key Flag Constant Default Description
zeroconf OPTION_NOZEROCONF true Enable Bonjour/mDNS advertisement (flag set when false)
advertise ssh OPTION_ANNOUNCESSH false Advertise SSH capability in Zeroconf
close vol OPTION_CLOSEVOL false Disconnect users when volume removed from config
client polling OPTION_SERVERNOTIF false Use client polling instead of server notifications (flag set when false)
use sendfile OPTION_NOSENDFILE true Use sendfile() for file transfers (flag set when false)
recvfile OPTION_RECVFILE false Use splice/recvfile for writes
solaris share reservations OPTION_SHARE_RESERV true Use Solaris fcntl F_SHARE locks
afpstats OPTION_DBUS_AFPSTATS false Run D-Bus thread for AFP statistics
afp read locks OPTION_AFP_READ_LOCK false AFP spec conforming read locks
spotlight OPTION_SPOTLIGHT_VOL false Enable Spotlight by default for volumes
spotlight expr OPTION_SPOTLIGHT_EXPR true Allow Spotlight logic expressions
veto message OPTION_VETOMSG false Send AFP message for veto file access
valid shellcheck OPTION_VALID_SHELLCHECK true Check for valid login shell
appletalk OPTION_DDP false Enable AppleTalk/DDP (compile-time: NO_DDP)

Password Options

Stored in options->passwdbits:

Config Key Flag Default Description
save password PASSWD_NOSAVE true Allow clients to save passwords (flag set when false)
set password PASSWD_SET false Allow users to change passwords

String Options

Config Key Field in struct afp_options Default
log level logconfig "default:note"
log file logfile NULL (syslog)
login message loginmesg NULL
guest account guest "nobody"
extmap file extmapfile _PATH_CONFDIR "extmap.conf"
passwd file passwdfile _PATH_AFPDPWFILE
uam path uampath _PATH_AFPDUAMPATH
uam list uamlist "uams_dhx.so uams_dhx2.so"
afp port port "548"
signature signatureopt ""
k5 service k5service NULL
k5 realm k5realm NULL
k5 keytab k5keytab NULL
afp listen listen NULL
afp interfaces interfaces NULL
hostname hostname System hostname via gethostname()
server name servername NULL (uses hostname); deprecated alias: zeroconf name
mimic model mimicmodel NULL
legacy icon legacyicon ""
nt domain ntdomain NULL
ad domain addomain NULL
nt separator ntseparator NULL
admin auth user adminauthuser NULL
ignored attributes ignored_attr NULL
fqdn fqdn NULL
map acls flags "rights" (sets OPTION_ACL2MACCESS)
cnid server Cnid_srv + Cnid_port "localhost:4700"
cnid mysql host cnid_mysql_host NULL
cnid mysql user cnid_mysql_user NULL
cnid mysql pw cnid_mysql_pw NULL
cnid mysql db cnid_mysql_db NULL
ddp address ddpaddr "0.0"
ddp zone zone NULL

Integer Options

Config Key Field Default Constraints
max connections connections 200
passwd minlen passwdminlen 0
tickleval tickleval 30 Clamped to ≥ 1
timeout timeout 4 Clamped to ≥ 1
dsireadbuf dsireadbuf 12 Clamped to ≥ 6
server quantum server_quantum DSI_SERVQUANT_DEF
volnamelen volnamelen 80 Clamped to 8–255
tcpsndbuf tcp_sndbuf 0 (OS default)
tcprcvbuf tcp_rcvbuf 0 (OS default)
sleep time sleep 10 (hours → tickles) Min 4 tickles
disconnect time disconnected 24 (hours → tickles)
splice size splice_size 65536 (64 KB)
sparql results limit sparql_limit 0 (unlimited)
fce holdfmod fce_fmodwait 60 (seconds)
fce sendwait fce_sendwait 0 (ms)

Misc Options

Config Key Field Default Description
log microseconds log_us_timestamp true Include μs timestamps in log
admin group admingid 0 Group name resolved to GID
force user force_uid not set Force all operations as this user
force group force_gid not set Force all operations as this group
vol dbnest (boolean, used in creatvol()) false Store CNID database inside the volume directory instead of vol dbpath

Charset Options

Config Key Field Default Scope
unix charset unixcodepage "UTF8" Global only
vol charset volcodepage Same as unix charset Global + Volume
mac charset maccodepage "MAC_ROMAN" Global + Volume

8. Cache Configuration Options

All cache options are [Global] section only, parsed in afp_config_parse().

Directory Cache

Config Key Field Default Description
dircache size dircachesize 8192 (DEFAULT_MAX_DIRCACHE_SIZE) Max directory cache entries
dircachesize (deprecated alias) Legacy name; prints deprecation warning
dircache mode dircache_mode 0 (LRU) Cache eviction policy: "lru" or "arc"
dircache validation freq dircache_validation_freq 1 (DEFAULT_DIRCACHE_VALIDATION_FREQ) Validate every Nth access (1 = every access)

Runtime bounds enforced in etc/afpd/dircache.h:

Constant Value Description
MIN_DIRCACHE_SIZE 1024 Minimum entries (testing/constrained)
DEFAULT_DIRCACHE_SIZE 65536 Production default (in dircache.h)
MAX_DIRCACHE_SIZE 1048576 Maximum entries (1M)

Note: DEFAULT_MAX_DIRCACHE_SIZE (8192) in globals.h is the config-level default when no option is specified. The dircache.h bounds are applied at runtime in dircache_init().

Resource Fork Cache (Tier 2)

The resource fork data cache stores small rfork content directly in directory cache entries, avoiding repeated disk I/O for icon and metadata resource forks.

Config Key Field Default Hard Cap Description
dircache rfork budget dircache_rfork_budget 0 (disabled) RFORK_BUDGET_MAX_KB = 10 GB Total memory budget for rfork cache (KB)
dircache rfork maxsize dircache_rfork_maxentry 1024 KB RFORK_ENTRY_MAX_KB = 10 MB Max rfork size per entry (KB)

Hard cap constants from globals.h:

#define RFORK_BUDGET_MAX_KB   (10 * 1024 * 1024)   /* 10 GB in KB */
#define RFORK_ENTRY_MAX_KB    (10 * 1024)           /* 10 MB in KB */

On 32-bit platforms, the budget is further clamped to SIZE_MAX / 1024 to prevent overflow when converting KB to bytes.

Runtime globals exposed in etc/afpd/dircache.h:

Variable Type Description
rfork_cache_used size_t Current memory used by cached rfork data
rfork_cache_budget size_t Configured budget in bytes
rfork_max_entry_size size_t Max entry size in bytes
rfork_lru_count unsigned int Entries in LRU eviction queue
rfork_lru q_t * LRU eviction queue pointer

Statistics counters (also in dircache.h):

Counter Description
rfork_stat_lookups Total cache lookups
rfork_stat_hits Cache hits
rfork_stat_misses Cache misses
rfork_stat_added Entries added
rfork_stat_evicted Entries evicted (LRU)
rfork_stat_invalidated Entries invalidated
rfork_stat_used_max High water mark of used memory

Cache Configuration Diagram

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph LR
    subgraph "afp.conf [Global]"
        A["dircache size = 8192"]:::orange
        B["dircache mode = lru"]:::orange
        C["dircache validation freq = 1"]:::orange
        D["dircache rfork budget = 0"]:::orange
        E["dircache rfork maxsize = 1024"]:::orange
    end

    F["struct afp_options"]:::green

    A --> F
    B --> F
    C --> F
    D --> F
    E --> F

    F --> G["dircache_init()"]:::blue
    G --> H["Tier 1:<br/>Directory Cache<br/>Hash + LRU/ARC"]:::cyan
    G --> I["Tier 2:<br/>Rfork Data Cache<br/>LRU within budget"]:::purple

    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 orange fill:#ff9f43,stroke:#333,rx:10,ry:10
    classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10

9. Volume Section Options

All volume options are parsed in creatvol() in netatalk_conf.c. Many support preset fallback via vol preset.

Required / Path Options

Config Key Field Default Description
path v_path required Filesystem path to share
volume name v_localname Section name Display name of volume
vol preset [Global] vol preset Inherit defaults from named section

Access Control Options

Config Key Lookup Function Description
valid users accessvol() Comma/space-separated users/groups (@group) allowed
invalid users accessvol() Users/groups denied access
hosts allow hostaccessvol() IP/CIDR whitelist
hosts deny hostaccessvol() IP/CIDR blacklist
rolist accessvol() Users/groups forced read-only
rwlist accessvol() Users/groups with read-write (others become read-only)

String Volume Options

Config Key Field Default
password v_password NULL
veto files v_veto NULL
vol charset v_volcodepage Global vol charset
mac charset v_maccodepage Global mac charset
cnid scheme v_cnidscheme DEFAULT_CNID_SCHEME
cnid server v_cnidserver:v_cnidport Global cnid server
vol dbpath v_dbpath _PATH_STATEDIR "CNID/"
legacy icon v_legacyicon Global legacy icon
volume uuid v_uuid Auto-generated
preexec v_preexec NULL
postexec v_postexec NULL

Numeric Volume Options

Config Key Field Default Format
umask v_umask Global umask Octal
directory perm v_dperm 0 Octal
file perm v_fperm 0 Octal
vol size limit v_limitsize 0 (unlimited) MiB

EA Configuration

Config Key EA Mode adouble Version Description
ea = sys AFPVOL_EA_SYS AD_VERSION_EA Native filesystem xattrs
ea = samba AFPVOL_EA_SYS + AFPVOL_EA_SAMBA AD_VERSION_EA Samba-compatible (appends NULL byte)
ea = ad AFPVOL_EA_AD AD_VERSION2 AppleDouble v2 files
ea = none AFPVOL_EA_NONE No EA support
(default) AFPVOL_EA_AUTO Try sys, fallback

EA mode constants from volume.h:

Constant Value
AFPVOL_EA_NONE 0
AFPVOL_EA_AUTO 1
AFPVOL_EA_SYS 2
AFPVOL_EA_AD 3

Casefold Options

Config Key Values Default
casefold tolower, toupper, xlatelower, xlateupper
case sensitive boolean true

Boolean Volume Flags

Each sets a bit in v_flags. Flag constants defined in volume.h.

Config Key Flag Default Description
read only AFPVOL_RO false Volume is read-only
invisible dots AFPVOL_INV_DOTS false Dot files are invisible
legacy volume size AFPVOL_LIMITSIZE false Limit size for older Macs
prodos AFPVOL_A2VOL false ProDOS volume
stat vol AFPVOL_NOSTAT true Stat volume (flag set when false)
unix priv AFPVOL_UNIX_PRIV true Support Unix privileges
cnid dev AFPVOL_NODEV true Use device number in CNID (flag set when false)
illegal seq AFPVOL_EILSEQ false Encode illegal sequences as-is
time machine AFPVOL_TM false Time Machine support
search db AFPVOL_SEARCHDB false Use CNID DB for searches
network ids AFPVOL_NONETIDS true Send network IDs (flag set when false)
acls AFPVOL_ACLS true Enable ACL support (compile-time: HAVE_ACLS)
convert appledouble AFPVOL_NOV2TOEACONV false Convert adouble v2→EA (flag set when false)
follow symlinks AFPVOL_FOLLOWSYM false Follow symlinks on server
spotlight AFPVOL_SPOTLIGHT Global spotlight Enable Spotlight indexing for volume
delete veto files AFPVOL_DELVETO false Delete veto files on directory removal
preexec close v_preexec_close false Close volume if preexec command fails
force xattr with sticky bit AFPVOL_FORCE_STICKY_XATTR false Write metadata xattr as root on sticky dirs (uses vdgoption_bool — inherits from [Global])

Chmod Request Handling

Value Flag Behavior
preserve (default) AFPVOL_CHMOD_PRESERVE_ACL Preserve ACLs during chmod
ignore AFPVOL_CHMOD_IGNORE Ignore chmod requests entirely
simple (no flag) Simple chmod passthrough

Ignored Attributes

The ignored attributes option (volume or global) controls which AFP attributes are ignored:

Value Effect
all Ignore nowrite, norename, nodelete
nowrite Ignore write-protect attribute
norename Ignore rename-protect attribute
nodelete Ignore delete-protect attribute

10. Homes Section

The [Homes] section auto-generates volumes for user home directories. Parsed in readvolfile() in netatalk_conf.c.

Config Key Default Description
basedir regex required POSIX extended regex matching user home parent directories
home name "$u's home" Volume name template (must contain $u)
path NULL Subdirectory within home to share

Matching logic:

  1. Get user’s pw_dir from passwd database
  2. Resolve to realpath
  3. Match against basedir regex (POSIX extended regex via regcomp()/regexec())
  4. If path is set, append it to the home directory
  5. Create volume with name from home name (with $u substituted)

11. Network Configuration

Listening Addresses

Configured in [Global] and processed in configinit() in afp_config.c:

[Global]
; Bind to specific addresses (comma or space separated)
afp listen = 192.168.1.100:548, [::1]:548

; Bind to network interfaces (requires getifaddrs)
afp interfaces = eth0, eth1

; Default port
afp port = 548

Processing order in configinit():

  1. If afp listen is set → create DSI listeners for each address
  2. If afp interfaces is set → resolve via getifaddrs() and create DSI listeners
  3. If neither produced a listener → create a wildcard DSI listener

TCP Tuning

Config Key Default Description
tcpsndbuf 0 (OS default) TCP send buffer size in bytes
tcprcvbuf 0 (OS default) TCP receive buffer size in bytes
dsireadbuf 12 (min 6) Scale factor: DSI read buffer = server_quantum × dsireadbuf
server quantum DSI_SERVQUANT_DEF DSI server quantum in bytes (typically 1 MB)

How tcpsndbuf / tcprcvbuf work: When non-zero, these are applied per-connection via setsockopt(SOL_SOCKET, SO_SNDBUF/SO_RCVBUF) on the client DSI socket in afp_over_dsi() in etc/afpd/afp_dsi.c. This overrides the OS kernel default socket buffer sizes for the AFP session’s TCP connection. When set to 0 (default), the kernel’s auto-tuning applies — most modern kernels dynamically scale socket buffers based on connection bandwidth-delay product. Setting explicit values disables this auto-tuning for the socket and uses the specified fixed size instead.

OS-specific behavior:

OS Auto-tuning sysctls Max buffer sysctl Notes
Linux net.ipv4.tcp_wmem / net.ipv4.tcp_rmem (3-tuple: min, default, max) net.core.wmem_max / net.core.rmem_max Kernel doubles the requested value internally for overhead accounting; SO_SNDBUF/SO_RCVBUF are clamped to wmem_max/rmem_max
macOS net.inet.tcp.sendspace / net.inet.tcp.recvspace (default values); auto-scaling via net.inet.tcp.doautorcvbuf/net.inet.tcp.doautosndbuf kern.ipc.maxsockbuf Setting explicit SO_SNDBUF/SO_RCVBUF disables auto-scaling for that socket
FreeBSD net.inet.tcp.sendbuf_auto / net.inet.tcp.recvbuf_auto (boolean, default on); net.inet.tcp.sendspace / net.inet.tcp.recvspace (defaults) kern.ipc.maxsockbuf Explicit setsockopt disables auto-tuning for that socket; values clamped to maxsockbuf

For high-latency or high-bandwidth links, larger values (e.g., 131072524288) can improve throughput by allowing more in-flight data without TCP window stalls. On most local networks, leaving at 0 (auto-tuning) is optimal.


12. Authentication Configuration

UAM Loading

Configured in [Global], loaded in configinit() via auth_load():

[Global]
uam list = uams_dhx.so uams_dhx2.so uams_pam.so uams_guest.so
uam path = /usr/lib/netatalk

Kerberos/GSSAPI

[Global]
uam list = uams_gss.so
k5 service = afpserver
k5 realm = EXAMPLE.COM
k5 keytab = /etc/krb5.keytab

The k5 keytab option sets the KRB5_KTNAME environment variable via putenv().

Guest Access

[Global]
guest account = nobody          ; Unix account for guest sessions

13. Logging Configuration

Logging is initialized early in afp_config_parse() via setuplog() to enable error reporting during the rest of config parsing.

Log Level Format

[Global]
log level = default:note              ; Single component
log level = default:info afpd:debug   ; Multiple components
log level = default:debug9            ; Maximum verbosity

Log Destination

[Global]
log file = /var/log/netatalk.log    ; Log to file
; log file =                         ; Default: syslog
log microseconds = yes               ; Include μs timestamps (default: yes)

14. FCE (File Change Event) Configuration

Parsed in configinit() in afp_config.c:

Config Key Default Description
fce listener NULL UDP endpoint for FCE events
fce coalesce NULL Event coalescing configuration
fce events NULL Which events to send
fce version 1 FCE protocol version (1 or 2)
fce ignore names ".DS_Store" File names to ignore
fce ignore directories NULL Directories to ignore
fce notify script NULL Script to run on events
fce holdfmod 60 Seconds to hold file modification events
fce sendwait 0 Milliseconds between UDP event sends

15. struct afp_options Key Fields

The struct afp_options is embedded in AFPObj at options. Defined in globals.h. Key field → config option mappings:

struct afp_options {
    int connections;              // "max connections" (default 200)
    int tickleval;                // "tickleval" (default 30)
    int timeout;                  // "timeout" (default 4)
    int flags;                    // Bitmask from boolean [Global] options
    int dircachesize;             // "dircache size" (default 8192)
    int dircache_mode;            // "dircache mode" (0=lru, 1=arc)
    int dircache_validation_freq; // "dircache validation freq" (default 1)
    int dircache_rfork_budget;    // "dircache rfork budget" (KB, default 0)
    int dircache_rfork_maxentry;  // "dircache rfork maxsize" (KB, default 1024)
    int sleep;                    // "sleep time" (hours → tickles)
    int disconnected;             // "disconnect time" (hours → tickles)
    int fce_fmodwait;             // "fce holdfmod" (seconds)
    int fce_sendwait;             // "fce sendwait" (ms)
    unsigned int tcp_sndbuf;      // "tcpsndbuf"
    unsigned int tcp_rcvbuf;      // "tcprcvbuf"
    unsigned char passwdbits;     // "save password" / "set password"
    unsigned char passwdminlen;   // "passwd minlen"
    uint32_t server_quantum;      // "server quantum"
    int dsireadbuf;               // "dsireadbuf" (scale factor, min 6)
    char *hostname;               // "hostname" or gethostname()
    char *listen, *interfaces;    // "afp listen", "afp interfaces"
    char *port;                   // "afp port"
    char *Cnid_srv, *Cnid_port;   // "cnid server" (host:port)
    char *configfile;             // -F cmdline or _PATH_CONFDIR "afp.conf"
    char *uampath, *uamlist;      // "uam path", "uam list"
    char *guest, *loginmesg;      // "guest account", "login message"
    char *k5service, *k5realm;    // "k5 service", "k5 realm"
    char *k5keytab;               // "k5 keytab"
    char *unixcodepage;           // "unix charset"
    char *maccodepage;            // "mac charset"
    char *volcodepage;            // "vol charset"
    char *servername;             // "server name"
    char *mimicmodel;             // "mimic model"
    char *logconfig, *logfile;    // "log level", "log file"
    bool log_us_timestamp;        // "log microseconds"
    int splice_size;              // "splice size" (default 64K)
    uint64_t sparql_limit;        // "sparql results limit"
};

16. Command-Line Options

afp_options_parse_cmdline() in afp_options.c accepts:

Flag Effect
-d Set OPTION_DEBUG in cmdlineflags
-F configfile Override config file path
-v Show version and compiled-in paths
-V Show extended version info (features, backends)
-h Show usage

17. Complete Configuration Example

[Global]
; Server identity
hostname = fileserver
server name = AFP Server
mimic model = Xserve

; Network
afp listen = 0.0.0.0:548
afp port = 548

; Authentication
uam list = uams_dhx2.so uams_dhx.so
guest account = nobody

; Logging
log level = default:info
log file = /var/log/netatalk.log

; Performance
max connections = 200
dsireadbuf = 12
tcpsndbuf = 0
tcprcvbuf = 0

; Cache tuning
dircache size = 8192
dircache mode = lru
dircache validation freq = 1
dircache rfork budget = 0
dircache rfork maxsize = 1024

; Charsets
unix charset = UTF8
mac charset = MAC_ROMAN

; Service discovery
zeroconf = yes

; Spotlight
spotlight = no

[Homes]
basedir regex = /home
home name = $u's home

[SharedDocs]
path = /srv/documents
volume name = Shared Documents
ea = sys
unix priv = yes
veto files = .DS_Store/.AppleDouble/._*

[TimeMachine]
path = /srv/timemachine
time machine = yes
vol size limit = 1048576

18. Configuration Reload

Volume configuration can be reloaded at runtime when the master afpd process receives SIGHUP. The reload flow in the parent event loop (main() in etc/afpd/main.c):

  1. reloadconfig flag set by signal handler
  2. Free existing DSI listeners via configfree()
  3. Re-parse [Global] via afp_config_parse()
  4. Re-initialize listeners via configinit()
  5. Send SIGHUP to all children via server_child_kill()
  6. Children call load_volumes() which detects the changed config file timestamp

Key behavior: Once a volume is loaded in a child process, its options are never changed — they are only deleted and recreated on config reload. This is enforced in creatvol() which checks for existing volumes by name and returns the existing struct vol if still present.

Footnotes

This is a mirror of the Netatalk GitHub Wiki

Last updated 2026-04-06