Low power wireless: Routing to the internet


Where we got to...

In the first article in this series we configured two Raspberry Pi's to talk using the low power wireless protocol 6LoWPAN over IEEE 802.15.4 using the OpenLabs wireless module. We showed Python code running a server on one to deliver CPU temperature data to a client on another.

...and where we are going

But that isn't the real point of using 6LoWPAN. We could have done the same using Zigbee, Bluetooth Low Energy, Z-Wave or other low power networking systems. The point of using 6LoWPAN is that it creates and sends IPv6 packets. This potentially brings it into the wider internet world where IP packets can be routed across multiple hosts without having to decode and re-code the packets.

The 6LoWPAN network generates IPv6 packets. The internet is very slowly moving across to IPv6, but much of it is still IPv4. If you have to deal with an IPv4 network then these are your choices:

I'm going to assume we can continue to use IPv6, so the RPi is in an IPv6 6LoWPAN network on one side, and an IPv6 ethernet/WiFi network on the other.

This article looks at how to get IPv6 packets from a 6LoWPAN network and route them into an IPv6 network (and of course, back the other way). As in the first article, we will use Raspberry Pi's with the same OpenLabs modules. And just like in the first article, there are a number of problems to be resolved along the way.

The goal is to get suitable IPv6 addresses generated on all the 6LoWPAN devices, and for one of these devices to act as a router (an "edge router") between the 6LoWPAN network and some other IPv6 network. This article ends up really being about routing between IPv6 link local networks, joining them into a global IPv6 network.

IPv6 address types

IPv6 has several different types of address, just like IPv4 does. Link local addresses are only visible on a single link, and you can't route them. They are like link local IPv4 addresses. They are in the address range fe80::/10. There are also site local addresses in the range fec0::/10 but these are deprecated. Multicast addresses are in the range ff00::/8. The loopback address is ::1/128. Every other address is a global address.

Getting a fixed link local address

Whenever you reboot the Openlabs module, it gives itself a new MAC address. This is used to generate the link local address, a process that is now common on IPv6 devices. Later we will see this used as part of the process to generate a global address. For the gateway, we will need a fixed global address, or external clients won't know how to find it. So for the gateway, we should fix the MAC address to ensure we get a 'known' global address.

General IPv6 addresses are also difficult to read and remember - 16 hexadecimal numbers. There are special simplification rules for addresses with zeroes in them, so I will exploit those so we get simple addresses (for this article only, of course!). I will set simple addresses on the gateway (needed) and on the sensor (convenient).

The MAC address 02:0:0:0:0:0:0:1 generates the IPv6 host address ::1 which is about as simple as we can get. We set that on the gateway by

	ip link set dev wpan0 address 02:0:0:0:0:0:0:1
to give IPv6 host part ::1 and for convenience on the sensor by
	ip link set dev wpan0 address 02:0:0:0:0:0:0:2
to give IPv6 host part ::2

Getting a routable IPv6 address

When any host starts its networking, it is automatically assigned an IPv6 link local address, based on its MAC address which we have just set on the gateway and sensor RPi's. Routing tables are also set up on the local link, so hosts on the same link can talk to each other directly.

It's easy to tell which addresses are link local addresses, they start with the prefix fe80:. The program ifconfig on any Linux/Unix box will show something like

	inet6 addr: fe80::84f1:df50:eb27:97ff/64 Scope:Link
Because I've fixed the MAC address on the gateway, it is just
	inet6 addr: fe80::1/64 Scope:Link

Link local packets aren't routable, that is, you can't send packets from one link to another link. To route packets from one link to another, they must have a unique global or unique local address.

Unique global addresses will be given to you by your internet provider or you buy them from an organisation such as ARIN. Australia's internet providers are way behind and hardly any of them support IPv6, and I don't want to buy one when I can't use it yet. But unique local addresses are good enough: I can route them across my private network for free, across all my network segments. I probably won't ever want to broadcast my temperature data across the whole internet anyway - at most I would process it on my own network or send it up to a particular cloud service.

The web site http://unique-local-ipv6.com/ generates random unique local /48 prefixes such as

