Files
Cort BuffingtonandClaude Opus 4.8 a3e99a2da9 docs: refresh for the SERVER/OUTBOUND rename, OBP_BRIDGES, and Unix-socket feed
Documentation audit for the recent changes:
  * MODE terminology MASTER->SERVER, PEER/CLIENT->OUTBOUND corrected across
    README, INSTALL, CONFIGURING, ACLS, and the dashboard docs (the code only
    accepts SERVER/OUTBOUND/OPENBRIDGE, so the old names in docs were wrong).
  * README gains a top-of-file breaking-change warning (OBP routing moved to
    OBP_BRIDGES; system-mode rename) with a pointer to the migration tool.
  * CONFIGURING documents REPORT_TRANSPORT/REPORT_SOCKET (Unix-socket feed) and
    a "Migrating from the old inline form" section for tools/migrate_obp_rules.py.
  * dashboard/README documents HBLINK_TRANSPORT/HBLINK_SOCKET and corrects the
    stale "changes appear on the next snapshot" claim (connect/disconnect are
    granular pushes now).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 13:42:08 -05:00

272 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HBlink3 Access Control Lists (ACLs)
Access Control Lists are one of the most powerful — and most under-documented —
features in HBlink3. They let you decide, packet by packet, **who** may use your
system, **which subscribers** may talk through it, and **which talkgroups** are
allowed on each timeslot. This document explains the ACL grammar, the exact
matching rules (verified against the code in `config.py` and `hblink.py`), a set
of worked examples from simple to advanced, and honest guidance on performance.
ACLs are cheap and safe to use. Read the [Performance](#performance) section and
you will see there is almost no reason *not* to use them.
---
## 1. The two-part grammar
Every ACL is a single string of the form:
```
ACTION:entry,entry,entry,...
```
* **ACTION** is either `PERMIT` or `DENY`.
* **entries** are comma-separated, and each entry is one of:
* a **single ID** — e.g. `3120101`
* a **range** — two IDs joined by a hyphen, low first — e.g. `1000-2000`
* the literal keyword **`ALL`** — matches every possible ID.
Examples of valid ACLs:
```
PERMIT:ALL
DENY:1
DENY:1,1000-2000,4500-60000,17
PERMIT:3120000-3120999,3123456
```
An **empty** value is treated as `PERMIT:ALL` (permit everything).
> There is exactly **one** ACTION per ACL. You cannot mix `PERMIT` and `DENY`
> entries in the same list. If you need "allow these but block those," you layer
> two ACLs — see [Section 6](#6-layering-global-and-system-acls).
---
## 2. The single most important rule: what happens to IDs you *didn't* list
This is the part that trips everyone up, so read it twice.
An ACL does two things at once. It defines an ACTION for the IDs you **list**,
and it *implies the opposite ACTION* for every ID you **don't** list.
| You write | A **listed** ID is… | An **unlisted** ID is… | Mental model |
|-----------|--------------------|------------------------|--------------|
| `PERMIT:…` | permitted | **denied** | **whitelist** — only these get in |
| `DENY:…` | denied | **permitted** | **blacklist** — everyone except these |
So:
* `PERMIT:3120100-3120199` means *"allow only IDs 3120100–3120199, block the
rest of the world."*
* `DENY:3120100-3120199` means *"block IDs 3120100–3120199, allow everyone
else."*
A listed ID gets the ACTION; an unlisted ID gets the opposite.
**Rule of thumb:** use `DENY:` when you want to block a small, known set and let
everyone else through. Use `PERMIT:` when you want a closed system where only an
explicit list is allowed.
`PERMIT:ALL` and `DENY:1` (deny the reserved ID 1, permit all real IDs) are the
two most common "effectively open" settings.
---
## 3. The four ACL types
HBlink3 applies four kinds of ACL. Each is a separate config key.
| Key | Gates | Value domain | Notes |
|-----|-------|--------------|-------|
| `REG_ACL` | **Repeater/peer registration** (login) | peer IDs, `1 … 4294967295` | Only meaningful on **SERVER** systems. Outbound/OpenBridge links don't accept registrations. **Always enforced** — see §5. |
| `SUB_ACL` | **Subscriber** (the source radio ID of a call) | subscriber IDs, `1 … 16776415` | Applied to every voice/data stream. |
| `TGID_TS1_ACL` | **Destination talkgroup on Timeslot 1** | talkgroup IDs, `1 … 16776415` | |
| `TGID_TS2_ACL` | **Destination talkgroup on Timeslot 2** | talkgroup IDs, `1 … 16776415` | |
The value domains matter: subscriber and talkgroup IDs are checked against a
maximum of **16776415** (`const.ID_MAX`), while registration (peer) IDs go up to
**4294967295** (`const.PEER_MAX`). An entry outside the valid range causes
HBlink3 to **exit at startup** with an `ACL CREATION ERROR` — this is a
guardrail, not a bug (see [Warnings](#warnings)).
---
## 4. Where each ACL is enforced
For an inbound **DMRD voice/data** frame, HBlink3 checks (in order):
1. GLOBAL `SUB_ACL` — source subscriber
2. GLOBAL `TGID_TS1_ACL` or `TGID_TS2_ACL` — destination TG, by slot
3. SYSTEM `SUB_ACL` — source subscriber
4. SYSTEM `TGID_TS1_ACL` or `TGID_TS2_ACL` — destination TG, by slot
The call is dropped at the **first** ACL that denies it. It must pass **all
four** to be forwarded.
For **registration** (a repeater logging into a master), both the GLOBAL and the
SYSTEM `REG_ACL` must permit the peer — if either one denies it, the login is
refused.
### OpenBridge special case
OpenBridge systems do not register (no `REG_ACL`), and **all OpenBridge traffic
is carried on Timeslot 1**. That means the **global `TGID_TS1_ACL`** is your
talkgroup filter for OpenBridge peers. TS2 is forced to `PERMIT:ALL` internally
for OpenBridge systems.
---
## 5. The `USE_ACL` switch — and the one ACL it can't turn off
Each stanza has a `USE_ACL` boolean.
* `USE_ACL: True` — enforce this stanza's `SUB_ACL` and TGID ACLs.
* `USE_ACL: False` — skip the subscriber and talkgroup checks for this stanza
(traffic passes freely).
**Registration ACLs are always enforced.** `REG_ACL` is *not* governed by
`USE_ACL`. Even with `USE_ACL: False`, a master will still refuse a repeater that
its `REG_ACL` denies. This is by design: registration is your front door.
> Note: the stanzas still require the ACL keys to be present even when
> `USE_ACL: False`. Leave them at `PERMIT:ALL` if unused.
---
## 6. Layering GLOBAL and SYSTEM ACLs
Because GLOBAL is checked first and SYSTEM second, and **both must pass**, you
can compose behaviors that a single `PERMIT`/`DENY` line cannot express. Think of
GLOBAL as your site-wide policy and SYSTEM as per-connection refinement.
**Example — a global blacklist plus a per-system whitelist.**
```ini
[GLOBAL]
USE_ACL: True
SUB_ACL: DENY:1,2,3 # site-wide: block a few abusive IDs, allow the rest
[LOCAL-CLUB-REPEATER]
USE_ACL: True
SUB_ACL: PERMIT:3120000-3120999 # this repeater: ONLY our club's ID block
```
A subscriber must (a) not be one of the globally blocked IDs **and** (b) fall
inside the club's block. The intersection is exactly what you want, and neither
ACL alone could express it.
---
## 7. Worked examples
All examples assume `USE_ACL: True` in the relevant stanza.
**Open system, tidy.** Allow every real subscriber, block only the reserved
ID 1:
```ini
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
```
**Closed talkgroup policy on TS1.** Permit only a curated set of statewide and
local talkgroups on Timeslot 1; leave TS2 open:
```ini
TGID_TS1_ACL: PERMIT:8,9,3100-3199,31201,31203
TGID_TS2_ACL: DENY:1
```
`3100-3199` covers the whole regional block in one range; `8`, `9`, `31201`,
`31203` are individual talkgroups. Everything else on TS1 is dropped.
**Ban a handful of subscribers network-wide** while permitting everyone else:
```ini
[GLOBAL]
SUB_ACL: DENY:1,2606234,3141592,3120555
```
**Members-only repeater.** Only your membership ID range may key up, and only on
approved talkgroups:
```ini
[MEMBERS-ONLY]
USE_ACL: True
SUB_ACL: PERMIT:3120000-3120499
TGID_TS1_ACL: PERMIT:2,9,3120
TGID_TS2_ACL: PERMIT:3120,31205
```
**Registration control on a server.** Accept only two specific repeater IDs onto
this server; reject all other login attempts:
```ini
[SERVER-1]
MODE: SERVER
REG_ACL: PERMIT:311111,311222
```
**Large but efficient range list.** Contiguous and adjacent ranges are merged at
load time, so this…
```ini
SUB_ACL: PERMIT:1000-1999,2000-2999,3000-3999,3120101
```
…collapses internally to a single span `1000–3999` plus the single ID `3120101`.
Write ranges however is clearest to *you*; the engine tidies them up.
---
## Performance
**Short version: turn ACLs on. They are fast.**
* All of the work of reading and organizing your ACLs happens **once, when
HBlink3 starts** — not while traffic is flowing.
* Checking a packet is near-instant, and it stays fast no matter how long your
lists get. A list with a thousand entries is checked essentially as quickly as
a list with ten. You will not see the difference on real traffic.
* Runs of consecutive IDs are **combined** internally, so a config that looks
huge often takes very little memory.
Where cost actually comes from — and it is small:
* **Every enabled ACL type adds one check per packet.** With GLOBAL + SYSTEM
layering that's up to four checks per frame. Constant, tiny, predictable.
* **Very large lists of thousands of individual (non-consecutive) IDs** use a
little more memory and startup time, but checking them stays fast.
Practical guidance:
* Use ranges (`1000-2000`) instead of listing many consecutive IDs — it's less to
type and smaller in memory.
* If a stanza genuinely uses no ACLs, `USE_ACL: False` skips the subscriber/TGID
checks entirely (registration is still enforced).
* Don't hesitate to write expressive ACLs. The design specifically optimizes for
exactly this; in practice the cost is negligible.
---
## Warnings
* **`PERMIT` is a whitelist — it denies everyone you didn't list.** The most
common mistake is writing `PERMIT:3120101` intending "also allow 3120101" and
accidentally locking out the entire rest of the world. Re-read [Section 2](#2-the-single-most-important-rule-what-happens-to-ids-you-didnt-list).
* **Out-of-range entries stop HBlink3 at startup.** A subscriber/TGID entry above
16776415, or a peer entry above 4294967295, triggers
`ACL CREATION ERROR, VALUE OUT OF RANGE` and the program exits. Check your
numbers if HBlink3 won't start after an ACL edit.
* **Write ranges low-to-high** (`1000-2000`, not `2000-1000`). A reversed range
will silently never match anything.
* **`REG_ACL` ignores `USE_ACL`.** You cannot "disable" registration filtering by
setting `USE_ACL: False`; set `REG_ACL: PERMIT:ALL` if you want it open.
* **GLOBAL and SYSTEM ACLs are ANDed.** If a call is mysteriously dropped, check
the GLOBAL stanza too — a global ACL can veto traffic a permissive system ACL
would otherwise allow.
* **The keys must exist even when unused.** Leave unused ACLs at `PERMIT:ALL`
rather than deleting them.