amateur-radio

Software development ethics

I’ve been doing a lot of thinking about my choices of software of late in the face of “the sloppening”. LLMs have infiltrated many open-source projects, some of these being ones I’ve used for decades.

This has put me between a rock and a hard place. On the one hand, I wish to support ethical choices in technology. LLMs are not ethical. On the other, security vulnerabilities will drag me down the road of LLM use whether I want it or not: we can’t simply ask attackers “please don’t use LLMs” — if that worked then our prison systems should be empty.

My objections to LLM use

I covered this in detail in my last post, but it boils down to this:

  1. LLM output, whilst itself not directly applicable to copyright (as it is not the work of a human), can sometimes contain the copyrighted work of other humans (e.g. like this) in violation of the license agreements that those copyrighted works are distributed. There exists a legal risk that users of “derivative works” of copyrighted material may be found liable for unauthorised use of this copyrighted material.I don’t want that risk!
  2. Current generation LLMs are trained on material that is merely scraped off the Internet using poorly coded and highly inefficient scraper bots, then labelled with the aide of humans — often slave labour in developing countries. — I do not wish to support the slave trade!
  3. Whilst the effort right now with LLMs is to achieve “human-level” intelligence, the ability of current technology to effectively simulate a neural network with the complexity and density of a human brain is severely lacking, requiring vast data centres that consume exorbitant amounts of electrical energy and producing vast amounts of thermal energy that must then be radiated away, represents a threat to our climate ecosystem that cannot be justified at any cost. — I value the natural environment that our society exists in!
  4. Development of these tools is progressing at such a rapid pace, that current generation equipment depreciates at a rapid rate, requiring frequent replacement. Semiconductor production is necessarily a resource intensive industry and the frequent manufacture–commissioning–decommissioning cycle is producing massive amounts of e-waste whilst putting heavy demand on these facilities, further compounding the already poor environmental footprint.
  5. The output of these tools is often wrong, requiring the user to send the work back to be re-worked. Wasted time and effort. Computers in the 1950s might’ve been wrong occasionally, but it was generally traced down to failure of hardware components, the mathematical concepts underpinning this were sound, better manufacturing and components fixed this. “Hallucination” (really confabulation) is presently believed to be unfixable.
  6. It is said that relying on these tools has been shown to reduce one’s cognitive abilities, increasing dependence on these tools for functional work.
  7. Financially, the companies running these systems are a house of cards on the verge of collapse. No LLM vendor right now is making a profit. When the real bills finally arrive, will you be able to afford them? If they go bust, can your work continue without?

My legal objection to LLM use

The first of these is a legal argument, and really it’s going to take time, but eventually there will be a test case that will confirm whether my suspicions are founded. I am not a lawyer, so take what you see there with a grain of salt, but to be a successful software developer, one must have at least some basic understanding of the legal principles at play here, otherwise you find yourself in court (or in gaol) instead of in front of the computer.

I don’t have the money to fend off a potential case of copyright infringement, nor do I want to spend time having to mop up after someone is found guilty of such infringement. Code from a LLM, even if it looks novel and new to us because we’ve never seen it before, existed somewhere, and few have the resources to properly conduct a check to see where it came from.

Ethical objections

Points 2-4 point out the ethical issues, notably sourcing of training material, slavery and environmental concerns.

I fully expect that as technology gets better, we may see hardware that is better able to simulate the biological systems these tools try to emulate. Greater density, lower power consumption, less thermal waste output. The Manchester Baby was an early implementation of the Von-Neumann computer architecture that underpins the basic principles of every personal computer today… it needed 3.5kW to run this early 32-bit computer which had a mere 128 bytes (in today’s measurement; 1024 bits) of memory and ran at around 1100 instructions per second. There are ARM Cortex M0+ cores today (also 32-bits) with thousands of times more memory, clock speeds in the hundreds of megahertz and run full speed with less than 1W of power.

That part will get better. Even if they do fix that problem though, they still likely will be enslaving people to do the labelling skulduggery and using pirated material to boot. They need to solve this problem too, but so far, their efforts have been on trying to be made exempt from those rules.

Practical concerns

The financial situation suggests prices will skyrocket once funding dries up, and when that happens, people using these tools will face a choice: hike their fees to cover the increased costs, or abandon the tools en masse and return to the “old way” of doing things.

I also question the logic of feeding tokens into a machine and pushing a button, hoping that this time, we’ll hit the jackpot and get useful output. If I wanted to earn an income that way, I’d visit the local poker machine.

My actions going forward

There are two classes of software I need to consider, the software packages I simply use, and those which I actually contribute to. In both cases, some have viable replacements that I can switch to, some may have viable replacements in the future, some I still need to keep using, some I can abandon.

Linux kernel

Dealing with this is a tough nut. The core kernel project is now using LLMs as part of their core workflow. Linus Torvalds basically told us to fork it or leave if we don’t like it. I considered both:

“Fork off”, use something else

This isn’t a full list, but I looked at several options:

  • NetBSD looks like a good option for a lot of my servers. It is open-source, POSIX-compatible and does many of the things I need of a server. At the moment this blog runs on AlpineLinux, but moving it over to NetBSD is very doable.

    As a desktop I gave it a try, hardware support being its big Achilles heel — it can’t tap into the hardware support Linux has without tainting the kernel with GPLv2-licensed code, so drivers must be written from first principles. Support for interfaces like Bluetooth are really lacking (mind you, it at least supports it unlike OpenBSD). It feels a lot like Linux circa early 2000s. It’s a good base we could build from, but lots of work is needed.

    For non-x86 platforms, the situation is quite spotty. My TS-7670s basically would require me to dig through the Freescale i.MX28 user manual and port the kernel to this new (to NetBSD) SoC, before then porting the TS-7670 rev D board. My Raspberry Pis are harder still, because much of the documentation for these is under a NDA from Broadcom. A Linux fork is really my best option for these.

    While the kernel is free of slop, there are some packages shipped in the base OS that have some slop in them (e.g. Postfix). Not a lot we can do there.
  • OpenBSD is also an option on the servers, and I already do run some with OpenBSD. tmux is vibe-coded now, but that’s not a core component of the OS (despite being shipped in base) and can be safely ignored — I use GNU screen anyway.
  • I can rule out FreeBSD as they now embrace the slop. Might as well stay with Linux in that case.
  • Similarly, I can strike out 4.4BSD fork DragonFlyBSD as they embrace slop too.
  • RedoxOS has a no-AI policy, but is a long way from being usable as a desktop as it lacks support for common hardware such as WiFi and is still in an early development phase. Worth looking at later, but not viable yet.
  • HaikuOS has come a long way. It’s a re-implementation of BeOS, and like RedoxOS, has a no-AI policy. Many common software packages run on it. I played with it in a VM and it actually did feel like a nice desktop OS. There is an ARM64 port of it, but when I tried that on my Raspberry Pi 400, it refused to boot. So not ready to displace Linux yet, but looking good.
  • Illumos is a fork of OpenSolaris, so basically a genetic Unix rooted in BSD and Sun ancestry. Has an anti-AI policy, supports a lot of software, but hardware support is lacking.
  • 9Front looks interesting, but very esoteric. It is a continuation of Bell Labs Plan9. It too has a no-AI policy, but hardware and software support is probably not going to make it a practical choice for me.
  • GNU Hurd doesn’t seem to have moved far from where it was in the 90s when Linux first started and displaced it as “the” GNU kernel. It is clean of slop as far as I can tell, and it is even supported by Debian and Gentoo, Hardware support is lacking, but being a GPLv2 kernel, we could possibly port Linux drivers and features over to it — this would be a big job though.

I might’ve missed something, but that looks like a pretty extensive list. Of that list, NetBSD seems to be the best approach, failing that maybe getting behind HaikuOS (for a desktop) or filling the gaps in Hurd (if we want to re-purpose GPLv2 code).

“Fork it” approach

Under the terms of the GPLv2, we’re allowed to do this. The “Linux™” name is trademarked, and while it’s tempting to mention an alternate name just now, I know the moment such a name is mentioned, it’ll be off to the races to the nearest domain registrar to snag the .org domain for it ahead of the squatters. Linus himself already knows about people squatting names and profiting from it, which is why he now holds the trademark in the first place.

This is not something I can do alone. Arguably, I don’t feel I’m a skilled enough coder to do this either. I know enough C code to be dangerous, but this is playing with stuff deep in a system where I have never needed to stray before. That said, in the aim of “getting the ball rolling”, I made a start. I also made a map of where known slop is in the kernel so in the meantime, I can avoid touching it.

An uncomfortable truth with this: we will get security vulnerability reports from people that used LLMs to discover them even if we ask them not to use such tools.

I don’t want to condone such use in any way, but nonetheless, a security bug is a security bug. I don’t think it’s unreasonable to push back on submitters and ask for a human-written-and-verified proof-of-concept, but people will be feeding this code to LLMs to find holes whether we like it or not. Such a fork should aim to patch vulnerabilities with slop-free commits, but refusing to fix it at all because a LLM was involved isn’t a viable option as the bug can still be exploited.

Another uncomfortable truth, people will sometimes neglect to disclose LLM use, either accidentally or deliberately. Where we have obvious and clear evidence, we should reject such commits. Otherwise I’m willing to operate on the principle of “innocent until found guilty”, as its nearly impossible to tell just scrutinising a commit in isolation.

That said, I feel like maintaining a long-term support fork that just focusses on keeping the security updates maintained will give those of us with existing systems a viable platform that will allow us to run our existing software stacks. This won’t be “big and professional” like Linux (yeah, I know), but if a few of us band together and pitch in, it’ll give many of us what we need in the immediate term whilst the alternatives get up to speed.

