BLIP Tutorial

From TinyOS Wiki
Revision as of 18:37, 13 September 2009 by Stevedh (talk | contribs) (add rev of blip tutorial)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

BLIP Tutorial

BLIP, the Berkeley Low-power IP stack, is an implementation in tinyos of a number of IP-based protocols. Using blip/tinyos, you will be able to form multi-hop IP networks consisting of different motes communicating over shared protocols. Due to the rapid evolution of the relevent IETF and IEEE standards, blip is currently not completely standards compliant; however, it does provide significant interoperability with other IP networks.

Goal and Prerequisites

In this tutorial, you will learn how to install and configure a blip-based IPv6 subnet. You should already have a working tinyos environment installed for any platforms you plan on using. Additionally, a Linux device or virtual machine image with standard tools (gcc, autoconf) is necessary for the edge router.

6lowpan/IPv6 Basics

A complete introduction to IPv6 network topology and addressing are beyond the scope of this tutorial; unfamiliar readers may wish to familiarize themselves with some of the concepts at play here.

hosts vs. routers: in an IP network, devices may participate as hosts or routers. Generally, hosts do not forward packets or participate in routing protocols, while routers do. Every mote running the blip network stack functions as a router and is capable of forwarding packets and making routing decisions. Motes thus form a multihop IP subnetwork.

subneting: The smallest unit of network management in IPv6 is the subnet. In blip, a subnet consists of a number of motes (node routers) and one or more higher function devices (edge routers) which perform a number of other routing functions for the network. The edge routers also may provide routing to other networks. In terms of network topology, these blip subnets are generally configured as stub networks.

MTU: the Maximum Transfer Unit refers to the largest payload which may be sent in a link-layer data frame. Most blip node routers use the IEEE 802.15.4 physical layer, which limits the MTU to around 100 octets. To provide the larger payloads to upper-layer protocols, blip implements 6lowpan "layer 2.5" fragmentation; as a result, the maximum size IP-layer datagram is 1280 octets.

Configuration Overview

Commissioning a simple blip subnetwork is not complicated. Here are the steps necessary to do so:

  • install and activate an edge router
  • (optional) configure routing to other networks
  • begin activating blip node routers.

Step 1: Install an Edge Router

Note: the edge router implementation is currently supported for all Linux-based platforms; it is also possible to cross-compile for use on embedded devices. Windows/cygwin support is forthcoming.

This step should be performed on the device to be used as the edge router; typically a laptop (for development) or an embedded linux device (for production.)

Steps:

  • Add an 802.15.4 interface to the device. This is typically accomplished using a mote programmed with a radio-serial bridge.
  • Compile and run the routing driver.

An application used to provide an IEEE 802.15.4 interface is located in $TOSROOT/apps/IPBaseStation. Go to that directory, and install the image on an attached mote.

$ cd $TOSROOT/apps/IPBaseStation
$ make <platform> blip install

Depending on your platform, you may need some extra arguments. For instance, to install program this on an attached micaz, I do

$ make micaz blip install mib510,/dev/ttyUSB0

Next, we need to build the routing driver. It has one dependency, the tinyos serial library (libmote.a). To build it, do the following.

$ cd $TOSROOT/support/sdk/c/sf
$ ./bootstrap.sh
$ ./configure
$ make

If bootstrap fails, you may need to install the necessary automake/autoconf packages for your distribution.

Next, repeat essentially the same steps to build the driver, in the $TOSROOT/support/sdk/c/blip directory.

$ cd $TOSROOT/support/sdk/c/blip
$ ./bootstrap.sh
$ ./configure
$ make

If these steps succeeded, you have successfully build the routing driver for the edge router; the binary is in driver/ip-driver

You can begin running the driver using the following:

$ sudo driver/ip-driver /dev/ttyUSB1 micaz

If you installed the IPBaseStation image on a different platform, use the appropriate platform here.

Edge Router Configuration

