Files
claudetools/tmp/r2-gemini-prompt.md
Mike Swanson fd99ee327c sync: auto-sync from GURU-5070 at 2026-06-12 05:57:38
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-12 05:57:38
2026-06-12 05:58:05 -07:00

8.8 KiB

GuruRMM command-delivery diagnosis — ROUND 2 (important topology correction)

You analyzed this in round 1 (your prior answer is included at the bottom). A FACTUAL CORRECTION to the architecture has come to light that invalidates part of the round-1 premise. Please re-evaluate from scratch where needed. Be willing to discard your prior top hypothesis.

CRITICAL CORRECTION to the ingress topology

In round 1 we told you: "agent -> Cloudflare -> nginx -> Rust server". That was wrong for the AGENT path. Verified facts now:

  • The agent's hard-coded WebSocket URL is wss://rmm-api.azcomputerguru.com/ws (from agent source: DEFAULT_SERVER_URL, and the agent config default). The installer and enrollment also use rmm-api.azcomputerguru.com.
  • DNS / Cloudflare proxy status for the two hostnames (both A-record to the same public IP 72.194.62.10):
    • rmm.azcomputerguru.com -> proxied = true (orange cloud; goes THROUGH Cloudflare) — this is the human DASHBOARD.
    • rmm-api.azcomputerguru.com -> proxied = false (grey cloud; DNS-only, BYPASSES Cloudflare) — this is what the AGENTS use.
  • Therefore Cloudflare is NOT in the agent's path at all. All round-1 hypotheses about Cloudflare WAF / CF WebSocket buffering / CF frame handling are moot for agent command delivery. (Cloudflare only fronts the dashboard.)

The ACTUAL agent path (verified)

agent (endpoint, e.g. PST-SERVER behind a UniFi UDR/"Cloudflare-Ultra"-class gateway)
  -> endpoint LAN/NAT
  -> public internet
  -> 72.194.62.10  (public IP; this is the NPM box)
  -> NPM = "Nginx Proxy Manager" on host 172.16.3.20  (terminates TLS; one nginx layer)
       NPM proxy-host settings for rmm-api: forward http://172.16.3.30:80, websockets=ON,
       http2_support=ON, block_exploits=ON (NPM "Block Common Exploits"), caching=OFF, advanced_config=EMPTY
  -> http://172.16.3.30:80   (the ORIGIN nginx; a SECOND nginx layer)  -- PLAINTEXT HTTP over the LAN here
  -> proxy_pass http://127.0.0.1:3001   (the Rust server)

So there are TWO nginx proxy layers in series (NPM on .20, then origin nginx on .30), no CDN.

Origin nginx /ws block (verbatim):

location /ws {
    proxy_pass http://127.0.0.1:3001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_read_timeout 86400;
}

