embeNET Border Router User Guide

Table of contents Table of contents

This is the embeNET Border Router User Guide. You can read through the following topics:

Networking basics

The following sections give brief information about the embeNET networking. This is just an overview. Most topics are covered in depth in the following chapters.

Overview of the network

The embeNET network is a wireless mesh network capable of providing communication between hundreds of nodes. It uses a special TSCH (Time Slotted Channel Hopping) scheme for accessing the radio channels, which greatly reduces idle listening and packet collisions even in dense networks. The network works with IEEE 802.15.4 compatible radio transceivers. The network is synchronized meaning that all nodes that joined the same network have a common sense of time, which is very precise and allows the nodes to schedule radio transmission and reception in exact moments.

Border router and nodes

The embeNET network is started by a special device called border router. Other nodes need to join the network started by the border router in order to be able to communicate. The border router may accept or reject a node based on the specifically configured joining rules.

Root node

The border router integrates a special node called root node. Your may think of the border router as a root node + router. The root node is a special kind of network node that is directly controlled by the router and may be regarded as a radio interface for the router. Typically root node and router are separate CPUs connected by a serial interface (like UART). Is such case root node has its own, fixed firmware.

IPv6 and multicast

The embeNET network uses the IPv6 protocol for addressing the nodes. It supports multicast addressing mode so that devices can be grouped and the data can be sent to all devices within a group simultaneously. Each device in the network is identified by a 64-bit unique identifier (UID) which is an EUI64 address. In most cases this number comes from the hardware platform the stack works on and should not be altered by the application code. In addition each network started by the border router is identified by a 64-bit network prefix. These two values - the network prefix and the UID are used to form a unicast IPv6 address of the node. A multicast address is being formed by the network prefix and group identifier.

Starting the network

In order to start a network the border router must assign a network key (K1), a network prefix and network ID (PANID).

The network key (K1) is shared across all devices within a network and typically can be the same for all devices used in an organization. It is a utility measure and network security is not based on this key. T

The network prefix is a 64-bit prefix that will constitute the first part of every IPv6 address within this network. It is beneficial for each network to have it's own network prefix, as it allows routing between the networks.

The network ID (PANID) is a 16-bit short network identifier that identifies the network.

Once these properties are set, the border router starts to advertise the network by periodically issuing BEACON packets. In reaction other nodes that wish to join the network may start the joining process.

Joining process and join rules

The joining of node to the network is a multistep process. Firstly the node synchronizes to the network, meaning that it can follow the scheduled slots in which the communication with other nodes takes place. It selects a node (called pledge) through which it wishes to join. Next it sends credentials including own UID and PSK to the border router.

The border router has a list of join rules that decide which nodes can be accepted in the network. Each join rule may require that the nodes UID matches, the PSK matches or both values match the rule. This allows to shape the joining rules according to specific requirements. Once the node is accepted it can communicate with any other node in the network. Nodes that don't fit any join rule will be rejected.

UPD and sockets

The embeNET network uses UDP protocol for transporting data between the nodes. The UDP datagrams are handled by sockets which are registered in the nodes as well as in the border router.

General information about the library

The embeNET Border Router library implements the functionality of the central gateway and the router in a wireless mesh network. From the user perspective it is a library that provides interfaces, that the user application (and other middleware - such as network services) can use. However, due to the fact that the library is also portable across multiple hardware platforms, it also relies on some required interfaces. Those need to be implemented in either the 'port' or 'bsp'. Here, we will only focus on the provided interfaces - see embeNET BRC Porting Guide for the description of the required interfaces and how to implement them on a new or custom hardware.

The embeNET Border Router library utilizes event-driven style of programming. Most actions taken by the library last for a relatively long time before results are observed. Thus many function calls take callbacks as arguments.

Interfaces

The embeNET Border Router library provides the following interfaces:

Border Router C interface

The embeNET Border Router C API provides a set of functions that allow to start and stop the network. It also allows to manage join rules that dictate which nodes can join the network. The border router can also list the nodes that are joined as well as list the groups to which the nodes are assigned.

UDP interface

The embeNET UDP C API provides a set of functions that allow to register network sockets, through which all user communication is carried out. The protocol of choice here is UDP. See embeNET UDP C API for the description of functions and Using UDP sockets for the information on how to use them.

Running the border router

In the following sections we will assume that the border router is run as a thread within some operating system. This may however also be a main loop of some embedded code.