The routing driver loads its configuration from a file when it starts. The default file is located in $TOSROOT/support/sdk/c/blip/serial_tun.conf. This file controls the IPv6 prefix used for the subnet, the address of the router on that subnet (both are taken from the 'addr'), as well as the 802.15.4 channel used. The channel set here must be the same as the channel used by node routers. The default file is shown below. It is a good start for experimentation.

# Before you can run the adaptation layer and router, you must
# configure the address your router will advertise to the subnet, and
# which network interface to proxy neighbor advertisements on.  
#

# set the debug level of the output
# choices are DEBUG, INFO, WARN, ERROR, and FATAL
# log DEBUG

# set the address of the router's 802.15.4 interface.  The interface
# ID must be a 16-bit short identifier.
addr fec0::64

# the router can proxy neighbor IPv6 neighbor discovery on another
# interface so that other machines on the subnet can discover hosts
# routing through this router.  This specifies which interface to proxy
# the NDP on.
proxy lo

# which 802.15.4 channel to operate on.  valid choices are 11-26.
channel 15

Step 2: Install and Activate Node Routers

Once an edge router has been commissioned, you can begin installing node routers. A sample application is provided in $TOSROOT/apps/UDPEcho. This application provides a UDP echo service on port 7, as well as a very simple UDP-based shell on port 2000. To install this application on an attached mote,

$ cd $TOSROOT/apps/UDPEcho
$ make <platform> blip install.ID

Again, you may need to add additional make flags depending on your target. Also, ensure the channel specified in the Makefile is the same as the channel being used by the edge routing driver.

Once installed, the newly installed mote should check in soon with the edge router. Its IPv6 address is formed by taking the prefix specified in serial_tun.conf and appending the node ID specified when the mote image was installed. For instance, if the prefix specified in the config file was fec0:: and the node id as 1, the mote would have address fec0::1.

You may need to install additional packages on your system to get utilities like ping6, tracert6, and nc6. Once you have done so, you should be able to verifiy the link to your newly installed node:

$ ping6 fec0::1
$ tracert fec0::1
$ nc6 -u fec0::1 2000

The final "nc6" command is merely a command pipe to the node; type "help" to get a list of commands provided by that mote.

Additional Topics

This basic tutorial to this point has covered installing an instance of a blip network. The next step is to begin developing your own applications.

Routing Driver Shell

The edge-routing driver provides an interactive shell on the console and over TCP port 6106. You may connect to the latter using telnet.

Type "help" for a list of commands. Some of the most useful ones are listed here.

  • links': the driver maintains the link state of the network. This displays the link state reported by each node router.
  • rebuild: topology information occasionally becomes very stale and out of date. This command rebuild routing state across all attached node routers from scratch. Use sparingly.
  • routes: packets from the edge router destined to a node router are source routed. This command displays the cached routes that will be used.

Programming Interface

blip is designed to allow you to easily build your own IP-based applications. This section of the tutorial covers some of the important basics.

Addressing Structures

The ip-stack provides a bare IP datagram interface to the network layer; this is documented in comments in the code.

For the purposes of socket programming, two data structures are most important. The 'struct sockaddr_in6' and the 'struct in6_addr'. They are substantially shared with the linux/bsd versions, and reproduced below.

struct in6_addr
  {
    union
      {
      uint8_t   u6_addr8[16];
      uint16_t u6_addr16[8];
      uint32_t u6_addr32[4];
      } in6_u;
#define s6_addr                 in6_u.u6_addr8
#define s6_addr16               in6_u.u6_addr16
#define s6_addr32               in6_u.u6_addr32
  };

struct sockaddr_in6 {
  uint16_t sin6_port;
  struct in6_addr sin6_addr;
};

void inet_pton6(char *addr, struct in6_addr *dest);


Usage

Example 1: Suppose we want to setup a sockaddr_in6 to point to ff02::5, port 10000:

 {
   struct sockaddr_in6 sa6;
   inet_pton6("ff02::5", &sa6.sin6_addr);
   sa6.sin6_port = htons(10000);
 }

