Skip to content
Merged
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
74 changes: 74 additions & 0 deletions docs/dev-time-tx-kill-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Dev-time TX kill-switch

DAPPS is pre-1.0 software. Until it is judged stable enough for unsupervised use on the air, every running node polls a single URL controlled by the project author and stops transmitting if that URL says so. This page exists so you know exactly what that means before you put a node on the air.

## What is it

Every DAPPS daemon, regardless of operator, polls

```
https://compute.oarc.uk/storage/public/folders/4803/dapps-devtime-killswitch.json
```

once a minute. The response is small JSON:

```json
{
"txAllowed": true,
"reason": "normal operations",
"appliesTo": ["*"]
}
```

When `txAllowed` is `false` and the local callsign matches one of the `appliesTo` patterns (or the list is `["*"]`), the daemon's bearer-level TX gate closes. While closed, no DAPPS-originated frame produces an on-air emission - forwards, floods, beacons, probes, polls, ACKs are all blocked at the AGW frame / RHP open / UDP send level. Inbound RX is unaffected; AX.25 disconnect and node-control admin frames continue to flow so the BPQ/XR session stays usable and in-flight sessions tear down cleanly.

The dashboard shows a red banner across every page when the gate is closed, with the `reason` text from the JSON.

## Why it is here

DAPPS is alpha-quality software running on shared amateur radio bandwidth. A bug shipped in a release could, in principle, cause a fleet of nodes to flood the air. Most operators won't catch a regression in a release within minutes; they may not even be at the keyboard. The kill-switch lets the project author gag every running node within roughly one minute of detecting a problem, without coordinating with operators individually.

This is a software-development safety net, not a regulatory mechanism, not a moderation tool. It exists so the worst-case "I shipped a bug that hammers 144.800" stays bounded to a few minutes of harm before every node goes silent.

## What you cannot do

- You cannot disable the polling.
- You cannot repoint it to a different URL.
- You cannot relax the cadence, the staleness window, or the fail-open behaviour.

The values are constants in the source (`TxKillSwitchPoller.cs`); a fork can change them but the published binaries cannot be configured at runtime. This is deliberate. A configurable kill-switch defeats its purpose - the whole point is that the author can rely on every node polling the one URL.

If that posture is unacceptable to you, the answer is to not run pre-1.0 DAPPS, or to fork. Both are valid choices.

## What you can do

- See the current state in the dashboard banner and at `GET /TxControl/status`.
- Continue to use the operator master TX-stop button independently. It is a separate signal; closing the local toggle gags TX even when the remote signal is allowing, and reopening the local toggle does *not* override a remote block.
- Monitor outbound HTTPS traffic to the kill-switch URL if you want to verify what's being polled. Nothing operator-identifying is sent: the request is a plain `GET` with no body and no auth.
- Read the `Services/TxKillSwitchPoller.cs` source. The whole mechanism is around two hundred lines.

## Failure modes

- **URL unreachable at startup**: the gate stays open. A new install with no internet does not silently refuse to TX.
- **URL unreachable after a successful poll**: the daemon keeps using the most recent successful state for ten minutes (the staleness window). After that it falls back to allow.
- **Malformed JSON**: same as unreachable - the failure is logged at debug level and the previous state is kept.

The staleness window is short enough that a genuinely stuck poller won't keep trusting hours-old state, and long enough to ride out the kind of network blip that's common on a domestic connection. Fail-open is the conservative posture for an amateur radio installation: an operator with a working RF stack and a flaky internet connection is not made worse off by losing transmissions on top.

## When it goes away

Before 1.0. Once the software is mature enough to be trusted to operators without the safety net, this whole subsystem is removed - not made configurable per-fleet, just deleted. The hardcoded URL becomes a dead endpoint at that point.

If the project pivots and a configurable per-fleet kill-switch becomes useful (a regional sysop wanting to gate their own nodes during a contest, for example), that's a separate feature with a separate design and a separate set of tradeoffs to argue through. It will not inherit the dev-time URL or the dev-time defaults.

## What the network sees

A `GET` request to the URL above, once a minute, from every running DAPPS node. No body, no headers beyond a User-Agent generated by the .NET HTTP stack, no cookies, no auth. The response is cached only in process memory.

If you operate a node in an environment where polling that URL is itself a problem (an isolated network, a regulator concerned about outbound traffic), the answer is to not run pre-1.0 DAPPS in that environment. Fail-open will keep TX working when the URL is unreachable, but the request will still be made on the polling cadence.

## Source

- Poller: [`src/dapps/dapps.core/Services/TxKillSwitchPoller.cs`](https://github.com/M0LTE/dapps/blob/master/src/dapps/dapps.core/Services/TxKillSwitchPoller.cs)
- Gate composition: [`src/dapps/dapps.core/Services/SystemOptionsBackedTxGate.cs`](https://github.com/M0LTE/dapps/blob/master/src/dapps/dapps.core/Services/SystemOptionsBackedTxGate.cs)
- Bearer-level enforcement: [`src/dapps/dapps.client/Tx/IDappsTxGate.cs`](https://github.com/M0LTE/dapps/blob/master/src/dapps/dapps.client/Tx/IDappsTxGate.cs)
4 changes: 4 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ The wire protocol is small and human-readable on the line: a peer connects, gets
- **Not a routing protocol replacement for the AX.25 layer.** DAPPS routes its own messages over whichever bearer is available, but it doesn't replace what your packet node does for connecting users.
- **Not BPQ-specific.** BPQ is one supported packet node; XRouter is another (via RHPv2). Anything that speaks AGW or RHPv2 works the same; MeshCore is in flight.

## Before you put it on the air

DAPPS is pre-1.0. While it is, every running node polls a single URL controlled by the project author and stops transmitting if that URL says so. It's a development-phase safety net - not configurable, removed before 1.0. See [Dev-time TX kill-switch](dev-time-tx-kill-switch.md) for the full rationale and what it means in practice.

## The journey

### 1. Install
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ nav:
- Multi-hop via non-DAPPS nodes: multi-hop.md
- Operate: operate.md
- Audit log: audit.md
- Dev-time TX kill-switch: dev-time-tx-kill-switch.md
- Update: update.md
- MCP for assistants: mcp.md
- App developers:
Expand Down
Loading
Loading