Setup 6LoWPAN Compression

We assume that you were able to set RADVD to sends packet through a virtual interface. If no, go back to OpenRaDvd.

OpenLBR 6LoWPAN

This page describes the compaction/decompaction of IPv6 headers at the Border Router of the Wireless Sensor Network (OpenLBR):

The packets that come from the Internet to the WSN carry a full IPv6 header. The mission of the OpenLBR is to read the IPv6 header, replace and compact this header into a shorter 6LoWPAN header. The packet with the compacted header then travels across the OpenWSN Network. This involves compacting addresses and removing redundant fields, wherever possible. The details of this procedure are detailed in the IETF document Compression Format for IPv6 Datagrams in 6LoWPAN Networks (draft-ietf-6lowpan-hc-07).

Similarly, the packets that come from the WSN whose destination is the Internet carry a compacted 6LoWPAN header that can only be used on the OpenWSN network. This header needs to be inflated to generate an full IPv6 header which replaces it so this packet can now travel across the Internet.

The OpenLBR is in charge of compacting and decompacting IPv6/6LoWPAN headers. Building upon the previous sections on IPv6 integration, the tasks to achieve this are:

  • Create a virtual network interface (see Setup 6LoWPAN Compression);
  • Configure an address and route to the virtual interface for the packets destined to the OpenWSN network (see Setup 6LoWPAN Compression);
  • Have a mote connected and configured properly so that the OpenLBR can send/receive packets to/from the mote (See Setup 6LoWPAN Compression);
  • Read raw packets on the virtual interface destined for the OpenWSN, compact the IPv6 to a 6LoWPAN header, and send the packet to the mote via serial port;
  • Read raw packets via the serial port from the mote, inflate an IPv6 header from the 6LoWPAN header, and send the packet to the Internet via the Ethernet network interface of the OpenLBR.

OpenLBR in Python

The rest of this page details how to achieve this functionality in Python. First of all we need to import the required modules:

#!python
import os
import sys
import struct
from fcntl import ioctl
import binascii

Then we need to define some variables/constants:

#!python
IPV6PREFIX   = '2001:470:1f05:98e'
IP64B_PREFIX = ''.join(['\x20','\x01','\x04','\x70','\x1f','\x05','\x09','\x8e'])
IFF_TUN      = 0x0001
TUNSETIFF    = 0x400454ca

IPHC_TF_4B         = 0
IPHC_TF_3B         = 1
IPHC_TF_1B         = 2
IPHC_TF_ELIDED     = 3

IPHC_NH_INLINE     = 0
IPHC_NH_COMPRESSED = 1

IPHC_HLIM_INLINE   = 0
IPHC_HLIM_1        = 1
IPHC_HLIM_64       = 2
IPHC_HLIM_255      = 3 

IPHC_CID_NO        = 0
IPHC_CID_YES       = 1

IPHC_SAC_STATELESS = 0
IPHC_SAC_STATEFUL  = 1

IPHC_SAM_128B      = 0
IPHC_SAM_64B       = 1
IPHC_SAM_16B       = 2
IPHC_SAM_ELIDED    = 3

IPHC_M_NO          = 0
IPHC_M_YES         = 1

IPHC_DAC_STATELESS = 0
IPHC_DAC_STATEFUL  = 1

IPHC_DAM_128B      = 0
IPHC_DAM_64B       = 1
IPHC_DAM_16B       = 2
IPHC_DAM_ELIDED    = 3

As a reminder, a 40B IPv6 header looks like this (without options):

We need the following two functions to assemble and disassemble an IPv6 packet into a data structure consisting of the IPv6 header fields, and the IPv6 payload. These functions ease further processing.

#!python
# disassemble IPv6 packet into dictionnary with IPv6 header fields and payload
def disassemble_ipv6_packet(data):
   pkt = {};
   pkt['version']        = ord(data[0]) >> 4
   pkt['traffic_class']  = ((ord(data[0]) & 0x0F) << 4) + (ord(data[1]) >> 4)
   pkt['flow_label']     = ((ord(data[1]) & 0x0F) << 16) + (ord(data[2]) << 8) + ord(data[3])
   pkt['payload_length'] = (ord(data[4]) << 8) + ord(data[5])
   pkt['next_header']    = ord(data[6])
   pkt['hop_limit']      = ord(data[7])
   pkt['src_addr']       = data[8:8+16]
   pkt['dst_addr']       = data[24:24+16]
   pkt['payload']        = data[40:len(data)]
   return pkt

# reassemble an IPv6 packet previously disassembled
def reassemble_ipv6_packet(pkt):
   pktw = []
   pktw.append(chr((6 << 4) + (pkt['traffic_class'] >> 4)))
   pktw.append(chr( ((pkt['traffic_class'] & 0x0F) << 4) + (pkt['flow_label'] >> 16) ))
   pktw.append(chr( (pkt['flow_label'] >> 8) & 0x00FF ))
   pktw.append(chr( pkt['flow_label'] & 0x0000FF ))
   pktw.append(chr( pkt['payload_length'] >> 8 ))
   pktw.append(chr( pkt['payload_length'] & 0x00FF ))
   pktw.append(chr( pkt['next_header'] ))
   pktw.append(chr( pkt['hop_limit'] ))
   for i in range(0,16):
      pktw.append( pkt['src_addr'][i] )
   for i in range(0,16):
      pktw.append( pkt['dst_addr'][i] ) 
   pktws = ''.join(pktw)
   pktws = pktws + pkt['payload']
   return pktws

