You would never break the chain (Part 10/12) 🕊️
Paul Tagliamonte 2026-09-08 diyNow that we have a working Layer 1, we have a way to send a block of bits from one place to anyone who cares to listen to us. This is very welcome news, but we are now facing a new, different and just as fun question – what shape should that data take?
Given our incredibly limited functionality of our nodes, we could definitely skip all this work and just stuff an IP packet into the link; but I decided to not since I am (eventually) interested in adding some sort of spanning tree-like protocol to implement network switching so not all nodes need to communicate directly with all other nodes – but that day is not today.

Given i’m going to stub most of that out, let’s take a look at what some
similar Layer 2 protocols use – things like Ethernet or WiFi frames. Both
contain structured information regarding the transmitter, desired recipient,
type of data, and the higher-level data itself (such as IP packets). As a
result of attempting to learn from others, the Pigeon Layer 2 (called, simply,
“link”) is also split into a fixed-length header, followed by the contents
described by the header.
The header is a fixed-length (23 byte) structure, which contains the source
MAC address (src mac), destination MAC address (dst mac), the ITU
coordinated ham radio callsign of the control operator of this message
(callsign), the type of payload to follow (type; defined below), and
the length of the data to follow the header (length as a 16 bit big-endian
unsigned integer).
The type field indicates how the payload is to be interpreted – currently
I’ve only defined 3 possible payload types so far:
| Type | Description |
| 0x01 | Raw (testing only) |
| 0x02 | Cal |
| 0x04 | Ipv6 |
Keen observers will perhaps infer that there used to be an Ipv4 type at
0x03 – which is true – however, i’ve since removed it since i’ve never once
used it and the codepath was more trouble than it was worth. As is my wont,
I’ve optend to just lean into Ipv6-only IP transport – it’s easy enough to
shim ipv4 in, if someone REALLY wanted to using something like 64:ff9b:1::/48
and a bit of code in the transmitter/receiver (or even using something like
jool and unbound’s
dns64-prefix
at the router). I don’t think I’ll bring it back, but just in case I have
to for some reason in the future, it’s there.
Additionally, friends of the pod may also recognize that this structure is basically the exact same structure as what I had in PACKRAT, which, is also true. I started this project off maintaining interoperability in the Layer 2 for pigeon and packrat, but at some point just gave up on it during one of the many cleanups. I’m hopeful I can maintain compatibility going forward, and that won’t have to muck with this header too much more. We’ll see what happens once I start to push the bounds of what is possible with pigeon.
Hopefully it feels like carrying IP data inside this frame to be a pretty
self-explanatory exercise – the 0th byte of the payload is the 0th byte of
an IPv6 header (followed by all the usual stuff, like UDP or TCP header(s) and
any carried data, just like you’d find anywhere else.