Jan 122019
 

For 6LoWHAM, it could work that we just use the link-local address space to directly communicate between stations and leave it at that.

If I want to send a message to VK4BWI-5 from my station VK4MSL-9, I could just fire off a packet to fe80::6894:49ff:feae:7318 directed to my 6LoWHAM interface and be done with it. This then requires one of two things:

  1. that VK4BWI-5 can directly communicate with me
  2. that the intermediate stations know to forward my message on to that station

(1) is easy enough. (2) raises the question of “what is local”?

Supposing that this protocol took off, and suddenly the WIA decides to earmark special frequencies on a few bands for 6LoWHAM, with a fairly complete network stretching up the eastern seaboard of Australia. If my station sends a router solicitation from my home QTH in Brisbane, does someone in Melbourne really care to hear it? I’d wager this is a recipe for a very clogged packet network!

In Thread, the “link local” scope only gets you as far as the nodes that can directly hear you. It does mean that protocols like mDNS, which rely on the “link-local” multicast scope aren’t going to reach all nodes, but it also means that far flung nodes don’t need to listen to all the low-level chatter. For communications between nodes, an “on-mesh” prefix is used, and for mesh-wide multicast, a “realm-local” prefix of ff03::/64 is defined.

In truth, it’s highly unlikely that we’d have “one” single network. More likely it’ll be a mesh of interconnected networks with trunk links going via some other band (or perhaps VPNs over the Internet). For that to work, we can’t rely on just link-local networking, we actually need a routable network address for the mesh.

The Thread “mesh local” prefix is actually defined by the network’s extended IEEE-802.15.4 PAN ID, which is a 64-bit number that you define when setting up the network. Thread simply takes the most significant 40 bits of this, slaps fd in front and pads it out with zeros to 64-bits. The PAN ID 0x0123456789abcdef forms the subnet fd01:2345:6789::/64. This can be seen in the OpenThread sources.

This wastes 16-bits of address space normally reserved for the ULA subnet ID and throws away 24-bits of the PAN ID. For our network, we don’t need 16-bits worth of subnets, we just need one. We also don’t have a PAN ID in AX.25.

The thinking is, we’ll use a “group” address. This will be a regular AX.25 SSID, which will translate to a MAC which has the group bit set. (Exactly how I’ll differentiate between a station SSID and a group SSID I’m not sure. Probably will look at the destination IP, if it’s multicast then the group bit gets set.)

Supposing we were to use this for the International Rally of Queensland (an event which is now defunct), we might create a 6LoWHAM network with a group address of “IROQ19”. The MAC address used for group-wide communications would be 03:01:cd:e5:a9:f8.

We can derive a prefix from this MAC address. A ULA normally consists of a 7-bit ULA prefix, a 1-bit “global/local” bit, a 40-bit global ID, and a 16-bit subnet ID.

The ULA prefix is fc::/7. The global/local bit is always set to 1 (local) because no one has come up with a way that ULAs can be globally administered. 40 bits is a bit tight, we could truncate our MAC to 40 bits and ignore the subnet ID like Thread do, that gives us a subnet of fd03:1cd:5ea9::/64.

The last 3 bits of the SSID though, are like a subnet ID. So if we move those 3 bits to set the last 3 bits of the prefix, we can make some use of that subnet ID, but still waste 13 bits with zeros.

Alternatively, we can consider the global ID and subnet ID to be one 56-bit field. We effectively shrink the subnet ID to 3 bits. That gives us a 53-bit global ID, which now fits the remaining 45-bits of our MAC and leaves us with 8 bits left over.

We can discard the lowest two bits in the first byte of the MAC as those (the group and local bits) will be the same for all groups, so that gives us another two bits. 10 bits isn’t a lot, but it’s enough to encode “AR” (amateur radio) in ITA-2, thus giving us a recognisable subnet mask for all 6LoWHAM networks. We wind up with the following:

┌─ULA─┐L┌──"AR"──┐┌───────────── Network Address ──────────────┐
1111110100010010100000000000000111001101111001011010100111111000
└──┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
   f   d   1   2 : 8   0   0   1 : c   d   e   5 : a   9   f   8 /64

This actually has me thinking whether the call-sign part of the SSID should be right-padded out to make the network address consistent. Maybe my SSID to MAC algorithm could do with a tweak there as it may make routing easier as it’ll put all those zeros to the right.

