Run a relay

A relay accepts feeders, subscribers and other relays on one port. It checks every batch’s signature, drops duplicates, and passes each batch on once, unchanged. It decodes nothing and decides nothing about aircraft.

Start one

tally-relay keygen > /etc/tally/relay.key
tally-relay --key /etc/tally/relay.key --max-upload 8Mbit/s --listen 0.0.0.0:10900 --beast-out 127.0.0.1:30105

--beast-out serves every reception as plain BEAST for a local decoder such as readsb. It has no authentication, so bind it to loopback.

--stats-listen serves what the relay knows of each receiver as JSON (GET /stations, and GET /stations/KEY with the key as base64url): sessions, first and last seen, batches, sequence gaps, messages a second, and the location and software each receiver declares. Bind it to loopback too.

To run it as a service, the release’s tally-relay.service runs a relay as a dynamic user, with its key at /etc/tally/relay.key, its arguments in TALLY_RELAY_ARGS and its upload cap in TALLY_RELAY_MAX_UPLOAD, both in /etc/default/tally-relay.

What it trusts

A relay trusts narrowly by default. Only keys you name with --accept-relay may connect as another relay, and --upstream links it to another relay. Each feeder is held to --max-feeder-bytes-per-sec, 256 KiB/s by default and told to the feeder in advance, and connections are capped overall and per address.

What it serves

A subscriber chooses its streams, and may change them at any time:

  • Raw: every receiver’s signed batches, by region, for relays’ MLAT and research. A region is where the receiver says it is, never where an aircraft is.
  • Deduplicated: one copy of each Mode-S message, signed by the relay as its own claim. It is the stream most clients want, about 30 Mbps for the whole world at 20,000 receivers.
  • Positions: aircraft located by relays’ MLAT, each signed by the relay that solved it.

How relays share the work

A feeder sends to the two nearest healthy relays on different networks. Nearest means inside a band of round-trip times, and within that band every feeder of an area makes the same pick, weighted by the spare capacity each relay declares, so an area’s feeders gather on the same few relays. A relay that is full redirects a new feeder to the next in line.

Each area’s MLAT is done by two or three relays, picked by the same kind of shared rule among the relays that offer MLAT capacity. The other relays holding that area’s feeders pass its raw data to them, one hop, so raw data never travels far.

Data spreads by area, like BitTorrent. The relays holding an area’s feeders send a fixed two or three copies however many want it. Each stream is split into stripes, and every relay that takes the area forwards its own slice to a few others: forwarding that slice is the duty of taking the area.

Areas form a fixed tree, from small areas up through regions, countries and continents to the world. A relay takes areas at whatever level it needs and shares at that level, and once it has gathered every area under a bigger one, it can offer the bigger one as a single bundle.

How the area tree works

A slow subscriber is dropped, never waited for. Taking data in costs a relay nothing but its server; what it sends out is billed, so every relay sets an upload cap with --max-upload, and its bill can never exceed the cap times the seconds in a month: 10 Mbit/s is at most about 3.3 TB. The cap never runs out. When more is asked for than it carries, control goes first, then positions and clock models, then raw batches to other relays, and plain subscribers last. A new feeder is turned away while the cap cannot carry what it would have to forward, and plain subscribers are dropped, the heaviest first, before any relay. The README’s “What a relay costs” prices common hosts.

The production rule

If you depend on Tally’s data, read it through a relay you run, not through someone else’s. Another operator can then degrade only the feeders it carries, and every feeder publishes to two relays on different networks.