Table of Contents
Packet White Paper: 245
AI extraction from PDF.
Status: Informational
Date: May 2024
Author: Paula Dowie
RHP Version 2 - AX25 Functionality
Abstract
Remote Host Protocol (RHP) is one of the Application Programming Interfaces for the “XRouter” Amateur Packet Radio networking software. RHP allows applications, sited either locally to XRouter, or remotely from it, to use XRouter as a multi-protocol “packet engine”.
This memo, aimed at application developers, is a brief guide, which describes only the AX25-layer functionality of RHP version 2. Some information has been omitted for clarity. For a full description of RHP version 2 see reference [2] “Remote Host Protocol”, PWP 222.
Status of This Memo
This memo provides information for the Packet Radio community. This memo does not specify a standard of any kind. Discussions and suggestions are welcomed. Distribution of this memo is unlimited.
Copyright Notice
Copyright © 2024 Paula Dowie. All rights reserved.
Table of Contents
- Introduction
- 1.1. RHP Sockets
- Overview of RHP Message Types
- RHP Message Types In Detail
- 3.1. The AUTH Message
- 3.2. The AUTHREPLY Message
- 3.3. The OPEN Message
- 3.4. The OPENREPLY Message
- 3.5. The ACCEPT Message
- 3.6. The STATUS Message
- 3.7. The STATUSREPLY Message
- 3.8. The SEND Message
- 3.9. The SENDREPLY Message
- 3.10. The RECV Message
- 3.11. The CLOSE Message
- 3.12. The CLOSEREPLY Message
- List of Error Codes
- Typical Session Flow
- 5.1. Outgoing Connection
- 5.2. Incoming Connection
- Websocket Endpoint
- Security Considerations
- References
1. Introduction
RHP is the acronym for REMOTE HOST PROTOCOL, so called because it allows a “host” application to be located remotely from XRouter, effectively using XRouter as a multi-protocol “packet engine”.
The protocol allows applications, sited either locally to XRouter, or remotely from it, to access the XRouter protocol stack at many ISO layers, including layer 7, i.e. XRouter's command line interface. However this document concerns only AX25 level 2.
RHP version 1, described in [1] uses an extensible binary packet format. Although efficient, it has been superseded by version 2, which passes JSON messages instead. Version 2 is more verbose, but more suited to modern paradigms.
This document outlines the JSON message format, normally used over a simple TCP connection to XRouter's port 9000. This port number is specified using the RHPPORT directive in XROUTER.CFG. The protocol may also be used via a WebSockets connection, using the same port number.
RHP2 is a client-server protocol which uses JSON “messages” in a persistent TCP stream, to manage packet connections and transfer data in both directions.
Within “normal” RHP2, JSON messages are “framed” by a simple two-byte “frame length”, sent high byte first. e.g. the two bytes 0x01 0x20 indicate that the message which follows them is 288 bytes long.
The framing for RHP in WebSockets is similar, albeit with a more complex header.
1.1. RHP Sockets
At the heart of RHP is the concept of “sockets”, i.e. communication endpoints. These are functionally similar to Berkeley (BSD) sockets, but with some extra features.
The diagram below depicts a client using RHP to control a socket, and to exchange data with it. In turn the socket is interacting with a target system using the AX25 protocol. Data is passed between the client and target via the socket, whilst RHP control signals pass only between client and server.
.----------------.
.--------. | .--------. | .--------.
| client |<-------------->| socket |<------------>| Target |
'--------' RHP | '--------' | AX25 '--------'
'----------------'
Server (XRouter)
Sockets must be “opened” before use, and “closed” after use. When a socket is opened, a numeric “handle” is returned, which must be used for all subsequent operations via that socket.
When opening a socket, the client must specify a “protocol family”, in this case “AX25”, and a “mode”.
There are 4 main socket “modes” applicable to AX25, as follows:
| Mode | Description |
|---|---|
| STREAM | AX25 connected mode |
| DATAGRAM | AX25 unconnected mode (UI frames) |
| RAW | AX25 un-decoded raw packet data |
| TRACE | AX25 decoded headers plus data |
Stream mode would be used for normal AX25 connections, datagram mode for APRS, trace mode for monitoring packet activity, and raw mode for doing your own packet tracing / injection.
Clients may open multiple sockets at once, provided they are not identical. For example, a TRACE socket may be opened to monitor packet activity, while at the same time a DGRAM socket may be opened to send and receive UI frames, while several STREAM sockets may be opened, either to make connections to other systems, or to listen for incoming connections from them.
The only limitation is that a client cannot open more than one TRACE or RAW socket on the same PORT, or more than one DGRAM socket with the same PORT and LOCAL address (callsign-ssid), or more than one STREAM socket with identical PORT, LOCAL and REMOTE addresses.
Some of these limitations also apply to multiple CLIENTS. For instance, only ONE client may open a STREAM listener on the same port using the same local callsign. This is because XRouter would have no way of knowing which client the incoming packets belonged to.
All sockets “owned” by a client are closed when the RHP client connection is terminated.
2. Overview of RHP Message Types
The most commonly used RHP message “types” are as follows:
| Message | Direction | Description |
|---|---|---|
| AUTH | Client to server | Sends credentials to authenticate the client. Only required if the client has IP address in a public range, and is not whitelisted in ACCESS.SYS. |
| AUTHREPLY | Server to client | Indicates success/failure of auth request. |
| OPEN | Client to server | Initiates an “active” connection to a specific AX25 station, or a “passive” listener to wait for incoming connections. |
| OPENREPLY | Server to client | Indicates success/failure of an OpenRequest, and returns a “handle” for further operations. |
| ACCEPT | Server to client | Conveys details of an incoming connection. Sent by “listener” sockets only. |
| STATUS | Bidirectional | Conveys flags such as “connected” and “busy”. |
| STATUSREPLY | Server to client | Indicates failure of a status request from client. |
| SEND | Client to server | Sends data from client to radio, encapsulated in AX25 numbered information frames (for STREAM sockets) or UI frames for DGRAM sockets. |
| SENDREPLY | Server to client | Acknowledges a SEND message. |
| RECV | Server to client | Sends data from radio to client. Each RECV message contains the payload from one AX25 packet. |
| CLOSE | Client to server | Requests closure of a socket or connection. |
| CLOSEREPLY | Server to client | Acknowledges closure of a socket or connection. |
NOTE: Additional BSD-style message types SOCKET, BIND, LISTEN, CONNECT, SENDTO etc may be used, but have been omitted for clarity.
3. RHP Message Types In Detail
All RHP2 messages MUST be in JSON format, beginning with the opening curly bracket { and ending with the closing curly bracket }.
White space (spaces, tabs, newlines, etc.) is allowed within the JSON messages, but is not mandatory. Message examples in this document may include white space for clarity.
All fields of an RHP message are mandatory unless otherwise stated. The order of fields within a message is unimportant.
All messages MUST have a type field.
The id field is optional. If present in a request, XRouter ALWAYS replies, returning the same ID in the reply. Using a different ID in each request allows replies to be matched with the corresponding requests, allowing asynchronous “pipelining” of requests.
If the id field is omitted, the only replies, other than OPENREPLY, will be those that contain a non-zero error code.
3.1. The AUTH Message
The AUTH request sends credentials to authenticate the client. It is only required if the client has an IP address in a public range, and is not whitelisted by an entry in ACCESS.SYS.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “auth” |
id | integer | (optional) |
user | string | {user's callsign} |
pass | string | {user's password} |
Example:
{"type": "auth", "user": "g9zzz", "pass": "petunias"}
3.2. The AUTHREPLY Message
This message is sent from server to client, in response to an AUTH message or any other request received in unauthorised state.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “authReply” |
id | integer | Same as request (if present) |
errCode | integer | 0 or 14 |
errText | string | “Ok” or “Unauthorised” |
If the usercall and password in an AUTH request match an entry in USERPASS.SYS, the value of errCode will be 0, and the value of errText will be “Ok”. Otherwise, the value of errCode will be 14, and the value of errText will be “Unauthorised”.
Examples:
{"type": "authReply", "id": 7, "errCode": 0, "errText": "Ok"}
{"type": "authReply", "errCode": 14, "errText": "Unauthorised"}
3.3. The OPEN Message
The OPEN request, from client to server, is used to open a socket.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “open” |
id | integer | Serial number of request (optional) |
pfam | string | “ax25” (protocol family) |
mode | string | “stream”, “dgram”, “trace” or “raw” |
port | string | port identifier (port number in XRouter) |
local | string | Local callsign |
remote | string | Remote callsign (active open only) |
flags | integer | Option flags |
Values for option flags:
| Value | Description |
|---|---|
| 0x00 | Passive open (listen) |
| 0x01 | Trace Incoming frames (modes RAW and TRACE) |
| 0x02 | Trace Outgoing frames (modes RAW and TRACE) |
| 0x04 | Trace Supervisory frames (mode TRACE only) |
| 0x80 | Active open (connect) |
Passive open with unspecified remote accepts any call.
Passive open with remote address accepts only that address.
Example — open an AX25 connection to GB7NXT from callsign G8PZT-5:
{
"type": "open",
"id": 22,
"pfam": "ax25",
"mode": "stream",
"port": 2,
"local": "g8pzt-5",
"remote": "gb7nxt",
"flags": 128
}
Example — open an AX25 TRACE socket on port 4:
{
"type": "open",
"id": 1,
"pfam": "ax25",
"mode": "trace",
"port": "4",
"flags": 7
}
3.4. The OPENREPLY Message
This message is sent from server to client in response to an OPEN request. It indicates success or failure of the request, and if the socket was successfully opened it returns a “socket handle” for further operations.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “openReply” |
id | integer | Same as request (if present) |
handle | integer | Socket handle |
errcode | integer | Error code (0 = no error) |
errtext | string | Error text in words, e.g. “Ok” |
Example:
{
"type": "openReply",
"id": 22,
"handle": 3,
"errcode": 0,
"errtext": "ok"
}
3.5. The ACCEPT Message
This message is sent from server to client, by “listener” sockets only. It conveys details of an incoming connection.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “accept” |
seqno | integer | Sequence number of this message |
handle | integer | Socket handle of the listener |
child | integer | Socket handle of the new connection |
remote | string | Remote (caller's) callsign |
local | string | Local (listener) callsign |
port | integer | XRouter port number of new connection |
Example:
{
"type": "accept",
"seqno": 347,
"handle": 3,
"child": 7,
"remote": "G4FPV-5",
"local": "g8pzt-1",
"port": 4
}
3.6. The STATUS Message
This message can be used in either direction. It can be sent asynchronously from server to client, to convey flags such as “connected” and “busy”. It can also be sent from client to server to request a status message from the server. In this second case, the server only sends a STATUSREPLY message if the request fails.
Fields (server to client):
| Name | Type | Value |
|---|---|---|
type | string | “status” |
seqno | integer | Sequence number of this message |
handle | integer | Socket handle |
flags | integer | socket flags (server to client only) |
Socket flags:
| Name | Value | Description |
|---|---|---|
| CONOK | 1 | OK to accept (listeners only) |
| CONNECTED | 2 | Downlink is connected |
| BUSY | 4 | Not clear to send |
Example (server to client):
{
"type": "status",
"seqno": 348,
"handle": 3,
"flags": 2
}
Fields (client to server):
| Name | Type | Value |
|---|---|---|
type | string | “status” |
id | integer | Serial number of request (optional) |
handle | integer | Socket handle |
Example (client to server):
{
"type": "status",
"id": 23,
"handle": 3
}
3.7. The STATUSREPLY Message
This message is only sent from server to client, only in reply to a STATUS request, and only if the request fails.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “statusReply” |
id | integer | Same as request (if present) |
handle | integer | Socket handle |
errcode | integer | Error code |
errtext | string | Error text |
Example:
{
"type": "statusReply",
"id": 23,
"handle": 3,
"errcode": 12,
"errtext": "Invalid handle"
}
3.8. The SEND Message
The SEND message sends data from client to server, for onward transmission to another system.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “send” |
id | integer | Serial number of request (optional) |
handle | integer | Socket handle |
data | string | Data to be sent |
Additional fields for datagram mode only:
| Name | Type | Value |
|---|---|---|
port | string | Destination port |
local | string | Local address |
remote | string | Remote address |
Reserved and control characters in the data field MUST be JSON-escaped. The total size of the message MUST NOT exceed 65535 bytes.
Example:
{
"type": "send",
"id": 23,
"handle": 3,
"data": "Hello Fred, are you there?"
}
3.9. The SENDREPLY Message
The SENDREPLY message is sent from server to client in response to a SEND message, to convey the result of the operation.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “sendReply” |
id | integer | Matches ID in SEND request (optional) |
handle | integer | Socket handle |
errcode | integer | Error number |
errtext | string | Description of the error |
status | integer | Status flags (STREAM only) |
Status flags:
| Name | Value | Description |
|---|---|---|
| CONNECTED | 2 | Downlink is connected |
| BUSY | 4 | Not clear to send |
Example:
{
"type": "sendReply",
"id": 23,
"handle": 3,
"errcode": 0,
"errtext": "Ok",
"status": 2
}
3.10. The RECV Message
The RECV message is sent asynchronously from server to client, to convey data that has been received from a remote system. Each RECV message contains the payload from one AX25 packet. This message type is also used to convey TRACE data if the socket mode is TRACE.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “recv” |
seqno | integer | Sequence number of this message |
handle | integer | Socket handle |
port | string | Port it was received on (datagram only) |
action | string | “sent” or “rcvd” (RAW & TRACE only) |
data | string | Data received from remote system |
RAW and TRACE sockets can monitor both sent and received traffic, hence the action member.
Example RECV for a STREAM socket:
{
"type": "recv",
"seqno": 349,
"handle": 3,
"data": "Yes I'm here, what's up?"
}
Example trace representing [4] T: G8PZT-1>G8PZT: <RR R F R1>:
{
"type": "recv",
"seqno": 349,
"handle": 1,
"action": "sent",
"port": "4",
"srce": "G8PZT-1",
"dest": "G8PZT",
"ctrl": 33,
"frametype": "RR",
"rseq": 1,
"cr": "R",
"pf": "F"
}
3.11. The CLOSE Message
The CLOSE message is sent from client to server, to request closure of a socket or connection. It can also be sent from server to client to inform the client that a connection has been closed by the remote link partner.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “close” |
id | integer | (client to server only) (optional) |
seqno | integer | Sequence number (server to client only) |
handle | integer | Socket handle |
Example (client to server):
{
"id": 3,
"type": "close",
"handle": 3
}
Example (server to client):
{
"type": "close",
"seqno": 350,
"handle": 3
}
3.12. The CLOSEREPLY Message
The CLOSEREPLY message is sent from server to client, in response to a CLOSE message, to acknowledge closure of a socket or connection.
Fields:
| Name | Type | Value |
|---|---|---|
type | string | “closeReply” |
id | integer | Matches the one in the CLOSE request |
handle | integer | Socket handle |
errcode | integer | Error number |
errtext | string | Description of the error |
Example of a Successful Close:
{
"id": 3,
"type": "closeReply",
"handle": 4,
"errcode": 0,
"errtext": "Ok"
}
Example of a Failed Close:
{
"id": 3,
"type": "closeReply",
"handle": 0,
"errcode": 12,
"errtext": "Invalid handle"
}
4. List of Error Codes
You are advised to parse the error CODE, not the error text, as the latter may change in future versions.
| Code | Text | Notes |
|---|---|---|
| 0 | “Ok” | No error |
| 1 | “Unspecified” | Catch-all error, might be transient |
| 2 | “Bad or missing type” | Unrecognised frame type, don't retry |
| 3 | “Invalid handle” | Invalid socket handle, don't retry |
| 4 | “No memory” | No memory, try later |
| 5 | “Bad or missing mode” | Invalid “mode” in SOCKET or OPEN |
| 6 | “Invalid local address” | (in OPEN, SOCKET, or BIND) |
| 7 | “Invalid remote address” | (in OPEN or CONNECT) |
| 8 | “Bad or missing family” | Unsupported address family |
| 9 | “Duplicate socket” | Socket / connection already exists |
| 10 | “No such port” | Invalid port number in OPEN or BIND |
| 11 | “Invalid protocol” | (in OPEN or SOCKET) |
| 12 | “Bad parameter” | Bad or missing parameter |
| 13 | “No buffers” | Output queue full (retry later) |
| 14 | “Unauthorised” | Request requires AUTHorisation |
| 15 | “No Route” | No route to target (L4 / TCP open) |
| 16 | “Operation not supported” | e.g. SEND on a TRACE socket |
5. Typical Session Flow
This section outlines the general flow of different types of session. It assumes that the client has already established connection with the server, either via direct TCP connection, or via Websockets, and has authenticated if necessary.
5.1. Outgoing Connection
An outgoing connection would proceed as follows:
- Client sends OPEN message, specifying the radio port, plus source and destination calls (including digipeaters if required).
- Server immediately replies with OPENREPLY, containing a socket “handle” for all subsequent operations on the socket.
- If the connection succeeds, the server asynchronously sends a STATUS message indicating “connected”. If the connection fails, the status message contains “disconnected” instead.
- The client sends data to the connection using SEND messages, the payload of which is JSON-escaped so it can handle full binary.
- SEND messages are acknowledged using SENDREPLY.
- If the client sends too much data for the AX25 link to handle, the server sends a STATUS message with the BUSY flag set. When clear to send again, the server sends a STATUS message with the BUSY flag unset.
- Data received from the downlink is sent to the client in RECV messages, with the payload JSON-escaped.
- If the downlink initiates a disconnect, the server sends a STATUS message to the client with the CONNECTED flag set to “false”. The client must now issue a CLOSE to dispose of the socket.
- Alternatively, if the client wishes to terminate the connection it issues a CLOSE request, and the server responds with a CLOSEREPLY.
5.2. Incoming Connection
An incoming connection proceeds as follows:
- The client sends an OPEN message, specifying the XRouter PORT number and the local callsign. This creates a “listener” socket.
- The server responds with an OPENREPLY message containing the socket handle (or an error code).
- If someone connects to the callsign associated with the socket, the server immediately sends an ACCEPT message to the client, containing the socket handle and the callsign of the connectee.
- The remainder of the connection proceeds as in step 4 of the Outgoing Connection flow above.
6. Websocket Endpoint
The endpoint for RHP over WebSockets is ws://{host}:{port}/rhp, e.g. ws://localhost:9000/rhp (this may change in future, as it would make more sense to use XRouter's web server port for everything).
7. Security Considerations
By default, RHP only allows usage by clients with “localhost” and “LAN” IP addresses. In this context LAN addresses are those in the ranges 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16.
Clients with non-LAN IP addresses MUST authorise, by sending a valid AUTH packet before they can use any other RHP commands. The AUTH packet MUST contain a username/password pair which matches one stored in USERPASS.SYS.
Non-LAN clients MAY be granted access without AUTH by including the client's IP address in ACCESS.SYS. Beware of granting such access to ranges of IP addresses, unless you have control of that range.
At the time of writing, authorisation uses plain text usernames and passwords, simply because RHP was never intended for use on the public internet. This will be rectified in future versions.
8. References
- Dowie P., “Remote Host Protocol”, PWP 144, December 2004.
- Dowie P., “Remote Host Protocol Version 2”, PWP 222, June 2023.
PWP245 RHP version 2 AX25 Functionality — May 2024
