Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions assets/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,60 @@ tls_key_file = "/etc/rsync-proxy/tls/server.key"

motd = "Served by rsync-proxy (https://github.com/ustclug/rsync-proxy)"

# Idle timeout (seconds) applied during the bidirectional relay phase
# of a client connection. If no data flows in either direction for
# this duration, the connection is terminated. 0 (the default)
# disables the timeout. This mirrors rsyncd's "timeout" setting in
# rsyncd.conf(5); 600 is a common choice for public mirrors.
#relay_idle_timeout = 0

# Hard upper bound (seconds) on the total wall-clock duration of the
# bidirectional relay phase. When exceeded the proxy closes the
# connection regardless of activity. rsync clients typically resume
# on reconnect. 0 (the default) disables this cap.
#relay_max_duration = 0

# TCP keepalive period (seconds) applied to accepted client
# connections and dialed upstream connections. Helps detect
# half-open connections (peer crashed, NAT entry reaped) within
# minutes rather than the OS default (~2 hours). 0 (the default)
# leaves the OS-default keepalive behavior in place.
#tcp_keepalive = 0

# Default per-IP per-upstream concurrent active connection cap.
# Limits how many simultaneous active relay connections a single
# client IP may have to any one upstream, preventing a single client
# from monopolizing upstream slots. 0 (the default) disables the
# limit. Each upstream may override this via its own
# per_ip_max_active_connections setting below.
#per_ip_max_active_connections = 0

# Connect timeout (seconds) when dialing upstreams. Bounds how long
# the proxy waits for an unresponsive upstream before failing fast
# and incrementing rsync_proxy_upstream_dial_errors_total. 0 (the
# default) leaves the OS connect-attempt behavior in place
# (typically ~75s of SYN retries on Linux).
#dial_timeout = 0

# Throughput floor enforced during the relay phase. A connection
# that transfers fewer than min_throughput_bytes over the most
# recent min_throughput_window seconds is treated as slow-leeching
# and terminated. min_throughput_grace gives a fresh connection
# this many seconds of ramp-up before the floor is enforced;
# defaults to min_throughput_window when unset. Set
# min_throughput_bytes to 0 (the default) to disable the check.
#min_throughput_bytes = 0
#min_throughput_window = 60
#min_throughput_grace = 60

[upstreams.u1]
address = "127.0.0.1:1234"
modules = ["foo"]
max_active_connections = 60
max_queued_connections = 60
# Override the proxy-wide per_ip_max_active_connections for this
# upstream. 0 means inherit the proxy-wide default.
#per_ip_max_active_connections = 4

[upstreams.u1_auto]
address = "127.0.0.1:1234"
Expand Down
54 changes: 54 additions & 0 deletions pkg/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ type Upstream struct {
UseProxyProtocol bool `toml:"use_proxy_protocol"`
MaxActiveConns int `toml:"max_active_connections"`
MaxQueuedConns int `toml:"max_queued_connections"`
// PerIPMaxActiveConns overrides the proxy-wide
// per_ip_max_active_connections setting for this upstream. A
// value of 0 (the default, i.e. field omitted) means the
// upstream inherits the proxy-wide value.
PerIPMaxActiveConns int `toml:"per_ip_max_active_connections"`
}

