Recently I’ve been experimenting with getting computers to talk to each other purely using AX.25 over VHF radio. That is, establishing connections and passing messages back and forth without any assistance whatsoever from TCP/IP.
The BSD networking API in Linux is very convenient for this. I’ve been able to write C programs that listen for connections, create connections and exchange data using code that doesn’t look all that different from what you would write for TCP/IP.
This post is a small showcase of working programs that perform these basic AX.25 tasks in Linux. Although the AX.25 HOWTO is extremely helpful (and required reading if you want to do any of this), it doesn’t go to this level of detail. The AX.25 apps source code is also a useful source of information, albeit more difficult to read.
I have two stations that I refer to in the code. One is called
VK7NTK-1 and the other is
VK7NTK-2. Each is a computer running Debian Linux with its own VHF radio attached. I am using the Dire Wolf soundmodem along with
kissattach to create a network interface
ax0. On both stations this is configured as an AX.25 port simply called
To keep things simple I have hard-coded station names inside the code. You can obtain these from the system—have a look at the header file
A server using ax25d
ax25d is kind of cheating but it’s a legitimate way to do things. Like
inetd does for TCP/IP, it’s a daemon you run that will take care of listening for connections. When a connection request arrives it will look at its configuration and decide which program to run to handle that request.
ax25d runs a program it will hook up the AX.25 input and output streams to the STDIN and STDOUT file descriptors. Sending data over the radio becomes as simple as a
printf statement. Additional metadata such as the callsign of the calling station can be provided with command line parameters.
This means that you can write an AX.25 server without having to write any AX.25 code. Here is an example that will ask the user their name, reply, then hang up on them.
You’ve probably noticed that there’s a lot of
\r going on. Way back in the days of packet they decided that a single carriage return was a good way to indicate the end of a line. If you use the
axcall command in Debian to call someone, that’s what it will transmit when you press enter. I’m not sure if all the TNCs do this but it seems like a good idea to support it. If you’re writing all the clients and servers for your application there’s no reason why you couldn’t just use
\n and save yourself some headaches.
This is super-tedious with the C standard library since it’s hard-coded to use
\n as a line terminator for the purposes of line buffering and flushing. After some trial and error I landed on the settings used above with
setvbuf. This worked reliably for me over the radio. Of course, ending lines with
\r doesn’t work very well if you want to test your program from a normal terminal so you may want to code in an option to use either
Once I got past that sticking point it was pretty simple. I can use
printf to output text and
getdelim to read text. They’re nice, normal functions.
If you put this in a file called
ax25d_example.c you can compile it on Debian like this:
cc ax25d_example.c -o ax25d_example
Next you need to install the binary somewhere and add a rule to your
ax25d configuration so that it will be run. I put the following in
/etc/ax25d.conf on the system
VK7NTK-2. The part in square brackets refers to the callsign of the receiving station. If your AX.25 port is not called
radio or if you put the program in a different location you will need to change it. The
%S refers to the callsign of the connecting station, which is used in the message that is sent when they connect.
[VK7NTK-2 via radio] default * * * * * * * root /usr/local/bin/ax25d_example ax25d_example %S
Finally, root needs to run the “ax25d” command. (Or you need to configure your system so it runs as a service.) Then you can connect to it! In this case I’m running the command on
sudo axcall radio VK7NTK-2
A client using socket API
This example uses the socket API to establish a connection to another AX.25 node using no digipeaters. It simply connects, sends a message to the other node, then disconnects after a short delay.
It is assumed that this is running on
VK7NTK-1, which wants to connect to
There are three main preparatory steps before you can begin sending and receiving data.
First I must create the socket using the address family
AF_AX25. Note the use of
SOCK_SEQPACKET. Although an AX.25 connection has much in common with TCP, it is a sequence of messages, not an octet stream, so I use a different constant here.
Second I must prepare a
sockaddr to specify which interface I want to use to connect. This is important if you have more than one radio attached—Linux has no magical knowledge that a particular radio will reach a particular callsign. So I set my own callsign including SSID and call
Third I must connect to the remote node. I have to prepare another
sockaddr and pass it to
If all of this is successful then the connection has been established. I can then use
read to send and receive data over the socket, like anything else. I won’t write any more about this—at this point it’s exactly the same as any other UNIX network programming.
Note that you must link in the AX.25 library to compile this program:
cc -lax25 client.c -o client
A server using socket API
This example is a standalone C program that will listen for a connection request on the radio interface, exchange data, then close the connection. It then terminates.
Again this is hard-coded to run on
VK7NTK-2, because that is the callsign used when choosing the interface to which to bind.
Like the client there are three steps that must be performed to accept a connection. As before the first is to create an AX.25 SOCK_SEQPACKET socket fd, and the second is to bind to the desired local interface. This is the radio that will be listening for connections.
The third step is to call
accept. This will wait for an incoming connection. When it does, the address of the calling station will be filled out in the struct
their_addr and the connection is established.
The utility function
ax25_ntoa converts the “network” form of the caller’s callsign-SSID to a string that I can print, like
Then I can simply use
write to exchange data. It looks like this on the server:
It looks like this on the client (using
Note that I have a used a shortcut here—I should really
read in a loop until I have received a
\r, then process the data up to that point. Since AX.25 tends to send data in largeish packets by design, this doesn’t present a problem with small strings. The user’s input with terminating
\r all arrives in a single
I have been exploring the Linux API for “unproto” operation, i.e., sending and receiving packets without establishing connections. Sending with SOCK_DGRAM is working fine but receive is not. This is annoying, because SOCK_RAW requires you to parse packets yourself. I would like to get to the bottom of this.