37 KiB
#FastSync
FastSync is a high-performance file synchronization tool designed to become a
drop-in replacement for common rsync workflows. It keeps the familiar
source/destination model and rsync-style options while adding optional
multithreading, streaming zstd compression, chunking, zero-copy TCP transfers,
and native TCP/TLS transports.
The release version is FastSync's client/server protocol version (printed by
fastsync --version); client and server must match. See
CHANGELOG.md for the history.
The compatibility target is straightforward:
- Existing rsync commands should keep the same meaning.
- FastSync-only performance options should be additive and optional.
- A normal compatibility-mode transfer should prioritize rsync filesystem semantics over maximum throughput.
FastSync currently speaks its own protocol to fastsync-server. SSH mode
starts that server remotely; it does not yet interoperate with an unmodified
rsync client or rsync daemon. See Compatibility Status
for the current boundary.
Why FastSync
FastSync uses a producer-consumer transfer pipeline and can combine several optimizations for large or high-latency transfers:
- Multithreaded scanning, loading, and sending.
- Streaming zstd compression with levels 1 through 22.
- Configurable file chunking and compact chunk serialization.
sendfile()zero-copy transfers over TCP.- Batched incremental checks to reduce round trips.
- Optional block-level delta transfer for FastSync peers.
- Bandwidth limiting, progress reporting, statistics, and backups.
- TCP, SSH, and TLS transports.
- Atomic temporary-file writes by default.
These optimizations are disabled or selected independently. Users can start with rsync-style commands and add FastSync options when they are useful.
Compatibility Status
FastSync is currently an rsync-compatible CLI in progress, not a complete replacement for every rsync feature or protocol mode.
Working today
- Recursive directory scanning.
- Rsync-style source and destination arguments.
- SSH transport using
user@host:destinationpaths below the remote authorized root. - TCP client/server transfers.
- Dry runs, excludes, includes, size filters, backups, statistics, and bandwidth limiting.
- Incremental size/mtime checks and optional xxHash64 content checks.
- FastSync-native delta transfer for changed files.
- Optional mode and timestamp preservation.
- Delete manifests with server-side delete authorization.
- Temporary-file writes with atomic rename by default.
- Path traversal checks and destination-root confinement.
Not yet equivalent to rsync
- The FastSync wire protocol is not the rsync wire protocol.
- SSH mode requires
fastsync-serveron the remote host. - Archive mode does not yet provide all of rsync's
-rlptgoDbehavior. - Symlink transfer is incomplete; link targets are not yet recreated in all modes.
- Owner/group, ACL, xattr, and hard-link handling is incomplete or unavailable.
- Device and special-file preservation is implemented with documented
divergences: recreated device nodes require
CAP_MKNODon the receiver (a non-root receiver skips the entry), and sockets cannot be recreated (FIFOs are). - Sparse-file hole preservation (
-S,--sparse) is implemented receiver-side: long all-zero runs are written as holes (no wire change; the full file image is already in memory). --partial,--partial-dir,-P,--append, and--append-verifykeep the write atomic (temp + rename). With--partial, a failed/interrupted write now retains the already-written temp at the destination path (best-effort) so a later--append/--append-verifyrun can resume it.--dirsis not implemented. Its compatibility aliases--old-dirsand--old-dare recognized but rejected explicitly rather than silently using FastSync's recursive directory behavior.- Short-option names are now rsync-parity (Phase 7 Wave A): FastSync's former
collisions were renamed (
-j/--threads,--preserve,--sendfile,--chunk-serialization,--timeout,--ssh-port), so-m,-M,-f,-s,-T,-p,-c,-a, and-zfollow rsync. SeeRSYNC_COMPAT.md.
The detailed flag matrix is maintained in
RSYNC_COMPAT.md. It distinguishes implemented,
partial, alternate, and planned behavior.
Quick Start
Build
compile_commands.json is a symlink to build/compile_commands.json and is used by clangd/editor tooling; its target is generated by the build, so it dangles until the first build.
Client
| Argument | Description |
|---|---|
| Positional | <source> <dest> — automatic SSH detection if dest contains : |
-c, --checksum |
Verify content by checksum instead of size+mtime |
-z, --compress [level] |
Enable streaming zstd compression (level 1–22, default 5) |
-a, --archive |
rsync archive mode (-rlptgoD): links, metadata, devices and specials (not compression/multithreading) |
-j, --threads[=N] |
Multithreading mode; N (1–256) sets the parallel scanner worker count, bare -j/--threads uses the default |
-m |
rsync --prune-empty-dirs (short form now rsync-parity) |
--chunk-serialization |
Chunk serialization (batch all files per chunk; long form only) |
-s |
rsync --secluded-args compatibility no-op (remote SSH argv is already injection-safe) |
--sendfile |
Sendfile zero-copy. Incompatible with compression / chunk serialization. TCP only. Long form only. |
--preserve |
Preserve supported file metadata (mode and mtime; ownership and atime are unsupported) |
-n, --dry-run |
Scan and print what would be transferred |
-p, --perms |
Preserve permission bits (part of the metadata bundle) |
--ssh-port <port> |
SSH port (default: 22) |
-v, --verbose |
Enable debug logging |
-q, --quiet |
Suppress non-error output |
--progress |
Show real-time transfer speed |
-P |
Enables partial-transfer mode + progress output; interrupted writes retain the already-written temp for resumption |
--delete |
Delete files on receiver not present in source (default timing: delete-after, i.e. only after the whole transfer succeeded) |
--delete-before |
Delete extras before the transfer starts (implies --delete) |
--delete-during, --del |
Delete extras once the keep-set is known, before data is applied (implies --delete) |
--delete-delay |
Delete extras only after a successful transfer (implies --delete) |
--delete-after |
Explicit delete-after timing (implies --delete) |
--exclude <pattern> |
Exclude files matching glob pattern (repeatable) |
--exclude-from <file> |
Read exclude patterns from a file (one per line) |
--include <pattern> |
Only transfer files matching glob pattern (repeatable, whitelist) |
--max-size <n> |
Skip files larger than n bytes |
--min-size <n> |
Skip files smaller than n bytes |
--max-alloc <SIZE> |
Maximum single allocation (binary units: B, K, M, G, T, P, E; default 1G) |
--incremental |
Skip files unchanged since last transfer (size + mtime). Auto-enables --preserve. Incompatible with --chunk-serialization. |
--existing |
Skip files not already present at the destination; update existing files normally. |
--bwlimit <KB/s> |
Bandwidth limit in kilobytes per second |
--chunk-size <n> |
Chunk size in bytes (default: 10485760) |
--timeout <sec> |
Positive I/O timeout in seconds, applied to both the socket (SO_RCVTIMEO/SO_SNDTIMEO, built-in default 30 s) and the per-message protocol poll deadline (built-in default 60 s). Omit the option to keep both built-ins; 0 is rejected. The server side keeps the built-in 60 s protocol window (the value is not sent on the wire). |
--contimeout <sec> |
Connection timeout in seconds (default: 10) |
--backup |
Backup existing destination files before overwriting |
--backup-dir <dir> |
Target directory for backups (requires --backup) |
--stats |
Print transfer statistics at end (bytes, files, timing) |
-h, --human-readable |
Format transfer byte sizes with binary units |
--max-depth <n> |
Maximum directory depth to recurse (0 = unlimited, default: 0) |
--log-file <path> |
Write log messages to file instead of stderr |
--source-dir <path> |
Source directory (overrides FASTSYNC_SOURCE_DIR) |
--dest-dir <path> |
Server destination directory (overrides FASTSYNC_DEST_DIR) |
--save-to-disk |
Write received files to disk |
--server-host <ip> |
Server IP address (default: 127.0.0.1) |
--server-port <n> |
Server port (default: 8080) |
--tls |
Enable TLS encryption |
--cert <path> |
TLS certificate file (PEM) |
--key <path> |
TLS private key file (PEM) |
--ca <path> |
TLS CA certificate file for verification (PEM) |
--client-cn <name> |
TLS client certificate common name; mandatory with --tls (a TLS connection always verifies the client CN) |
Per-message vs. connection timeouts. --timeout bounds each individual protocol
send/receive (the poll() deadline), so a peer that stops mid-frame is dropped. It
does not, by itself, stop a peer that keeps sending well-formed frames forever. The
receiver therefore also enforces two wall-clock (CLOCK_MONOTONIC) bounds on a
connection: a 1 hour idle limit and a 24 hour overall session cap. Only
frames that move real work (not STATUS_KEEPALIVE/STATUS_ABORT and not an
empty STATUS_CHECK_BATCH/STATUS_DIR_TIMES) refresh the idle timestamp, so a
peer cannot hold a connection slot by emitting cheap empty frames; a peer that
fabricates minimal non-empty frames can still occupy a slot until the 24 hour
cap, since no bound can require actual payload without risking a legitimate
long operation. Both are deliberately generous so a legitimate long-running
transfer is never aborted.
Server
| Argument | Description |
|---|---|
--stdio |
Run in stdio mode (for SSH transport; single connection then exits) |
-p <port> |
TCP listen port (default: 8080, range: 1–65535) |
--tls |
Enable TLS encryption |
--cert <path> |
TLS certificate file (PEM) |
--key <path> |
TLS private key file (PEM) |
--ca <path> |
TLS CA certificate file for verification (PEM) |
--destination-root <path> |
Authorized destination root (default: .) |
--allow-delete |
Permit manifest deletion |
--allow-super |
Standalone TCP listener only: keep super-user activities enabled for a root receiver. Without it a root standalone server forces SUPER_MODE_OFF, so client --devices/--write-devices/--super and client-chosen ownership requests are skipped/refused. Rejected with --stdio (the SSH remote argv is client-composed, so a client could otherwise pass it and defeat the secure default; operators exposing fastsync-server --stdio over SSH must use a forced command if the default must hold). No effect when not root. |
--allow-unauthenticated |
Permit plaintext TCP clients. For an auth users module this opts in loopback plaintext only; remote auth still requires verified TLS, so the flag never permits remote plaintext auth. |
-v, --verbose |
Enable debug logging |
--help |
Show help |
Environment Variables
| Variable | Default | Description |
|---|---|---|
FASTSYNC_SOURCE_DIR |
— | Source directory fallback |
FASTSYNC_DEST_DIR |
— | Destination directory fallback |
FASTSYNC_SAVE_TO_DISK |
false |
Disk persistence fallback |
FASTSYNC_SSH_PORT |
22 |
Default SSH port |
FASTSYNC_SERVER_HOST |
127.0.0.1 |
Default server host |
FASTSYNC_SERVER_PORT |
8080 |
Default server port |
FASTSYNC_TLS_CERT |
— | Default TLS certificate path |
FASTSYNC_TLS_KEY |
— | Default TLS private key path |
FASTSYNC_TLS_CA |
— | Default TLS CA certificate path |
Implementation Details
Data Structures
- Chunk — collection of files (~10 MB total by default)
- File — path, content (
Data), optionalFileMetadatapointer - FileMetadata —
mode,uid,gid,mtime_sec,mtime_nsec; uid / gid are advisory wire fields and are never applied by the receiver; atime is unsupported - Config — runtime parameters (transported over wire, TLS settings excluded). Includes
timeout,contimeout,quiet,backup,backup_dir,stats,max_depth,log_file. - Queue — thread-safe bounded queue with condition variables
- DirectoryScanner — recursive BFS traversal with exclude and include pattern support, max-depth enforcement
Key Algorithms
- File scanning — BFS directory traversal;
entries matched against exclude and include patterns,
max - depth enforced 2. * *Chunking ** — files accumulated until
chunk_sizethreshold, then flushed 3. * *Compression ** — streaming zstd viaZSTD_compressStream2/ZSTD_decompressStream4. * *Network protocol ** — status - code - driven exchange with metadata packing, keep - alive, and abort support 5. * *Incremental check ** — client sendsSTATUS_CHECK+ path + size + mtime and, with--checksum, XXH64 content checksum; server compares against destination. Can be batched viaSTATUS_CHECK_BATCHfor reduced round-trips. - Bandwidth limiting — token-bucket algorithm with
nanosleepthrottling on 64 KB write chunks - Metadata restoration —
chmod(),chown(),utimensat()on the receiving side --delete— sender tracks all sent paths; receiver walks destination tree and removes unlisted files / directories 9. * *SSH transport * * —socketpair()+fork()+execvp("ssh", ...)withControlMasterand port support 10. * *TLS transport ** — OpenSSLSSL_CTXwith TLS 1.2 minimum, mutual CA verification, transparentSSL_read/SSL_writeviaio_set_ssl()11. * *Path traversal protection ** —has_path_traversal()rejects any file path containing..components, preventing directory escape attacks 12. * *Connection limiting ** — server tracks active connections and rejects new ones beyondmax_connections(default 100)13. * *Keep - alive ** — idle connections receive periodicSTATUS_KEEPALIVEto detect half - open TCP connections 14. * *Abort handling ** —SIGINTsets an abort flag; the next protocol operation sendsSTATUS_ABORTfor clean server cleanup- Atomic writes — files are written to a
.tmpsuffix then atomically renamed viarename(), preventing partial files - Backup — before overwriting, existing files are moved to
--backup-dir(or same directory with~suffix) preserving the original
Security Features
Path Traversal Protection
All received file paths are validated by has_path_traversal() before any disk operation. Any path containing .. components is rejected with STATUS_ERROR, preventing directory escape attacks.
TLS Certificate Verification
TLS requires --ca and performs mutual TLS verification (SSL_VERIFY_PEER with depth 4). Connections without certificate verification are rejected.
Connection Limits
The server enforces a maximum of 100 concurrent connections (configurable via max_connections in Server). When the limit is reached, new connections are immediately rejected and closed.
Abort Handling
If the client receives SIGINT (Ctrl+C) during a transfer, it sends STATUS_ABORT to the server. The server then cleans up temporary files and exits the child process, preventing incomplete files from remaining on disk.
Atomic Writes
Received files are written to a temporary path (suffixed with .tmp) and then atomically renamed to the final filename via rename(). This prevents partial or corrupted files from appearing at the destination if the transfer is interrupted.
Build Requirements
- C11 compiler
- CMake >= 3.22
- zstd library
- OpenSSL (development headers and libraries)
- pthreads
- SSH client (for SSH transport mode only)
Installing Dependencies
Ubuntu/Debian:
sudo apt install cmake build-essential libzstd-dev libssl-dev openssh-client
Nix:
nix-shell # provides zstd, openssl, cmake, gcc
Building
cmake -B build -S .
cmake --build build -j$(nproc)
With Nix:
nix-shell
cmake -B build -S .
cmake --build build -j$(nproc)
SSH transfer
The remote host must have fastsync-server available in PATH, or use
--fastsync-server-path. SSH starts fastsync-server --stdio in its remote
working directory, so use a destination below that directory unless the
remote server is otherwise configured with a matching authorized root.
The remote --stdio server argv is composed by the client, so it must never
be trusted to opt a root receiver into super-user activities: --allow-super
is rejected with --stdio and super stays off on that path. Operators
exposing fastsync-server --stdio over SSH must use a forced command (e.g. an
authorized_keys command= entry) if the default must hold.
ssh user@host 'mkdir -p destination'
./build/client /path/to/source user@host:destination
FastSync is push-only: the source (first argument) is always a local
directory and only the destination may be remote. A remote source such as
client user@host:src ./local (a "pull") is intentionally not supported; see
RSYNC_COMPAT.md.
TCP transfer
Start the FastSync server:
./build/server --destination-root /path/to -p 8080 --allow-unauthenticated
Then run the client:
./build/client --server-host 127.0.0.1 --server-port 8080 \
--source-dir /path/to/source --dest-dir /path/to/destination \
--save-to-disk
Plain TCP requires the explicit --allow-unauthenticated server option. Use TLS for
authenticated network connections.
TLS transfer
./build/server --destination-root /path/to --tls --cert server.pem --key server-key.pem -p 8443
./build/client --tls --cert client.pem --key client-key.pem --ca ca.pem \
--server-host example.com --server-port 8443 \
--source-dir /path/to/source --dest-dir /path/to/destination \
--save-to-disk
Common Workflows
These examples show the intended rsync-style workflow. Options marked as FastSync-native are optional performance or transport extensions.
#Basic synchronization
./build/client /source/ /destination/
#Archive - style synchronization(current FastSync archive behavior)
./build/client -a /source/ user@host:destination/
#Preview a transfer without changing the destination
./build/client -n /source/ /destination/
#Exclude temporary and object files
./build/client --exclude '*.tmp' --exclude '*.o' \
/source/ user@host:destination/
#Remove destination entries not present in the source
./build/client --delete /source/ user@host:destination/
#Skip unchanged files using size and modification time
./build/client --incremental /source/ user@host:destination/
#Verify content when size and time are not sufficient
./build/client --incremental --checksum /source/ user@host:destination/
#Preserve supported mode and timestamp metadata
./build/client --preserve /source/ user@host:destination/
#Keep backups of overwritten destination files
./build/client --backup --backup-dir backups \
/source/ user@host:destination/
FastSync Extensions
FastSync-native options are intended to add performance or operational features without changing the meaning of ordinary compatibility options.
| Option | Purpose |
|---|---|
-j, --threads[=N] |
Enable the multithreaded scanner/loader/sender pipeline. N (1–256) sets the parallel scanner worker count; bare -j/--threads uses the default. |
-z [level], --compress [level] |
Enable streaming zstd compression, levels 1-22. |
--compress-level <n> |
Set the zstd compression level. |
--zc <alg> |
Alias for --compress-choice. FastSync supports zstd and none. |
--zl <n> |
Alias for --compress-level. |
--skip-compress <list> |
Skip compression for comma-separated suffixes; incompatible with --chunk-serialization. |
--compress-threads <n> |
Use n zstd compression workers. Requires compression and a zstd build with threaded support; the setting affects sender CPU work only. |
--chunk-size <bytes> |
Set the transfer chunk size. |
--chunk-serialization |
Enable FastSync chunk serialization (long form only; -s is rsync's --secluded-args). |
--sendfile |
Use TCP sendfile() zero-copy transfer. Incompatible with compression and chunk serialization. Long form only. |
--delta |
Use FastSync-native block delta transfer. Requires --incremental. |
--delta-block <bytes> |
Set the FastSync delta block size (--block-size is an alias). |
--delta-max <bytes> |
Limit files eligible for FastSync delta transfer. |
--server-host <host> |
Select the TCP server host. |
--server-port <port> |
Select the TCP server port (--port <port> and --port=<port> are rsync-friendly aliases). |
--tls |
Enable TLS for TCP transport. |
--bwlimit <KB/s> |
Apply token-bucket bandwidth limiting. |
--progress |
Show transfer progress and throughput. |
--stats |
Print transfer statistics. |
--timeout <seconds> |
Set the socket and per-message protocol I/O timeout (positive seconds). Omit to keep the built-in 30 s socket / 60 s protocol defaults. |
--contimeout <seconds> |
Set connection timeout. |
Short-option conflicts with rsync have been resolved for the CLI namespace
(Phase 7): -c is now rsync's --checksum, -m is --prune-empty-dirs, -M
is --remote-option, -f is --filter, -s is --secluded-args, -p is
--perms, and -T is --temp-dir. FastSync's own flags were renamed to
long-form-only or new shorts: multithreading is -j/--threads, metadata
is --preserve, sendfile is --sendfile, chunk serialization is
--chunk-serialization, timeout is --timeout, and SSH port is --ssh-port.
-a/--archive is now real rsync archive (-rlptgoD).
--secluded-args (and its short form -s) is accepted as a compatibility
no-op. It does not change FastSync's transport or protocol behavior, because
remote SSH argv is already built injection-safe.
Client Options
Selection and transfer
| Option | Description |
|---|---|
-a, --archive |
rsync archive mode (-rlptgoD): links, metadata, devices and specials. |
-n, --dry-run |
Scan and report without writing files. |
--delete |
Request removal of destination entries absent from the source. The server must allow deletion. Default timing is delete-after: extras are removed only after the whole transfer succeeded. |
--delete-before |
Delete extras before the transfer starts (implies --delete). |
--delete-during, --del |
Delete extras once the keep-set manifest is known, before data is applied (implies --delete; early mode, same engine behaviour as --delete-before). |
--delete-delay |
Delete extras only after a successful transfer (implies --delete; commit mode, same behaviour as --delete-after). |
--delete-after |
Explicit delete-after timing: delete only after the transfer succeeded (implies --delete). |
--exclude <pattern> |
Exclude matching paths. Repeatable. |
--include <pattern> |
Include matching paths. Repeatable. |
--exclude-from <file> |
Read exclude patterns from a file. |
--include-from <file> |
Read include patterns from a file. |
--max-size <bytes> |
Skip files larger than the limit. |
--min-size <bytes> |
Skip files smaller than the limit. |
--max-depth <n> |
Limit recursive scanning depth; |
| zero means unlimited. | |
--checksum |
Include xxHash64 content checks in incremental comparisons. |
| Back up overwritten files. | |
--suffix<suffix> |
Set the backup filename suffix. |
| Select partial - transfer handling. On failed/interrupted writes the | |
| already-written temp file is retained (best-effort) for resumption. | |
With --partial --partial-dir <dir>, completed files are written under the |
|
| partial directory and installed atomically. | |
| Set a relative partial - transfer directory below the server destination root. | |
Use with --partial. |
|
--inplace |
Write directly to the destination instead of using a temporary file. |
Metadata and links
| Option | Description |
|---|---|
--preserve |
Preserve supported file metadata, currently mode and modification time (long form only). |
-l, --links |
Request symlink preservation; |
| link-target transfer remains incomplete. | |
--copy-links |
Copy symlink referents. |
--safe-links |
Skip symlinks that point outside the transfer tree. |
--copy-unsafe-links |
Copy unsafe symlink referents. |
-S, --sparse |
Sparse-file handling: receiver preserves holes (zero runs are written as holes; no wire change). |
Output and logging
| Option | Description |
|---|---|
-v, --verbose |
Enable debug logging. |
--progress |
Show live transfer progress. |
--stats |
Print transfer statistics. |
--log-file <path> |
Write log output to a file. |
-V, --version |
Print the FastSync protocol version. |
--help |
Print command usage. |
Paths and transport
| Option | Description |
|---|---|
--ssh-port <port> |
SSH port for the SSH transport (default: 22). Note the short -p is now rsync's --perms. |
--fastsync-server-path <path> |
Remote FastSync server path for SSH mode. |
--source-dir <path> |
Set the source directory explicitly. |
--dest-dir <path> |
Set the destination directory explicitly. |
--save-to-disk |
Enable server-side disk persistence. |
--server-host <host> |
TCP server address. |
--server-port <port> |
TCP server port. --port <port> / --port=<port> is an alias. |
--tls |
Enable TLS. Requires --cert and --key. |
--cert <path> |
TLS certificate file. |
--key <path> |
TLS private key file. |
--ca <path> |
CA file for peer verification. |
Server Options
| Option | Description |
|---|---|
--stdio |
Serve one SSH connection over standard input/output. |
-p <port> |
TCP listen port. |
--tls |
Enable TLS. |
--cert <path> |
TLS certificate file. |
--key <path> |
TLS private key file. |
--ca <path> |
CA file for peer verification. |
--destination-root <path> |
Confine received files to this server-side root; |
| defaults to the current directory. | |
--allow-delete |
Permit client delete manifests. Deletion is refused by default. This also gates --force (which can recursively replace/remove a destination directory tree). |
--allow-super |
Standalone TCP listener only: keep super-user activities enabled for a root receiver. Without it a root standalone server forces SUPER_MODE_OFF, so client --devices/--write-devices/--super and client-chosen ownership requests are skipped/refused. Rejected with --stdio (the SSH remote argv is client-composed; use a forced command if the default must hold). No effect when not root. Daemon modules opt in per module with client owner = yes. |
-v, --verbose |
Enable debug logging. |
--help |
Print server usage. |
Daemon configuration
fastsync-server --daemon --config FILE reads a line-based module config (an
implicit global section, then [module] sections). Besides port, motd file,
and address, the global section accepts:
max connections = N— global cap on concurrent connections, default 100. The listener enforces it;0, negative, and non-numeric values are parse errors.max connections per host = N— cap on concurrent connections from a single source IP, default 0 (unlimited). Enforced across all forked connection children through a shared registry.auth failure delay = MS— milliseconds to sleep after a failed authentication, default 500.0disables it and the value is capped at 5000, so online password guessing is rate-limited per connection. Successful auths are never delayed.auth lockout threshold = N— number of failed authentications from one source IP before that source is locked out, default 10;0disables the lockout. The failure counter is shared across every connection child, so the lockout holds even when the next attempt is handled by a different forked child.auth lockout duration = SECONDS— how long a locked-out source is refused (default 300). A locked-out client is refused before any SCRAM challenge is sent; a successful authentication clears the counter.hosts allow/hosts deny— comma- and/or whitespace-separated host access patterns.
A [module] may also set max connections (0 = unlimited; enforced per module
across all connection children) and its own hosts allow/hosts deny.
The per-host cap and the shared auth lockout identify a source by its numeric
peer IP. Loopback peers (127.0.0.0/8, IPv6 ::1) are exempt: every local
client shares that one address, so counting or locking them out would let one
local process deny service to all the others. The per-module and global
max connections caps still apply to loopback. Because the key is the peer IP,
max connections per host and auth lockout also cannot distinguish clients
behind the same NAT, proxy, or reverse-proxy address — they share one budget and
one lockout counter, so an over-aggressive lockout can affect unrelated users
behind that address. Prefer TLS client certificates (--client-cn) plus
hosts allow/hosts deny for per-client policy when clients share an address,
and size auth lockout threshold accordingly.
The shared per-source table has a bounded lifetime: an entry with no live connection is reclaimed once its lockout has expired, or after it has been idle (300 s). If every entry is still live or locked, a new source is admitted without per-host accounting (fail open) and a rate-limited warning is logged; the per-module cap and host ACLs still apply. The occupancy counters are re-derived from the shared slot table after every child exit, so a child killed mid-transfer (or mid-registration) cannot leak a slot or an occupancy count.
Host patterns are * (match all), IPv4/IPv6 literals, or IPv4/IPv6 CIDR
(10.0.0.0/8, 2001:db8::/32). Hostnames are not resolved, so hostname globs
are rejected at parse time rather than silently never matching. A matching
hosts deny rejects; if any hosts allow entries exist, a peer matching none of
them is rejected; deny takes precedence over allow. The global list is checked
before the module list, before authentication, and the connecting peer address
(IPv4 or IPv6) appears in the connection and authentication audit log lines.
Architecture
Client
- Recursively scans the source tree with include, exclude, size, and depth filters.
- Sends individual files or serialized chunks.
- Performs incremental checks and optional content checksums.
- Uses a multithreaded producer-consumer pipeline when requested.
- Sends over TCP, TLS-wrapped TCP, or an SSH subprocess.
- Supports progress, statistics, backups, timeouts, and bandwidth limiting.
Server
- Runs as a TCP listener or one-shot SSH
--stdioserver. - Receives and reassembles files and decompresses streaming zstd data.
- Applies supported metadata and writes files through a confined destination root.
- Uses temporary files and atomic rename by default.
- Handles delete manifests only when explicitly authorized.
- Enforces connection, message-size, and path-safety limits.
Protocol and Security
FastSync protocol version 2.21.0 is shared by the client and server. The
current protocol is sender-driven and includes configuration negotiation,
including the maximum allocation limit, incremental checks, checksums,
manifests, keep-alives, abort handling, per-file remove-source results, and
FastSync-native delta messages.
Client and server versions must currently match exactly.
Daemon modules that declare auth users authenticate with a SCRAM-SHA-256-style
challenge/response against a salted PBKDF2 verifier store: no password and no
replayable bearer credential crosses the wire or is stored on the daemon. All
store entries share one iteration count, and an unknown user is answered with a
deterministic per-username dummy challenge, so probing the daemon cannot
enumerate users. Store lines are generated with
fastsync-server --hash-credentials <plaintext-file> (see RSYNC_COMPAT.md);
redirect that output to an owner-only (mode 0600) file, and note that legacy
user:SHA256HEX stores are rejected. FastSync also maintains an owner-only
(mode 0600) <store>.dummykey sidecar next to the store: it holds the store-wide
dummy key, is auto-created on first load, and must be preserved across daemon
restarts so the dummy challenge for an unknown user stays stable (the key is
never regenerated while the sidecar exists). The sidecar is secret material and
must be protected like the credential store: keep it owner-only (mode 0600) and
include it with the store in backups and credential rotation. If the sidecar
cannot be created (a process-substitution/FIFO store path such as /dev/fd/N, a
read-only filesystem, a missing directory, or a create, write, fsync, link, or
fchmod failure), the daemon logs a warning and uses a transient key, so the
cross-restart guarantee does not hold for those deployments. One residual is
accepted: the store
iteration count is observable pre-auth by design, since the miss path must match
a hit.
An auth users module accepts credentials only when one of two conditions
holds: (a) the connection is an encrypted, verified TLS connection whose client
certificate matches the server's --client-cn, or (b) the connection is
plaintext from a loopback peer and the operator explicitly passed
--allow-unauthenticated. A remote plaintext peer is refused before any
challenge is sent, and --allow-unauthenticated never permits remote plaintext
auth: remote peers still require verified TLS regardless of the flag. Clients
sending daemon credentials with --password-file to a non-loopback daemon must
therefore use --tls; the client rejects a non-local plaintext credential
destination before any network I/O. Daemon modules are a --daemon-only
feature: the SSH --stdio path never loads a daemon config and is not an auth
transport for them.
Because the loopback allowance trusts whichever peer the kernel reports as
127.0.0.1, it assumes nothing relays remote connections to the daemon. A local
TCP forwarder or a TLS-terminating proxy in front of an auth-module listener
makes remote clients appear as loopback and bypasses the mutual-TLS identity
check, so do not front an auth-module listener with such a relay. --tls always
mandates --client-cn, so a TLS connection to an auth-required module always
has its client CN verified (--client-cn matches the certificate's CN only, not
a subjectAltName, which is acceptable for a private CA).
TLS provides encrypted TCP transport. Supplying --ca enables certificate
verification; without it, traffic is encrypted but peer identity is not
verified. Use certificate verification for deployments where authentication
matters. The default TCP transport is not encrypted.
The receiver protects its destination root with path validation, openat()
directory traversal, O_NOFOLLOW, temporary files, and atomic renames. Delete
operations require the server's explicit --allow-delete policy.
Compatibility Roadmap
The project will reach the drop-in replacement goal in stages:
- Correct rsync option meanings, including short options, combined options,
and
--option=valuesyntax. - Add differential tests that compare FastSync and rsync contents, metadata, links, deletes, filters, dry runs, and exit codes.
- Make
-aimplement the expected recursive, links, permissions, times, owner/group, and supported special-file behavior. - Complete symlink, sparse-file, metadata, delete-policy, and resumable-write semantics.
- Add rsync remote-shell and daemon protocol interoperability.
- Keep FastSync performance options as negotiated, optional extensions.
The exhaustive implementation matrix and compatibility notes are in
RSYNC_COMPAT.md.
Testing
Run the unit test binary:
./build/tests
Run the Python integration suite:
python3 -m pytest tests/integration/ -n 4 --dist=load -m "not setpriv"
For stricter local validation:
cmake -B build-strict -S . -DSTRICT_WARNINGS=ON
cmake --build build-strict -j$(nproc)
cmake -B build-asan -S . -DSANITIZER=address
cmake --build build-asan -j$(nproc)
The benchmark tool compares FastSync configurations with rsync under controlled local and network conditions:
python3 benchmark/bench.py --help
Benchmark results measure transfer performance only. They do not establish rsync protocol or filesystem-semantic compatibility.
Performance Guidance
- Use
-mfor workloads with many files or enough CPU parallelism. - Use
-cor-zwhen network bandwidth is more constrained than CPU. - Tune
--chunk-sizefor file sizes, memory limits, and network latency. - Use
-ffor large uncompressed TCP transfers where zero-copy I/O helps. - Use
--incrementalto avoid retransmitting unchanged files. - Use
--deltafor changed files when both endpoints are FastSync peers. - Use
--bwlimitwhen sharing a link with other traffic.
Always validate the compatibility behavior required by a deployment before replacing an existing rsync job.