type ProxySettings struct {
Expand All @@ -26,6 +31,55 @@ type ProxySettings struct {
ErrorLog string `toml:"error_log"`
TLSCertFile string `toml:"tls_cert_file"`
TLSKeyFile string `toml:"tls_key_file"`
// RelayIdleTimeoutSecs is the idle timeout (in seconds) applied
// during the bidirectional relay phase of a connection. If no data
// flows in either direction for this duration, the connection is
// terminated. 0 (the default) disables the timeout.
//
// This mirrors the semantics of the rsyncd "timeout" setting (see
// rsyncd.conf(5)), which is an I/O timeout. A common choice for
// public mirrors is 600.
RelayIdleTimeoutSecs int `toml:"relay_idle_timeout"`
// RelayMaxDurationSecs is the maximum total wall-clock duration
// (in seconds) of the bidirectional relay phase. When exceeded
// the proxy closes the connection regardless of activity. 0 (the
// default) disables this hard cap. rsync clients will typically
// reconnect and resume on the next run.
RelayMaxDurationSecs int `toml:"relay_max_duration"`
// TCPKeepAliveSecs enables TCP keepalive on accepted client
// connections and on dialed upstream connections. The value is
// the keepalive period in seconds; 0 (the default) leaves the
// OS-default keepalive behavior in place (typically: disabled or
// ~2 hours). Enabling this helps detect half-open connections
// (peer crashed, NAT reaped) within minutes rather than hours.
TCPKeepAliveSecs int `toml:"tcp_keepalive"`
// PerIPMaxActiveConns is the proxy-wide default for the per-IP
// per-upstream concurrency cap applied during the relay phase.
// 0 (the default) disables the limit. Each upstream may
// override this via [upstreams.X].per_ip_max_active_connections.
PerIPMaxActiveConns int `toml:"per_ip_max_active_connections"`
// DialTimeoutSecs caps how long the proxy waits when dialing an
// upstream rsync server. A value of 0 (the default) leaves the
// OS-default TCP connect behavior in place (~75s on Linux). When
// an upstream is unreachable this surfaces a fast failure to
// the client and increments rsync_proxy_upstream_dial_errors_total
// without tying up the listener for the full kernel SYN-retry
// budget.
DialTimeoutSecs int `toml:"dial_timeout"`
// MinThroughputBytes, MinThroughputWindowSecs and
// MinThroughputGraceSecs together implement a throughput floor
// during the relay phase. Within any window of
// min_throughput_window seconds the connection must transfer at
// least min_throughput_bytes (counting both directions); if it
// does not, the proxy closes the connection. The grace period
// suppresses the check for the first min_throughput_grace seconds
// after the relay starts to avoid killing a slow-start session.
// Setting min_throughput_bytes or min_throughput_window to 0 (the
// default) disables the floor. min_throughput_grace defaults to
// the value of min_throughput_window when 0.
MinThroughputBytes int64 `toml:"min_throughput_bytes"`
MinThroughputWindowSecs int `toml:"min_throughput_window"`
MinThroughputGraceSecs int `toml:"min_throughput_grace"`
}

type Config struct {
Expand Down
32 changes: 32 additions & 0 deletions pkg/server/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,38 @@ func (s *Server) writePrometheusMetrics(w io.Writer, now time.Time) {
prometheusEscapeLabelValue(u.Name), c.dialError.Load())
}

_, _ = fmt.Fprintln(w, "# HELP rsync_proxy_per_ip_rejected_total Total connections rejected by the per-IP per-upstream concurrency cap.")
_, _ = fmt.Fprintln(w, "# TYPE rsync_proxy_per_ip_rejected_total counter")
for _, u := range upstreams {
c := s.getUpstreamCounters(u.Name)
_, _ = fmt.Fprintf(w, "rsync_proxy_per_ip_rejected_total{upstream=\"%s\"} %d\n",
prometheusEscapeLabelValue(u.Name), c.perIPRejected.Load())
}

_, _ = fmt.Fprintln(w, "# HELP rsync_proxy_relay_idle_timeout_terminated_total Total relay connections terminated by the idle timeout watcher per upstream.")
_, _ = fmt.Fprintln(w, "# TYPE rsync_proxy_relay_idle_timeout_terminated_total counter")
for _, u := range upstreams {
c := s.getUpstreamCounters(u.Name)
_, _ = fmt.Fprintf(w, "rsync_proxy_relay_idle_timeout_terminated_total{upstream=\"%s\"} %d\n",
prometheusEscapeLabelValue(u.Name), c.idleTerminated.Load())
}

_, _ = fmt.Fprintln(w, "# HELP rsync_proxy_relay_max_duration_terminated_total Total relay connections terminated for exceeding the max-duration cap per upstream.")
_, _ = fmt.Fprintln(w, "# TYPE rsync_proxy_relay_max_duration_terminated_total counter")
for _, u := range upstreams {
c := s.getUpstreamCounters(u.Name)
_, _ = fmt.Fprintf(w, "rsync_proxy_relay_max_duration_terminated_total{upstream=\"%s\"} %d\n",
prometheusEscapeLabelValue(u.Name), c.maxDurationTerminated.Load())
}

_, _ = fmt.Fprintln(w, "# HELP rsync_proxy_throughput_floor_terminated_total Total relay connections terminated for falling below the configured throughput floor per upstream.")
_, _ = fmt.Fprintln(w, "# TYPE rsync_proxy_throughput_floor_terminated_total counter")
for _, u := range upstreams {
c := s.getUpstreamCounters(u.Name)
_, _ = fmt.Fprintf(w, "rsync_proxy_throughput_floor_terminated_total{upstream=\"%s\"} %d\n",
prometheusEscapeLabelValue(u.Name), c.throughputFloorTerminated.Load())
}

_, _ = fmt.Fprintln(w, "# HELP rsync_proxy_unknown_module_requests_total Total requests for unknown modules.")
_, _ = fmt.Fprintln(w, "# TYPE rsync_proxy_unknown_module_requests_total counter")
_, _ = fmt.Fprintf(w, "rsync_proxy_unknown_module_requests_total %d\n", s.unknownModuleCount.Load())
Expand Down
Loading
Loading