Line buffering shenanigans with axcall and ax25_call

axcall vs ax25_call

axcall is the standard program for making outgoing AX.25 connections on Linux. It’s part of the ax25-apps package on Debian and has a full-screen TUI. When you press enter at the end of a line it automatically converts the line feed to a single carriage return character, as is generally expected by packet radio nodes. It supports all the features you would want when communicating with a BBS, like file transfers and logging.

# radio port, destination callsign
axcall vhf VK7AAA-1

ax25_call is a much simpler program which operates more like the nc command. Installed with the ax25-tools package, it’s part of a collection of lightweight tools for making outgoing connections that also includes netrom_call, rose_call, and tcp_call. It prints out a couple of status messages while connecting, then passes data back and forth without any translation of line endings. This means if you connect to a normal packet station from a Linux shell, all output appears on a single line that keeps overwriting itself, which is not normally very useful. It wasn’t meant to be used that way.

# radio port, my callsign, destination callsign
ax25_call vhf VK7NTK-1 VK7AAA-1

According to the documentation, the *_call suite of tools were designed to be used within other programs like ax25d and node, making it easy to redirect callers to other systems via a range of protocols. It’s logical that this task was split out into separate binaries since it could be used in a range of applications. Being “glue” in the middle, it also makes sense that the line endings are not mangled because it’s probably already been done by axcall further up the chain. It’s clear that it was always intended to be used within a CR-line-ending environment because its own status messages like *** Connected are all terminated with CRs.

One situation where ax25_call is very useful is when the remote station is not a normal packet radio node. If you run a normal Linux program via ax25d, it’s still expecting to send and receive line feeds. For example, this ax25d.conf will dump the connecting station into a Bourne shell running as user guest:

[VK7NTK-1 via vhf]
default	  * * * * * *	*	guest	/bin/sh sh

You can connect using axcall if you want, but since it sends a CR at the end of each line instead of the expected LF, commands never get executed. In this situation, substituting ax25_call is a quick and easy workaround.

Make ax25_call work with normal stations

Depending on your preferences or the type of node you’re communicating with, you may not like how axcall separates the input and output into their own parts of the screen. ax25_call is more like the nc or telnet of old, scrolling inexorably upward and intermingling your input with the output.

Linux has utilities for switching between line endings, dos2unix and unix2dos. With the -c mac option it will perform conversions between LF and CR, which is what we want for packet radio.

You might think that you could filter your keyboard input on the way in, and the received messages on the way out:

# Doesn't quite work
unix2dos -c mac | ax25_call vhf VK7NTK-1 VK7HDM-9 | dos2unix -c mac

If you run this command, it gets you halfway there. The output is now split into multiple lines:

Connecting to VK7HDM-9 ...
*** Connected
[FBB-7.0.8-AB1FHMRX$]
VK7HDM BBS, Hobart.

There are two problems. Firstly, input doesn’t work. You can press enter as many times as you like but nothing gets transmitted. Secondly, the output is always “one line behind”—you need to receive the next line before it prints the previous one.

These problems are due to buffering in stdin/stdout. Glibc is hard-coded to use LF characters when performing line-based buffering. To avoid waking up programs unnecessarily with incomplete input, by default it buffers content until it sees a LF. Fortunately, there is a wrapper program called stdbuf that lets you turn off buffering for stdin and stdout for another program.

# This actually works. It's an AX.25 connection but it looks and feels like using nc.
stdbuf -i0 -o0 unix2dos -c mac | ax25_call vhf VK7NTK-1 VK7HDM-9 | stdbuf -i0 -o0 dos2unix -c mac

Putting this inside a convenient shell script or alias is left as an exercise to the reader.

Line conversion for a UNIX program in ax25d?

What about the opposite? Can I use these sorts of tricks to make it so that a remote station can connect with axcall (or an old-school TNC)? I want to somehow handle that connection with a Linux program through ax25d, while automatically translating their CRs into LFs and vice versa. As it turns out, this is basically possible.

[VK7NTK-1 via vhf]
default   * * * * * *   *       guest	/usr/bin/stdbuf stdbuf -o0 script -q -c /bin/bash /dev/null

This isn’t actually quite right. I’m still getting CRLFs coming back for each line which means that axcall shows a blank line between each line of output. My attempts to fix this introduced a new problem—if you do an unbuffered write() for every character, the radio will transmit a separate packet for every character, which is of course extremely slow.

Really the right solution here would be to write a wrapper binary that carefully manages its input and output buffering, handles line ending conversion, and consolidates writes to avoid wasting packets. This is mostly pointless and I don’t think I’m ever going to do this. I’m going to leave these notes here for anyone who’s motivated enough to try.