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 curl extension
  • 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

  1. Open your BinktermPHP admin panel and go to Packet BBS → Nodes.
  2. Add a new node:
    • Node ID: your bridge callsign (e.g. N0BBS-1)
    • Interface Type: AX.25 TNC
  3. Generate an API key for the node and paste it into config.json as api_key.

Configuration

Copy config.example.json to config.json and fill in the values:

KeyDescription
mycallYour BBS station callsign (e.g. N0BBS-1). Used as the AX.25 source when transmitting.
bridge_node_idNode ID registered in the BBS admin panel. Usually the same as mycall.
api_keyBearer token generated by the BBS for this node.
bbs_urlBBS base URL without trailing slash (e.g. http://192.168.1.10:1244).
tnc.typetcp for software TNCs; serial for hardware TNCs.
tnc.hostTCP hostname for software TNC (default: localhost).
tnc.portTCP port for software TNC (Direwolf default: 8001).
tnc.deviceSerial device for hardware TNC (e.g. /dev/ttyUSB0).
tnc.baudSerial baud rate (default: 1200).
poll_interval_secHow often to poll the outbound queue for active callsigns (default: 15).
active_ttl_secSeconds of inactivity before a callsign is dropped from the poll list (default: 900).
max_frame_infoMaximum AX.25 information field size in bytes. Long responses are split across multiple frames (default: 236).
log_levelDEBUG, INFO, WARNING, or ERROR (default: INFO).
log_filePath to log file. Set to "" to disable file logging.
log_echoSet to true to also print log output to stdout.
beacon.enabledSend periodic beacon frames (default: true).
beacon.interval_secSeconds between beacons (default: 1800 = 30 minutes). A beacon is also sent immediately on TNC connect.
beacon.destAX.25 destination callsign for beacon frames (default: "BEACON").
beacon.textText 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:

CommandDescription
LOGIN <user> <code>Authenticate (TOTP)
HELPList available commands
NList netmail
NR <id>Read netmail
E <tag>Open echo area
EM <id>Read echomail
MNext page
QQuit / 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:

  1. A monitoring station observes a frame containing LOGIN alice 123456 (or observes that a callsign has an active session from the WHO response).
  2. The monitoring station transmits a frame with that source callsign addressed to the bridge.
  3. 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_minutes short (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.