That leaves you 80 bits (128-48) for any subnets you want to create, and unique addresses within those subnets. So you can specify any 80 bits you want, or easier, any 20 hex digits. I'm going to cheat a bit, and simplify this to prefix fd28::/64 as a 64-bit prefix. I'll use this on the gateway explicitly by setting
	ip addr add fd28::1/64 dev lowpan0

Packet forwarding

The RPi we are going to use as the gateway must have two NICs. Well, ours does: the 6LoWPAN device and the ethernet device. But just like any router in any Unix system it has to be configured for packet forwarding between the NICs. This is really easy: edit the file /etc/sysctl.conf and uncomment the line

Then reboot, and it is an IPv6 router.

Router advertisements

We now have one fixed routable address that will be used for external clients to talk to the gateway/router. We also have a fixed link local address for hosts on this local link to talk to the gateway. At present we have only one other RPi in the network, but our 6LoWPAN network might consist of hundreds or even thousands of nodes, and these need to be configured too, ending up with routable addresses so that external clients can get and set information on the sensors/actuators. But we don't want to be manually assigning addresses to every one of them!

The answer is stateless address autoconfiguration using router solicitation and router advertisements. We have to set up and configure router advertising, but then it becomes a no-brainer. This is the IPv6 equivalent of DHCP.

A new IPv6 node attempting to join a network will send out a router solicitation message using IPv6 multicast on its link local network. A router will then generate a router advertisement which it will send back using unicast, which will contain enough information for the new node to configure itself.

The information supplied in the router adverts is basically two components:

That's why we need a fixed link local address for the router, to be used in router adverts. This is in addition to the fixed routable address so that external clients can talk to the 6LoWPAN side of the gateway.

radvd - router advertisement daemon for IPv6

The Linux daemon to act as as router advertisement daemon for Unix-like systems is radvd. The version in the RPi repositories (1.9.1) is unfortunately out of date, so you have to get a current version from Github and build it. I got version 2.13.

git clone https://github.com/linux-wpan/radvd.git -b 6LoWPAN
cd radvd
./configure --prefix=/usr/local --sysconfdir=/etc --mandir=/usr/share/man
sudo make intall
You may have to install bison and dlex from the repositories if it can't findyacc or flex and autoconf if it can't find autoreconf/.

Once built and installed, radvd uses a configuration file /etc/radvd.conf with contents

interface lowpan0
    AdvSendAdvert on;
    # UnicastOnly on;
    AdvCurHopLimit 255;
    AdvSourceLLAddress on;

    prefix fd28::/64
        # AdvOnLink off;
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;

    abro fe80::1
        AdvVersionLow 10;
        AdvVersionHigh 2;
        AdvValidLifeTime 2;
This is adapted from Sebastian Meiling's page Setup native 6LoWPAN router using Raspbian and RADVD The prefix is the random prefix I used earlier, fd28::/64. The abro ("Authoritative Border Router Option") is the link local address of the router. You will need to set your own addresses - at a minimum, the routable prefix.

I've made a couple of changes to Sebastian's configuration: I've set AdvOnLink to On whereas he has it as Off. Setting the advert to On means two things:

I've also removed the UnicastOnly on setting. The reasons are

The setting UnicastOnly on stops radvd from sending out these adverts so we need to remove it to allow the routing tables on hosts to be renewed.

Router configuration

All the work on the 6LoWPAN side is now done. On the ethernet side I also want to have an IPv6 network, and as I am using unique local addresses this network will be my private network, probably with many link segments. To change it to be internet global, I would just need to change the unique local addresses to unique global addresses.

Initially I had problems routing IPv6 packets on my private network. My home router (a Linksys EA6900) didn't seem to want to route packets from my "external" host through to the gateway. I fixed that by using a cross-over cable directly from my "external" host to the gateway, and then after all the home router decided to co-operate . Then with radvd also delivering adverts on the ethernet side to my "external" host I could ping from the 6LoWPAN network to the ethernet network and vice versa.

In summary, the steps we go through on the 6LoWPAN side are

The radvd configuration file is described above. The startup script for the rest should be run as root and is