Linux distributions

I already use Gentoo as my desktop OS of choice, so there I’m clear. I have a few machines that run other OSes, notably a couple of Ubuntu and Debian servers, my Ceph storage cluster all run Debian, my Mastodon instance is Debian. I have a tablet running Debian 13, and Raspberry Pi OS on a few Raspberry Pis.

Most of my servers run AlpineLinux, I do not know what their policy is, but so far I’ve not seen any slop creeping in.

I have run Gentoo on servers before, but I think if a re-load is on the cards, I’ll be looking at NetBSD first. Failing that, it may be a case of I figure out a build environment that can keep grinding away at security updates for my fleet so I can perform updates in a timely manner.

Desktop environment

My current desktop is FVWM. This is a X11 window manager, and while FVWM itself is slop-free, the next release of X.org server is not. I am watching CoW very closely, and have given it a try from time to time. Wayland itself seems to be slop-free, but it is lacking some features of X11, notably accessibility is a big issue.

For my tablet, a decent on-screen keyboard is a must, right now I use Onboard which I’ve found to be the best option (although I note now, they have jumped in bed with Claude as of June this year), but this only works on X11. Things I need: basically look at any laptop keyboard — letters and digits, with symbols in their usual places, all modifier keys, function keys and navigation keys. Just because I’m using a tablet does not mean I won’t be interacting with pointer-naïve terminal applications, possibly via SSH… and sometimes interacting with a text field is easier done with on-screen arrow keys than with touch, not even Apple or Google can make pure-touch work.

I need to figure out a screen locking solution, I use XScreensaver at present which is a good option, but it depends on features that are X11-only. It also does not work with on-screen keyboards. I theoretically could fork it, but given recent experience, I’m not sure that my derivative work would be welcome. Better approach might be to deal with a screen locker that maybe isn’t as pretty, but achieves the necessary job: locking the workstation securely on a Wayland desktop. For the tablet, it’ll need on-screen keyboard support — I need to be able to unlock without a USB keyboard plugged in.

I have not seen a Wayland launcher that I like, so I’m quietly working on my own based on ideas I came up with when I last pondered desktop environments.

Other applications, I’ve previously used a lot from the KDE desktop, but that is something I’m starting to review as they embrace the slop. I historically have disliked GTK+ for its insistence on spewing error logs to stderr, but nonetheless, as at this time, this is the only main-stream widget set now that remains slop-free with Qt, wxWidgets and FLTK all embracing slop.

So where I had a Qt preference years ago, I’m now moving to GTK+: it appears the UI toolkit might be finally over, with the opposition choosing self-sacrifice and GTK+ winning by default.

Bluetooth stack

Right now I use BlueZ, and it is useful as a means of moving files to/from the mobile phone as well as a key part in managing the audio links to headsets. If I switched OSes, I’d be looking at something with a similar feature-set.

Sadly it embraces the slop now. Thankfully I haven’t needed to dig into its code… I can put up with it for now, but a more ethical option would be welcome.

Audio subsystem

Right now I use pipewire for managing audio as it is a very usable blend of features from PulseAudio and JACK. I like the ability to filter audio through applications where needed and would like to keep this feature. Its integration into the BlueZ Bluetooth stack is good too.

I think JACK is cross-platform, but PipeWire is likely Linux-only, and it recently has embraced the slop. I know OpenBSD has its own called sndiod, this probably can compete with PulseAudio but not with JACK/Pipewire.

For now unless I need to actually debug it, I can “put up” with it.

Web Browser

I dislike Chromium because of its multi-day long builds from source code, it’s a very time consuming package to compile from source. As a Gentoo user, that is the default way it is shipped, as source code you compile yourself. I also dislike the idea of an Internet web browser mono-culture. We had this 25 years ago with IE and it was hell.

The only thing in Chromium’s favour is that it is open-source, so we can port it and build it ourselves, but that’s about where the advantages end. The principle maintainer, Google, are a major LLM vendor, and very much want to encourage people to use their services including Gemeni. I haven’t checked, but I can’t imagine Google not using LLMs to code their browser engine.

I’m a Netscape user from way back: in 1997 it was the only viable option for getting on the Internet with Linux and so that’s what I got to know. When Mozilla formed from Netscape’s ashes, I switched over to using the full Mozilla suite (which lives on as Seamonkey) and when Firefox came, I switched to that. Building Firefox from source can be done in a couple of hours on even reasonably modest hardware, vs about 5 days for my teenage laptop to build Chromium.

Mozilla have since come out as pro-LLM, a tone-deaf stance if ever I heard one, so much they not only use LLMs to code, they also integrate LLM features in the browser.

That said, they do include an OFF switch for the LLM features, and that I do respect. I’d have preferred if they made it an extension you chose to install if you wanted it, but they at least let me turn it off. This is akin to having a gun in a safe at home but not possessing any ammunition for it. I’d rather not have the gun in the house at all, but at least I’m not forced to have it loaded.

Servo Browser is one to watch in this space. It’s a ground-up implementation of a new HTML renderer to compete with Blink/Webkit/KHTML and Netscape Gecko. It’s a long way from daily driver status, but well worth getting behind, and they are making good progress. I wish them well and continue to follow from a distance. I’ll probably be testing some of my web-based projects with this engine going forward.

I’ve tried building Waterfox, but so far haven’t cracked it… so for now I stick with Firefox ESR. Last time I had to delve into its source code, it was trying to produce a compromise patch that would enable Firefox to build on more common MIPS platforms (e.g. SGI and Lemote) whilst retaining the compatibility for the PlayStation II that the Mozilla developers wanted to keep. Firefox is partially coded in Rust now so this is a moot point: Rust doesn’t work on MIPS.

As such, I haven’t needed to mess with its code in a long time. So short term, I’ll stay where I am, and start looking at getting behind Servo as the replacement.

Office Suite

Text documents I tend to do with LaTeX. This so far remains slop-free, but they’ve recently adopted a policy that permits LLM-generated commits so it won’t remain that way for long.

For spreadsheeting, gnumeric is “good enough”, and in fact its Engineering notation I find is a killer feature. It remains slop-free.

I do have LibreOffice installed for more complex tasks and interfacing with Microsofties. They’ve said they won’t add LLM features to LibreOffice (a stance I appreciate), but they have allowed its use with writing its code. Again, I don’t need to debug it, so I’ll deal with that later.

EDA

I don’t just write code, I design circuits too. I’ve bounced between a few, but the two I’ve used in the open-source world are KiCAD and GEDA. KiCAD is by far the better supported package. Sadly contains a lot of slop in it now, but once again, so long as I don’t interact with its code I’m not tainting myself.

There are alternative toolchains too, maybe the answer isn’t a “graphical” schematic capture, but to do something more like is done for FPGAs, a hardware-description language that translates to a netlist?

Graphics

Right now I use three tools principally: GIMP, Krita and Inkscape. GIMP recently started accepting slop (just the one commit) as have Inkscape (two commits). Krita remains clean (for now).

I mostly still use GIMP v2 because of my use of the XSane plug-in which has not yet been ported to GIMP v3. Inkscape taking this slop on is annoying, but so far I’ve not needed to debug it, so I can possibly put up with it for now until I can jump ship to an alternative.

Audio editing

I normally use Audacity for basic needs, I should give Tenacity a look.

For more advanced tasks I’ve used Ardour but given I’ve not gone deep into learning Ardour, switching to something like LMMS isn’t a big issue.

Video editing

Historically I’ve used Kdenlive, so far it remains clean. ffmpeg though is a mess.

Text editing

My bread-and-butter, editing plain text files is how I’ve been earning a living for the past 18 years or so of professional life. My go-to until recently was gvim, but as they now embrace the slop, I switched over to hard fork gevi. An easy switch because EVi at this time, is practically identical to Vim, all my plug-ins and configs JustWork™.

This blog

This blog currently uses WordPress 7.1, which includes their Gutenberg block editor. I did look at alternatives some time back, I use this WordPress instance as an ActivityPub node, and that’s not something that’s well supported in the other platforms.

Migrating to something else is also not so easy… thankfully I host it myself so I have control of my data either way.

My social media instance

I run my own Mastodon server, have done for a couple of years now. There is some (two commits) slop there, but generally things have remained clean. The modifications I’ve made were done long before this slop appeared, and I’ve merely been using git stash save / update / git stash pop to move the patch between releases.

Migrating to something else is not exactly trivial, even changing hostnames isn’t easy.

Writing my own software

So I write a lot of my own software. When I needed an embedded AX.25 stack for a project for Brisbane WICEN, I wrote aioax25. When I needed a SSTV encoder after finding pySSTV wasn’t performant-enough, I wrote libsstvenc. I’m in the process of writing my on launcher.

You’ll see lots of examples on my Github and Codeberg repositories.

Going forward, I’ll probably start looking more carefully at my dependencies. Notably, I do not wish to force a hard-dependency on a LLM-infected project unless there is no viable alternative.

aioax25 is actually tested on Python 3.9 and up, and I’ll keep supporting this as long as I practically can. The code is written with Python 3.5 in mind, so it should still work there too even though I don’t regularly test it on that. This in theory opens the door to use on other Python-compatible language interpreters like Omegathon (untested). It also has no hard dependency on an OS: it should in theory work just fine on NetBSD/OpenBSD as it does on Linux.

libsstvenc is straight C and doesn’t itself have any dependencies, the example programs support libgd which is clean of slop for now. GNU GCC have stated they won’t allow LLM-generated commits for the compiler itself, although there are a couple of commits already for their Fortran compiler, their C/C++ compiler remains clean for now. If I remain conservative with the version of C standard, this allows use of any pre-slop C compiler that meets the spec.

