A simple web service to test the federation setup of a Matrix homeserver. This tool checks DNS, .well-known
configuration, server keys, TLS certificates, and federation endpoints for a given Matrix server name.
- Resolves both IPv4 and IPv6 addresses for the target server.
- Fetches and validates the
/.well-known/matrix/serverendpoint for all resolved IPs. - Checks DNS SRV, A, and AAAA records.
- Validates server keys and TLS certificates.
- Reports detailed results as JSON.
- Optional anonymized, opt-in federation request statistics with Prometheus export.
First you should take a look at config.yaml.example
and create your own config.yaml file.
The service will look for config.yaml in the current working directory.
After that, run the database migrations and start the service:
cargo run --package migration -- up --database-url <sqlite or postgres URL>
cargo run --release --bin rust-federation-testerThe service will listen on 0.0.0.0:8080 by default.
Returns a detailed JSON report about the federation status of the given server.
Example:
GET /api/federation/report?server_name=matrix.org
Returns GOOD if federation is OK, otherwise BAD.
Example:
GET /api/federation/federation-ok?server_name=matrix.org
A standalone CLI for testing a single Matrix server's federation compliance — similar in spirit to matrix-sytest but without requiring a second homeserver.
cargo install --path crates/test-harnessOr run directly from the workspace:
cargo run -p matrix-federation-test -- --server matrix.example.commatrix-federation-test --server <SERVER_NAME> [--format pretty|json|tap] [--timeout <SECS>]Examples:
# Human-readable output
matrix-federation-test --server matrix.org
# TAP format for CI pipelines (exits 0 on pass, 1 on failure)
matrix-federation-test --server matrix.org --format tap
# JSON output for scripting
matrix-federation-test --server matrix.org --format json| Name | What it checks |
|---|---|
well_known_reachable |
At least one IP reached /.well-known/matrix/server |
well_known_valid |
At least one response contains a valid m.server |
well_known_consistent |
No split-brain (all IPs return the same m.server) |
srv_or_dns_resolves |
DNS resolves to at least one address |
tls_valid |
At least one connection has valid TLS certificates |
version_reachable |
/_matrix/federation/v1/version returns a parseable response |
keys_reachable |
/_matrix/key/v2/server is reachable |
keys_valid_ed25519 |
ed25519 key is present and the signature verifies |
federation_ok |
Overall FederationOK is true |
A minimal Docker image is available that contains only the CLI (no database, no SMTP, no configuration needed):
docker run --rm ghcr.io/mtrnord/matrix-federation-test:latest --server matrix.example.comThe pure federation-checking logic is published as the matrix-federation-tester crate. Add it as a dependency:
[dependencies]
matrix-federation-tester = { git = "https://github.com/MTRNord/rust-federation-tester" }use matrix_federation_tester::{FederationConfig, connection_pool::ConnectionPool, response::generate_json_report};
let pool = ConnectionPool::default();
let config = FederationConfig::default();
let report = generate_json_report("matrix.example.com", &resolver, &pool, &config).await?;
println!("FederationOK: {}", report.federation_ok);Run all tests:
cargo test --workspaceGenerate coverage (requires cargo install cargo-llvm-cov):
chmod +x coverage.sh
./coverage.shOpen target/coverage/html/index.html in a browser for a detailed report.
Two top-level keys control how the federation checks are performed:
# Network timeout in seconds for each individual federation check (DNS, TLS, HTTP).
# Default: 3 — suitable for the public internet.
# Increase for high-latency intranet links (e.g. 10–30).
federation_timeout_secs: 3
# When true, the SSRF guard that blocks private/internal IP addresses is disabled.
# Required for intranet / closed-federation deployments.
#
# WARNING: Do NOT enable this on a public-facing deployment. It allows any user to
# probe internal network resources (RFC 1918, link-local, ULA, etc.) via the
# well-known delegation mechanism.
allow_private_targets: false| Range | Description |
|---|---|
| 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | RFC 1918 private networks |
| 169.254.0.0/16 | Link-local / AWS instance metadata |
| 100.64.0.0/10 | CGNAT / Tailscale |
| fc00::/7 | IPv6 ULA |
The service can (optionally) record per-request federation statistics on a strict opt-in basis and expose anonymized aggregates via GET /metrics in Prometheus text format.
Statistics are only recorded if both of these are true:
statistics.enabledistrueinconfig.yaml.- The incoming request to
/api/report(or other future endpoints) includes the query parameterstats_opt_in=1.
If either condition is not met, the request is processed normally but no event is persisted.
Server names are never stored or exported in raw form to Prometheus. Instead, each server name is hashed using: blake3(anonymization_salt || "::" || server_name).
To prevent correlation across deployments, you MUST configure a unique, secret anonymization_salt. Changing the salt will rotate (invalidate) all previously emitted anonymized identifiers.
If statistics.enabled and statistics.prometheus_enabled are both true but anonymization_salt is empty, startup will fail (validation error).
statistics:
enabled: false # Master switch. When false, nothing is recorded.
prometheus_enabled: true # Expose /metrics with anonymized counters.
anonymization_salt: "change-me" # REQUIRED (non-empty) when both enabled + prometheus_enabled are true.
raw_retention_days: 30 # Inactive rows (no updates for > N days) are pruned periodically.Only aggregate counters are stored currently; there is no raw per-event table yet. A pruning task runs every 12h and deletes rows whose last_seen_at is older than raw_retention_days.
federation_request_total{server="<anon>",result="success|failure",software_family="<family>",software_version="<version>"}
Per anonymized server + outcome. software_family and software_version are heuristically extracted from the Matrix server's reported version string (currently detects synapse, conduit, dendrite). Missing values are omitted.
federation_request_family_total{software_family="<family>",result="success|failure"}
Aggregated by software family (allows trends without per-instance granularity).
Both counters are monotonic and backed by a lightweight aggregate table maintained through high-level SeaORM entity operations (no manual SQL upsert logic). Each opted-in request results in either an insert (first time a server is seen) or an update (incrementing existing counters and refreshing last_seen_at).
curl "http://localhost:8080/api/report?server_name=example.org&stats_opt_in=1"Then scrape metrics:
curl http://localhost:8080/metricsFor non github contributors as well as a place to discuss we offer a mailinglist:
It is meant to be used for the projects at:
- https://github.com/MTRNord/rust-federation-tester
- https://github.com/MTRNord/matrix-connection-tester-ui
- https://connectivity-tester.mtrnord.blog
- https://stage.connectivity-tester.mtrnord.blog
Patches are welcome. Have a look at https://git-send-email.io/ or https://www.youtube.com/watch?v=mjYac9SwIK0 and https://www.youtube.com/watch?v=p79IjNay4mY regarding how to do mailinglist based contributions.
Curently PRs via github are also welcome however this may change in the future.
AGPL-3.0-or-later
This project is inspired by matrix-org/matrix-federation-tester, but implemented in Rust and with a slightly different JSON response format.