Example 2: Do the same thing, but without the overhead of storing and parsing the string address representation.

 {
   struct sockaddr_in6 sa6;
   memset(&sa6, 0, sizeof(struct sockaddr_in6));
   sa6.sin6_addr.s6_addr16[0] = htons(0xff02);
   sa6.sin6_addr.s6_addr[15] = 5;
   sa6.sin6_port = htons(10000);
 }

UDP

blip provides a UDP sockets layer as a basic application transport service. The UDP interface is located in

tos/lib/net/b6lowpan/interfaces/UDP.nc

and is simple:

interface UDP {

  /*
   * bind a local address.  to cut down memory requirements and handle the 
   * common case well, you can only bind a port; all local interfaces are 
   * implicitly bound.  the port should be passed in host byte-order (is 
   * this confusing?
   */
  command error_t bind(uint16_t port);

  /*
   * send a payload to the socket address indicated
   * once the call returns, the stack has no claim on the buffer pointed to
   */ 
  command error_t sendto(struct sockaddr_in6 *dest, void *payload, 
                         uint16_t len);

  /*
   * indicate that the stack has finished writing data into the
   * receive buffer.  if error is not SUCCESS, the payload does not
   * contain valid data and the src pointer should not be used.
   */
  event void recvfrom(struct sockaddr_in6 *src, void *payload, 
                      uint16_t len, struct ip_metadata *meta);

}

Usage

Each socket must be allocated using the generic component UdpSocketC.

For clients, no initialization is necessary; they may send to a destination without calling bind. The stack will allocate a unique ephemeral port number and send out the datagram.

Servers wishing to provide a service using a well-known port should call bind() on that port number before generating datagrams.

The simplest server is an echo service running on port 7.

Because of the buffer semantics, it is safe to call send directly from a receive event handler.

  event void Boot.booted() {
    call Echo.bind(7);
  }

  event void Echo.recvfrom(struct sockaddr_in6 *from, void *data,
                           uint16_t len, struct ip_metadata *meta) {
    call Echo.sendto(from, data, len);
  }

The wiring is as follows.

  components new UdpSocketC();
  UDPEchoP.Echo -> UdpSocketC;

UDP-based Shell

UDPShell is a simple text-based command processor which comes with the ip-stack. It is an optional, although convenient way of implementating debugging commands on a mote.

By default, the shell contains only a few simple commands: help, echo, uptime, ping, and ident. It is designed to be very easy to extend by adding your own commands.

To include just the basic shell, include the UDPShellC component in your application. To augment the shell with a new shell command, use the generic component ShellCommandC.

Suppose we want to implement `expr`, a simple arithmetic evaluator. First, bind the 'expr' command string in your application configuration.

configuration App {} implementation {
  components AppImplP;
  components new ShellCommandC("expr") as Expr;
  AppImplP.Expr -> Expr;
}

Within AppImplP, you must implement the ShellCommand interface. The interface has only one event, 'eval' which has the same prototype as main() in a typical c program.

event char *Expr.eval(int argc, char **argv) {
  static char ret[10];
  return ret;
}

If expr returns a non-null value, it is assumed to be a null-terminated string which will be echoed back to a connected client. The buffer returned must obviously not be allocated on the stack. The shell does maintain a single buffer which components can use to print their reply to; it can be requested with a call to Expr.getBuffer(uint16_t len)

Fully fleshed out examples of code using this interface are available within the stack; see tos/lib/net/b6lowpan/shell/FlashShell[CP].nc and tos/lib/net/b6lowpan/nwprog/NWProg[CP].nc

TCP

WARNING: the TCP stack is still experimental, and may not provide the performance or reliability you are accustomed to.

TCP is the standard Internet protocol for reliable, in-order delivery of data across the network. Although inefficient, its ubiquity makes it impossible to ignore; thus, blip provides a very simple TCP stack. TCP is considerably more complicated then UDP, and requires careful use in the embedded setting to prevent resource exhaustion. It is essential that one understand the BSD sockets API; this brief README does not cover many details.

For memory-constrained operation, blip's TCP does not do any receive-side buffering. Instead, it will immediately dispatch new, in-order data to the application and otherwise drop the segment. Blip does provide send-buffering so that it can automatically retransmit missing segments; this buffer may be of any size and is provided by the application.