In Thread, the mesh-local prefix isn’t route-able beyond the mesh, there’s a separate prefix handed out by border routers for that. In our case, I don’t think there’s any point in complicating matters by having more than one route-able prefix for a mesh. If a station participates in two networks that share a frequency, then sure, that node may have an address on each network, but each network should share a common identity.

Thus in the contrived example of having a large network along the coastline: it’d be an “inter-network” of smaller meshes, linked together via router nodes which know how to hop between them. Those routes may be via point-to-point microwave links, HF, Internet tunnels, etc.

The subnets used for these other networks may be assigned a “context identifier” which is 4-bits. I’ll have to figure out if there’s a sane way to do that on a given network. Most 802.15.4 networks have a “PAN co-ordinator” which could be looking after that. Thread networks elect a “leader” node.

Given the small number of identifiers, and the low probability of this being used, this should be manually administered. Even without a context ID being assigned, one can still route between the subnets, just that the full IPv6 address needs to be given for the foreign node, so you incur a 16-byte penalty doing so. Thus the context IDs will probably be handed out for “popular routes”, with the mesh prefix being “context 0”.

I haven’t yet given thought to how this “context” would be disseminated over the mesh or kept updated. That is a can of worms for another day.

Jan 122019
 

One of the aims of 6LoWHAM was to provide a means to send IPv6 traffic between user applications and the AX.25 network.

In order to do this, the applications have to have some way of injecting their IP traffic. The canonical way this is done is through the operating system’s TCP/IP stack. This requires that we have an interface to the operating system kernel in order to receive that IP traffic destined for the airwaves.

Now, we could write a kernel driver for this, but it’s going the long way around to do it. Especially as we intend to interface to software that runs in userspace for the actual transmission. Our driver at best would be just taking the raw Ethernet frame, extracting the IP part, and forwarding that back to our program running in userspace.

There’s a driver that does that for us: TUN/TAP. This driver can either create a TUNnel device, which forwards IP datagrams, or a TAP device, which forwards Ethernet frames. We’ll focus on the TUN mode of this driver here.

The idea is this will create an IP tunnel, with one side exposing a network device to the kernel, and the other side being a file descriptor in a userspace application that just reads and writes raw IP frames. How it generates and processes those frames is entirely up to the software author. Most famous uses for this device are VPNs, so taking the IP datagram, encrypting it, then encapsulating it in an IP datagram (usually UDP) to be sent over the Internet to some other peer, which reverses the process and writes the original packet to its tunnel file descriptor.

In our case, we’ll be dissecting it a bit to extract the key fields, then applying our own “compression” defined in the 6LoWHAM specs, then forwarding it on to our AX.25 stack (probably LinBPQ or Direwolf) to be sent as an AX.25 UI frame.

The first step in this journey was actually figuring out what the packets look like on a tunnel device. I created this little program to explore the idea.

It just needs the usual C toolchain and libraries on a Linux system. I tested with Gentoo and Linux kernel 4.15. Building it is a simple make command. If you then run the resulting binary as root, you’ll find a tun0 device (or maybe some other number) created.

Bring the interface up, and you should start to see some traffic as the host tries to talk to is new (and very much mute) peer:

RC=0 stuartl@rikishi ~/projects/6lowham/packetdumper $ make 
cc    -c -o linuxtun.o linuxtun.c
cc    -c -o main.o main.c
cc -o packetdumper linuxtun.o main.o
RC=0 stuartl@rikishi ~/projects/6lowham/packetdumper $ sudo ./packetdumper 
Password: 
^Z
[1]+  Stopped(SIGTSTP)        sudo ./packetdumper
RC=148 stuartl@rikishi ~/projects/6lowham/packetdumper $ sudo ip link set dev tun0 up
RC=0 stuartl@rikishi ~/projects/6lowham/packetdumper $ fg
sudo ./packetdumper
Flags: 0x0000  Protocol: 0x86dd
  48:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
   0: 60 00 00 00 00 08 3a ff fe 80 00 00 00 00 00 00
  16: 5e be 89 41 7b 19 d5 60 ff 02 00 00 00 00 00 00
  32: 00 00 00 00 00 00 00 02 85 00 44 bd 00 00 00 00
Flags: 0x0000  Protocol: 0x86dd
  48:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
   0: 60 00 00 00 00 08 3a ff fe 80 00 00 00 00 00 00
  16: 5e be 89 41 7b 19 d5 60 ff 02 00 00 00 00 00 00
  32: 00 00 00 00 00 00 00 02 85 00 44 bd 00 00 00 00

