Skip to content

802.15.4

thewasabiguy edited this page Aug 13, 2023 · 1 revision

Some advice and scripts on how to get 802.15.4 working and solving RFCTF challenges.

Background

Standard for low-power, low data rate wireless communication between small devices and forms the basis wireless personal area networks (WPANs) with low transmitter power, small MTU, low power consumption and low cost. Because of these facts 802.15.4 is ideally used in industrial control and monitoring, sensor networks, intelligent agriculture, security systems and smart grids. 802.15.4 is a MAC and PHY layer protocol (OSI layers 1 and 2) which is used by many types of higher-level protocols (layer 3) like Zigbee, Z-Wave and 6Lowpan.

Building Support

Kernel

Assumes a host platform of RPi3 B running rasbian stretch in userspace with a 4.9 kernel. The openlabs ATMEL 802.15.4 radio is the radio transceiver being used (https://openlabs.co/OSHW/Raspberry-Pi-802.15.4-radio). The following was also tested on a 5.2 kernel but the userspace didn't seem to work. YMMV with any other kernel version than 4.9.

To install a 4.9 kernel you can easily do the following

rpi-update 936a8dc3a605c20058fbb23672d6b47bca77b0d5

Or build the kernel yourself. For this you'll need to make sure some modules are already built with your kernel. Whether we enable them as modules or as built-ins doesn't much matter, but we'll have to be sure to copy the modules over to the RPi later if we choose to build them as modules. At the very least the following will need to be built.

CONFIG_IEEE802154
CONFIG_IEEE802154_6LOWPAN
CONFIG_MAC802154
CONFIG_IEEE802154_AT86RF230
CONFIG_IEEE802154_FAKELB

Follow the instructions at https://www.raspberrypi.org/documentation/linux/kernel/building.md in order to build the proper kernel. Remember to install the modules as well if you built them that way.

Overlays

The AT86RF233 is an SPI device, so it can't be auto-enumerated and needs to go into the device tree so the kernel is aware of it at boottime; essentially telling the kernel which driver to use and what the pinout is. Because of this we'll need to install the appropriate overlay. This can be handled in one of two ways.

Use Existing Overlay

For the particular kernel in use (5.0.15) there already exists an overlay for the radio transceiver (should be at86rf233-overlay.dtb or at86rf233.dtbo). You'll need to just copy this to /boot/overlays/.

From here just plug in the radio transceiver into pins 15-26 where the hat points inwards towards the pi. You should have 7 pins to the left and to right (centered) among all the gpio pins. From here just enable the transceiver in the /boot/config.txt by adding/uncommenting the following line.

dtoverlay=at86rf233

Now reboot the pi!

Build Overlay

If the pi has no overlay for this transceiver (at86rf233-overlay.dtb or at86rf233.dtbo) by default you'll need to build and install one yourself. First install the device tree compiler.

apt-get install device-tree-compiler

Then create the overlay file at86rf233-overlay.dts with the following contents.

/dts-v1/;
/plugin/;

/* Overlay for Atmel AT86RF233 IEEE 802.15.4 WPAN transceiver on spi0.0 */

/ {
        compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709";

        fragment@0 {
                target = <&spi0>;
                __overlay__ {
                        #address-cells = <1>;
                        #size-cells = <0>;

                        status = "okay";

                        lowpan0: at86rf233@0 {
                                compatible = "atmel,at86rf233";
                                reg = <0>;
                                interrupt-parent = <&gpio>;
                                interrupts = <23 4>; /* active high */
                                reset-gpio = <&gpio 24 1>;
                                sleep-gpio = <&gpio 25 1>;
                                spi-max-frequency = <3000000>;
                                xtal-trim = /bits/ 8 <0xf>;
                        };
                };
        };

        fragment@1 {
                target = <&spidev0>;
                __overlay__ {
                        status = "disabled";
                };
        };

        fragment@2 {
                target = <&gpio>;
                __overlay__ {
                        lowpan0_pins: lowpan0_pins {
                                brcm,pins = <23 24 25>;
                                brcm,function = <0 1 1>; /* in out out */
                        };
                };
        };

        __overrides__ {
                interrupt = <&lowpan0>, "interrupts:0",
                        <&lowpan0_pins>, "brcm,pins:0";
                reset     = <&lowpan0>, "reset-gpio:4",
                        <&lowpan0_pins>, "brcm,pins:4";
                sleep     = <&lowpan0>, "sleep-gpio:4",
                        <&lowpan0_pins>, "brcm,pins:8";
                speed     = <&lowpan0>, "spi-max-frequency:0";
                trim      = <&lowpan0>, "xtal-trim.0";
        };
};

Compile and install:

dtc -@ -O dtb -o at86rf233.dtbo at86rf233-overlay.dts
cp at86rf233.dtbo /boot/overlays/.

From here just plug in the radio transceiver into pins 15-26 where the hat points inwards towards the pi. You should have 7 pins to the left and to right (centered) among all the gpio pins. From here just enable the transceiver in the /boot/config.txt by adding/uncommenting the following line.

dtoverlay=at86rf233

Now reboot the pi!

Testing for Success

To test that the 802.15.4 network stack is in place you can easily look for the loaded modules if you built them that way.

root@raspberrypi:/home/pi# lsmod | grep 802
mac802154              73728  1 at86rf230
ieee802154             81920  1 mac802154
crc_ccitt              16384  1 mac802154
cfg80211              610304  1 brcmfmac

Or you can load up some of the userspace tools and that will provide feedback that the 802.15.4 stack is in place.

Userspace Tools

At the very least install both wpan-tools and lowpan-tools. If needed a recent version of wpan-tools is available to download and build at https://github.com/linux-wpan/wpan-tools. The lowpan-tools can be found at https://github.com/linux-wpan/lowpan-tools.git (horribly outdated source code).

apt-get install lowpan-tools wpan-tools

List current IEEE 802.15.4 wpan interfaces.

root@raspberrypi:/home/pi# iz list
wpan0 (3)
    link: IEEE 802.15.4 MAC interface
    phy phy0
    hw 2e:6c:d2:a0:52:d3:22:74 pan 0xffff short 0xffff

Access the nl802154 netlink inteface and checking the network connectivity and capabilities of the attached 802.15.4 interfaces.

root@raspberrypi:/home/pi# iwpan list
wpan_phy phy0
supported channels:
	page 0: 11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 
current_page: 0
current_channel: 13,  2415 MHz
cca_mode: (1) Energy above threshold
cca_ed_level: -80
tx_power: 4
capabilities:
	iftypes: node,monitor 
	channels:
		page 0: 
			[11]  2405 MHz, [12]  2410 MHz, [13]  2415 MHz, 
			[14]  2420 MHz, [15]  2425 MHz, [16]  2430 MHz, 
			[17]  2435 MHz, [18]  2440 MHz, [19]  2445 MHz, 
			[20]  2450 MHz, [21]  2455 MHz, [22]  2460 MHz, 
			[23]  2465 MHz, [24]  2470 MHz, [25]  2475 MHz, 
			[26]  2480 MHz  
	tx_powers: 
			4 dBm, 3.7 dBm, 3.4 dBm, 3 dBm, 2.5 dBm, 2 dBm, 
			1 dBm, 0 dBm, -1 dBm, -2 dBm, -3 dBm, -4 dBm, 
			-6 dBm, -8 dBm, -12 dBm, -17 dBm, 
	cca_ed_levels: 
			-94 dBm, -92 dBm, -90 dBm, -88 dBm, -86 dBm, -84 dBm, 
			-82 dBm, -80 dBm, -78 dBm, -76 dBm, -74 dBm, -72 dBm, 
			-70 dBm, -68 dBm, -66 dBm, -64 dBm, 
	cca_modes: 
		(1) Energy above threshold
		(2) Carrier sense only
		(3, cca_opt: 0) Carrier sense with energy above threshold (logical operator is 'and')
		(3, cca_opt: 1) Carrier sense with energy above threshold (logical operator is 'or')
	min_be: 0,1,2,3,4,5,6,7,8 
	max_be: 3,4,5,6,7,8 
	csma_backoffs: 0,1,2,3,4,5 
	frame_retries: 0,1,2,3,4,5,6,7 
	lbt: false

If the above userspace tools work then the 802.15.4 stack in the kernel is in place. From here be sure to setup the second pi. You did buy two RPis and two radios, right?

802.15.4 Tricks

Pinging at the 802.15.4 layer

Pinging at the IEEE 802.15.4 level is a good test to make sure that the hats are working correctly and you can communicate. Make sure you can get this working before moving on.

Server setup on one of the pis.

ip link set wpan0 down
iwpan phy0 set channel 0 15
iwpan dev wpan0 set pan_id 0x1111
iwpan dev wpan0 set short_addr 0x0001
ip link set wpan0 up

$ wpan-ping -d 0x0002   

Client setup on the other pi.

ip link set wpan0 down
iwpan phy0 set channel 0 15
iwpan dev wpan0 set pan_id 0x1111
iwpan dev wpan0 set short_addr 0x0002
ip link set wpan0 up

$ wpan-ping -a 0x0001  -s 5 -c5
PING 0x0001 (PAN ID 0x1111) 5 data bytes
5 bytes from 0x0001 seq=0 time=4.9 ms
5 bytes from 0x0001 seq=1 time=4.8 ms
5 bytes from 0x0001 seq=2 time=5.1 ms
5 bytes from 0x0001 seq=3 time=5.8 ms
5 bytes from 0x0001 seq=4 time=4.7 ms

--- 0x0001 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss
rtt min/avg/max = 4.695/5.075/5.818 ms

802.15.4 Chat

A chat session can be started on a net PANid, using addr sourceAddr talking with destAddr. This is included with the iz toolset (lowpan-tools) which can be used to chat directly over the 802.15.4 interface. On one pi assign the pan id the source and target address.

$ izchat 0x1111 0x0002 0x0001
help
> hello

On the other pi

$ izchat 0x1111 0x0001 0x0002
> help
hello

Monitor Mode (tcpdump + wireshark)

The iz tool makes it very easy to add a monitor interface to a 802.15.4 physical interface. By doing this promiscuous mode just works; this includes packets complete with original FCS and packets that aren't valid 802.15.4. First find the physical interface exposed by the radio.

root@raspberrypi:/home/pi# iz listphy
phy0       IEEE 802.15.4 PHY object
    page: 0  channel: 13
    channels on page 0: 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

You'll want to delete all wpan interfaces which sit on top of the wpan phy. You can get a list of all currently running phy interfaces with iwpan and then delete each one using iwpan. This is done because when sniffing we'll need to change the channel of the phy which will affect all wpan interfaces attached. So only having a single wpan interface (monitor) will help in this respect.

iwpan dev
iwpan dev $IFNAME del

Now activate a mon0 interface on the displayed physical interface

iz monitor phy0 mon0

From here you can tell that the mon0 interface is in monitor mode by using the iwpan tool

root@raspberrypi:/sys/class/net/mon0# iwpan dev
phy#0
	Interface mon0
		ifindex 5
		wpan_dev 0x2
		extended_addr 0x0000000000000000
		short_addr 0xffff
		pan_id 0xffff
		type monitor
		max_frame_retries 3
		min_be 3
		max_be 5
		max_csma_backoffs 4
		lbt 0
		ackreq_default 0

Change the channel to the channel you care to sniff.

iwpan phy phy0 set channel 0 25

Check that the channel is correct

root@raspberrypi:/sys/class/net/mon0# iz listphy
phy0       IEEE 802.15.4 PHY object
    page: 0  channel: 25
    channels on page 0: 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

Then bring the interface up

ifconfig mon0 up

Wireshark will work with an 802.15.4 interface out of the box. From here we're gonna sniff packets on the RPi with tcpdump and send them over ethernet using netcat to Wireshark running on another computer. On thr other computer start wireshark listening on the port.

nc -vlp 9000 | wireshark -ki -

Then on the pi start tcpdump sending over netcat. Be sure to set the correct ip of the laptop

tcpdump -i mon0 -Uw - | nc <laptop ip> 9000

To switch channels (monitor) we can just run the set channel command in a loop.

# Background tcpdump so we can switch the channel
Ctrl-Z && bg

# Switch the channel every 30 seconds
while true; do for i in $(seq 11 26); do iwpan phy phy0 set channel 0 $i; echo $i; sleep 30; done; done

With wireshark you should be able to see the chat data and the ping data. There does seem to be a lot of malformed packets in the capture and not sure if maybe this is a result of no antenna or what...

6lowpan Tricks

Pinging at the 6loWPAN layer

On of the pis

echo 0 >  /proc/sys/net/ipv6/conf/all/disable_ipv6

ip link set lowpan0 down
ip link set wpan0 down

iwpan dev wpan0 set pan_id 0xcafe
iwpan phy0 set channel 0 26

ip link add link wpan0 name lowpan0 type lowpan

ip link set wpan0 up
ip link set lowpan0 up

ip addr add 2001:db8::1/64 dev lowpan0 

On the other pi

echo 0 >  /proc/sys/net/ipv6/conf/all/disable_ipv6

ip link set lowpan0 down
ip link set wpan0 down

iwpan dev wpan0 set pan_id 0xcafe
iwpan phy0 set channel 0 26

ip link add link wpan0 name lowpan0 type lowpan

ip link set wpan0 up
ip link set lowpan0 up

ip addr add 2001:db8::2/64 dev lowpan0

Ping over IPv6

$ ping6 -I lowpan0 <full ipv6 address if the other pi>
PING fe80::f86f:2842:b16f:20ae(fe80::f86f:2842:b16f:20ae) from fe80::c8fe:ff:fe00:2%lowpan0 lowpan0: 56 data bytes
64 bytes from fe80::f86f:2842:b16f:20ae%lowpan0: icmp_seq=1 ttl=64 time=12.0 ms
64 bytes from fe80::f86f:2842:b16f:20ae%lowpan0: icmp_seq=2 ttl=64 time=14.3 ms
64 bytes from fe80::f86f:2842:b16f:20ae%lowpan0: icmp_seq=3 ttl=64 time=12.9 ms
^C
--- fe80::f86f:2842:b16f:20ae ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 12.056/13.119/14.308/0.933 ms

ssh over 6lowpan

If you got the 6lowpan ping to work then you can easily ssh from one pi to the other over 6lowpan.

ssh pi@fe80::f86f:2842:b16f:20ae%lowpan0

6lowpan Server + Client

Included is some python code allowing a 6lowpan server and client to exchange temperature data; assuming you have both 6lowpan interfaces already up and running according to the above instructions.

6lowpan_client.py

#!/usr/bin/env python3

# Connect to the server and grab its temp

import socket
import time

 # the other RPi
ADDR = 'fe80::f04a:bde1:f1da:32e7'
PORT = 2016

scope_id = socket.if_nametoindex('lowpan0')
while True:
    s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
    s6.connect((ADDR, PORT, 0, scope_id))
    data = s6.recv(1024)
    print(data.decode('utf-8'), end='')

    # get it again after 10 seconds
    time.sleep(10)

6lowpan_server.py

#!/usr/bin/env python3

# Wait for clients to connect and report back this pis temp

import socket
from subprocess import PIPE, Popen

HOST = ''
PORT = 2016

def get_cpu_temperature():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
    output, _error = process.communicate()
    return output

s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
s6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
scope_id = socket.if_nametoindex('lowpan0')
s6.bind((HOST, PORT, 0, scope_id))
s6.listen(1)

while True:
    conn, addr = s6.accept()
    conn.send(b'server: ' + get_cpu_temperature())
    conn.close()
# Server
$ ./6lowpan_server.py

# Client
$ ./6lowpan_client.py 
server: temp=47.8'C
server: temp=47.2'C

Packet Analysis

4 types of packets

  • beacon: Sent by Coordinator to set up the Superframe structure. Transmitted by coordinators periodically. Offers synchronization to the network and to identify the network and its structure.
  • ack: Provide confirmation of reception
  • data: transfers application data.
  • command: Associate, Disassociate,Beacon request, GTS request

beacon-enabled versus nonbeacon-enabled network: passive vs active scanning

pages and channels: page 0: 0 at 868MHz, 1-10 in 915mhz, 11-26 in 2.4GHz, 27-31 reserved page 1: -10 page 2: 0-10 page 3: 0-13 page 4: 0-15 page 5: 0-7 page 6: 0-10

  • frame type: beacon, ack, data, command
  • Security Enabled=1 and Frame Version>0 (frame versions 2006 and later), an Auxiliary Security Header field will be present in the MHR and will need to be further parsed by the packet processor if this is a MAC Command frame (see 4.1.3 Source Address Matchingsection below). There is no other use of Security Enabled by the packet processor
  • Frame Pending: ignored
  • Ack Request: Will be stored internally by the sequence manager. An auto-TxAck frame will follow the incoming receive frame if necessary conditions are met
  • PAN ID Conpression:
  • Sequence Number: Directly following the Frame Control Field is the Sequence Number field. The Sequence Number field of the MHR is captured by the packet processor. If an auto-TxAck frame follows the incoming receive frame, the captured Sequence Number will be copied to the transmitted Acknowledge packet, and the captured Frame Version will be inserted into the Frame Control Field of the transmitted packet.
        .... .... .... .001 = Frame Type: Data (0x1)
        .... .... .... 0... = Security Enabled: False
        .... .... ...0 .... = Frame Pending: False
        .... .... ..1. .... = Acknowledge Request: True
        .... .... .1.. .... = PAN ID Compression: True
        .... .... 0... .... = Reserved: False
        .... ...0 .... .... = Sequence Number Suppression: False
        .... ..0. .... .... = Information Elements Present: False
        .... 10.. .... .... = Destination Addressing Mode: Short/16-bit (0x2)
        ..00 .... .... .... = Frame Version: IEEE Std 802.15.4-2003 (0)
        10.. .... .... .... = Source Addressing Mode: Short/16-bit (0x2)
    Sequence Number: 93
    Destination PAN: 0x1111
    Destination: 0x0001
    Source: 0x0002
    FCS: 0xbdf8 (Incorrect, expected FCS=0xae25)
    [Expert Info (Warning/Checksum): Bad FCS]

LimeSDR and Wireshark

In wireshark udp setup use rftap (lower case) Listen udp:port must match GNU rfTAP. From wireshark startview (config dialog): Wireshark - Interface Options:UDP Listener UDP remore capture:udpdump Listen Port should match what is in GmuRadio:Socket PDU Port number, such as 52006 Payload Type: rftap

In GnuRadio/RFtap Encapulation use: Type=Custom dissector, Data Link Type = -1, Disector=wpan NOTE lower case wpan is the id of IEEE 802.15.4 N.B. this is different from the RFtap example as Data Link Type 195 did not work for me

Clone this wiki locally