(No explicit proxy_buffering off;, no proxy_send_timeout. NPM's generated config for the proxy host is the standard NPM template with websockets enabled + block_exploits; we have not yet dumped its exact directives.)

What this changes / things to reconsider

  • The "asymmetry" symptom is unchanged and still the core puzzle: for affected agents the server keeps receiving inbound (heartbeats, last_seen fresh), the agent does NOT trip its 90s no-inbound reconnect (so it appears to be receiving at least the server's 30s WS Ping frames), yet server->agent COMMAND (Text) frames are never acked/executed — both ~4 KB commands AND ~80 B hostname commands. One command DID succeed earlier (right after the agent updated/reconnected). A server-forced eviction + fresh reconnect did NOT restore command delivery (a tiny command on the fresh connection still never acked) — but note we have NO confirmation the agent received anything server->agent on that fresh connection either.
  • Affected: PST-SERVER + PST-SERVER2 (same site/UDR), and GTS-PEDRO-H (different site). ~40 other agents deliver commands fine through this SAME NPM+nginx path.
  • Because rmm-api is grey-cloud, the agent's TLS connection goes straight from the endpoint's gateway to the public IP — there is no CDN normalizing MTU/keepalive/buffering on the path; the endpoint's own gateway (e.g. PST's UDR) is directly in the path.

A note on one earlier "test"

We tried to tcpdump the origin loopback (nginx->Rust on 127.0.0.1:3001) during a marked command dispatch and saw 0 packets — but that capture almost certainly FAILED (the backgrounded tcpdump was killed when the SSH session closed), so treat it as NO DATA, not as evidence that the loopback is silent.

Please now provide (round 2)

  1. Your REVISED ranked hypotheses given Cloudflare is out of the agent path and there are two nginx layers + the endpoint gateway directly in the path. Explicitly say which round-1 hypotheses you drop or demote.
  2. The single most diagnostic next test (we can run packet captures on the .30 host on the plaintext .20->.30:80 leg and on the 127.0.0.1:3001 loopback; we can read NPM's generated nginx config on .20; we can query the UDR; we currently CANNOT get shell/logs on the affected endpoints). For each plausible outcome, what it tells you.

YOUR ROUND-1 ANSWER (for reference)

GuruRMM — agent command delivery failure: diagnostic dossier (neutral)

The evidence suggests a highly selective delivery failure where outbound (Server -> Agent) WebSocket Text frames are being dropped, mangled, or ignored, while WebSocket Control frames (Ping) and inbound (Agent -> Server) traffic remain functional.

1. Candidate Root-Cause Hypotheses

Hypothesis Reasoning / Evidence FOR Reasoning / Evidence AGAINST
H1: MTU / Fragmentation (Large Frame Blackhole) Common in site-to-site tunnels (UniFi UDR). Large commands (4KB) + overhead might exceed path MTU, causing fragmentation. If the path drops fragments, the Text frame never arrives. Refuted by T2/T3/T9: Tiny hostname commands (~80B) also failed. MTU issues usually spare small packets.
H2: Application-Layer Deadlock (Agent Rx Loop) If the Agent's handle_server_message or the internal tx (to the executor) blocks or panics without crashing the read loop, the agent stops processing Text frames but continues to receive Pings (keeping the 90s deadline alive). Weakened by T7/T8: A server-forced eviction and fresh reconnect (fresh Agent state) did not restore functionality.
H3: Intermediate Proxy / Firewall (Text-Frame Filtering) Cloudflare or a local "Security Gateway" (UniFi) may be inspecting WebSocket payloads. If the Command JSON pattern matches a signature or if the Upgrade handshake is being "half-policed," Text frames might be dropped. The fleet baseline (40 agents working) suggests it's not a global Cloudflare issue, but could be specific to "enhanced" security profiles at certain sites.
H4: Protocol Desync (ConfigUpdate Serialization) On connect, the Server sends a ConfigUpdate (multi-KB). If this frame is malformed or if tokio-tungstenite on the server-side has a buffer issue with specific frame sequences, the Agent's parser might be stuck/errored for that specific stream. Supported by T8: T8 confirms that even after a reconnect, delivery fails. This points to a failure during or immediately after the handshake/ConfigUpdate sequence.
H5: Nginx/Proxy Buffer "Clogging" proxy_read_timeout is long, but proxy_buffers or internal memory limits on the origin/nginx could be hit for specific connections if the socket becomes "write-blocked" from the origin to Nginx. Against: Nginx logs would typically show 499s or buffer overflows; send_to on the server returns success, meaning it's reaching the OS/loopback buffer.

2. The Verdict on the Evidence

  • Inference Check: The claim that "the agent was receiving SOME inbound frames" (T4) is an unverified inference. While the 90s timeout didn't trigger, we cannot distinguish between "the agent received a Ping" and "the agent's internal timer is somehow stalled/incorrect."
  • Contradiction: T8 is the most troubling. If a fresh connection (after eviction) doesn't fix it, the issue is either environmental (Network/Path) or intrinsic to the Agent's 0.6.63 startup logic (e.g., it crashes its own Text handler immediately upon receiving the first ConfigUpdate or Command).

3. The SINGLE Most Diagnostic Test

Action: Dispatch a WebSocket Ping from the Server to a stuck agent and explicitly log the receipt of the corresponding Pong.

Reasoning:

  • If Pong is received: We confirm the path is fully transparent to WebSocket Control frames. This isolates the failure to Text-frame serialization, size, or content inspection (H3/H4).
  • If Pong is NOT received: The agent is "ghosting" the server entirely. This means the 90s timeout in the agent (T4) is the actual failure point — the agent should have reconnected but didn't. This points to an Agent-side Rx loop deadlock (H2).

Outcome-based path:

  • Pong Received: Inspect Nginx/Cloudflare for "Web Application Firewall" (WAF) drops or "WebSocket inspection" settings.
  • Pong NOT Received: The agent is dead-locked. Investigate the tokio task management in the Agent's 0.6.63 read loop.