The embeNET Border Router follows a simple init-proc-deinit rule. Before using the router you need to initialize the library through a call to EMBENET_BR_Init. Once the library is initialized it is expected that the EMBENET_BR_Proc is called periodically - usually within the main loop of a thread. When (and if) the application is done with the library it may call EMBENET_BR_Deinit to deinitialize it.

The following example illustrates the idea:

#include "embenet_br.h" // embeNET Border Router API
// Main function or thread
int brcThread(void)
{
// Initialize embeNET Border Router library
while (1) {
// Process the embeNET Border Router
// Add som delay
Sleep(10);
}
// Deinitialize the library (if needed)
return 0;
}
Border Router API.
void EMBENET_BR_Deinit(void)
Deinitializes the border router network process.
void EMBENET_BR_Proc(void)
Runs the border router process.
EMBENET_Result EMBENET_BR_Init(void)
Initializes the border router network process.

Exception handling

There is just one more thing to consider. The embeNET Border Router stack uses EXPECT error handling utility for handling input validation and unrecoverable errors. Whenever the stack detects an really faulty condition it calls the EXPECT_OnAbortHandler which must be defined in the user code. The program must not continue operation after calling this function. A typical behavior of such function is to:

  • go to a safe state
  • log the error
  • halt or reset the application

Below is the empty implementation of such function that you can copy, paste and extend.

void EXPECT_OnAbortHandler(char const* why, char const* file, int line) {
// TODO
while (1) { ; }
}
EXPECT_INTERNAL_NORETURN void EXPECT_OnAbortHandler(char const *why, char const *file, int line)

Once the border router is initialized the following elements of the API are available:

Starting the network

The border router is responsible for starting the network. This is done through a call to EMBENET_BR_StartNetwork. This function accepts two arguments - the network configuration (EMBENET_BR_Config) and the list of event handlers (EMBENET_BR_EventHandlers).

Network configuration

In order to start the network we need to provide four things:

  • network key (K1) - This is a common 128-bit key shared across all the nodes within a single network. This key is not used for security and must be the same in all the nodes wishing to communicate, including the border router.
  • networkPrefix - This is a 64-bit number that will constitute the first part of every IPv6 address within the network
  • panId - This is a 16-bit wireless network identifier. It is used to distinguish various network and should be chosen in a way, so that no two wireless networks within common range of communication share the same PAN ID.
  • randomSeed - This is a 64-bit number that must be chosen randomly. It is used to initialize the internal pseudo-random generator within the stack. Special care should be taken to ensure that this number is truly random, as several security mechanism within the stack rely on it. If possible use true random generator peripheral or equivalent method of generating random values.

Example

The following example illustrates the starting of the network.

#include "embenet_br.h" // embeNET Border Router API
#include <stdio.h>
// Network key (K1):
static uint8_t k1[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee};
// Network prefix
uint8_t psk[16] = {0x1b, 0xac, 0xe4, 0x43, 0x41, 0xfd, 0x31, 0x70, 0xab, 0xef, 0xa1, 0x72, 0x99, 0xe1, 0xf3};
static void onNetworkStarted(void) {
puts("Network was started");
}
// Main function or thread
int brcThread(void)
{
// Initialize embeNET Border Router library
// Prepare network configuration structure
memcpy(config.k1.val, k1, 16);
config.subnetPrefix = 0x1000200030004000;
config.panid = 0x1234;
config.randomSeed = rand();
// Prepare event handlers structure and clear all handlers
EMBENET_BR_EventHandlers eventHandlers = {NULL};
// Set event handler that will be called once the network is started
eventHandlers.onNetworkStarted = onNetworkStarted;
// Start the network
EMBENET_BR_StartNetwork(&config, &eventHandlers);
while (1) {
// Process the embeNET Border Router
// Add som delay
Sleep(10);
}
// Deinitialize the library (if needed)
return 0;
}
EMBENET_Result EMBENET_BR_StartNetwork(EMBENET_BR_Config const *config, EMBENET_BR_EventHandlers const *eventHandlers, unsigned timeoutMs)
Starts network operation.
Stores Border Router configuration needed to start the network.
Definition: embenet_br.h:73
EMBENET_RandomSeed randomSeed
Random seed used to initialize internal Pseudorandom Number Generator.
Definition: embenet_br.h:78
EMBENET_PANID panid
Network ID.
Definition: embenet_br.h:79
EMBENET_K1 k1
Network key used to validate origin of the messages across all of the network nodes....
Definition: embenet_br.h:74
EMBENET_NetworkPrefix subnetPrefix
IPv6 subnet prefix. The network always uses \64 subnet prefix.
Definition: embenet_br.h:76
Structure holding embeNET Border Router stack event handlers.
Definition: embenet_br_event_handlers.h:70
EMBENET_BR_OnNetworkStarted onNetworkStarted
Event hander that is called when the network is started in the border router.
Definition: embenet_br_event_handlers.h:72
uint8_t val[16]
Stored value.
Definition: embenet_brc/embenet_defs.h:94