Checkpoint Reporter is written in pure Java using the OpenJDK v8 SDK. I can confirm it runs fine on NetBSD, and so far, big LLM boosters Oracle have requested that people not use LLMs for contributing to OpenJDK. This position I do welcome, but it does make me raise an eyebrow given how pro-LLM, if it’s supposedly so good, why not? Their lawyers must know something they’re not willing to share. I might have to review its use of Maven, this was a default option when creating a project in Netbeans, it may be that good ol’e Ant might be the better option. I’ll have a look.

My biggest gripe with Java remains their reluctance to consider serial devices: all solutions I can find require JNI (with bindings compiled for each desired platform, so much for write-once-run-everywhere, Ugh!). Highly annoying.

The launcher I’m writing, right now I’m trying gtkmm and friends as an alternative to Qt, which was my previous go-to UI toolkit. Again, if I stick to older C++ standards (gtkmm requires C++11, C++20 is widely supported too), people can choose to use an older release of GCC or LLVM that’s pre-slop, or in theory, any other compiler that supports that revision of C++. gtkmm itself too, is clean, and if I support as old a release as practical, that’ll enable the launcher’s use on many pre-slop versions of Linux distributions and other Unix-like OSes. I use C++ here because it allows the tools use on platforms where more recent languages such as Rust and Zig are not available.

I might have a closer look at Rust, I’ve done some hello-world level stuff with it. The memory-management approach is an interesting one, and while their policy allows “LLM ingestion” (meaning they let people feed its code to a LLM), they’ve stated that any contributions must be human-written (not just human-reviewed).

Zig may also be worth a look, it is manual memory management like C/C++, but it doesn’t have some of C’s flaws, and they have an anti-AI policy. Right now they use the LLVM back-end (which is itself, quite heavily LLM-tainted) but I hear they’re working on an alternative, so maybe in time they may sever that dependency (at least on common platforms).

eC is another I might look at. Rather than implementing the full compiler, theirs is effectively a transpiler to C. So it can sit atop gcc, clang or anything else.

I have written Go code in the past, but won’t be doing so from here on in. Especially as Go as a language isn’t anything special memory-management wise: it uses garbage-collection the same as Java and C#.

I mentioned Omegathon which is a Python 3.9 fork, Tauthon forks Python 2.7 and adds some interesting features from Python 3. Given I usually enjoy working with Python, these might be worth looking at closer.

For JavaScript, I lately have been using ReactJS because of my need to understand it at work, but I really haven’t enjoyed dealing with ReactJS and so will look at other options. The elephant in the room here is tools like webpack that are LLM-infested. Avoiding slop in this ecosystem is not an easy or trivial task, especially as there isn’t a mainstream open-source browser that is clean.

In short, while slop is invading a lot of projects, there looks to be some scope to dodge the slop when creating new software, and there are things I can do to ensure existing software I write can be used in a mostly slop-free environment if that is something people want. I aim to try to do this where practical.

Using image resources with Maven Java projects in Netbeans

Last Easter, I was running a checkpoint at Imbil as I’ve done before… operating a checkpoint at Derrier Hill Grid with horses passing through from three different events simultaneously, coming from two different directions, and getting more confused than a moth in a light shop. At that time I thought it’d be really handy to have a software program that could “sort ’em all out”. I punch in the competitor numbers, it tells me what division they’re in and records the time… I then assign the check-points and update the paperwork.

We have such a program, a VisualBASIC 6 application written by one of the other amateurs, however I use Linux. My current tablet, a Panasonic FZ-G1 Mk1, won’t run any supported version of Windows well (Windows 10 on 4GB RAM is agonisingly slow… and it goes out of support in October anyway), but otherwise would be an ideal workhorse for this, if I could write a program.

So I rolled up my sleeves, and wrote a checkpoint reporting application. Java was used because then it can be used on Windows too, as well as any Linux distribution with OpenJDK’s JRE. I wanted a single “distribution package” that would run on any system with the appropriate runtime, that way I wouldn’t need a build environment for each OS/architecture I wanted to support.

One thing that troubled me through this process… was getting image resources working. I used the Netbeans IDE to try and make it easier for others to contribute later on if desired: it has a GUI form builder that can help with all the GUI creation boilerplate, and helps keep the project structure more-or-less resembling a “standard” structure. (This is something that Python’s tkinter seriously lacks: a RAD tool for producing the UIs. The author of the aforementioned VB6 software calls it “T-stinker”, and I find it hard to disagree!)

Netbeans defaults to using the Maven build system for Java projects, although Ant and Gradle are both supported as well. (Not sure which one of the three is “preferred”, I know Android often use Gradle… thoughts Java people?) It also supports adding bitmap resources to a project for things like icons. I used some icons from the GTK+ v3 (LGPLv2) and Gnome Adwaita Legacy (CCBYSA3) projects.

The problem I faced, was actually using them in the UI. I was getting a NullPointerException every time I tried setting one, and Netbeans’ documentation was no help at all. It just wasn’t finding the .png files no matter what I did:

2025-06-15T06:32:54.461Z [FINEST] com.vk4msl.checkpointreporter.ui.ReporterForm: Choose nav tree node test
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
        at javax.swing.ImageIcon.<init>(ImageIcon.java:217)
        at com.vk4msl.checkpointreporter.ui.event.EventPanel.initComponents(EventPanel.java:239)
        at com.vk4msl.checkpointreporter.ui.event.EventPanel.<init>(EventPanel.java:63)
        at com.vk4msl.checkpointreporter.ui.ReporterForm.showEvent(ReporterForm.java:895)
        at com.vk4msl.checkpointreporter.CheckpointReporter.showEntity(CheckpointReporter.java:532)
        at com.vk4msl.checkpointreporter.ui.ReporterForm.navTreeValueChanged(ReporterForm.java:480)
        at com.vk4msl.checkpointreporter.ui.ReporterForm.access$100(ReporterForm.java:70)
        at com.vk4msl.checkpointreporter.ui.ReporterForm$2.valueChanged(ReporterForm.java:182)

Maybe it’s my search skills, or the degradation of search, but I could not put my finger on why it kept failing… the file was where it should be, the path in the code was correct according to the docs, why was it failing?

Turns out, when Maven does a build, it builds all the objects in a target/classes directory. When Netbeans runs your project, it does so out of that directory. Maven did not bother to copy the .png files across, because Netbeans never told it to.