The following functions are used to print the packets in different formats. They help us know what's going on for debugging purposes. Note that the last one uses the output_wireshark() function defined in OpenVirtualInterface.

#!python
# print IPv6 packet
def print_ipv6(pkt):
   print "\n--IPv6 packet--"
   print "Version:", pkt['version']
   print "Traffic class:", pkt['traffic_class']
   print "Flow label:", pkt['flow_label']
   print "Payload length:", pkt['payload_length']
   print "Next header:", pkt['next_header']
   print "Hop limit:", pkt['hop_limit']
   print "Src address:", binascii.hexlify(pkt['src_addr'])
   print "Dst address:", binascii.hexlify(pkt['dst_addr'])
   print "Payload:", binascii.hexlify(pkt['payload'])

# print 6lowPAN packet
def print_lowpan(pkt6):
   print "\n--6LowPAN packet--"
   output_wireshark(pkt6)

The following function is a minimal function to compact the IPv6 header into a 6LoWPAN header:

#!python
#compact IPv6 header into 6LowPAN header
def ipv6_to_lowpan(pkt, tf, nh, hlim, cid, sac, sam, m, dac, dam):
   # header
   pkt6 = [];
   # Byte1: 011(3b) TF(2b) NH(1b) HLIM(2b)
   pkt6.append(chr((3 << 5) + (tf << 3) + (nh << 2) + (hlim << 0)))
   # Byte2: CID(1b) SAC(1b) SAM(2b) M(1b) DAC(2b) DAM(2b)   
   pkt6.append(chr((cid << 7) + (sac << 6) + (sam << 4) + (m << 3) + (dac << 2) + (dam << 0)))
   # tf
   if (tf == IPHC_TF_3B):
      pkt6.append(chr((pkt['flow_label'] & 0xff0000) >> 16))
      pkt6.append(chr((pkt['flow_label'] & 0x00ff00) >> 8))
      pkt6.append(chr((pkt['flow_label'] & 0x0000ff) >> 0))
   elif (tf == IPHC_TF_ELIDED):
      # no tf
      pass
   elif (tf == IPHC_TF_4B):
      # unsupported
      pass
   elif (tf == IPHC_TF_1B):
      # unsupported
      pass
   else:
      print "ERROR: Wrong TF value."
   # next header
   if (nh == IPHC_NH_INLINE):
      pkt6.append(chr(pkt['next_header']))
   elif (nh == IPHC_NH_COMPRESSED):
      # unsupported
      pass
   else:
      print "ERROR: Wrong NH value."
   # hop limit
   if (hlim == IPHC_HLIM_INLINE):
      pkt6.append(chr(pkt['hop_limit']))
   # IPHC_HLIM1,64,255 unsupported
   else:
      print "ERROR: Wrong HLIM value."
   # source address
   if (sam == IPHC_SAM_ELIDED):
      # no sa
      pass
   elif (sam == IPHC_SAM_16B):
      pkt6.append(pkt['src_addr'][14])
      pkt6.append(pkt['src_addr'][15])
   elif (sam == IPHC_SAM_64B):
      for i in range(8,16):
         pkt6.append(pkt['src_addr'][i])
   elif (sam == IPHC_SAM_128B):
      for i in range(1,16):
         pkt6.append(pkt['src_addr'][i])
   else:
      print "ERROR: Wrong SAM value."
   # destination address
   if (dam == IPHC_DAM_ELIDED):
      # no da
      pass
   elif (dam == IPHC_DAM_16B):
      pkt6.append(pkt['dst_addr'][14])
      pkt6.append(pkt['dst_addr'][15])
   elif (dam == IPHC_DAM_64B):
      for i in range(8,16):
         pkt6.append(pkt['dst_addr'][i])
   elif (dam == IPHC_DAM_128B):
      for i in range(1,16):
         pkt6.append(pkt['dst_addr'][i])
   else:
      print "ERROR: Wrong DAM value."
   # payload
   #print "6LowPAN header:", len(pkt6), " Bytes, Payload:", len(pkt['payload']), " Bytes."
   #if (len(pkt['payload'])+len(pkt6) > 120): # TODO: adjust this value
   #   print "WARNING: This datagram of length ", len(pkt['payload'])+len(pkt6), " should be fragmented."
   for i in range(0,len(pkt['payload'])):
      pkt6.append(pkt['payload'][i])
   pkt6s = ''.join(pkt6)
   return pkt6s

The following function is a minimal function to inflate the 6LoWPAN header into an IPv6 header:

