BinktermPHP AX.25 KISS Bridge
May 18, 2026 · View on GitHub
This adapter is experimental. Features may be incomplete and behaviour may change between releases without notice. Please report issues to GitHub.
Caution
All traffic on AX.25/KISS links is transmitted in plain text — every login command, TOTP code, and BBS response is visible to any station on the same frequency. AX.25 source callsigns can be trivially spoofed, which means an active session can be hijacked by any station on-frequency. Read the Security section before deploying on a shared or public frequency.
A standalone bridge daemon that connects a KISS TNC to a BinktermPHP BBS, giving amateur packet radio stations access to the PacketBBS over AX.25.
[Radio Hardware]
|
| serial / USB
v
[KISS TNC] (hardware TNC, or software TNC such as Direwolf)
|
| KISS over TCP / serial
v
[ax25_kiss_bridge] (this repo)
|
| HTTP API (Bearer token)
v
[BinktermPHP BBS]
Each AX.25 source callsign is treated as a separate PacketBBS node. Operators send plain-text BBS commands as AX.25 UI frames addressed to the BBS callsign. The bridge forwards commands to the BBS and transmits responses back over air.
Requirements
- PHP 8.2 or later with the
curlextension - Composer
- A KISS-capable TNC: any hardware TNC in KISS mode, or a software TNC such as Direwolf
- A running BinktermPHP instance (1.11.0 or later)
Installation
git clone https://github.com/awehttam/binktermphp-ax25kiss.git
cd binktermphp-ax25kiss
composer install
cp config.example.json config.json
Edit config.json with your callsign, BBS URL, and API key (see Configuration below).
BBS Setup
- Open your BinktermPHP admin panel and go to Packet BBS → Nodes.
- Add a new node:
- Node ID: your bridge callsign (e.g.
N0BBS-1) - Interface Type:
AX.25 TNC
- Node ID: your bridge callsign (e.g.
- Generate an API key for the node and paste it into
config.jsonasapi_key.
Configuration
Copy config.example.json to config.json and fill in the values:
| Key | Description |
|---|---|
mycall | Your BBS station callsign (e.g. N0BBS-1). Used as the AX.25 source when transmitting. |
bridge_node_id | Node ID registered in the BBS admin panel. Usually the same as mycall. |
api_key | Bearer token generated by the BBS for this node. |
bbs_url | BBS base URL without trailing slash (e.g. http://192.168.1.10:1244). |
tnc.type | tcp for software TNCs; serial for hardware TNCs. |
tnc.host | TCP hostname for software TNC (default: localhost). |
tnc.port | TCP port for software TNC (Direwolf default: 8001). |
tnc.device | Serial device for hardware TNC (e.g. /dev/ttyUSB0). |
tnc.baud | Serial baud rate (default: 1200). |
poll_interval_sec | How often to poll the outbound queue for active callsigns (default: 15). |
active_ttl_sec | Seconds of inactivity before a callsign is dropped from the poll list (default: 900). |
max_frame_info | Maximum AX.25 information field size in bytes. Long responses are split across multiple frames (default: 236). |
log_level | DEBUG, INFO, WARNING, or ERROR (default: INFO). |
log_file | Path to log file. Set to "" to disable file logging. |
log_echo | Set to true to also print log output to stdout. |
beacon.enabled | Send periodic beacon frames (default: true). |
beacon.interval_sec | Seconds between beacons (default: 1800 = 30 minutes). A beacon is also sent immediately on TNC connect. |
beacon.dest | AX.25 destination callsign for beacon frames (default: "BEACON"). |
beacon.text | Text transmitted in each beacon frame. |
Running
Foreground (development / testing)
php ax25_kiss_bridge.php --config=config.json --echo-log
Background daemon
php ax25_kiss_bridge.php --config=config.json --daemon --pid-file=/var/run/ax25_kiss_bridge.pid
Stop the daemon:
kill $(cat /var/run/ax25_kiss_bridge.pid)
systemd service
[Unit]
Description=BinktermPHP AX.25 KISS Bridge
After=network.target
[Service]
Type=simple
User=bbs
WorkingDirectory=/opt/binktermphp-ax25kiss
ExecStart=/usr/bin/php ax25_kiss_bridge.php --config=config.json --echo-log
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Direwolf (software TNC) quick-start
In your direwolf.conf:
MYCALL N0BBS-1
KISSPORT 8001
Start Direwolf:
direwolf -c direwolf.conf
The bridge will connect to localhost:8001 by default.
BBS commands
Operators send commands as plain text in AX.25 UI frames addressed to the BBS callsign. The full command set is documented in the BinktermPHP User Guide under User Settings.
Common commands:
| Command | Description |
|---|---|
LOGIN <user> <code> | Authenticate (TOTP) |
HELP | List available commands |
N | List netmail |
NR <id> | Read netmail |
E <tag> | Open echo area |
EM <id> | Read echomail |
M | Next page |
Q | Quit / end session |
Security
Caution
On unencrypted radio links such as AX.25/KISS, all traffic is transmitted in plain text. Any station on the same frequency can read every packet, including login commands and BBS responses.
Session hijacking after login
PacketBBS sessions are keyed by the sender's AX.25 source callsign. AX.25 provides no cryptographic proof that the source callsign in a frame matches the station that actually transmitted it. Any operator with a radio and appropriate software can set their source callsign to any value and transmit on the same frequency.
This creates a session-hijacking exposure:
- A monitoring station observes a frame containing
LOGIN alice 123456(or observes that a callsign has an active session from theWHOresponse). - The monitoring station transmits a frame with that source callsign addressed to the bridge.
- The bridge receives the spoofed frame, looks up the active session, and executes the command as the logged-in user.
The TOTP code itself expires after 30 seconds, so replaying a captured login is a narrow window. However, commands sent after login do not require a fresh code — the session persists for up to session_timeout_minutes. Any spoofed frame with the correct source callsign can issue BBS commands for the duration of that session.
Mitigations
- Keep
session_timeout_minutesshort (the BBS default of 15 minutes is a reasonable balance; consider 5–10 minutes on high-risk links). - Avoid performing sensitive operations by radio on shared or high-traffic frequencies where monitoring is likely.
- Treat the PacketBBS session as a shared-secret login, not a secure channel. The TOTP authenticator verifies the user at login time; it does not protect individual commands issued within the same session.
This is an inherent limitation of the AX.25 protocol and is not specific to BinktermPHP. Operators should understand their local RF environment and assess the likelihood of active spoofing before using authenticated PacketBBS features on the air.
Licence
BSD 3-Clause — see LICENSE.
This project is part of the BinktermPHP ecosystem.