Files
HBlink4/config/config_sample.json
cortandClaude Opus 5 35fd2c2492 Require authentication before a radio ID changes address
A repeater's registration was displaced by an unauthenticated RPTL: any
packet carrying a known radio ID, from any source address, tore down the
live registration before anything was proven. Radio IDs are public, so
this was enough to knock any repeater off the network with an 8-byte
packet, repeatable at will.

A login claiming an ID that is already registered from a different
sockaddr is now held in _pending_logins and challenged. The incumbent is
removed only after the claimant proves the passphrase; on any failure the
claimant is discarded and the registration is untouched. Recovery for a
genuine NAT remap is unchanged in speed — login, auth, config, one round
trip later.

Liveness is now gated on the address matching too. _check_repeater_timeouts
decides purely on last_ping, which was refreshed by any packet bearing a
known ID before validation, so a stale or spoofed source could keep a dead
entry from ever aging out.

Report reconnects instead of diagnosing them. When a radio ID logs in
again, log how long the previous session lasted as D:HH:MM:SS. Hours means
a NAT lease expired or the device was power cycled; seconds, repeating,
means it cannot stay online — and two addresses alternating is a duplicate
radio ID. The operator reads that in a glance; no thresholds are guessed
at in code. reconnect_report_threshold (default 3600, 0 disables) filters
out routine long sessions. Only reported once a session actually existed,
since connect_time is meaningless before the connected state.

Fix dashboard uptime resetting for repeaters that never dropped. The
dashboard stamped its own event-arrival time as connected_at, but
repeater_connected is also emitted on missed pings, on recovery, and on
state replay when the dashboard reconnects. The server's connect_time now
rides in the payload, with a fallback for older servers.

Logging: ping and pong lines now both carry radio ID and sockaddr, as do
all inbound dispatch lines, so either field can be grepped for a whole
exchange. Fixes an f-string whose conditional sat inside the string
literal, raising AttributeError and surfacing as a generic datagram error
for exactly the wrong-address pings this change is about.

Adds tests/test_id_takeover.py: 31 tests over the auth gate, the login
races, threshold filtering and connect_time. Verified by mutation — each
reverted behavior fails a specific test.

Also documents three unreachable/misleading branches in the dispatch chain
found along the way, in code and in docs/TODO.md. Behavior unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 09:32:49 -05:00

51 lines
3.2 KiB
JSON

{
"_comment": "Minimal working HBlink4 configuration. Copy to config/config.json and change the passphrase under repeater_configurations.default -- that is the only edit needed to accept your first repeater. Everything omitted here has a built-in default. For blacklists, per-repeater patterns, links to other servers and OpenBridge trunks, see config/config_advanced_sample.json and docs/configuration.md.",
"global": {
"_comment": "port_ipv4 is the port repeaters connect to (62031 is the HomeBrew standard -- open it in your firewall, UDP). bind_ipv4 is the local address to listen on; 0.0.0.0 means every interface. Shipped IPv4-only, which is what most repeaters use. IPv6 and dual-stack are fully supported: set disable_ipv6 to false and IPv6 comes up on port_ipv6 alongside IPv4, already set to a separate port here because each address family gets its own socket. See docs/configuration.md for the other layouts, including serving both families on one port.",
"bind_ipv4": "0.0.0.0",
"port_ipv4": 62031,
"bind_ipv6": "::",
"port_ipv6": 62032,
"disable_ipv6": true,
"max_missed": 3,
"timeout_duration": 30,
"_comment_reconnect_report_threshold": "When a repeater logs in again, the log shows how long its previous session lasted. Sessions that ran longer than this many seconds are routine (NAT lease expiring, power cycle) and are not reported; shorter ones are, because a repeater reconnecting every few seconds cannot stay online. Set to 0 to report every reconnect. Connections themselves are always logged regardless of this setting.",
"reconnect_report_threshold": 3600,
"logging": {
"file": "logs/hblink.log",
"console_level": "INFO",
"file_level": "DEBUG",
"retention_days": 30
},
"stats_interval": 60,
"report_stats": true,
"stream_timeout": 2.0,
"stream_hang_time": 10.0,
"user_cache": {
"timeout": 600
}
},
"dashboard": {
"_comment": "OUTBOUND connection to the dashboard: HBlink4 dials the dashboard and pushes events to it. These settings must match the 'event_receiver' section of dashboard/config.json -- if they disagree the dashboard just shows HBlink4 as disconnected. The defaults here already match the shipped dashboard sample, so leave them alone unless you move the dashboard to another host.",
"enabled": true,
"transport": "unix",
"unix_socket": "/tmp/hblink4.sock",
"host_ipv4": "127.0.0.1",
"host_ipv6": "::1",
"port": 8765,
"disable_ipv6": false,
"buffer_size": 65536
},
"repeater_configurations": {
"_comment": "'default' applies to every repeater that connects. CHANGE THE PASSPHRASE -- it is the shared secret each repeater must be configured with. slot1_talkgroups/slot2_talkgroups are the talkgroups allowed on each timeslot; list the ones your network uses. To give particular repeaters different rules, add a 'patterns' list -- see config/config_advanced_sample.json.",
"default": {
"passphrase": "CHANGE-ME",
"slot1_talkgroups": [8],
"slot2_talkgroups": [8]
}
}
}