#!python
# inflate 6LoWPAN header into IPv6 header
def lowpan_to_ipv6(data):
   pkt = dict()
   ptr = 2
   if ((ord(data[0]) >> 5) != 0x003):
      print "ERROR: Not a 6LowPAN packet"
      return   
   # tf
   tf = (ord(data[0]) >> 3) & 0x03
   if (tf == IPHC_TF_3B):
      pkt['flow_label'] = (ord(data[ptr]) << 16) + (ord(data[ptr+1]) << 8) + (ord(data[ptr+2]) << 0)
      ptr = ptr + 3
   elif (tf == IPHC_TF_ELIDED):
      pkt['flow_label'] = 0
   else:
      # not supported or erroneous
      print "ERROR: unsupported or erroneous flow label format"
   # next header
   nh = (ord(data[0]) >> 2) & 0x01
   if (nh == IPHC_NH_INLINE):
      pkt['next_header'] = ord(data[ptr])
      ptr = ptr+1
   elif (nh == IPHC_NH_COMPRESSED):
      # unsupported
      pass
   else:
      print "ERROR: erroneous next header format"
   # hop limit
   hlim = ord(data[0]) & 0x03
   if (hlim == IPHC_HLIM_INLINE):
      pkt['hop_limit'] = ord(data[ptr])
      ptr = ptr+1
   elif (hlim == IPHC_HLIM_1):
      pkt['hop_limit'] = 1
   elif (hlim == IPHC_HLIM_64):
      pkt['hop_limit'] = 64
   elif (hlim == IPHC_HLIM_255):
      pkt['hop_limit'] = 255
   else:
      print "ERROR: erroneous hop limit format"
   # source address
   sam = (ord(data[1]) >> 4) & 0x03
   if (sam == IPHC_SAM_ELIDED):
      pkt['src_addr'] = ''.join([chr(0)]*16) # TODO: is this what we want here?
   elif (sam == IPHC_SAM_16B):
      a1 = data[ptr]
      a2 = data[ptr+1]
      ptr = ptr+2
      s = ''.join(['\x00','\x00','\x00','\x00','\x00','\x00',a1,a2])
      pkt['src_addr'] = IP64B_PREFIX+s
   elif (sam == IPHC_SAM_64B):
      pkt['src_addr'] = IP64B_PREFIX+join(data[ptr:ptr+8])
      ptr = ptr + 8
   elif (sam == IPHC_SAM_128B):
      pkt['src_addr'] = data[ptr:ptr+16]
      ptr = ptr + 16
   else:
      print "ERROR: erroneous source address format"
   # destination address
   dam = (ord(data[1]) & 0x03)
   if (dam == IPHC_DAM_ELIDED):
      pkt['dst_addr'] = ''.join([chr(0)]*16) # TODO: is this what we want here?
   elif (sam == IPHC_DAM_16B):
      a1 = data[ptr]
      a2 = data[ptr+1]
      ptr = ptr+2
      s = ''.join(['\x00','\x00','\x00','\x00','\x00','\x00',a1,a2])
      pkt['dst_addr'] = IP64B_PREFIX+s
   elif (sam == IPHC_DAM_64B):
      pkt['dst_addr'] = IP64B_PREFIX+join(data[ptr:ptr+8])
      ptr = ptr + 8
   elif (sam == IPHC_DAM_128B):
      pkt['dst_addr'] = data[ptr:ptr+16]
      ptr = ptr + 16
   else:
      print "ERROR: erroneous destination address format"
   # rest
   pkt['version'] = 6
   pkt['traffic_class'] = 0
   pkt['payload'] = data[ptr:len(data)]
   pkt['payload_length'] = len(pkt['payload'])
   #print_ipv6(pkt)
   return pkt

All these functions can be used in a main Python program as follows. We assume here that you have already created a virtual interface, configured its IPv6 address, set a route and enabled IPv6 forwarding (see OpenVirtualInterface). Here f is the handler returned when opening the tun interface (typically /dev/net/tun).

#!python
while True:
   #get IPv6 packet from tun interface
   data = os.read(f,1500)
   data = data[4:len(data)]
   #disassemble the IPv6 packet
   pkt = disassemble_ipv6_packet(data)
   print "\n\n"
   print_ipv6(pkt)
   #compact IPv6 to 6LoWPAN
   pkt6s = ipv6_to_lowpan(pkt, IPHC_TF_ELIDED, IPHC_NH_INLINE, IPHC_HLIM_INLINE, \
                        IPHC_CID_NO, IPHC_SAC_STATELESS, IPHC_SAM_16B, IPHC_M_NO, \
                        IPHC_DAC_STATELESS, IPHC_DAM_16B)
   print_lowpan(pkt6s)
   #inflate 6LoWPAN to IPv6
   pkt_dec = 6lowpan_to_ipv6(pkt6s)
   print_ipv6(pkt)
   #reassemble the IPv6 packet
   pkt_ipv6 = reassemble_ipv6_packet(pkt)
   print binascii.hexlify(pkt_ipv6)
   pkt_check = disassemble_ipv6_packet(pkt_ipv6)
   print_ipv6(pkt_check)

The program above reads a packet from the virtual interface, prints it, compacts it, prints it, inflates it, prints it. In practice, you want it to send the compact version to the mote, and inflates compacted packets it received from the mote.

If everything works, go on to Setup 6LoWPAN Compression.