In the above example only one event handler (EMBENET_BR_EventHandlers::onNetworkStarted) is set. This handler will be called when the network is actually started by the border router.

At any time after the network was started the application can call EMBENET_BR_GetNetworkPrefix to get the network prefix that was set in the provided network configuration and EMBENET_BR_GetOwnUid to get the UID of the root node.

Stopping the network

At any time the border router can stop the network by calling the EMBENET_BR_StopNetwork function. When the network is stopped the EMBENET_BR_EventHandlers::onNetworkStopped event handler is called.

Join rules management

One of the key responsibilities of the border router is to give access to nodes that wish to join the network. When a node wishes to join the network it sends a join request along with the nodes 64-bit unique identifier (UID) and a device-specific 128-bit key called pre-shared-key (PSK). The border router decides whether to allow the node to join the network or not based on a set of rules called join rules. These rules are defined by the application using EMBENET_BR_AddJoinRule and EMBENET_BR_RemoveJoinRule calls. The rules are stored in a list that can have multiple entries (currently up to 500 entries).

Each join rule is a set of UID and PSK: we can denote it as [UID:PSK]. A special value of 0 (zero) for UID means that the rule applies to every UID This means that the border router may apply one of these authorization strategies:

Allow all with a single global PSK

This strategy allows every node to join the network as long as they use a pre-shared matching PSK key. This is useful for development and applications requiring limited security. The authorization of the nodes relies on a common PSK being known to the joining nodes. The network is secure as long as that can be kept secret. This strategy can be applied by adding a single rule of [0:PSK], where PSK is a common pre-shared key.

Allow all with a multiple global PSK

This strategy is a simple evolution of the single global PSK strategy. It just allows to use more than one PSK. This is however even less secure as there are now more than one keys that need to be kept secret. However in some scenarios this solution may be desired - for example to more control in more granular way when a set of devices is allowed or not allowed to join. This strategy can be applied by adding a multiple rules: [0:PSK1], [0:PSK2], ... where PSKs are a common pre-shared keys.

Allow only nodes with matching UID with global PSK

This strategy allows to only join nodes with matching UID, however still only one (or couple) global pre-shared keys are used. This strategy can be applied by adding a rule for each device: [UID1:PSK], [UID2:PSK], [UID3:PSK],

Allow only matching pairs of UID and PSK

This strategy is most secure and requires each node to have own PSK matching its UID. It also requires most preparation in the border router as the UID+PSK pairs need to be provided as join rules. This strategy can be applied by adding a rule for each device: [UID1:PSK1], [UID2:PSK2], [UID3:PSK3],

These strategies can also be mixed together. When the join request is received from a node, the list is searched and if any rule allows the node to join, the node is let in to the network.

The join rules are cleared during initialization (EMBENET_BR_Init). They can be added and managed after the stack is initialized.

To add a rule the application should use EMBENET_BR_AddJoinRule. The rule can be later removed using EMBENET_BR_RemoveJoinRule. At any time after initialization the application can get a list of rules by first calling EMBENET_BR_GetJoinRulesCount to get the number of rules and then EMBENET_BR_GetJoinRuleByIndex to get each specific rule.

The code below illustrates the usage:

#include "embenet_br.h" // embeNET Border Router API
#include <stdio.h>
// Main function or thread
int brcThread(void) {
// Initialize embeNET Border Router library
// Add join rule to allow all nodes with a single matching PSK
EMBENET_BR_JoinRule rule1 = {0, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}};
// Add join rule to allow all nodes with another matching PSK
EMBENET_BR_JoinRule rule2 = {0, {{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}}};
// Add join rule to allow node with UID=0x1002 if it matches PSK
EMBENET_BR_JoinRule rule3 = {0x1002, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}};
// ... start of network ...
EMBENET_Result EMBENET_BR_AddJoinRule(EMBENET_BR_JoinRule *rule)
Single rule that governs the constrained join process.
Definition: embenet_br.h:83