# set the MAC address
ip link set dev wpan0 address 02:0:0:0:0:0:0:1

iwpan dev wpan0 set pan_id 0xbeef
ip link add link wpan0 name lowpan0 type lowpan
ifconfig wpan0 up
ifconfig lowpan0 up

# set the gateway address on the 6LoWPAN side
ip addr add fd28::1/64 dev lowpan0

# start the router advert daemon
radvd -m stderr

On the ethernet side I had also configured /etc/radvd.conf to deliver adverts with prefix fd44::, but didn't get around to simplifying the ethernet MAC addresses.

The resulting IPv6 addresses on the gateway are

eth0      Link encap: ...
          inet6 addr: fd44:::4adf:10a9:5c79:7954/64 Scope:Global
          inet6 addr: fe80::4adf:10a9:5c79:7954/64 Scope:Link

lowpan0   Link encap: ...
          inet6 addr: fd28::1/64 Scope:Global
          inet6 addr: fe80::1/64 Scope:Link

Sensor configuration

The RPi acting as sensor doesn't have to do so much: radvd does most of it. The startup script is just


ip link set dev wpan0 address 02:0:0:0:0:0:0:2
iwpan dev wpan0 set pan_id 0xbeef
ip link add link wpan0 name lowpan0 type lowpan
ifconfig wpan0 up
ifconfig lowpan0 up

But - courtesy of radvd - the device now has an IPv6 routable address fd28::2 as shown by ifconfig:

lowpan0   Link encap: ...
	  inet6 addr: fd28::2/64 Scope:Global
          inet6 addr: fe80::2/64 Scope:Link

The routing table on the sensor RPi looks like

$ route -A inet6
Kernel IPv6 routing table
Destination   Next Hop   Flag Met Ref Use If
fd28::/64     ::         UAe  256 0     0 lowpan0
fe80::/64     ::         U    256 0     0 lowpan0
::/0          fe80::1    UGDAe 1024 0     0 lowpan0
ff00::/8      ::         U    256 1    18 lowpan0
As you can see, addresses with our prefix fd28::/64 are on the link through the lowpan0 device. The address ::/0 is the default route address so all other packets are routed through the lowpan0 NIC via the Next Hop address fe80::1.

Testing routing

You can test this from each RPi by pinging the other RPi. That just tests local routing though. To test this properly, you need to be able to talk through the ethernet/WiFi NIC on the RPi router to another IPv6 device.

I've got the RPi router talking to an "external" host through a cross-over cable for simplicity, with radvd delivering router adverts to it. This sets up routing through the gateway automatically.

So then from the "external" host I can ping my RPi sensor:

$ping6 fd28::2
PING fd28::2(fd28::2) 56 data bytes
64 bytes from fd28::2: icmp_seq=1 ttl=254 time=14.0 ms
64 bytes from fd28::2: icmp_seq=2 ttl=254 time=16.4 ms
64 bytes from fd28::2: icmp_seq=3 ttl=254 time=17.9 ms
If you get successful pings, then you know it works.

With that in place, the server code we had in the last article can be modified to use routable addresses rather than link local addresses. This basically means that we don't have to specify the "scope id" (the NIC) any more temp_global_server.py:


import socket
from subprocess import PIPE, Popen

HOST = ''       # Symbolic name meaning all available interfaces
PORT = 2016     # Arbitrary non-privileged port

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

def main():
    s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
    s6.bind((HOST, PORT, 0, 0))
    while True:
        conn, addr = s6.accept()

if __name__ == '__main__':


The "external" client gets modified similarly, omitting the scope id temp_global_client.py:


import socket
import time

ADDR = 'fd28::2'
PORT = 2016

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

        # get it again after 10 seconds

if __name__ == '__main__':


The output from that on the client is



This article has shown that 6LoWPAN devices can communicate to other IPv6 systems on a routable network. It has been mainly a journey about configuring IPv6 systems and setting up the IPv6 equivalent of DHCP. However, the story for low power wireless isn't over yet. The IoT at the application layer is standardising on the CoAP and MQTT protocols, and in the next article we will look at the CoAP application protocol.