Skip to content

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:

#define DISCSTR    "!!DISCONNECT!!"
#define NEWKEYSTR  "!NEWKEY!"
#define NEWKEY1STR "!NEWKEY1!"

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);
}

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_helper is called (app_rpt.c:4621-4622), setting lastrealrx = 1. After audio stops, the link un-keys once rxlingertimer (RX_LINGER_TIME = 50 ms, app_rpt.h:206) elapses — see the un-key check at app_rpt.c:3369-3371.
  • AST_CONTROL_RADIO_KEY control frames are ignored. The inbound control-frame handler explicitly gates on the mode not being RADIO_KEY_NOT_ALLOWED before honoring a RADIO_KEY (app_rpt.c:4750 and :6407):

    // app_rpt.c:4750
    if ((f->subclass.integer == AST_CONTROL_RADIO_KEY) &&
        (l->link_newkey != RADIO_KEY_NOT_ALLOWED)) {
        rxkey_helper(myrpt, l);
    }
    

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 up
  • AST_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 on l->name[0] <= '9', app_rpt.c:7161), and
  • newkeytimer is zeroed (app_rpt.c:7096), with the link already in RADIO_KEY_NOT_ALLOWED — so voice-keys-the-link applies from the start and no TEXT handshake is required. This matches what astar-lib observed: 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).

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!!, and L <list> are app_rpt application semantics, not IAX2. RFC 5456 has no notion of "keying," link lists, or these strings.
  • RADIO_KEY / RADIO_UNKEY are Asterisk control frames, carried as IAX2 CONTROL frames (RFC 5456 §8.2 control subclass), but the specific subclasses 12/13 and their meaning are Asterisk/app_rpt extensions, 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::SendText emits a reliable full TEXT frame via build_text; the body is delivered to the peer exactly as passed.
  • PTT keying: AppCommand::SendPtt(true/false) emits build_radio_key or build_radio_unkey respectively — the outbound RADIO_KEY/RADIO_UNKEY control frames used in RADIO_KEY_ALLOWED / RADIO_KEY_ALLOWED_REDUNDANT link modes.
  • Inbound !NEWKEY1!: echoed back automatically; the caller need not handle it.
  • Inbound !!DISCONNECT!!: triggers a peer-initiated hangup (Disconnected event, HangupOrigin::Peer).
  • Inbound RADIO_KEY / RADIO_UNKEY: surfaced to the application as AppEvent::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.


  1. The frame is constructed with .frametype = AST_FRAME_TEXT and .datalen = strlen(txt) + 1rpt_channel.c:439-444 (send_usb_txt), :466-469 (send_link_pl), and app_rpt.c:1892-1897 / :3410-3415. AST_FRAME_TEXT is frametype 7 in Asterisk's frame.h (enum ast_frame_type). The actual write happens via ast_sendtext() (rpt_channel.c:490, :500), which the IAX2 channel driver chan_iax2 serializes as an IAX2 full TEXT frame per RFC 5456 §8.3.