AllStar app_rpt IAX2 TEXT commands¶
Once an IAX2 call between two AllStarLink nodes is established, app_rpt
runs its own link-layer protocol inside the call, carried by IAX2 TEXT
frames. This is a layer on top of standard IAX2 — RFC 5456 defines the
TEXT frame transport (full frame, frametype = AST_FRAME_TEXT), but the
command vocabulary below (!NEWKEY1!, !NEWKEY!, !!DISCONNECT!!,
L <linklist>) is entirely an app_rpt invention with no RFC basis.
All of these are sent as Asterisk AST_FRAME_TEXT frames. In IAX2 a TEXT
frame is a full frame with frametype = 7 (AST_FRAME_TEXT); the command
string is the frame payload (NUL-terminated:
datalen = strlen(txt) + 1).1
Command summary¶
| TEXT payload | Who sends it | When | IAX2 frame | Receiver behavior |
|---|---|---|---|---|
!NEWKEY1! |
The answering node (and the caller echoes it back) | Right after the call is answered, on a node-to-node link only | Full AST_FRAME_TEXT (frametype 7) |
Sets link_newkey = RADIO_KEY_NOT_ALLOWED and clears newkeytimer. From now on, VOICE frames key the link; AST_CONTROL_RADIO_KEY control frames are ignored. |
!NEWKEY! |
A node selecting the legacy redundant keying mode | In response to a received !NEWKEY!, or to opt into redundant keying |
Full AST_FRAME_TEXT (frametype 7) |
Sets link_newkey = RADIO_KEY_ALLOWED_REDUNDANT, clears newkeytimer, and replies with its own !NEWKEY!. Keying is then via periodic RADIO_KEY/RADIO_UNKEY control frames. |
!!DISCONNECT!! |
Either node tearing the link down gracefully | On disconnect | Full AST_FRAME_TEXT (frametype 7) |
Marks the link disced (RPT_LINK_DISCONNECT) and forces retry exhaustion (retries = max_retries + 1) so the link is dropped. |
L <linklist> |
Every node, periodically, to each connected link | Every linkpost_time seconds (default 30 s, range 10–40) |
Full AST_FRAME_TEXT (frametype 7) |
Stores the space-stripped remainder as that link's linklist (the peer's view of the connected-node topology). |
The string constants are defined in apps/app_rpt/app_rpt.h:492-494:
Receiver dispatch: handle_link_data¶
Inbound TEXT frames on a link are dispatched in handle_link_data
(app_rpt.c:1885), reached from the link-channel read loop at
app_rpt.c:4708-4713. The relevant arms:
// app_rpt.c:1901 — graceful disconnect
if (!strcmp(str, DISCSTR)) {
mylink->disced = RPT_LINK_DISCONNECT;
mylink->retries = mylink->max_retries + 1; // force the link down
return;
}
// app_rpt.c:1906 — legacy redundant keying
if (!strcmp(str, NEWKEYSTR)) {
if ((!mylink->link_newkey) || mylink->newkeytimer) {
mylink->newkeytimer = 0;
mylink->link_newkey = RADIO_KEY_ALLOWED_REDUNDANT;
send_newkey_redundant(mylink->chan); // reply with !NEWKEY!
}
return;
}
// app_rpt.c:1914 — modern node-to-node keying
if (!strcmp(str, NEWKEY1STR)) {
mylink->newkeytimer = 0;
mylink->link_newkey = RADIO_KEY_NOT_ALLOWED;
return;
}
// app_rpt.c:1930 — periodic link-list update
if (*str == 'L') {
if (strlen(str) < 3) return;
ast_str_set(&mylink->linklist, 0, "%s", str + 2); // drop the "L "
return;
}
There is also an !IAXKEY! compatibility arm (app_rpt.c:1922) that is
silently ignored — a no-longer-used message once generated by the iaxRpt
application.
The TEXT senders¶
| Function | File:line | Emits |
|---|---|---|
send_newkey |
rpt_channel.c:484-494 |
ast_sendtext(chan, NEWKEY1STR) → !NEWKEY1! |
send_newkey_redundant |
rpt_channel.c:496-504 |
ast_sendtext(chan, NEWKEYSTR) → !NEWKEY! |
L <list> sender |
app_rpt.c:3399-3420 |
a TEXT frame "L " + __mklinklist(...) via rpt_qwrite |
// rpt_channel.c:484
void send_newkey(struct ast_channel *chan)
{
ast_channel_lock(chan);
if (ast_sendtext(chan, NEWKEY1STR)) // "!NEWKEY1!"
ast_log(LOG_WARNING, "Failed to send text %s ...", NEWKEY1STR);
ast_channel_unlock(chan);
}
The three keying modes (link_newkey)¶
enum defined at apps/app_rpt/app_rpt.h:498-502:
enum {
RADIO_KEY_ALLOWED, // = 0 AST_CONTROL_RADIO_KEY allowed
RADIO_KEY_ALLOWED_REDUNDANT, // = 1 "!NEWKEY!" — RADIO_KEY allowed, redundant
RADIO_KEY_NOT_ALLOWED // = 2 "!NEWKEY1!" — RADIO_KEY control frames ignored
};
These control how the link decides it is "keyed" (PTT down / receiving audio) — the core of half-duplex radio bridging.
RADIO_KEY_NOT_ALLOWED (2) — the modern node-to-node mode¶
This is what a current node-to-node IAX2 link uses. It is selected by the
!NEWKEY1! handshake, or set directly without any handshake for
non-node peers.
- VOICE frames themselves key the link. When a voice frame arrives and
the link is not already keyed,
rxkey_helperis called (app_rpt.c:4621-4622), settinglastrealrx = 1. After audio stops, the link un-keys oncerxlingertimer(RX_LINGER_TIME = 50ms,app_rpt.h:206) elapses — see the un-key check atapp_rpt.c:3369-3371. -
AST_CONTROL_RADIO_KEYcontrol frames are ignored. The inbound control-frame handler explicitly gates on the mode not beingRADIO_KEY_NOT_ALLOWEDbefore honoring aRADIO_KEY(app_rpt.c:4750and:6407):
Practical consequence for an interoperable client
In NOT_ALLOWED mode, sending an AST_CONTROL_RADIO_KEY control frame
does nothing on the receiving node — your audio is what keys the
link. To make an app_rpt node "hear" you transmit, you must actually
send voice frames (and a node won't relay/echo until it considers
the link keyed). This was wire-confirmed against the parrot node: with
no audio, nothing is echoed.
RADIO_KEY_ALLOWED (0) and RADIO_KEY_ALLOWED_REDUNDANT (1) — legacy¶
In these modes, keying is signalled explicitly with Asterisk control frames rather than inferred from voice:
AST_CONTROL_RADIO_KEY(control subclass 12) → key upAST_CONTROL_RADIO_UNKEY(control subclass 13) → key down
(The subclass values 12/13 are Asterisk core enum ast_control_frame_type
constants; app_rpt references them by name, e.g. app_rpt.c:3222, :3238,
:4569-4572.)
The redundant variant (mode 1, selected by !NEWKEY!) additionally
re-asserts the current key state on a timer: every
REDUNDANT_TX_TIME = 2000 ms (app_rpt.h:98) it re-sends RADIO_KEY or
RADIO_UNKEY to match lasttx (app_rpt.c:3422-3430), guarding against a
lost control frame:
// app_rpt.c:3422
if (l->link_newkey == RADIO_KEY_ALLOWED_REDUNDANT) {
if ((l->retxtimer += elap) >= REDUNDANT_TX_TIME) {
l->retxtimer = 0;
if (l->lasttx) ast_indicate(l->chan, AST_CONTROL_RADIO_KEY);
else ast_indicate(l->chan, AST_CONTROL_RADIO_UNKEY);
}
}
When the !NEWKEY1! handshake happens (and when it doesn't)¶
On call setup, the link starts in RADIO_KEY_ALLOWED and is immediately
downgraded to RADIO_KEY_NOT_ALLOWED for ordinary node links
(app_rpt.c:7090-7094):
// app_rpt.c:7089
l->rxlingertimer = RX_LINGER_TIME; // 50 ms
l->newkeytimer = NEWKEYTIME; // 2000 ms
l->link_newkey = RADIO_KEY_ALLOWED;
if ((phone_mode == RPT_PHONE_MODE_NONE) && (l->name[0] != '0') &&
!CHAN_TECH(chan, "echolink") && !CHAN_TECH(chan, "tlb")) {
l->link_newkey = RADIO_KEY_NOT_ALLOWED; // modern default
}
...
if (l->name[0] > '9') l->newkeytimer = 0; // non-numeric peer: skip handshake
The answering node sends !NEWKEY1! only when the peer name is a numeric
node (l->name[0] <= '9'), i.e. a genuine node-to-node link
(app_rpt.c:7161-7163), and again from answer_newkey_helper
(app_rpt.c:6574-6582). The caller echoes !NEWKEY1! back on receipt
(via handle_link_data, app_rpt.c:1914) and on its own keyup path
(app_rpt.c:4731).
Web Transceiver callers skip the handshake
A Web Transceiver / phone caller presents a non-numeric name (a portal
token or callsign, first char > '9'), so:
- the node does not send
!NEWKEY1!(gated onl->name[0] <= '9',app_rpt.c:7161), and newkeytimeris zeroed (app_rpt.c:7096), with the link already inRADIO_KEY_NOT_ALLOWED— so voice-keys-the-link applies from the start and no TEXT handshake is required. This matches whatastar-libobserved: the parrot never sent a!NEWKEY1!.
Handshake timeout (failure mode, not fatal)¶
newkeytimer starts at NEWKEYTIME = 2000 ms (app_rpt.h:88). If a
connected node-to-node link never receives the expected !NEWKEY1! and the
timer expires while still in RADIO_KEY_NOT_ALLOWED, app_rpt does not
hang up — it logs a warning and falls back to RADIO_KEY_ALLOWED
(app_rpt.c:3343-3356), un-keying if it had been stuck keyed. If the link
isn't connected yet, it simply re-arms the timer for another NEWKEYTIME
(app_rpt.c:3358-3363).
Periodic L <linklist> frames¶
Every linkpost_time seconds (default 30 s, configurable 10–40 via
rpt_config.c:869), each node sends each connected link a TEXT frame
"L " + <link list> describing its connected-node topology
(app_rpt.c:3399-3420). The receiver stores the part after "L " as that
link's linklist (app_rpt.c:1930-1938). These frames are how nodes learn
the wider mesh of who is connected to whom; they are informational and carry
no keying or auth semantics.
Relationship to RFC 5456¶
- Transport is standard IAX2. TEXT frames are full frames with
frametype = AST_FRAME_TEXT(7), reliably delivered and ACKed exactly as RFC 5456 §8.3 specifies. An interoperable client only needs to send and receive IAX2 TEXT frames; the payloads are opaque to the protocol. - The vocabulary is ASL3-specific.
!NEWKEY1!,!NEWKEY!,!!DISCONNECT!!, andL <list>areapp_rptapplication semantics, not IAX2. RFC 5456 has no notion of "keying," link lists, or these strings. RADIO_KEY/RADIO_UNKEYare Asterisk control frames, carried as IAX2CONTROLframes (RFC 5456 §8.2 control subclass), but the specific subclasses 12/13 and their meaning are Asterisk/app_rptextensions, not part of the RFC's enumerated control subclasses.
Implementation status in astar-lib¶
The following are implemented in handlers_outbound.rs:
- TEXT send:
AppCommand::SendTextemits a reliable full TEXT frame viabuild_text; the body is delivered to the peer exactly as passed. - PTT keying:
AppCommand::SendPtt(true/false)emitsbuild_radio_keyorbuild_radio_unkeyrespectively — the outboundRADIO_KEY/RADIO_UNKEYcontrol frames used inRADIO_KEY_ALLOWED/RADIO_KEY_ALLOWED_REDUNDANTlink modes. - Inbound
!NEWKEY1!: echoed back automatically; the caller need not handle it. - Inbound
!!DISCONNECT!!: triggers a peer-initiated hangup (Disconnectedevent,HangupOrigin::Peer). - Inbound
RADIO_KEY/RADIO_UNKEY: surfaced to the application asAppEvent::RemotePtt(true)/AppEvent::RemotePtt(false). - Other inbound TEXT: surfaced as
AppEvent::TextReceived; the application is responsible for interpreting command payloads (e.g.L <linklist>,!NEWKEY!, etc.).
See the Web Transceiver call flow page for the media rules specific to the WT guest path.
-
The frame is constructed with
.frametype = AST_FRAME_TEXTand.datalen = strlen(txt) + 1—rpt_channel.c:439-444(send_usb_txt),:466-469(send_link_pl), andapp_rpt.c:1892-1897/:3410-3415.AST_FRAME_TEXTis frametype7in Asterisk'sframe.h(enum ast_frame_type). The actual write happens viaast_sendtext()(rpt_channel.c:490,:500), which the IAX2 channel driverchan_iax2serializes as an IAX2 full TEXT frame per RFC 5456 §8.3. ↩