The TCP interface is located in

$TOSROOT/tos/lib/net/blip/interfaces/Tcp.nc

. For the most part,

it should be familier.

Since the application is responsible for buffering, both accept() and connect() require the implementer to include a buffer for the stack's use. Once passed to the stack, the buffer is reserved until a closed() event is signaled on that socket.

A few of the most important caveats/brokeness:

  • there is no listen(). calling bind() on a socket also begins to listen.
  • there is no way to accept() multiple sockets like you can in Unix. More precisely, all the code would support it but then there is dynamic allocation since you have to allocate a new socket struct on the fly.
  • As a result of these, if the socket is closed, you have to call bind() if you want to continue listening.
  • You'll need to carefully manage buffer size by hand if you want to be sure of correct operation. Make sure you check return codes from send() since it will fail if there is not enough local buffer for the entire request.


Example

configuration {
  components new TcpSocketC() as TcpEcho;
  TCPEchoP.TcpEcho -> TcpEcho;
}

module {} implementation {
  // allocate a send buffer
  char tcp_buf[150];

  // accept connections from anyone.  no need to save the endpoint,
  // but this is the only time its available (add an API call?)
  event bool TcpEcho.accept(struct sockaddr_in6 *from, 
                            void **tx_buf, int *tx_buf_len) {
    *tx_buf = tcp_buf;
    *tx_buf_len = 150;
    // indicates we are accepting the connection
    return TRUE;
  }

  event void TcpEcho.connectDone(error_t e) {}

  // just echo the data back.
  event void TcpEcho.recv(void *payload, uint16_t len) {
    call TcpEcho.send(payload,len);
  }

  // rebind to accept other connections.
  event void TcpEcho.closed(error_t e) {
    call Leds.led0Toggle();
    call TcpEcho.bind(7);
  }

Network Programming

nwprog is a method of over-the-air programming. It uses much of the machinery Deluge has developed, like the boot loader and flash layout, but substitutes a simpler transport using UDP for Deluge's dissemination algorithm. This means that it is point-to-point, and not incredibly appropriate for reprogramming an entire network all at once.


nwprog differs from Deluge in several important regards:

  • no dissemination
  • no base station or serial port for injection

The application is very simple: flash is formatted into several volumes (a golden image and three application volumes), which are used to store application images. Flash management, boot loading, and image formatting are all provided by Deluge.

How to use it

Build your application with support by include a line in your application Makefile, and include the IPDispatchC component.

Put this somewhere in your application Makefile:

BOOTLOADER=tosboot

Also, it is necessary to include a volumes xml file for your flash chip; examples for the stm25p and at45db are present in apps/UDPEcho.

First built the tosboot bootloader for your platform by going to tinyos-2.x/tos/lib/tosboot and typing `make <platform>`.

Then just build and install your application like usual. If networking is working, you should have no problem following the rest of the instructions.

Interactions with the motes happen using the 'nwprog' tool in a shell. Connect the shell with `nc6 -u 2001:470:1f04:56d::65 2000`. It has three commands:

`nwprog list`: examine the flash and print out information on volumes
       containing images believed to be valid
`nwprog reboot`: reboot into the same image
`nwprog boot N`: reboot, and flash the mote with the binary stored in
       volume N

In order to upload new images, use the tos-nwprog tool, located in $TOSROOT/tools/tinyos/misc. This tool provides minimal functionality; only erasing and uploading are supported. To use it, you will ensure that the directory $TOSROOT/support/sdk/python/ is in your PYTHONPATH environment variable.

`./tos-nwprog -e 0 fec0::65`: erase image 0 from the
       mote at the given IP address.
`./tos-nwprog -u 0 tos_image.xml fec0::65`: upload the
       image in tos_image.xml to volume 0 on the mote at the IP
       address.  This will erase the volume before uploading it.

tos-nwprog provides several other features: `tos-nwprog --help` will print information about them.

To integrate with your own application, there are several internal interfaces which can be used to examine the flash. Looking at the example code in UDPShellP component is the best way of finding out about these.