Skip to content

Reply with @ERROR on unknown module so clients exit non-zero#32

Merged
taoky merged 1 commit into
ustclug:masterfrom
yaoge123:fix-unknown-module-exit-code
May 24, 2026
Merged

Reply with @ERROR on unknown module so clients exit non-zero#32
taoky merged 1 commit into
ustclug:masterfrom
yaoge123:fix-unknown-module-exit-code

Conversation

@yaoge123
Copy link
Copy Markdown
Contributor

Problem

When a client requests a module that the proxy is not configured to serve, rsync-proxy currently writes:

unknown module: <name>
@RSYNCD: EXIT

A real rsyncd answers the same situation with:

@ERROR: Unknown module '<name>'

Rsync's client treats @RSYNCD: EXIT as a normal end-of-listing and exits 0, while it treats an @ERROR: line as a fatal protocol error and exits 5 (RERR_FERROR_XFER). The current proxy reply therefore looks like a successful no-op to anything that checks rsync's exit code.

Impact

This silently broke long-running mirrors. Concrete example: NJU's loongnix mirror runs daily against rsync://rsync.mirrors.ustc.edu.cn/loongnix/. USTC retired that module roughly 10 months ago, so the proxy began returning unknown module: loongnix, but tunasync kept marking each run as success because rsync exited 0. Local data sat at the July 2024 snapshot for the better part of a year while the dashboard stayed green.

You can reproduce the difference today against any healthy upstream:

$ rsync rsync://mirrors.tuna.tsinghua.edu.cn/does-not-exist/  # real rsyncd
... motd ...
$ echo $?
5

$ rsync rsync://rsync.mirrors.ustc.edu.cn/does-not-exist/     # rsync-proxy
Served by rsync-proxy (https://github.com/ustclug/rsync-proxy)

unknown module: does-not-exist
$ echo $?
0

The wire capture (via nc) confirms the proxy is sending plain text rather than @ERROR::

@RSYNCD: 32.0 sha512 sha256 sha1 md5 md4
Served by rsync-proxy (https://github.com/ustclug/rsync-proxy)

unknown module: does-not-exist
@RSYNCD: EXIT

The relevant rsync source (clientserver.c):

if (strncmp(line, "@ERROR", 6) == 0) {
    rprintf(FERROR, "%s\n", line);
    return -1;            // becomes exit code 5
}
if (!lp_list(i))
    io_printf(f_out, "@ERROR: Unknown module '%s'\n", name);

Fix

Drop the trailing @RSYNCD: EXIT and prefix the message with @ERROR: so the wire format matches rsyncd. The client closes the connection on its own and exits with a non-zero status. The format string mirrors rsyncd's exactly (@ERROR: Unknown module '<name>').

Tests

  • pkg/server: TestUnknownModuleSendsErrorPrefix asserts the proxy emits @ERROR: and does not emit @RSYNCD: EXIT for an unknown module. Verified red/green by reverting the fix locally — the test fails on the old behavior.
  • test/e2e: TestUnknownModuleExitCode drives a real rsync binary at the proxy, asserts cmd.Run() returns a non-zero *exec.ExitError, and that the captured output contains @ERROR and the requested module name. Both new tests pass under go test -race ./... together with the existing suite.

No behavior change for known modules, and no protocol/version bump.

Real rsyncd answers a request for an unknown module with
"@error: Unknown module '<name>'". The rsync client recognises that
prefix and exits with code 5 (RERR_FERROR_XFER), surfacing the
configuration breakage to whatever invoked rsync.

rsync-proxy was instead writing
"unknown module: <name>\n@RSYNCD: EXIT\n". The "@rsyncd: EXIT"
makes rsync treat the response as a normal end-of-listing and exit 0,
so downstream tooling (notably tunasync) marked failed jobs as
'success'. NJU's loongnix mirror was stale for ~10 months because of
this: USTC retired the module, the proxy returned 'unknown module',
and tunasync recorded a successful run with no actual data transfer.

Drop the trailing '@rsyncd: EXIT' and use '@error:' so the client
treats it as fatal, matching rsyncd. Add unit and end-to-end
regression tests.
Copilot AI review requested due to automatic review settings May 24, 2026 14:46
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns rsync-proxy’s “unknown module” response with real rsyncd wire behavior so rsync clients treat it as a fatal protocol error (non-zero exit), preventing silent “success” on misconfiguration or retired modules.

Changes:

  • Emit @ERROR: Unknown module '<name>' (and stop sending @RSYNCD: EXIT) when a requested module isn’t configured.
  • Add a unit test to assert the server replies with @ERROR: and does not send @RSYNCD: EXIT for unknown modules.
  • Add an e2e test that runs a real rsync client and asserts it exits non-zero and reports the @ERROR line.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
pkg/server/server.go Changes unknown-module response to @ERROR: format to trigger non-zero rsync client exit.
pkg/server/server_test.go Adds regression test ensuring @ERROR: is sent and @RSYNCD: EXIT is not sent for unknown modules.
test/e2e/e2e_test.go Adds e2e regression test verifying real rsync exits non-zero and includes @ERROR on unknown module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@taoky taoky merged commit f31523d into ustclug:master May 24, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants