Outbound-only network probe: measures the same targets from many countries at once
247
A lightweight measurement agent for distributed network observation. Run it on cheap VPS boxes in as many countries as you like; each one measures the same list of targets and pushes results to the GeoPulse hub.
The point is the question single-vantage-point monitors cannot answer: not "is it up?" but "up from where?" github.com resolving fine from Frankfurt and timing out from Muscat at the same minute is the finding — and you only see it if something is watching from both.
Every 60 seconds the probe runs its checks and pushes the results outbound to the hub.
| Check | What it measures |
|---|---|
Http | Request timing and status class against a URL |
Dns | A / AAAA lookups, queried twice — once against a chosen public resolver, once against the box's own /etc/resolv.conf resolver |
Tcp | Handshake latency to an open port |
Tls | Handshake, certificate issuer and expiry |
There is no ICMP check. TCP connect timing replaces it, which means no CAP_NET_RAW, no root, no privileged container.
At the top of each hour the probe folds its own buffer into one row per check — counts, min/max/sum, and a 14-bucket log-spaced latency histogram — and pushes that too. Aggregation happens on the probe's CPU, so adding nodes doesn't add hub load.
All traffic is outbound. The hub never dials the probe. No inbound port, no public IP, no per-node certificate, and NAT is irrelevant.
The container exposes exactly one HTTP endpoint, GET /health, for your orchestrator's liveness check. It returns liveness only — never measurement data. Nothing else listens.
Every payload to the hub is HMAC-SHA256 signed and AES-GCM encrypted, on every route, always. Two keys derived from one shared secret via HKDF; the secret itself never crosses the wire. There is no plaintext mode and no flag to disable it.
The check list comes from the hub over that same authenticated channel, so a probe needs no target configuration of its own. Nothing else can hand it a host to measure — no unauthenticated path accepts a target, ever. A probe that checks attacker-supplied targets is a free scanning proxy and gets the underlying VPS suspended, so that door doesn't exist.
docker run -d \
--name geopulse-probe \
--restart unless-stopped \
-e Probe__NodeId=helsinki-01 \
-e Probe__HubUrl=https://geopulse.rafinia.info/ \
-e Probe__SharedSecret=... \
nrafinia/geopulse-probe:latest
Or with compose:
services:
probe:
image: nrafinia/geopulse-probe:latest
restart: unless-stopped
environment:
Probe__NodeId: helsinki-01
Probe__HubUrl: https://geopulse.rafinia.info/
Probe__SharedSecret: ${GEOPULSE_SECRET}
Double underscore is the .NET section separator, so Probe__HubUrl maps to Probe:HubUrl.
| Key | Default | Notes |
|---|---|---|
Probe__NodeId | — | Required. Stable identifier for this box. |
Probe__HubUrl | — | Required. |
Probe__SharedSecret | — | Required. Must match the hub. |
Probe__BufferMinutes | 180 | In-memory resend window. |
No location configuration is needed. On startup the probe asks a free IP-info API for its own public IP, country, city and ASN, and reports them in the heartbeat. Deploy the same image anywhere with no per-node setup. It rechecks every six hours in case the IP moves.
Hub downtime does not lose data. Results sit in a per-check ring buffer — three hours by default — and the probe only advances its pointer once the hub acknowledges a watermark. Hourly rollups are held up to 48 hours. Bring the hub back and the gap fills itself.
A NOERROR with no answers is a success, not a failure. Plenty of domains legitimately have no AAAA record. The probe records it as success with a "no record" detail. NXDOMAIN on a target you explicitly configured is a failure — the thing you asked it to watch stopped existing. SERVFAIL and timeout are both failures, and stay distinguishable from each other, because a resolver failing while another succeeds is the entire product.
State survives restarts. The buffer snapshots to a single file once a minute. Losing up to 60 seconds on restart is expected and fine.
Built with NativeAOT into a single self-contained binary — no runtime to install, no reflection, fast start.
Designed for 256–512 MB shared-vCPU boxes, which is what free and near-free tiers actually give you. Outbound traffic runs around 4 KB per minute of results plus a heartbeat, deliberately below the threshold where free-tier providers start asking questions. Checks are spread deterministically across the minute (stableHash(checkId) % 60000), so load is flat rather than spiky and the pattern reproduces across restarts.
| Tag | Meaning |
|---|---|
latest | Newest build |
<sha> | Exact commit — pin this in production |
Also published to ghcr.io/nrafinia/geopulse-probe.
This image is only half the system. It needs a GeoPulse hub to push to — that's where results are stored, rolled up, and drawn as a cross-country comparison: https://geopulse.rafinia.info/
Content type
Image
Digest
sha256:22c2ce3c9…
Size
11.7 MB
Last updated
27 days ago
docker pull nrafinia/geopulse-probe