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.
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:
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 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.
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:1to 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:2to give IPv6 host part
::2
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:LinkBecause 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
fd28:e5e1:869::/48That 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
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
net.ipv6.conf.all.forwarding=1Then reboot, and it is an IPv6 router.
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:
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 ./autogen.sh ./configure --prefix=/usr/local --sysconfdir=/etc --mandir=/usr/share/man make sudo make intallYou 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:
radvd
; and
radvd
,
entries will be made in the routing table to route
fd28::/64
addresses through the 6LoWPAN device.
Addresses with this prefix are "on this link".
::/0
)
will route using the lowpan0
NIC
through the link local gateway address fe80::1
to the external world.
I've also removed the UnicastOnly on
setting.
The reasons are
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.
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
/etc/radvd.conf
radvd
The radvd
configuration file is described above. The
startup script for the rest should be run as root and is
#!/bin/bash # 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
The RPi acting as sensor doesn't have to do so
much: radvd
does most of it.
The startup script is just
#!/bin/bash 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 lowpan0As 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
.
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 msIf 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:
#!/usr/bin/python3 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)) s6.listen(1) while True: conn, addr = s6.accept() conn.send(get_cpu_temperature()) conn.close() if __name__ == '__main__': main()
The "external" client gets modified similarly, omitting the scope id temp_global_client.py:
#!/usr/bin/python3 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 time.sleep(10) if __name__ == '__main__': main()
The output from that on the client is
temp=38.5'C temp=38.5'C temp=39.0'C ...
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.