The Linux kernel AX.25 implementation (and user space) is pretty poor. I’ve encountered many problems. E.g.:

  • you can’t read() and write() from the same socket at the same time

  • DGRAM receiving just plain doesn’t work.

  • CRC settings default such that at least all my radios (and direwolf) drop the first two packets sent. (fix with kissparms radio -c 1)

  • Setting CRC mode resets all other settings.

  • On 64bit Raspberry Pi OS setsockopt for some flags don’t take effect at all (e.g. setting AX25_EXTSEQ), and treat other obvious correct ones as invalid (e.g. can’t set AX25_WINDOW to any value at all).

  • I also get kernel null pointer dereferences on 32bit Raspberry Pi OS when testing AX.25. Not exactly comforting.

  • Other OSs don’t have AX.25 socket support. E.g. OpenBSD. And it’s not obvious to me that this is best solved in kernel space.

  • It doesn’t seem clear to anyone how the AX.25 stack in the kernel is supposed to work. E.g. should axparms -assoc be an enforcing ACL? It’s not, but is it supposed to be?

  • I’ve also seen suggestions that AX.25 should be ripped out of the Linux kernel. Don’t know how plausible that is though.

In any case it seems to me that had AX.25 been designed today I don’t think it would have been integrated in the Linux kernel.

There’s no need for good CPU performance with these low speeds (kbps), and nowadays one should expect a bugfix to a kernel implementation to take several years before users will receive it. Long gone are the days when you can expect users to “just recompile the kernel”.

I’ve sent a patch or two, but they don’t appear to get much interest. Nor does anyone seem to have a vision of how it should work.

You could probably create a good implementation in user space and get it onto users machines before fixes to any of the above problems trickle down through distribution kernel updates.

So I’m implementing AX.25 in user space

Here’s the code.

The way I think makes most sense is to split out the different functions into different binaries. There’s in my opinion no reason one tool should be doing soundmodem, IGate, and radio repeater (i.e. direwolf). It makes it harder to set up one audio-based TNC next to another radio that has a built-in TNC.

By splitting the functionality controllable by the user, it’ll be easier to build a layer at a time.

The exposed API is gRPC, thus making it language agnostic and with clear API boundaries. And being a network protocol it’ll make it easier to have amateur radio tools on one machine utilize radios all over the world by connecting instead of routing.

E.g. you can run direwolf on a raspberry pi up in your attic, but on it only run the daemon that interfaces KISS with the gRPC API. That’s easier than setting up AX.25 routing between the two machines, or being forced to use higher protocols.

ax25spyd does part of this, but still relies on the kernel implementation.

It’s not done

At this stage it’s almost on a proof-of-concept level. The code is ugly, and the packet scheduler is just the first implementation I could think of that should work.

But more importantly the bigger picture is unclear. I have some ideas about a higher level routing protocol, using internet and radio links as appropriate. But since I don’t yet have much experience with NET/ROM or ROSE, and I don’t want to start designing too much without first fully understanding what’s out there.

But luckily that doesn’t prevent me implementing AX.25 as data link layer 2. NET/ROM et. al. could be implemented on top of it.

But does it work?

Yeah. Here’s a video showing a simple connection to an axsh server:

The packet decoder runs on the right hand side, but only decodes the received packets. That’s another TODO, but at least you can see that packets are decoded.

(Most of the packet decoding effort is actually to decode the many types of APRS reports, and it’s also not complete)

What’s next?

  • Server-side sockets for SEQPACKET.
  • Finish SEQPACKET implementation, with SABME, windows, etc…
  • Complete APRS decode.
  • LD_PRELOAD library to overload socket() calls.