Generic (GNRC) network stack¶
RIOT’s modular default IP network stack.
See also
Martine Lenders’ master thesis about GNRC’s design and evaluation and the slide set of its defense.
About¶
This module is currently the default network stack for RIOT and includes many components ranging from a Network interface API through a fully-featured IPv6 implementation with 6LowPAN extensions to an UDP implementation and RPL.
A list of all features contained in the Generic (GNRC) network stack is available in the Modules
section above.
Integration into RIOT¶
From the application layer the Generic (GNRC) network stack can be accessed through the Sock API, while the interface to the Netdev - Network Device Driver API is defined by the Network interface API.
Architecture¶
Each layer of the network stack runs in its own thread and each lower layer thread has a higher priority than any upper layer thread. In this regard, the thread of the MAC layer implementation has the highest priority and threads on the application layer have the lowest priority. The communication between threads is handled by the kernel’s Messaging / IPC functionality and by the GNRC communication interface. Most of the times IPC will take place between threads of neighboring layers for packets that traverse the network stack up or down.
Due to the design of GNRC and the nature of inter-process communication, it is crucial for a new module that introduces a new thread to follow a certain programming construct if it desires to interact with other threads without blocking the system: Utilizing an event loop
.
Hence, a thread for GNRC will usually consist of four basic steps.
- Initialize a message queue (note that its size must be a power of two, see
msg.h::msg_init_queue()
) - register for a Protocol type
- wait for a message
- react appropriately to a message and return to 3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void *_event_loop(void *arg)
{
static msg_t _msg_q[Q_SZ];
(void)arg;
msg_init_queue(_msg_q, Q_SZ);
gnrc_netreg_entry me_reg = GNRC_NETREG_ENTRY_INIT_PID(
GNRC_NETREG_DEMUX_CTX_ALL,
sched_active_pid);
gnrc_netreg_register(GNRC_NETTYPE_IPV6, &me_reg);
while (1) {
msg_receive(&msg);
switch (msg.type) {
case TYPE1:
callback1();
break;
...
}
}
return NULL;
}
|
Receiving / Transmitting Packets¶
Packets can be received or transmitted by interacting with the GNRC communication interface.
Receiving Packets¶
The reception of a Packet from another thread is handled by the GNRC communication interface module. In order to receive a Packet of a specific type, it is necessary to register for the appropriate Protocol type first. Your thread will then be able to receive certain commands defined in the GNRC communication interface module (e.g. netapi.h::GNRC_NETAPI_MSG_TYPE_RCV
) for all Packets that your thread registered for.
The following example will sketch how to receive incoming and outgoing UDP traffic on port 80.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | void *_event_loop(void *arg)
{
static msg_t _msg_q[Q_SZ];
msg_t msg, reply;
reply.type = GNRC_NETAPI_MSG_TYPE_ACK;
reply.content.value = -ENOTSUP;
msg_init_queue(_msg_q, Q_SZ);
gnrc_pktsnip_t *pkt = NULL;
gnrc_netreg_entry me_reg = { .demux_ctx = 80, .pid = thread_getpid() };
gnrc_netreg_register(GNRC_NETTYPE_UDP, &me_reg);
while (1) {
msg_receive(&msg);
switch (msg.type) {
case GNRC_NETAPI_MSG_TYPE_RCV:
pkt = msg.content.ptr;
_handle_incoming_pkt(pkt);
break;
case GNRC_NETAPI_MSG_TYPE_SND:
pkt = msg.content.ptr;
_handle_outgoing_pkt(pkt);
break;
case GNRC_NETAPI_MSG_TYPE_SET:
case GNRC_NETAPI_MSG_TYPE_GET:
msg_reply(&msg, &reply);
break;
default:
break;
}
}
return NULL;
}
|
Note
When receiving a message of type netapi.h::GNRC_NETAPI_MSG_TYPE_SET
or netapi.h::GNRC_NETAPI_MSG_TYPE_GET
, it is necessary to acknowledge it by calling msg.h::msg_reply()
with a message of type netapi.h::GNRC_NETAPI_MSG_TYPE_ACK
which contains the actual size of the GET message’s content on success or an error code otherwise.
Note
Do not forget to unregister with netreg.h::gnrc_netreg_unregister()
if you leave the function context
Transmitting Packets¶
A packet is transmitted by relaying it to threads interested in handling (and dispatching) packets of its type. To do this, the GNRC communication interface offers dispatch helper functions called netapi.h::gnrc_netapi_dispatch_send()
and netapi.h::gnrc_netapi_dispatch_receive()
.
The following example sketches the usage and assumes a valid Packet named pkt
.
1 2 3 4 5 6 7 8 9 10 11 12 | gnrc_pktsnip_t *pkt;
pkt = gnrc_pktbuf_add(NULL, data, size, GNRC_NETTYPE_UNDEF);
if (pkt == NULL) {
puts("Error: unable to copy data to packet buffer\n");
return;
}
if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, 80, pkt)) {
puts("Error: no thread is interested");
gnrc_pktbuf_release(pkt);
return;
}
|
First, the data to be sent is added to the packet buffer. This ensures its intactness during the sending process. After the data to be sent has been added to the packet buffer, its parent data structure can safely be freed or re-used.
Then, the pkt will be sent to all threads that registered for nettype.h::gnrc_nettype_t::GNRC_NETTYPE_UDP and the demux context 80
. Every registered thread will receive a netapi.h::GNRC_NETAPI_MSG_TYPE_SND
command and can access the Packet. Note that at this point, the threads receiving pkt act as its owners, so please don’t modify pkt after calling any dispatch function.
If netapi.h::gnrc_netapi_dispatch_send()
is replaced by netapi.h::gnrc_netapi_dispatch_receive()
then threads will receive the netapi.h::GNRC_NETAPI_MSG_TYPE_RCV
command instead, again with access to the Packet.
Note
If the data to be sent requires extra headers to be added for successful transmission (in the example, this would be IP and UDP headers), these have to be built manually before calling netapi.h::gnrc_netapi_dispatch_send()
. In the interest of conciseness, this is omitted in this tutorial; please refer to gnrc/udp.h::gnrc_udp_hdr_build()
, gnrc/ipv6/hdr.h::gnrc_ipv6_hdr_build()
etc. for more information.
Note
GNRC is implemented according to the respective standards. So please note, that sending to a IPv6 link-local address always requires you by definition to also provide the interface you want to send to, otherwise address resolution might fail.
Receiving Packets¶
The reception of a Packet from another thread is handled by the GNRC communication interface module. In order to receive a Packet of a specific type, it is necessary to register for the appropriate Protocol type first. Your thread will then be able to receive certain commands defined in the GNRC communication interface module (e.g. netapi.h::GNRC_NETAPI_MSG_TYPE_RCV
) for all Packets that your thread registered for.
The following example will sketch how to receive incoming and outgoing UDP traffic on port 80.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | void *_event_loop(void *arg)
{
static msg_t _msg_q[Q_SZ];
msg_t msg, reply;
reply.type = GNRC_NETAPI_MSG_TYPE_ACK;
reply.content.value = -ENOTSUP;
msg_init_queue(_msg_q, Q_SZ);
gnrc_pktsnip_t *pkt = NULL;
gnrc_netreg_entry me_reg = { .demux_ctx = 80, .pid = thread_getpid() };
gnrc_netreg_register(GNRC_NETTYPE_UDP, &me_reg);
while (1) {
msg_receive(&msg);
switch (msg.type) {
case GNRC_NETAPI_MSG_TYPE_RCV:
pkt = msg.content.ptr;
_handle_incoming_pkt(pkt);
break;
case GNRC_NETAPI_MSG_TYPE_SND:
pkt = msg.content.ptr;
_handle_outgoing_pkt(pkt);
break;
case GNRC_NETAPI_MSG_TYPE_SET:
case GNRC_NETAPI_MSG_TYPE_GET:
msg_reply(&msg, &reply);
break;
default:
break;
}
}
return NULL;
}
|
Note
When receiving a message of type netapi.h::GNRC_NETAPI_MSG_TYPE_SET
or netapi.h::GNRC_NETAPI_MSG_TYPE_GET
, it is necessary to acknowledge it by calling msg.h::msg_reply()
with a message of type netapi.h::GNRC_NETAPI_MSG_TYPE_ACK
which contains the actual size of the GET message’s content on success or an error code otherwise.
Note
Do not forget to unregister with netreg.h::gnrc_netreg_unregister()
if you leave the function context
Transmitting Packets¶
A packet is transmitted by relaying it to threads interested in handling (and dispatching) packets of its type. To do this, the GNRC communication interface offers dispatch helper functions called netapi.h::gnrc_netapi_dispatch_send()
and netapi.h::gnrc_netapi_dispatch_receive()
.
The following example sketches the usage and assumes a valid Packet named pkt
.
1 2 3 4 5 6 7 8 9 10 11 12 | gnrc_pktsnip_t *pkt;
pkt = gnrc_pktbuf_add(NULL, data, size, GNRC_NETTYPE_UNDEF);
if (pkt == NULL) {
puts("Error: unable to copy data to packet buffer\n");
return;
}
if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, 80, pkt)) {
puts("Error: no thread is interested");
gnrc_pktbuf_release(pkt);
return;
}
|
First, the data to be sent is added to the packet buffer. This ensures its intactness during the sending process. After the data to be sent has been added to the packet buffer, its parent data structure can safely be freed or re-used.
Then, the pkt will be sent to all threads that registered for nettype.h::gnrc_nettype_t::GNRC_NETTYPE_UDP and the demux context 80
. Every registered thread will receive a netapi.h::GNRC_NETAPI_MSG_TYPE_SND
command and can access the Packet. Note that at this point, the threads receiving pkt act as its owners, so please don’t modify pkt after calling any dispatch function.
If netapi.h::gnrc_netapi_dispatch_send()
is replaced by netapi.h::gnrc_netapi_dispatch_receive()
then threads will receive the netapi.h::GNRC_NETAPI_MSG_TYPE_RCV
command instead, again with access to the Packet.
Note
If the data to be sent requires extra headers to be added for successful transmission (in the example, this would be IP and UDP headers), these have to be built manually before calling netapi.h::gnrc_netapi_dispatch_send()
. In the interest of conciseness, this is omitted in this tutorial; please refer to gnrc/udp.h::gnrc_udp_hdr_build()
, gnrc/ipv6/hdr.h::gnrc_ipv6_hdr_build()
etc. for more information.
Note
GNRC is implemented according to the respective standards. So please note, that sending to a IPv6 link-local address always requires you by definition to also provide the interface you want to send to, otherwise address resolution might fail.
How To Use¶
Generic (GNRC) network stack is highly modular and can be adjusted to include only the desired features. In the following several of the available modules will be stated that you can include in your application’s Makefile.
- To include the default network device(s) on your board:
1
USEMODULE += gnrc_netdev_default
- To auto-initialize these network devices as GNRC network interfaces
1
USEMODULE += auto_init_gnrc_netif
- You may choose to build either as an IPv6 Node or as an IPv6 Router
1
USEMODULE += gnrc_ipv6_default
An IPv6 Router can forward packets, while an IPv6 Node will simply drop packets not targeted to it. If an IEEE 802.15.4 network device is present 6LoWPAN (with 6LoWPAN Fragmentation and IPv6 header compression (IPHC)) will be included automatically.1
USEMODULE += gnrc_ipv6_router_default
- For basic IPv6 (and 6LoWPAN) functionalities choose instead or
1
USEMODULE += gnrc_ipv6
respectively. Those modules provide the bare minimum of IPv6 functionalities (no ICMPv6). Because of that, the Neighbor Information Base for IPv6 needs to be configured manually. If an IEEE 802.15.4 device is present 6LoWPAN will be included automatically, but no fragmentation or header compression support will be provided.1
USEMODULE += gnrc_ipv6_router
- For ICMPv6 echo request/reply (ping) functionality:
1
USEMODULE += gnrc_icmpv6_echo
- For UDP support include
1
USEMODULE += gnrc_udp
- To use UDP sock API with GNRC include
1
USEMODULE += gnrc_sock_udp
- To include the RPL module This will include the RPL module. To provide forwarding capabilities it is necessary to build the application with
1
USEMODULE += gnrc_rpl
gnrc_ipv6_router_default
(orgnrc_ipv6_router
), notgnrc_ipv6_default
(orgnrc_ipv6
).