After having written a user space AX.25 stack in C++, I got bitten by the Rust bug. So this is the third time I’ve written an AX.25 stack, and I’ve become exceedingly efficient at it.

Here it is:

The reason for a user space stack remains from last time, but this time:

  1. It’s written in Rust. Yay! I know people say Rust has a honeymoon period, but I guess that’s where I am, still.
  2. It’s a normal library first. The previous C++ implementation started off as microservices, which in retrospect was needlessly complex and put the cart before the horse.

I’ve added almost an excessive amount of comments to the code, to cross reference with the specs. The specs that have a few bugs, by the way.

Rust

I’m not an expert in Rust, but it allows for so much more confidence in your code than any other language I’ve tried.

I think I know enough Rust to know what I don’t fully know. Sure, I’ve successfully added lifetime annotations, created macros, and built async code, but I’m not fluent in those yet.

Interestingly, I’ve so far managed to not need any type annotations or macros in rax25. It’s starting to feel a bit like template metaprogramming in C++: you can code for weeks not needing them, but they are a dynamic puzzle piece that can make your whole design fall into place.

What with waiting for multiple timers, incoming packets, and user instructions such as “write data” or “disconnect”, I think async should make for the best API for this in the end. But so far I just made a (pretty bad) sync API.

Microservices

For amateur radio packet radio there are a few components you’ll need to have. And they need to talk to each other.

You’ll need:

  • A modem (e.g. Bell 202, G3RUH, VARA, VARA FM)
  • A connection stack, let’s call it (e.g. AX.25, VARA, or TCP/IP on top of AX.25 UI frames)
    • For AX.25 there’s Direwolf and the Linux kernel implementation.
  • An application (e.g. axcall, Winlink, axsh)

You’ll also need these pieces to talk to each other:

  • KISS is the de facto standard for talking to a modem. Direwolf, Linux, and even VARA support it.
  • AGW is a protocol for talking to a connection stack. I made an AGW library to be able to use the one in Direwolf.
  • The Linux kernel has a socket interface for the connection stack.

The problem is that basically all of these things suck.

All parts are bad

The functionality

  • Bell 202 and even G3RUH are far from state of the art modems.
  • AX.25 doesn’t have FEC.
    • IL2P improves things a bit over standard AX.25, though.
  • VARA is closed source and closed spec.
  • The Linux kernel AX.25 stack is very incomplete, and buggy. And even when it does get fixed it takes years before you’ll get the fixed kernel. Then they break it again, and you’ll have to wait years for the fix to roll out.
    • Some distributions don’t even compile it in, so your kernel may not have AX.25 support, and with things like Raspberry Pi and ZFS patches, you may not want to keep patching and building your kernel. It’s also a hard sell on other people that in order to run your application, they must first recompile their kernel to a supported version.
  • Direwolf is fairly fine. I just have a couple of problems with it, and they’re probably all fixable:
    • You always have to run it with -t 0, or your eyes will get cancer.
    • If you only want to use it for its connection stack, and not its modem, then it seems to take 100% CPU. I think there’s some busyloop reading from /dev/zero or something if you specify ADEVICE null null.
    • It’s too much of an all in one stack. It makes it harder to experiment with different modems but the same connection stack.

The interfaces

  • AX.25 connection stack doesn’t have what we on the internet would call “ports”, but a connection is just between one call+SSID to another. Basically every callsign only have 16 ports, that all devices using that callsign have to share. Because if IGates it’s probably not even a good idea to reuse call+SSID even on different frequencies.
  • KISS doesn’t allow for:
    • Reporting SNR/RSSI.
    • Changing modem parameters according to radio conditions.
    • Interfacing with carrier sense.
    • Queue management.
  • AGW is a pretty terrible protocol. Ok, it gets the job done, but it’s just… eww.

So basically it’d be great if we could redesign the interfaces between these functionalities, so that we can then basically rewrite them all. Well, as implementations go, Direwolf would just need to add these interfaces.

I made the mistake with the C++ stack of jumping the gun on these interfaces, instead of starting from an implementation.

How usable is my new crate rax25?

Well, it works. Its API is not great, but works well enough to connect to my local BBS GB7CIP, and seems to interoperate well with the Linux kernel implementation when I test it locally.

It just needs a modem behind a KISS interface, like Direwolf or the Kenwood TH-D75, and you can run it with:

$ cargo build --example client
$ ./target/debug/examples/client -r -p /dev/rfcomm0 -s M0XXX-1 GB7CIP-7

But it still has many TODOs. E.g. if its send queue becomes full, it’s unable to buffer that and just exits, instead. I expect to work on these over the next few weeks. But pull requests are welcome.