I needed the following bit of code in my pom.xml file:

       <resources>
         <resource>
           <targetPath>com/vk4msl/checkpointreporter/ui/components/icons</targetPath>
           <directory>${project.basedir}/src/main/java/com/vk4msl/checkpointreporter/ui/components/icons</directory>
           <includes>
             <include>**/README.md</include>
             <include>**/*.png</include>
           </includes>
         </resource>
       </resources>

That tells Maven to pick up those .png files (in the com.vk4msl.checkpointreporter.ui.components.icons package) and put them, along with the README.md, in the staging directory for the application. Then Java would be able to find those resources, and they’d be in the .jar file in the right place.

Other suggestions have been to move the project to using Ant (which was the old way Java projects were built, but seems to be out of favour now?)… not sure if Gradle has this problem… maybe some people more familiar with Java’s build systems can comment. This is probably the most serious Java stuff I’ve done in the last 20 years.

I used Java because it produced a single platform-independent binary that could run anywhere with the appropriate runtime, and featured a runtime that had everything I needed in a format that was easy to pick back up. C# I’ve used for command-line applications at university, but I’ve never done anything with Windows Forms, so I’d have to learn that from scratch as well as wrestling MSBuild (yuck!). Python almost was it, but as I say, dealing with tkinter and trying to map that to all the TK docs out there that assume you’re using TCL, made it a nightmare to use. I didn’t want to bring in third-party libraries like Qt or wxWidgets as that’d complicate deployment, and other options like C++, Rust and Go all produce native binaries, meaning I’d have to compile for each platform. (Or force people to build it themselves.)

Java did the job nicely. Not the prettiest application, but the end result is I have a basic Java program, using the classical Swing UI interface that should be a big help at Southbrook later this month. I’ll probably build on this further, but this should go a big way to scratching the itch I had.

Hacking SSTV

Slow-scan television is a means of transmitting still images over HF radio. It has its origins in the 1950s, where operators would use a CRT monitor with a long-sustain phosphor in a darkened room to see the image, but modern implementations today use digital signal processing computer software on a conventional OS like Windows or Linux.

For the non-radio folk, you can think of it as a radio-flavoured facsimile service. A picture is taken, scanned (or created directly on the computer), the resulting image file is then encoded as a series of audio tones that are then transmitted over the air. The vast majority of standards out there use frequency modulation (FM) for the actual base-band signal sent to the radio, although there are some newer ones (like the Digital Radio Mondiale-based HamDRM, of which EasyPAL is probably its best known implementation) that use more advanced modulation techniques.

I would like the ability to send and receive SSTV on the bicycle. The Raspberry Pi 4 I have earmarked for the bike can do it, and I’ve been using it here with QSSTV to send and receive pictures as a home station. However, while this is a very capable client, being a desktop application, it is awkward to use on a headless Raspberry Pi. Best solution I’ve come up with is to use a VNC desktop, which is tedious.

I looked around for what else there was, and the answer was, not a lot. So I’d have to get my hands dirty.

Transmitting

Transmitting actually isn’t that difficult, there exist several CLI programs that will take an image file, and spit out an audio file (usually Microsoft RIFF .wav) that I can then play out the sound card. Not sure if they do the FSK ID at the end, but that’s not exactly difficult to figure out, and I can probably modify a tool to add it.

In my early experiments with SSTV, this is exactly how I sent some of my early transmissions. But I’d need to fire up an image editor to compose the image — it was cumbersome. However with the raster image already in-hand, it wasn’t too bad.

Keying the radio is done simply, I have two ways actually… one is to use the “Computer Aided Transceiver” port on my radio (Yaesu FT-897D in this case) to send PTT on/off commands. The other was via the GPIO pins on the Pi, which on the NWDR DRAWS board, are mapped to the appropriate pin on the data port. Both work, and are relatively straightforward.

So whilst there’s a few loose ends to tie up here, it seems that is largely handled.

Receiving

This was a big problem, and one I was stuck on for a while. Nearly everything I could find was a GUI application. I didn’t want to run a GUI for this, I wanted a CLI tool that sat in the background and did its thing, ideally launched at boot by systemd. I wanted a few things:

  • capture of metadata parameters into a separate file
  • periodic update of the partial image during reception
  • execute a script when something happens

I found one CLI receiver, there’s this one, but it assumes I have a recording of it already. I’d need something to spot the VIS header then start recording. QSSTV did nearly everything I wanted, but was a GUI application, couldn’t write out the metadata nor could it trigger a script. slowrx was the same.

I do note that the KiwiSDR seems to use slowrx in its decoder, and they’ve managed to solve this issue, so I took a closer look. With some work, I was able to split slowrx into two parts, a back-end I called libslowrx.a, and a front-end slowrx application. I then wrote a crude slowrxd application that ran on the CLI and used libgd to write image files (I did think of using libpng directly, but its API is fiddly to use). I started this a few days back and polished it off over the week-end. This is released on Github.

As a proof-of-concept, I threw together this SSTV cam website. There, I took inspiration from VK7OO’s SSTV site. I don’t have an SDR, but that doesn’t mean I can’t create a waterfall of the transmission, I made slowrxd write the transmission to a file, then used good ol’e sox to generate a spectrogram.

I made slowrxd generate a NDJSON log file as it received the image, which meant all kinds of metadata could be emitted live as it happened, including the SSTV mode at the start and the FSK ID at the end, timestamped to the millisecond. This could be parsed with sed and jq to pull out the necessary bits.

file could tell me how big the image was (I guess I could have put that in the log), and netpbm has lots of tools for putting images together. Drawing text was more tricky, but I found convert (from ImageMagick) could give me an image that had the text I wanted in a font I liked, then I could tell netpbm‘s pamcat and pamcomp could put the pieces together.

bash ran the whole show, generated the HTML, then used rsync to upload over SSH. Easy.

Without much work, I could replace this script with another that just does a nc -u localhost port and sends me a single UDP message with the event details so it could be handled by a separate daemon. So I think now I have a solution for that now.

Templating

This is a sore point in QSSTV, it has a template editor, but it uses its own format that’s basically proprietary to QSSTV using Qt abstractions. The code is open-source, so we can pick that apart to figure out exactly what its reading/writing, but it’d be a longer project… and the QSSTV templates are pretty limited.

The more I thought about it, the more I started to think about SVG as a template format — it has everything I need and some. I could use a well-known and highly capable editor like Inkscape to design the templates, leaving fields for my application to fill-in, then use a SVG rasteriser to spit out the image at any resolution I wanted.

For a rasteriser, whilst not ideal, I can use Inkscape itself quite successfully. At the cost of dragging in GTK+ libs (and it complaining that DISPLAY not being set), I can rasterise a SVG with one command:

inkscape -o output.png output.svg

The catch was, how do you do templates in Inkscape? Initial research was disheartening, but then I wondered… SVG supports CSS just like HTML. While you can attach style attributes to a CSS class, you don’t have to. SVG doesn’t care if you don’t… and with that, I can identify nodes in the SVG document by the class assigned.

Inkscape makes this possible through the Selectors and CSS palette accessed through the Object menu. Also useful here is the Object Properties palette available in the same menu.

The Object menu, showing the Selectors and CSS option highlighted.

Inkscape Template workflow

To create a template in Inkscape… firstly, create your image as you normally would. Put whatever dummy text in your text fields and dummy images in embedded bitmaps you need to change from code later. Get it looking the way you want with that dummy text.

Next, use the Object Properties palette to assign meaningful values to the objects you want to manipulate. Each ID you assign must be unique, so this by itself isn’t useful for fields that are duplicated, but will help later when you come to assign CSS classes to these fields. The IDs should contain only alpha-numeric characters (0-9, a-z, A-Z), hyphens (-) and underscores (_) (apparently other characters are allowed except white-space, but sticking to this limit will make life a lot easier).

Inkscape assigning IDs to the various elements we want to manipulate.

Finally, we use the Selectors and CSS palette to assign classes. Click on an element you wish to assign classes to:

Selectors and CSS palette open with an object selected.

You’ll see there are two panes, either side-by-side or one above the other, the little button group down the bottom-right switches the view. Depending on which one is selected, you want either the bottom one or the right-hand one.

On the same row is + and - buttons for adding new “CSS selectors”. When you click + a pop-up box appears with a default being the ID of the object prefixed with a #. Delete the default text, then type in a . followed by the CSS class name, again just stick to alpha-numerics, hyphens and underscores… don’t try anything fancy. Then click OK.

You should find in the right (or bottom) pane, you now have the class defined, and your object (identified by the ID you assigned earlier) is listed as one of the members of that class. There’s a + icon beside the class which you can click to add another selected object to the same class, and a garbage can icon beside each member to remove that member from the class.

In the left (upper) pane, you can set style information for this class as well as for the specific selected object. Leave the style information blank for each class (you can apply styles if you like, but we’re simply using the class name as a label here to convey semantic information).

When you’re happy, save the SVG image. Inkscape SVG is fine unless you’re using something different for a rasteriser that requires standard SVG.

Editing the template is done by simply opening the SVG file in Inkscape and editing it like any other document, keeping in mind those two special panes for assigning CSS classes.

Python-based Templating engine

I did a quick experiment today, and came up with this very crude Python script to demonstrate a templating engine:

#!/usr/bin/env python3

from xml.etree import ElementTree
from collections import namedtuple
import datetime

SVGField = namedtuple("SVGField", ["element", "classes"])

svgdoc = ElementTree.parse("test.svg")
svgroot = svgdoc.getroot()

if svgroot.tag == "svg":
    namespaces = None
else:
    assert svgroot.tag.startswith("{") and svgroot.tag.endswith(
        "}svg"
    ), "Root element is not a SVG tag"
    # Pick out the namespace URI
    namespaces = dict(svg=svgroot.tag[1:-4])

# Figure out what the text and image tags will be called, they'll have
# the same namespace as the `<svg>` tag.
TEXT_TAG = "{%s}text" % namespaces["svg"]
IMAGE_TAG = "{%s}image" % namespaces["svg"]

# Figure out all classes defined and the elements using them
classnames = {}
for elem in svgdoc.iterfind(".//*[@class]", namespaces=namespaces):
    elem_classes = set(
        [cls for cls in elem.attrib.get("class", "").split(" ") if cls]
    )

    field = SVGField(elem, elem_classes)

    for cls in elem_classes:
        classnames.setdefault(cls, []).append(field)

print("All classes: %s" % ", ".join(sorted(classnames.keys())))
NOW = datetime.datetime.utcnow()

# These would be sourced from command line arguments or some file, the
# following shows how to replace the content of text fields.
for cls, fields in classnames.items():
    if cls == "freq":
        value = "123456.789 kHz"
    elif cls == "sstvmode":
        value = "Dummy SSTV Mode"
    elif cls == "utcdate":
        value = NOW.strftime("%Y-%m-%d")
    elif cls == "utctime":
        value = NOW.strftime("%H:%Mz")
    elif cls == "heading":
        value = "Test heading"
    elif cls == "message":
        value = "Test message text"
    else:
        print("Skip fields of class %s" % cls)
        continue

    for field in fields:
        if field.element.tag == TEXT_TAG:
            for tspan in field.element.iterfind(
                "./svg:tspan", namespaces=namespaces
            ):
                tspan.text = str(value)
        elif field.element.tag == IMAGE_TAG:
            # Iterate over the attributes and look for the href tag, which
            # may be prefixed by a namespace.  Make a note of the new value.
            changes = {}
            for attr in field.element.attrib.keys():
                if attr == "href" or (
                    attr.startswith("{") and attr.endswith("}href")
                ):
                    changes[attr] = str(value)

            if changes:
                # Apply the changes
                field.element.attrib.update(changes)


svgdoc.write("output.svg")

If I run this script against my mock template… I get an SVG that renders to this:

Rendered template with all text fields filled in from user-supplied data.

Next steps

What I need to do next I guess is to write a web-based front-end that I can use with my tablet, but the mechanics of what I need is all there now. I can design templates for simple messages or embedding a copy of the image another station sent me so I can reply to their transmission. I can render those templates to raster images for transmission. I can capture and display the incoming SSTV traffic.

I just need to put it together now.

New project: a replacement head unit for the Yaesu FT-857(D)

So, for a long while now I’ve been a user of a Yaesu FT-857D. I bought it back in 2011 as shop-soiled stock (someone bought it before me thinking they could make it work on 27MHz!) and have used it on the bicycle pretty much ever since.

The FT-857D is a great rig. Capable of all common amateur bands from 160m through to 70cm, 100W on MF/HF/6m, 50W on 2m, 20W on 70cm, and able to work AM/FM/SSB/CW, in a nice small package. It’s ideal for the bike in that regard. The only thing I could wish for is an actually waterproof head unit, but the stock one has been good, until now.

Last time I rode the bike I had no issues with the head unit display, things were stable and working just fine. That was some months back. Today fired it up to check the battery voltage: it seems I’ve got the dreadded zebra stripe issue. The bike has been in the garage for the past few months, so under cover, not in the sun… anecdotal evidence is that this problem is caused by vibration/heat in vehicle installations, but some reports suggest this can happen for indoor fixed installations too.

The problem

Either way, the zebra has made its home in my radio’s head unit and the display is now pretty much unreadable. Reports suggest I can send it back to Yaesu, pay them $200 (I presume that’s USD, and does not include shipping), and they will replace the defective LCD. However… given they discontinued making these things a few years back, I think I’ve missed the boat on that one!

Time for replacement?

Buying a new replacement isn’t viable right now — Yaesu don’t make anything equivalent: the FT-991A is too big (same size as the FT-897D), the FT-891 doesn’t do 2m/70cm, the FT-818ND is only QRP. Icom’s IC-7100 is the nearest competitor, not out of the question, except it’s a pricey unit for something that will be out in the weather.

Also, a lot of these options are out-of-stock with a big lead time.

Most of the Chinese units only do FM, and are at best quad-banders. Not that I’m interested in buying one: I hear they’re not the longest-lived of transceivers and right now I wish to avoid buying from China anyway.

Kenwood are basically out of the market here in Australia, and they never had an offering like the Icom or Yaesu units; their TS-480SAT was the closest, but does not cover 2m/70cm. The TS-2000 is a monster.

Alinco don’t have anything in a mobile format that competes either. The DX-SR9T does not cover 2m/70cm and is rather big; none of their 2m/70cm sets do HF or SSB.

Keeping the old faithful going

The radio itself works fine. It looks like the wreck of the Hesperus… with paintwork rubbed off the body, screws missing, a DIY fix on the antenna ports, and miscellaneous fixes to other bits. It still works though.

DIY Repair

This could be tricky as I’m not entirely sure what the issue is. It could be just a need for re-flowing everything, or there’s talk of parts needing replacement. The information I have is pretty murky and I could wind up making my partially-working head unit completely non-working.

Replacement used head unit

If someone had a working head unit that they were willing to part with, that might be an option. That said, the used unit could have the same problems my existing unit has, so no guarantee it’ll fix the problem.

CAT port auxiliary display

There are projects that link to the CAT port and present a UI on a separate screen. I was planning on putting a Raspberry Pi 4 there for SDR work, so that’s an option.

Homebrew head unit

Another option is to make a new front head unit. It turns out this has been partially reverse-engineered, so might be a worthy avenue to consider. That would give me a head unit that I can purpose-build for the bike: an attractive option. The hardware interface is 5V TTL UART with a 62kbps baud rate and 8-bits, no parity, two stop bits.

I have a big LCD (128×64) that has been kicking around for a while as well as some TFT resistive touchscreen displays with STM32F103VEs.

The Raspberry Pi 4 scraping the data and presenting it via a remote UI is also an option, in fact may be the direction I wind up going simply because Python on an ARM CPU is much easier to use prototyping something than doing C on a MCU whilst I bed down the finer details of the protocol.

The attraction of this is that I can use what I have on-hand now. Possibly use my tablet as the front-end in the short term. Not good in the rain, but can’t argue with the price!

I’ll go ponder this some more… one thing I am short of though is time to work on this stuff. This week-end is through, and the next one I’ll already be tied up on the Saturday, so I guess I’ll have to squeeze something in.

Thinking about SDR on the bike

So, for close to a decade now, I’ve had a bicycle-mobile station. Originally just restricted to 2m/70cm FM, it expanded to 2m SSB with the FT-290RII, then later all-band using a FT-857D.

It’s remained largely unchanged all this time. The station is able to receive MW/SW stations as well, and with some limitations, FM broadcast as well. My recent radio purchases will expand this a bit, freeing up the FT-857D’s general-coverage receiver to just focus on amateur bands. It’s been a long-term project though to move to SDR for reception.

What I have now

Already acquired is a Raspberry Pi 4 (8GB model) and a NWDR DRAWS interface board. I actually started out with a Raspberry Pi 3 + DRAWS and was waiting for the case for it to fit into. At that stage was the idea that the FT-897D would do much as it does now, no SDR involved, and I’d put a small hand-held with its own antenna as an APRS rig being driven by the second port on the DRAWS.

Since then; I bought the HackRF One for work (I needed something that could give me a view of the 2.4GHz ISM band for development of the WideSky Hub), the SDR bug firmly bit. Initially it was just DAB+ reception, I decided to get a RTL-SDR to do that so my radio listening wouldn’t be interrupted when a colleague needed to borrow the HackRF. That RTL-SDR saw some use receiving UHF CB traffic at horse endurance ride events at Imbil — I stated to consider whether maybe this might be a better option as a receiver for more than just commercial radio broadcasts.

Hence I purchased the Pi4: I figured that’d have enough CPU grunt that it’d still be able to decode a reasonable amount even if the CPU throttled itself for thermal management purposes. A pair of SDR interfaces would allow me to monitor a couple of bands simultaneously, such as 2m and 70cm together, or 2m/70cm and one of the HF bands.

Even the RTL-SDR v3 dongles are wide enough to watch the entire 2m band. With CAT control of the FT-857D, it’d be possible for the Pi4 to switch the FT-857D to the same frequency and possibly manage some antenna switching relays as well.

A rough design

This morning I came up with this:

A rough design of the SDR set-up

A critical design feature is that this must have a “pass-through” option so that in the event the computer crashes/fails, I can bypass all the fancy stuff and still use the FT-857D safely as I do now without all the fancy SDR stuff.

So while in SDR mode: the station pushbuttons on the handlebar go to a small sequencing MCU that can report events to the Pi4, on transmit the Pi4 can then instruct that MCU to connect the antennas into bypass mode, short-out the SDR inputs to protect them, then engage the PTT on the FT-857D, and transmit audio can either be delivered direct via the analogue inputs as they are now, or over USB/WiFi/Bluetooth through the MiniDIN6 DATA port.

The thinking is to have two SDRs, one of which is “agile” between HF/6m and 2m/70cm modes.

The front-end will be handled via the tablet: a Samsung Galaxy Active3 which can connect over WiFi or USB CDC-Ethernet.

I’ve shown gain-blocks between the antennas and the receivers, this is largely for impedance matching as well as to account for the losses involved in antenna sharing. Not sure what these will technically look like.

The two on the HF side should be ideally 0-60MHz devices. If I use the AirSpy HF+ as pictured, the VHF/UHF LNA connected to it only has to concentrate on the VHF band below 260MHz (really 144-148MHz, but let’s widen that to 87-230MHz for FM broadcast, air-band and DAB+) since that’s where the AirSpy stops.

The other, for now I’m looking at a RTL-SDR since I have one spare, but that could be any VHF/UHF capable SDR including the AirSpy Mini — the LNA on it, as well as the one feeding the FT-857D in receive mode will both need to handle 144-450MHz at a minimum.

It may be these frequency bands are “too wide” for a single device, and so I need to consider band-pass filters + separate band-specific LNAs and additional switching circuitry.

SDR selection

There are a couple of options I’ve considered:

The thing I don’t like about the SDRPlay Duo is the non-free nature of its libraries which seem to be only available for i386 or AMD64. Otherwise on paper it looks like a nice option.

KerberosSDR/KrakenSDR seems like overkill. It’s basically four (or five) RTL-SDRs sharing a common oscillator which is essential for direction-finding, but let’s face it, I’ll never have enough antennas to make such an application feasible on the bicycle. It looks like an echidna now!

BladeRF looks nice, but is pricey and stops short of the HF band so would need an up-converter like the RTL-SDR — not a show-stopper. That said, it’s dual-channel and can transmit as well as receive, so cross-band repeater would be doable.

I should try this with the HackRF One some day, see if I can combine a conventional transceiver + RPi + DRAWS/UDRC + HackRF One to make a cross-band repeater.

The Airspy HF+ is available domestically, and isn’t too badly priced. It doesn’t transmit like the HackRF does, but then again I could stuff one of my Wouxun KG-UVD1Ps in there wired up to the second DRAWS port if I wanted a traditional cross-band set-up.

Next steps

It would seem the LNA / antenna sharing side of things needs consideration next. RF relays will need to be procured that can handle seeing 100W of RF. Where I’ve drawn a single switch, that’ll likely be multiple in reality — when the transmitter is connected to the antenna, the receivers should all be shorted to ground so they don’t get blown up by stray RF.

Maybe the LNAs feeding the FT-857D will need to be connected to a dummy-load to protect them, not sure. Perhaps LNAs aren’t strictly necessary, and I can “cheat” by just connecting receivers in parallel, but I’m not comfortable with this idea right now. So this is the area of research I’m focusing on right now.

6LoWHAM: Thoughts on how to distribute context and applications

So, one evening I was having difficulty sleeping, so like some people count sheep, turned to a different problem…6LoWPAN relies on all nodes sharing a common “context”. This is used as a short-hand to “compress” the rather lengthy IPv6 addresses for allowing two nodes to communicate with one another by substituting particular IPv6 address subnets with a “context number” which can be represented in 4 bits.

Fundamentally, this identifier is a stand-in for the subnet address. This was a sticking-point with earlier thoughts on 6LoWHAM: how do we agree on what the context should be? My thought was, each network should be assigned a 3-bit network ID. Why 3-bit? Well, this means we can reserve some context IDs for other uses. We use SCI/DCI values 0-7 and leave 8-15 reserved; I’ll think of a use for the other half of the contexts.

The node “group” also share a SSID; the “group” SSID. This is a SSID that receives all multicast traffic for the nodes on the immediate network. This might be just a generic MCAST-n SSID, where n is the network ID; or it could be a call-sign for a local network coordinator, e.g. I might decide my network will use VK4MSL-0 for my group SSID (network 0). Probably nodes that are listening on a custom SSID should still listen for MCAST-n traffic, in case a node is attempting to join without knowing the group SSID.

AX.25 allows for 16 SSIDs per call-sign, so what about the other 8? Well, if we have a convention that we reserve SSIDs 0-7 for groups; that leaves 8-15 for stations. This can be adjusted for local requirements where needed, and would not be enforced by the protocol.

Joining a network

How does a new joining node “discover” this network? Firstly, the first node in an area is responsible for “forming” the network — a node which “forms” a network must be manually programmed with the local subnet, group SSID and other details. Ensuring all nodes with “formation” capability for a given network is beyond the scope of 6LoWHAM.

When a node joins; at first it only knows how to talk to immediate nodes. It can use MCAST-n to talk to immediate neighbours using the fe80::/64 subnet. Anyone in earshot can potentially reply. Nodes simply need to be listening for traffic on a reserved UDP port (maybe 61631; there’s an optimisation in 6LoWPAN for 61616-61631). The joining node can ask for the network context, maybe authenticate itself if needed (using asymmetric cryptography – digital signatures, no encryption).

The other nodes presumably already know the answer, but for all nodes to reply simultaneously, would lead to a pile-up. Nodes should wait a randomised delay, and if nothing is heard in that period, they then transmit what they know of the context for the given network ID.

The context information sent back should include:

  • Group SSID
  • Subnet prefix
  • (Optional) Authentication data:
    • Public key of the forming network (joining node will need to maintain its own “trust” database)
    • Hash of all earlier data items
    • Digital signature signed with included public key

Once a node knows the context for its chosen network, it is officially “joined”.

Routing to non-local endpoints

So, a node may wish to send a message to another node that’s not directly reachable. This is, after-all, the whole point of using a routing protocol atop AX.25. If we knew a route, we could encode it in the digipeater path, and use conventional AX.25 source routing. Nodes that know a reliable route are encouraged to do exactly that. But what if you don’t know your way around?

APRS uses WIDEN-n to solve this problem: it’s a dumb broadcast, but it achieves this aim beautifully. n just stands for the number of hops, and it gets decremented with each hop. Each digipeater inserts itself into the path as it sends the frame on. APRS specs normally call for everyone to broadcast all at once, pile-up be damned. FM capture effect might help here, but I’m not sure its a good policy. Simple, but in our case, we can do a little better.

We only need to broadcast far enough to reach a node that knows a route. We’ll use ROUTE-n to stand for a digipeater that is no more than n hops away from the station listed in the AX.25 destination field. n must be greater than 0 for a message to be relayed. AX.25 2.0 limits the number of digipeaters to 8 (and 2.2 to 2!), so naturally n cannot be greater than 8.

So we’ll have a two-tier approach.

Routing from a node that knows a viable route

If a node that receives a ROUTE-n destination message, knows it has a good route that is n or less hops away from the target; it picks a randomised delay (maybe 0-5 seconds range), and if no reply is heard from another node; it relays the message: the ROUTE-n is replaced by its own SSID, followed by the required digipeater path to reach the target node.

Routing from a node that does not know a viable route

In the case where a node receives this same ROUTE-n destination message, does not know a route, and hasn’t heard anyone else relay that same message; it should pick a randomised delay (5-10 second range), and if it hasn’t heard the message relayed via a specific path in that time, should do one of the following:

If n is greater than 1:

Substitute ROUTE-n in the digipeater path with its own SSID followed by ROUTE-(n-1) then transmit the message.

If n is 1 (or 0):

Substitute ROUTE-n with its own SSID (do not append ROUTE-0) then transmit the message.

Routing multicast traffic

Discovering multicast listeners

I’ll have to research MLD (RFC-3810 / RFC-4604), but that seems the sensible way forward from here.

Relaying multicast traffic

If a node knows of downstream nodes that ordinarily rely on it to contact the sender of a multicast message, and it knows the downstream nodes are subscribers to the destination multicast group, it should wait a randomised period, and forward the message on (appending its SSID in the digipeater path) to the downstream nodes.

Application thoughts

I think I have done some thoughts on what the applications for this system may be, but the other day I was looking around for “prior art” regarding one-to-many file transfer applications.

One such system that could be employed is UFTP. Yes, it mentions encryption, but that is an optional feature (and could be useful in emcomm situations). That would enable SSTV-style file sharing to all participants within the mesh network. Its ability to be proxied also lends itself to bridging to other networks like AMPRnet, D-Star packet, DMR and other systems.

6LoWHAM: Route discovery thoughts

Thinking about the routing problem a little more… if I wanted to do a purely “native” routing scheme not involving Net/ROM routing update broadcasts, one has to wonder what such a system would look like.

Net/ROM L3 is really just intended to “bootstrap” things… there’s the prospect of using Net/ROM L4 for tunnelling TCP traffic, but really it’s the L3 part that interests me as a way of hopping between fragments of the mesh that may be linkable via a non-6LoWHAM capable digipeater.

Net/ROM’s periodic broadcasts are inefficient, divulging a node’s entire routing table is not an ideal situation.  So what’s the alternative?  IPv6 nodes already send a “neighbour discovery” packet when they don’t know the MAC address of a neighbour, this is a trigger for a “neighbour advertisement” response.

I’m thinking 6LoWHAM will send NAs periodically anyway.  ACMA rules require identifying every 10 minutes.  Since the NA will include the call-sign of the station (in bit-shifted ASCII), doing that every 10 minutes takes care of the ACMA requirement.  An IPv6 NA message is not a big payload.

Given this will be sent to the ff02::1 multicast group, all nodes able to hear the beaconing station will receive it.  Unlike a IEEE 802.11 or 802.3 network though, not all nodes on the mesh will hear it.

The same is true of ND messages.  If the neighbour is in ear-shot and able to respond, it likely will, but that isn’t a guarantee.  Something in the link-local scope will likely be the answer, probably a daemon listening on a UDP port and sending to the ff02::1 group.

Unicast routing

When a station wishes to make contact with a station that’s not an immediate neighbour, I’m thinking of a broadcast similar to how APRS does things.  APRS uses special call-signs WIDEn-m, where the hop-limit is encoded in those messages.

A UDP message would be constructed asking “Who can reach X within N hops?” and sent to ff02::1 to some “well-known” port.

The first second is reserved for responses from nodes that know a route, either through Net/ROM, or maybe they’ve been in contact with that station before.  They respond something along the lines of “X via A,B,C, quality Q”, where A, B, C are digipeaters and Q is some link quality value.

Not sure how I’ll derive Q just yet.  Possibly based on packet loss… we’ll think of something.

If no responses are heard, the routers that heard the message re-broadcast it and listen for replies.  In the re-broadcast, each router appends its 48-bit 6LoWHAM address and a link quality to the message payload.  The hop limit would also get decremented.  That way, it can break cycles, and it gives a direct unicast path for the distant node to respond.

The same algorithm applies: wait a second for immediate responses, then any routers downstream append their addresses/link quality values, decrement the hop limit, and re-broadcast.

Again, any node that overhears the message (including the target node), may respond.  It does so via a direct unicast, sent using conventional AX.25 digipeating.  Any router en route that relays the message may also cache the result.  The “mesh” gets to learn of where everyone is as-required rather than by default with Net/ROM.

If the hop limit reaches zero, no further re-broadcasts are made, the message stops there.

When the source node hears the replies, each reply resets a 100msec timer.  100msec after the last reply, it chooses three “best” routes, and sends a ICMPv6 ND message via each one to the target station.  The station replies to all three back via those routes with an ICMPv6 NA.  If a message is lost via one of those routes, that route is demoted in quality.

Once replies have arrived back at the source, it picks the best route based on the updated quality information, and begins communications via that route.

Multicast routing

This, is more tricky.  I think the link-local should mean what it means on Thread… that is ff02::/16 just gets processed by immediate neighbours that are in direct RF range.

Realm-local (RFC-7346), ff03::/16 should be used for stuff that’s mesh-wide.  Those messages may be repeated by routers provided those routers have at least one subscriber for the given multicast group/port listening.

Multicast Listener Discovery looks to be the tool for that, although it could do with some 6LoWPAN-style optimisation.

I’m thinking the first time a router hears a datagram destined for a particular group, it should send a query out asking “who is listening” to the said group.

Following that first message, it should be up to the downstream node to inform the local routers that it intends to receive messages from a given group.  This should be periodic, maybe hourly, so that routers are not re-broadcasting messages for a node that has gone off-air.

Routers that have no listeners for a group, do not rebroadcast that group’s traffic.  Similarly, if the hop limit has been exhausted, the messages do not get rebroadcast.

6LoWHAM: A draft protocol specification

Today, I decided to get cuddly with the relevant RFCs and see if I could adapt them into something that would work for AX.25. The following roughly describes how one might stuff IPv6 datagrams into AX.25.

Much of this is heavily influenced by RFC-4944 and RFC-6282, the latter of which looks to be the heart-and-soul of Thread.


Stateless Automatic Addressing

We have a mechanism by which an AX.25 call+SSID can be losslessly mapped to a 48-bit MAC address. This is built on Radix-50 and can work as a stand-in for the EUI-48. The pseudo EUI-48 procedure mentioned in section 6 of the RFC-4944 standard is not required.

An EUI-64 is generated from an EUI-48 by chopping the EUI-48 in half and inserting the bytes ff:fe in the middle. So the EUI-48:

00:11:22:33:44:55

becomes the following EUI-64:

00:11:22:ff:fe:33:44:55

SLAAC therefore will work the same way it does for Ethernet.

Frame format

1. AX.25 UI Frame header

Size: (17 + (D*7) bytes, where D is the number of digipeaters being used

  • PID = 1100 0101 (tentative) IPv6
  • Control = 0000 0011
    • Frame type: UI, P/F = 0 (final)
  • Must contain source and destination AX.25 callsigns, may contain up to 8 digipeater AX.25 callsigns.

For a direct station-to-station contact:

  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
├───┴───┴───┴───┴───┴───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
│       AX.25 Flag (0x7e)       │ Destination AX.25 Call+SSID   │
├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├───────────────────────────────────────────────────────────────┤
│ Source AX.5 Call+SSID                                         │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬───────────────────────────────┤
│                               │          AX.25 PID            │
├───────────────────────────────┴───────────────────────────────┤
╎             AX.25 UI frame payload starts here                ╎
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

or for contact via a few digipeaters:

  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
├───┴───┴───┴───┴───┴───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
│       AX.25 Flag (0x7e)       │ Destination AX.25 Call+SSID   │
├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├───────────────────────────────────────────────────────────────┤
│ Source AX.5 Call+SSID                                         │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬───────────────────────────────┤
│                               │    Digipeater 1 Call+SSID     │
├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├───────────────────────────────────────────────────────────────┤
│ Digipeater 2 Call+SSID                                        │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬───────────────────────────────┤
│                               │          AX.25 PID            │
├───────────────────────────────┴───────────────────────────────┤
╎             AX.25 UI frame payload starts here                ╎
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

2. Mesh Addressing Header

To be used when two stations are not able to directly communicate, or when multicasting.

In this scenario, the AX.25 frame source and destination indicate the addresses of the directly-communicating nodes (e.g. source and digipeater, intermediate digipeaters, or digipeater and destination), and the fields given here will be the addresses of the source and destination AX.25 stations.

e.g. sending from VK4MSL-0 to VK4MDL-9 via
VK4RZB-0 and VK4RZA-0:

  1. First transmission:
    • AX.25 Src: VK4MSL-0
    • AX.25 Dst: VK4RZB-0
    • Mesh Src: VK4MSL-0
    • Mesh Dst: VK4MDL-9
    • Hops: 7
  2. Intermediate hop:
    • AX.25 Src: VK4RZB-0
    • AX.25 Dst: VK4RZA-0
    • Mesh Src: VK4MSL-0
    • Mesh Dst: VK4MDL-9
    • Hops: 6
  3. Final delivery:
    • AX.25 Src: VK4RZA-0
    • AX.25 Dst: VK4MDL-9
    • Mesh Src: VK4MSL-0
    • Mesh Dst: VK4MDL-9
    • Hops: 5

Unlike 802.15.4, we do not have 16-bit short addresses. Since these bits would otherwise always be set to 0, we will use these to provide a 6-bit “hops left” field. We shall use the value 63 (0x3f) to indicate when there are 63 or more hops remaining.

We will use the raw 48-bit addresses here. In keeping with amateur radio conventions, the source and destinations are flipped compared to RFC-4944.

Header format (13 bytes):

  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
├───┴───┼───┴───┴───┴───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
│ 1   0 │       Hops Left       │      Destination Address      │
├───────┴───────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬───────────────────────────────┤
│                               │         Source Address        │
├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│                                                               │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬───────────────────────────────┤
│                               │    Remaining AX.25 Payload    │
├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
╎                                                               ╎
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

4. Fragmentation header

To be used when a IPv6 datagram is greater than L bytes, where L may be defined to be between 64 and 216 bytes.

This part is identical to that of RFC-4944 (section 5.3). I’ll come back to this bit.

5. IPv6 datagram

This can be encoded in a number of ways depending on requirements:

5.1. Raw IPv6 datagram

  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
├───┴───┼───┴───┴───┴───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
│ 0   1 │       6LP_IPV6        │                               │
├───────┴───────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
╎                  Raw IPv6 datagram with payload.              ╎
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

6LP_IPV6 is the value 0x01, as per RFC-4944. The IPv6 datagram is encoded as per RFC-2460, and includes its payload.

The AX.25 frame is finished off with the frame-check sequence.

5.2. Compressed IPv6 datagram

In this format, the datagram fields are compressed, either through making static assumptions, or by deriving them from things such as the AX.25 header, or a previously agreed-to context.

The first field in such payloads is the 6LP_IPHC field:

  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
├───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤
│ 0   1   1 │               6LP_IPV6 with CID=1                 │
├───────────┴───────────────────┬───────────────────────────────┤
│        Context ID Byte        │                               │
├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
╎             Compressed IPv6 datagram with payload.            ╎
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

or without the context ID

  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
├───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤
│ 0   1   1 │               6LP_IPV6 with CID=0                 │
├───────────┴───────────────────────────────────────────────────┤
╎             Compressed IPv6 datagram with payload.            ╎
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

The 6LP_IPHC field is a 13-bit field, optionally followed by a context ID extension byte. The bit allocations are as follows:

  0   1   2   3   4   5   6   7   8   9  10  11  12
├───┴───┼───┼───┴───┼───┼───┼───┴───┴───┼───┼───┼───┤
│  TF   │ NH│ HLIM  │CID│SAC│  SAM      │ M │DAC│DAM│
└───────┴───┴───────┴───┴───┴───────────┴───┴───┴───┘
  • (MSB) 0-1: TF Traffic Class, Flow Label. See 5.2.1 below.
  • 2: NH Next Header encoding
    • =0: Given explicitly
    • =1: Encoded using 6LP_NHC
  • 3-4: HLIM Hop Limit
    • =00: Given explicitly
    • =01: is set to 1
    • =10: is set to 64
    • =11: is set to 255
  • 5: CID Context Identifier Extension
    • =0: No CID byte follows
    • =1: A CID byte follows
  • 6-8: SAC Source Address Compression / SAM Mode
    • =000: No compression applied, whole address given
    • =001: Prefix is link-local prefix, remaining bits are given.
    • =x10: Not used in 6LoWHAM (we don’t support 16-bit addresses)
    • =011: Prefix is link-local, figure the rest out from the source address in the AX.25 header.
    • =100: Unspecified address ::
    • =101: See the context for the prefix, remaining bits are given.
    • =111: Figure out the address from the AX.25 header and context.
  • (LSB) 9-12: M Multicast, DAC Destination Address Compression
    DAM Mode

    • =0000: No compression, not multicast, whole address given
    • =0001: Prefix is link-local prefix, remaining bits are given. Not multicast.
    • =xx10: Not used in 6LoWHAM (we don’t support 16-bit addresses)
    • =0011: Prefix is link-local, figure the rest out from the destination address in the AX.25 header. Not multicast.
    • =0100: Reserved
    • =0101: See the context for the prefix, remaining bits are given. Not multicast.
    • =0111: Figure out the address from the AX.25 header and context. Not multicast.
    • =1000: No compression, multicast address, whole address given
    • =1001: 48-bits of multicast address given, fill in the blanks: ff__::00__:____:____.
    • =1010: 32-bits of multicast address given, fill in the blanks: ff__::00__:____.
    • =1011: 8-bits of multicast address given, fill in the blanks: ff02::00__.
    • =1100: 48-bits RFC-3306/RFC-3956 address, ff__:__LL:PPPP:PPPP:PPPP:PPPP:____:____ where P and L come from the context.
    • =1101: Reserved
    • =1110: Reserved
    • =1111: Reserved

The context ID extension byte has the following format:

  0   1   2   3   4   5   6   7
├───┴───┴───┴───┼───┴───┴───┴───┤
│      SCI      │      DCI      │
└───────────────┴───────────────┘
  • (MSB) 0-3: Source Context Identifier
  • (LSB) 4-7: Destination Context Identifier

These two sub-fields indicate which specific context is being used to fill in the blanks.

5.2.1: Traffic Class and Flow Label

These may be partially or completely omitted depending on the TF setting in the previous field.

  • TF=00:
      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
    ├───┴───┼───┴───┴───┴───┴───┴───┼───┴───┴───┴───┼───┴───┴───┴───┤
    │  ECN  │         DCSP          │ 0   0   0   0 │               │
    ├───────┴───────────────────────┴───────────────┴ ─ ─ ─ ─ ─ ─ ─ ┤
    │                          Flow Label                           │
    └───────────────────────────────────────────────────────────────┘
    
  • TF=01:
      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
    ├───┴───┼───┴───┼───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤
    │  ECN  │ 0   0 │                                               │
    ├───────┴───────┴ ─ ─ ─ ─ ─ ─ ─ ┬───────────────────────────────┤
    │           Flow Label          │                               │
    ├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
    ╎                   Remainder of IPv6 datagram.                 ╎
    └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
    
  • TF=10:
      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
    ├───┴───┼───┴───┴───┴───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
    │  ECN  │         DCSP          │                               │
    ├───────┴───────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
    ╎                   Remainder of IPv6 datagram.                 ╎
    └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
    
  • TF=11: Flow label, ECN and DCSP are set to 0.

5.2.2: Next Header

If 6LP_NHC is not explicitly enabled, the next header byte will appear next.

5.2.3: Hop Limit.

Again, if not explicitly defined in the 6LP_IPHC header, the hop-limit byte will appear next.

5.2.4: Source address

The format here is determined by the values of SAC/SAM:

  • 000: Entire IPv6 address, 16 bytes given here.
  • x01: Last 8-bytes of the address given here
  • For all other values, the source address is omitted.

5.2.5: Destination address

The format here is determined by the values of M/DAC/DAM:

  • x000: Entire IPv6 address, 16 bytes given here.
  • 0x01: Last 8-bytes of the address given here.
  • 1001: 6-bytes of address given here, fill-in-the-blanks.
  • 1010: 4-bytes of address given here, fill-in-the-blanks.
  • 1011: Last byte of address given here, fill-in-the-blank.
  • 1100: 6-bytes of address given here, fill-in-the-blanks.
  • For all other values, the destination address is omitted.

6. 6LoWPAN Next Header

This is used to encode selected IPv6 extensions or L4 protocol headers.

6.1. IPv6 extension headers

A select number of IPv6 extensions may be encoded by replacing the usual “Next Header” byte with the following:

  0   1   2   3   4   5   6   7
├───┴───┴───┴───┼───┴───┴───┼───┤
│ 1   1   1   0 │    EID    │ N │
└───────────────┴───────────┴───┘

where EID (bits 4-6) is one of:

  • =0 IPv6 Hop-By-Hop options
  • =1 IPv6 Routing
  • =2 IPv6 Fragment
  • =3 IPv6 Destination Options
  • =4 IPv6 Mobility
  • =7 IPv6 Header

and N (bit 7) indicates whether the header’s payload is followed by another 6LowPAN Next Header, or a regular IPv6 Next Header (with its “Next Header” byte). For EID=7, N MUST be 0.

Length fields within the header payload should be counted in bytes instead of 8-byte blocks.

7. Datagram payload

7.1. Non-UDP payloads

For payloads other than UDP packets, these should be inserted into the AX.25 payload as-is following the extensions.

UDP packets with uncompressed headers should also be inserted
in this manner.

7.2. UDP payloads with header compression

For these payloads, the following UDP header should be used:

  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
├───┴───┴───┴───┴───┼───┼───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
│ 1   1   1   1   0 │ C │   P   │           Source Port         │
├───────────────────┴───┴───────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
╎                                                               ╎
├───────────────────────────────────────────────────────────────┤
╎                         Destination Port                      ╎
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
╎                                                               ╎
├───────────────────────────────────────────────────────────────┤
│                     Checksum (unless C=1)                     │
└───────────────────────────────────────────────────────────────┘
  • (MSB): bits 0-4: Compressed UDP header marker. Literal 11110₂
  • Bit 5: C Compressed UDP checksum
    • 0= UDP checksum is given (recommended value)
    • 1= UDP checksum is omitted
  • Bits 6-7: P Ports
    • 00=Both source and destination addresses are
      given in full

        0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
      ├───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤
      │                         Source Port                           │
      ├───────────────────────────────────────────────────────────────┤
      │                      Destination Port                         │
      └───────────────────────────────────────────────────────────────┘
      
    • 01=Source port is given in full, Least significant 8-bits of destination given, destination port is 0xff00-0xffff (65280-65535)
        0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
      ├───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤
      │                         Source Port                           │
      ├───────────────────────────────┬───────────────────────────────┤
      │       Destination Port        │                               │
      ├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
      ╎                    Remainder of UDP packet                    ╎
      └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
      
    • 10=Destination port is given in full, Least significant 8-bits of source given, source port is 0xff00-0xffff (65280-65535)
        0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
      ├───┴───┴───┴───┴───┴───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
      │          Source Port          │        Destination Port       │
      ├───────────────────────────────┼───────────────────────────────┤
      │    Destination Port (cont.)   │                               │
      ├───────────────────────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
      ╎                    Remainder of UDP packet                    ╎
      └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
      
    • 11=Only least significant 4-bits of source and destination ports are given. Port LSB range is 0xf0b0-0xf0bf (61616-61631)
        0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
      ├───┴───┴───┴───┴───┴───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┤
      │         Source Port           │       Destination Port        │
      └───────────────────────────────┴───────────────────────────────┘
      

The C bit should only be set if the upper-level application asks for it. Whilst 802.15.4 does its own CRC as does AX.25, the field is mandatory in UDP and the recommendation is to only drop it if the application says it’s okay.

6LoWHAM: Existing AX.25 software

Having discussed the idea with a few people, both on the linux-hams mailing list and off-list, I’m starting to formalise a few plans for how this might work.

One option is to augment existing software stacks and inter-operate not just over-the-air, but at an API level.  Brisbane WICEN have a fleet of TNCs all running TheNet X1J, which was a popular Net/ROM software stack for TAPR TNC2-compatible TNCs in the early 90s.  Slowly, these are being replaced with Raspberry Pis equipped with Pi-TNCs and running LinBPQ.

These two inter-operate quite well, and the plan looks to be, to slowly upgrade all the sites to LinBPQ nodes.

Now, 6LoWHAM on TNCs that are nearly as old as I am just isn’t going to fly, but if I can link up to LinBPQ, this alternate protocol can be packaged up and installed along-side LinBPQ in an unobtrusive manner.

There are two things I need to be able to do:

  • Send and receive raw AX.25 frames
  • Read the routing table from LinBPQ

Sending and receiving raw frames

Looking at the interfaces that LinBPQ (and BPQ32) offers, the most promising option looks to be the AGWPE-compatible interface.  The protocol is essentially a TCP link over which the AX.25 frames are encapsulated and sent.

There’s a good description of the protocol here, and looking at the sources for LinBPQ (third link from the bottom of the page), it looks as if the necessary bits of the protocol are present to send and receive raw frames.

In particular, to send raw UI frames, I need to send these as ‘M’ (direct) or ‘V’ frames (via digipeater), and to receive them, I need to make use of the monitoring mode (‘m’ frame).

Reading the routing table

This, is where things will be “fun”.  The AGWPE interface does offer a “heard” frame, which can report on what stations have been heard.  This I think isn’t going to be the holy grail I’m after, although it’ll be a start, maybe.

Alternatively, a way around this might be to “eavesdrop” on the Net/ROM routing frames.  In monitor mode, I should theoretically hear all traffic, including these Net/ROM beacons.  It’s not as nice as being able to simply read LinBPQ’s routing table, but at least I don’t have to generate the Net/ROM messages.

The other way would be to connect to the terminal interface on LinBPQ, and use the NODES command, parsing that.  Ugly, but it’ll get me by.  On that same page is NRR… which looks to be similar in function to TCP/IP’s traceroute.  The feature is also supported by JNOS 2.0, which was released in 2006.  Not old by packet radio standards, but old enough.

Identifying if a remote station supports 6LoWHAM

Now, this is the tricky bit.  Identifying an immediate neighbour is easy enough, you can simply send an ICMPv6 neighbour solicitation message and see if they respond.  In fact, I’m thinking that could be the immediate first step.  There’s no support for service discovery as such, but nodes could advertise an “alias” (just one).

The best bet may be a suck-it-and-see approach.  We should be able to “digipeat” via intermediate nodes as if they were plain L2 AX.25 digipeaters, thus if we have a reason to contact a given node (i.e. there’s unicast traffic queued up to be sent there), we can just try routing an AX.25 frame with a ICMPv6 neighbour solicitation and see if we get a neighbour advertisement.

This carries a risk though: a station may not react well to unknown traffic and may try to parse the message as something it is not.  Thus for unicast, it is not a fail-safe method.

Multicast traffic however will be a challenge, and much of IPv6 relies on multicast.  The Net/ROM station will not know anything about this, as it simply wasn’t a concept back in the day.

For subnets like ff03::1, which on Thread networks usually means “all full-function Thread devices”, this could be sent via non-6LoWHAM digipeaters by broadcasting via that digipeater to the AX.25 station alias “6LHMC” (6LoWHAM Multicast).

This could be used to provide tunnelling of multicast traffic where a route to a station has been discovered via Net/ROM and we need to safely test whether the station can in fact understand 6LoWHAM traffic without the risk of crashing it.

I think the next step might be to look at how a normal IPv6 node would “register” interest in a multicast group so that routers between it and the sender of such a group know where to forward traffic.  IPv6 does have such a mechanism, and I think understanding how multicast traverses subnets is going to be key to making this work.

6LoWHAM: ARQ over a “mesh” network

So earlier, I had mentioned that it’s really not desirable to have ARQ (automatic repeat request) on a link carrying TCP datagrams.  My comment is based on this observation:

http://sites.inka.de/bigred/devel/tcp-tcp.html

In that article, the discussion is about one TCP connection being tunnelled over another TCP connection.  Basically it comes down to the lower layer buffering and re-sending the TCP datagrams just as the upper layer gives up on hearing a reply and re-sends its own attempt.

Now, end-to-end ACKs have been done on long chains of AX.25 networks before.  It’s generally accepted to be an unreliable mechanism.  UDP for sure can benefit, but then many protocols that use UDP already do their own handling of lost messages.  CoAP for instance does its own ARQ, as does TFTP.

Gerald Wagenknecht, Markus Anwander and Torsten Braun discuss some of the impacts of this on a 802.15.4 network in their thesis “Hop-to-Hop Reliability in IP-based Wireless Sensor Networks – a Cross-Layer Approach“.  In this, they talk about a variant of TCP called TSS: TCP Support for Sensor Networks.  This was discussed at depth in a thesis by Adam Dunkels, “Towards TCP/IP for Wireless Sensor Networks“.

This latter document, was apparently the inspiration for 6LoWPAN.  Section 4.4.3 discusses the approaches to handling ARQ in TCP.  Section 9.6 goes into further detail on how ARQ might be handled elsewhere in the network.

Thankfully in our case, it’s only the network that’s constrained, the nodes themselves will be no smaller than a Raspberry Pi which would have held its own against the PC that Adam Dunkels used to write that thesis!

In short, it looks as if just routing IP packets is not going to cut it, we need to actually handle the TCP side of things as well.  As for other protocols like CoAP, I guess the answer is be patient.  The timeout settings defined in RFC-7252 are usually tuneable, and it may be desirable to back those off just a little for use over AX.25.