Listing joined nodes

Once the network is started it is possible to list all the nodes that are currently joined. This can be done using EMBENET_BR_GetJoinedNodesCount to get the number of nodes and then using EMBENET_BR_GetJoinedNodeUidByIndex to get the UID of each joined node.

The following code illustrates the usage:

// Get the total number of joined nodes
size_t nodeCount = EMBENET_BR_GetJoinedNodesCount();
for (size_t n = 0; n < nodeCount; ++n) {
// For each node print its uid
printf("Node with uid=%" PRIu64 " joined this network\n", uid);
}
EMBENET_EUI64 EMBENET_BR_GetJoinedNodeUidByIndex(size_t index)
size_t EMBENET_BR_GetJoinedNodesCount(void)
uint64_t EMBENET_EUI64
Unique identifier of the node.
Definition: embenet_brc/embenet_defs.h:49

The application may also easily check if the node with a given UID is joined using EMBENET_BR_IsNodeJoined function.

Listing joined groups

Nodes can be organized in groups. The idea is to enable multicast addressing of such a group so that UDP packets can be sent to all the members of the group simultaneously. It is the node that decides to which groups it belongs to. The border router only observes the grouping behavior of nodes and can list all the group members, but it cannot directly make a given node join or leave particular group.

Each group is identified by a EMBENET_GroupId group identifier, which is freely chosen by the nodes.

Once the network is started it is possible to list all the groups that the joined nodes have currently joined to. This can be done using EMBENET_BR_GetGroupCount to get the number of groups and then using EMBENET_BR_GetGroupIdByIndex to get the group identifier of each registered group.

In addition, for every joined node it is possible to list the groups it belongs to using first EMBENET_BR_GetGroupCountForNode to get the number of groups and then EMBENET_BR_GetGroupIdForNodeByIndex to get each groupId.

The following code illustrates the usage:

// Get the total number of groups
size_t groupCount = EMBENET_BR_GetGroupCount();
puts("The following groups are registered:");
for (size_t g = 0; g < groupCount; ++g) {
// get groupIf od each registered group
printf("groupId=%" PRIu16 ":\n", gid);
}
// Get the total number of joined nodes
size_t nodeCount = EMBENET_BR_GetJoinedNodesCount();
for (size_t n = 0; n < nodeCount; ++n) {
// Get uid of each joined node
printf("Node with uid=%" PRIu64 " joined the following groups:\n", uid);
// Get the number of groups this node belongs to
size_t nodeGroupCount = EMBENET_BR_GetGroupCountForNode(uid);
for (size_t g = 0; g < nodeGroupCount; ++g) {
// For each group print groupId
printf("groupId=%" PRIu16 ":\n", gid);
}
}
size_t EMBENET_BR_GetGroupCountForNode(EMBENET_EUI64 uid)
Returns amount of multicast groups joined by the node with given UID.
EMBENET_GroupId EMBENET_BR_GetGroupIdByIndex(size_t index)
EMBENET_GroupId EMBENET_BR_GetGroupIdForNodeByIndex(EMBENET_EUI64 uid, size_t index)
Returns Mutlicast Group ID at given index from UID joined group list The maximum allowed index can be...
size_t EMBENET_BR_GetGroupCount(void)
uint16_t EMBENET_GroupId
Multicast group ID.
Definition: embenet_brc/embenet_defs.h:52

Event handlers

The EMBENET_BR_EventHandlers structure gathers all the event handlers. If a particular event handler is not used it should be set to NULL.
The following table lists all the event handlers with their description:

Event handler Description
EMBENET_BR_EventHandlers::onNetworkStarted Called when the network is started (see Starting the network)
EMBENET_BR_EventHandlers::onNetworkStopped Called when the network is stopped (see Stopping the network)
EMBENET_BR_EventHandlers::onNodeJoined Called when a node joins the network
EMBENET_BR_EventHandlers::onNodeLeft Called when a node leaves the network
EMBENET_BR_EventHandlers::onNodeAddedToGroup Called when a node is added to a group
EMBENET_BR_EventHandlers::onNodeRemovedFromGroup Called when a node is removed from a group
EMBENET_BR_EventHandlers::onDatagramOnUnregisteredPort Called when an UDP datagram is received but no socket can handle it