I didn’t bother to decode the IP datagram further, but if you look at the Wikipedia IPv6 Packet article, it isn’t difficult to see what’s going on. In this case, we can see it’s an IPv6 packet both from the Protocol field (0x86dd is the Ethertype for IPv6), and from the first 4 bits of the frame payload.

The traffic class and flow label are both 0s here. The IPv6 payload length is just 8 bytes, so most of this is in fact IPv6 header data. Next header is type 0x3a (IPv6 ICMP) and the hop limit is 255. This is followed by the source address (my laptop’s link-local address fe80::5ebe:8941:7b19:d560) and the destination address (all link-local routers multicast address ff02::2).

The ICMPv6 message is the last 8 bytes; and in this case, it’s type is 0x85 (router solicitation), the code is 0x00, the two bytes after that are the checksum and the message (4 bytes) is all zeros.

Quite how that address was chosen is something I’ll have to get to grips with. Yes, it’s SLAAC, but where did it get the hardware address from? That I’ll have to figure out.

The alternative is to use a TAP interface, which means I choose the MAC address, and thus can control what the SLAAC-derived address becomes. Ohh, and it goes without saying that the privacy extensions will be a big no no on the air: we’re relying on the fact that we can derive the IPv6 address from the SSID of the station both for technical reasons and to legally meet the requirements for stations to “identify” who they are and whom they are talking to. SLAAC privacy will make a mess of that.

So controlling this link-local address is a must. I guess next stop: let’s look at a tap device. I’ve just made some changes to explore the differences from the application end. There isn’t a lot of difference here.

RC=130 stuartl@rikishi ~/projects/6lowham/packetdumper $ sudo ./packetdumper -tap
Password: 
^Z
[1]+  Stopped(SIGTSTP)        sudo ./packetdumper -tap
RC=148 stuartl@rikishi ~/projects/6lowham/packetdumper $ sudo ip link set tap0 up
RC=0 stuartl@rikishi ~/projects/6lowham/packetdumper $ fg
sudo ./packetdumper -tap
Flags: 0x0000  Protocol: 0x86dd
  90:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
   0: 33 33 00 00 00 16 ce 65 0c 34 48 34 86 dd 60 00
  16: 00 00 00 24 00 01 00 00 00 00 00 00 00 00 00 00
  32: 00 00 00 00 00 00 ff 02 00 00 00 00 00 00 00 00
  48: 00 00 00 00 00 16 3a 00 05 02 00 00 01 00 8f 00
  64: 27 22 00 00 00 01 04 00 00 00 ff 02 00 00 00 00
  80: 00 00 00 00 00 01 ff 34 48 34
Flags: 0x0000  Protocol: 0x86dd
  86:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
   0: 33 33 ff 34 48 34 ce 65 0c 34 48 34 86 dd 60 00
  16: 00 00 00 20 3a ff 00 00 00 00 00 00 00 00 00 00
  32: 00 00 00 00 00 00 ff 02 00 00 00 00 00 00 00 00
  48: 00 01 ff 34 48 34 87 00 af 03 00 00 00 00 fe 80
  64: 00 00 00 00 00 00 cc 65 0c ff fe 34 48 34 0e 01
  80: 61 78 48 c1 ac aa

The big difference is now we have an Ethernet header prepended. The proto field in the packet information now duplicates what we can see in the Ethernet frame header (bytes 12 and 13), and the IPv6 packet starts from byte 14.

I think this is the mode 6LoWHAM will use. It’s possible to set the MAC address on the created tap0 device to whatever 46 bits we like, the remaining two bits in the MAC address are for defining whether the address is global or local (we’ll set ours to “local”), and the other sets whether this is a multicast or unicast address. The SLAAC address will closely match this address with two differences:

  1. The MAC will have the bytes 0xff 0xfe inserted into the middle.
  2. The “global/local” bit is inverted. So for the 2001:db8::/64 prefix:
    • aa:bb:cc:dd:ee:ff becomes 2001:db8::a8bb:ccff:fedd:eeff
    • a8:bb:cc:dd:ee:ff becomes 2001:db8::aabb:ccff:fedd:eeff

That latter point had me confused at first, I thought it might’ve been that a bit got cleared, but instead it’s just inverted, so completely reversible.