1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #16750 from benpicco/gnrc_ipv6_auto_subnets

gnrc/ipv6_auto_subnets: relax topology requirements
This commit is contained in:
benpicco 2021-09-28 19:07:21 +02:00 committed by GitHub
commit a39c0e1010
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 559 additions and 16 deletions

View File

@ -0,0 +1,66 @@
' to generate SVG run plantuml -tsvg gnrc_ipv6_auto_subnets-flow.puml
@startuml
<style>
participant {
FontColor #white
BackGroundColor #275a4b
LineColor #3fa687
LineThickness 2.0
}
actor {
FontColor #white
BackGroundColor #275a4b
LineColor #3fa687
LineThickness 2.0
}
note {
FontColor white
BackGroundColor #30735f
LineColor #388d73
}
arrow {
LineColor #3fa687
LineThickness 2.0
}
</style>
skinparam sequence {
LifeLineBorderColor #275a4b
LifeLineBorderThickness 2
}
participant "**A**\n2e:a3:9e:a9:68:<i>23</i>" as A
participant "**B**\n2e:a3:9e:a9:68:<i>42</i>" as B
participant "**C**\n2e:a3:9e:a9:68:<i>f6</i>" as C
note across: <i>Address of **A** < Address of **B** < Address of **C**</i>
note over A: index: 0\nlocal subnets: 2
/ note over C: index: 0\nlocal subnets: 1
/ note over B: index: 0\nlocal subnets: 1
A -> C: I want to create **2** local subnets
A -> B: I want to create **2** local subnets
note over C: index: **2**\ntotal subnets: **3**
/ note over B: index: **2**\ntotal subnets: **3**
C -> A: I want to create **1** local subnet
C -> B: I want to create **1** local subnet
note over A: index: 0\ntotal subnets: **3**
/ note over B: index: 2\ntotal subnets: **4**
B -> C: I want to create **1** local subnet
B -> A: I want to create **1** local subnet
note over A: index: 0 local: 2\ntotal subnets: **4**
/ note over C: index: **3** local: 1\ntotal subnets: **4**
/ note over B: index: 2 local: 1\ntotal subnets: 4
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,80 @@
' to generate SVG run plantuml -tsvg gnrc_ipv6_auto_subnets.puml
@startuml
<style>
nwdiagDiagram {
network {
BackGroundColor #275a4b
LineColor #3fa687
LineThickness 2.0
}
server {
BackGroundColor #275a4b
LineColor #3fa687
FontColor #white
LineThickness 2.0
}
arrow {
LineColor #3fa687
LineThickness 2.0
}
}
</style>
nwdiag {
network level1 {
address = "2001:db8::/60";
router_a [address = "2001:db8::<color:#8a8a8a>c8f4:13ff:fece:3f43", description = "1st level router #1"];
router_b [address = "2001:db8::<color:#8a8a8a>804b:fcff:feb6:43fb", description = "1st level router #2"];
}
network level2_1 {
address = "2001:db8:0:4::/62";
description = "level 2.1"
router_b [address = "2001:db8:0:4:<color:#8a8a8a>2ca3:9eff:fea9:68f7"];
router_e [address = "2001:db8:0:4:<color:#8a8a8a>5075:35ff:fefa:30bb", description = "2nd level router #3"];
router_f [address = "2001:db8:0:4:<color:#8a8a8a>14c4:7bff:fe63:c449", description = "2nd level router #4"];
}
network level2_2 {
address = "2001:db8:0:8::/62";
description = "level 2.2"
router_a [address = "2001:db8:0:8:<color:#8a8a8a>3c27:6dff:fe25:e95d"];
router_c [address = "2001:db8:0:8:<color:#8a8a8a>c8f4:13ff:fece:3f43", description = "2nd level router #1"];
router_d [address = "2001:db8:0:8:<color:#8a8a8a>a440:e4ff:fe55:a059", description = "2nd level router #2"];
}
network level3_1 {
address = "2001:db8:0:9::/64";
description = "level 3.1"
router_c [address = "2001:db8:0:9:<color:#8a8a8a>48f7:1cf:74cc:3f13"];
}
network level3_2 {
address = "2001:db8:0:a::/64";
description = "level 3.2"
router_d [address = "2001:db8:0:a:<color:#8a8a8a>a8d9:e1ff:feab:d543"];
}
network level3_3 {
address = "2001:db8:0:5::/64";
description = "level 3.3"
router_e [address = "2001:db8:0:5:<color:#8a8a8a>1848:79ff:fe20:cf59"];
}
network level3_4 {
address = "2001:db8:0:6::/64";
description = "level 3.4"
router_f [address = "2001:db8:0:6:<color:#8a8a8a>8cbf:adff:fef0:4092"];
}
}
@enduml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -1,5 +1,27 @@
' to generate SVG run plantuml -tsvg gnrc_ipv6_auto_subnets_simple.puml
@startuml
<style>
nwdiagDiagram {
network {
BackGroundColor #275a4b
LineColor #3fa687
LineThickness 2.0
}
server {
BackGroundColor #275a4b
LineColor #3fa687
FontColor #white
LineThickness 2.0
}
arrow {
LineColor #3fa687
LineThickness 2.0
}
}
</style>
nwdiag {
network level1 {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -1,16 +1,24 @@
# Auto-configuration for nested subnets on a (simple) tree topology
# Auto-configuration for nested subnets on a tree topology
This example demonstrates IPv6 subnet auto-configuration for networks on a
tree topology.
This example demonstrates IPv6 subnet auto-configuration for networks on
a tree topology.
This allows to connect multiple links with individual subnets and route
between them.
Each link can have an arbitrary number of hosts, but there can be only
a single router on each link.
Each link can have an arbitrary number of hosts and routers.
Routers can have multiple interfaces to connect different downlinks.
![](../../doc/doxygen/src/gnrc_ipv6_auto_subnets.svg)
If you can ensure there is only a single router on each link, you can
skip the coordination protocol and save some resources by enabling
the `gnrc_ipv6_auto_subnets_simple` module.
![](../../doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.svg)
Routers can still have multiple downstream interfaces but there can be
only a single router in each subnet.
## Setup on native
To simulate such a network on `native` a `setup_taps.sh` script is provided that

View File

@ -40,6 +40,8 @@ PSEUDOMODULES += fmt_%
PSEUDOMODULES += gcoap_dtls
PSEUDOMODULES += fido2_tests
PSEUDOMODULES += gnrc_dhcpv6_%
PSEUDOMODULES += gnrc_ipv6_auto_subnets_auto_init
PSEUDOMODULES += gnrc_ipv6_auto_subnets_simple
PSEUDOMODULES += gnrc_ipv6_default
PSEUDOMODULES += gnrc_ipv6_ext_frag_stats
PSEUDOMODULES += gnrc_ipv6_router

View File

@ -279,6 +279,11 @@ void auto_init(void)
gnrc_dhcpv6_client_simple_pd_init();
}
if (IS_USED(MODULE_GNRC_IPV6_AUTO_SUBNETS_AUTO_INIT)) {
extern void gnrc_ipv6_auto_subnets_init(void);
gnrc_ipv6_auto_subnets_init();
}
if (IS_USED(MODULE_AUTO_INIT_MULTIMEDIA)) {
LOG_DEBUG("auto_init MULTIMEDIA\n");
if (IS_USED(MODULE_DFPLAYER)) {

View File

@ -116,10 +116,19 @@ ifneq (,$(filter gnrc_rpl,$(USEMODULE)))
USEMODULE += evtimer
endif
ifneq (,$(filter gnrc_ipv6_auto_subnets_simple,$(USEMODULE)))
USEMODULE += gnrc_ipv6_auto_subnets
endif
ifneq (,$(filter gnrc_ipv6_auto_subnets,$(USEMODULE)))
USEMODULE += gnrc_ipv6_nib_rtr_adv_pio_cb
CFLAGS += -DCONFIG_GNRC_IPV6_NIB_ADV_ROUTER=0
CFLAGS += -DCONFIG_GNRC_IPV6_NIB_ADD_RIO_IN_LAST_RA=1
ifeq (,$(filter gnrc_ipv6_auto_subnets_simple,$(USEMODULE)))
DEFAULT_MODULE += gnrc_ipv6_auto_subnets_auto_init
USEMODULE += gnrc_udp
endif
endif
ifneq (,$(filter gnrc_netif,$(USEMODULE)))

View File

@ -14,17 +14,29 @@
* About
* =====
*
* This module provides an automatic configuration for networks with a simple
* This module provides an automatic configuration for networks with a (simple)
* tree topology.
*
* If a sufficiently large IPv6 prefix (> /64) is provided via Router Advertisements,
* a routing node with this module will automatically configure subnets from it
* by dividing it into sub-prefixes for each downstream interface.
*
* There can only be a single routing node on each level of the network but an
* arbitrary number of leaf nodes.
* When using the `gnrc_ipv6_auto_subnets_simple` module, there can only be a single
* routing node on each level of the network but an arbitrary number of leaf nodes.
*
* ![Example Topology](gnrc_ipv6_auto_subnets_simple.svg)
* !['Skinny Tree' Example Topology](gnrc_ipv6_auto_subnets_simple.svg)
*
* If there are multiple routing nodes on the same link, coordination between the
* routers is required.
* For this the `gnrc_ipv6_auto_subnets` implements a simple UDP based synchronisation
* protocol where each router announces the number of subnets they want to create.
*
* ![Synchronisation Algorithm](gnrc_ipv6_auto_subnets-flow.svg)
*
* The layer 2 address of the sender then determines the order in which the prefixes
* are assigned.
*
* ![Example Topology](gnrc_ipv6_auto_subnets.svg)
*
* The downstream network(s) receive the sub-prefix via Router Advertisements
* and the process repeats until the bits of the prefix are exhausted.
@ -41,8 +53,8 @@
* Usage
* =====
*
* Simply add the `gnrc_ipv6_auto_subnets` module to the code of the nodes that
* should act as routers in the cascading network.
* Simply add the `gnrc_ipv6_auto_subnets` or `gnrc_ipv6_auto_subnets_simple` module
* to the nodes that should act as routers in the cascading network.
* The upstream network will be automatically chosen as the one that first
* receives a router advertisement.
*
@ -52,14 +64,129 @@
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#include "net/gnrc/ipv6.h"
#include "net/gnrc/netif.h"
#include "net/gnrc/netif/hdr.h"
#include "net/gnrc/udp.h"
#include "net/gnrc/ipv6/nib.h"
#include "net/gnrc/ndp.h"
#include "random.h"
#include "xtimer.h"
/**
* @brief Port for the custom UDP sync protocol
*/
#ifndef CONFIG_GNRC_IPV6_AUTO_SUBNETS_PORT
#define CONFIG_GNRC_IPV6_AUTO_SUBNETS_PORT (16179)
#endif
/**
* @brief Max number of other routers on the same link
*/
#ifndef CONFIG_GNRC_IPV6_AUTO_SUBNETS_PEERS_MAX
#define CONFIG_GNRC_IPV6_AUTO_SUBNETS_PEERS_MAX (4)
#endif
/**
* @brief How often the number subnets should be announced by the routers
*/
#ifndef CONFIG_GNRC_IPV6_AUTO_SUBNETS_TX_PER_PERIOD
#define CONFIG_GNRC_IPV6_AUTO_SUBNETS_TX_PER_PERIOD (3)
#endif
/**
* @brief How long to wait for other routers annoucements before resending
* or creating subnets when the retry counter is exhausted
*/
#ifndef CONFIG_GNRC_IPV6_AUTO_SUBNETS_TIMEOUT_MS
#define CONFIG_GNRC_IPV6_AUTO_SUBNETS_TIMEOUT_MS (50)
#endif
#define SERVER_THREAD_STACKSIZE (THREAD_STACKSIZE_DEFAULT)
#define SERVER_MSG_QUEUE_SIZE (CONFIG_GNRC_IPV6_AUTO_SUBNETS_PEERS_MAX)
#define SERVER_MSG_TYPE_TIMEOUT (0x8fae)
#define ENABLE_DEBUG 0
#include "debug.h"
static char addr_str[IPV6_ADDR_MAX_STR_LEN];
#if !IS_USED(MODULE_GNRC_IPV6_AUTO_SUBNETS_SIMPLE)
/**
* @brief Custom UDP sync protocol
*/
typedef struct __attribute__((packed)) {
uint8_t version; /**< version number, must be 0 */
uint8_t num_subnets; /**< number of subnets a host wants to create */
} _auto_subnets_request_v0_t;
/* keep a copy of PIO information in memory */
static gnrc_netif_t *_upstream;
static ndp_opt_pi_t _pio_cache;
static char auto_subnets_stack[SERVER_THREAD_STACKSIZE];
static msg_t server_queue[SERVER_MSG_QUEUE_SIZE];
/* store neighbor routers l2 address to ignore duplicate packets */
static uint8_t l2addrs[CONFIG_GNRC_IPV6_AUTO_SUBNETS_PEERS_MAX]
[CONFIG_GNRC_IPV6_NIB_L2ADDR_MAX_LEN];
/* PID of the event thread */
static kernel_pid_t _server_pid;
static int _send_udp(gnrc_netif_t *netif, const ipv6_addr_t *addr,
uint16_t port, const void *data, size_t len)
{
gnrc_pktsnip_t *payload, *udp, *ip;
/* allocate payload */
payload = gnrc_pktbuf_add(NULL, data, len, GNRC_NETTYPE_UNDEF);
if (payload == NULL) {
DEBUG("auto_subnets: unable to copy data to packet buffer\n");
return -ENOBUFS;
}
/* allocate UDP header, set source port := destination port */
udp = gnrc_udp_hdr_build(payload, port, port);
if (udp == NULL) {
DEBUG("auto_subnets: unable to allocate UDP header\n");
gnrc_pktbuf_release(payload);
return -ENOBUFS;
}
/* allocate IPv6 header */
ip = gnrc_ipv6_hdr_build(udp, NULL, addr);
if (ip == NULL) {
DEBUG("auto_subnets: unable to allocate IPv6 header\n");
gnrc_pktbuf_release(udp);
return -ENOBUFS;
}
/* add netif header, if interface was given */
if (netif != NULL) {
gnrc_pktsnip_t *netif_hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
if (netif_hdr == NULL) {
DEBUG("auto_subnets: unable to allocate netif header\n");
gnrc_pktbuf_release(ip);
return -ENOBUFS;
}
gnrc_netif_hdr_set_netif(netif_hdr->data, netif);
ip = gnrc_pkt_prepend(ip, netif_hdr);
}
/* send packet */
if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP,
GNRC_NETREG_DEMUX_CTX_ALL, ip)) {
DEBUG("auto_subnets: unable to locate UDP thread\n");
gnrc_pktbuf_release(ip);
return -ENETUNREACH;
}
return 0;
}
#endif /* !IS_USED(MODULE_GNRC_IPV6_AUTO_SUBNETS_SIMPLE) */
static void _init_sub_prefix(ipv6_addr_t *out,
const ipv6_addr_t *prefix, uint8_t bits,
uint8_t idx, uint8_t idx_bits)
@ -130,7 +257,7 @@ static bool _remove_old_prefix(gnrc_netif_t *netif,
return false;
}
static void _configure_subnets(uint8_t subnets, gnrc_netif_t *upstream,
static void _configure_subnets(uint8_t subnets, uint8_t start_idx, gnrc_netif_t *upstream,
const ndp_opt_pi_t *pio)
{
gnrc_netif_t *downstream = NULL;
@ -141,7 +268,7 @@ static void _configure_subnets(uint8_t subnets, gnrc_netif_t *upstream,
const uint8_t prefix_len = pio->prefix_len;
uint8_t new_prefix_len, subnet_len;
DEBUG("auto_subnets: create %u subnets\n", subnets);
DEBUG("auto_subnets: create %u subnets, start with %u\n", subnets, start_idx);
/* Calculate remaining prefix length.
* For n subnets we consume floor(log_2 n) + 1 bits.
@ -165,7 +292,7 @@ static void _configure_subnets(uint8_t subnets, gnrc_netif_t *upstream,
}
/* create subnet from upstream prefix */
_init_sub_prefix(&new_prefix, prefix, prefix_len, subnets--, subnet_len);
_init_sub_prefix(&new_prefix, prefix, prefix_len, ++start_idx, subnet_len);
DEBUG("auto_subnets: configure prefix %s/%u on %u\n",
ipv6_addr_to_str(addr_str, &new_prefix, sizeof(addr_str)),
@ -213,6 +340,228 @@ void gnrc_ipv6_nib_rtr_adv_pio_cb(gnrc_netif_t *upstream, const ndp_opt_pi_t *pi
return;
}
_configure_subnets(subnets, upstream, pio);
#if IS_USED(MODULE_GNRC_IPV6_AUTO_SUBNETS_SIMPLE)
/* if we are the only router on this bus, we can directly choose a prefix */
_configure_subnets(subnets, 0, upstream, pio);
#else
/* store PIO information for later use */
_pio_cache = *pio;
_upstream = upstream;
/* start advertising by sending timeout message to the server thread */
msg_t m = {
.type = SERVER_MSG_TYPE_TIMEOUT
};
msg_send(&m, _server_pid);
#endif /* !IS_USED(MODULE_GNRC_IPV6_AUTO_SUBNETS_SIMPLE) */
}
#if !IS_USED(MODULE_GNRC_IPV6_AUTO_SUBNETS_SIMPLE)
/**
* @brief Check if memory region is set to 0
*
* @param[in] The memory array to check
* @param[in] The size of the memory array
*
* @return true if all bytes are set to 0
*/
static bool _all_zero(const uint8_t *addr, size_t len)
{
for (const uint8_t *end = addr + len; addr != end; ++addr) {
if (*addr) {
return false;
}
}
return true;
}
/**
* @brief Allocates a l2 address in the `l2addrs` array
*
* @param[in] addr The l2 address to insert
* @param[in] len l2 address length
*
* @return 1 if the address was added to the `l2addrs` array
* 0 if the address was already in the array
* -1 if there was no more space in the `l2addrs` array
*/
static int _alloc_l2addr_entry(const void *addr, size_t len)
{
int empty = -1;
for (unsigned i = 0; i < CONFIG_GNRC_IPV6_AUTO_SUBNETS_PEERS_MAX; ++i) {
if (_all_zero(l2addrs[i], len)) {
empty = i;
continue;
}
if (memcmp(addr, l2addrs[i], len) == 0) {
return 0;
}
}
if (empty < 0) {
return -1;
}
memcpy(l2addrs[empty], addr, len);
return 1;
}
/**
* @brief Compare the l2 address of the received packet with the l2 address of the
* interface it was received on.
*
* Only the first packet from a host generates a comparison, all subsequent
* packets will be ignored until the `l2addrs` array is reset.
*
* @param[in] upstream interface, ignore if the source does not match
* @param[in] pkt a received packet
*
* @return 1 if the sender l2 address is in order before the local l2 address
* @return 0 if the order could not be determined or a packet from the sender
* was already processed
* @return -1 if the sender l2 address is in order behind the local l2 address
*/
static int _get_my_l2addr_rank(gnrc_netif_t *iface, gnrc_pktsnip_t *pkt)
{
const void *src_addr;
gnrc_pktsnip_t *netif_hdr;
gnrc_netif_hdr_t *hdr;
if (iface == NULL) {
return 0;
}
netif_hdr = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_NETIF);
if (netif_hdr == NULL) {
return 0;
}
/* ignore packet if it was received on the wrong interface */
hdr = netif_hdr->data;
if (iface->pid != hdr->if_pid) {
return 0;
}
/* ignore packets without source address */
src_addr = gnrc_netif_hdr_get_src_addr(hdr);
if (src_addr == NULL) {
return 0;
}
/* check if we have seen the host before */
if (_alloc_l2addr_entry(src_addr, iface->l2addr_len) == 0) {
return 0;
}
return memcmp(iface->l2addr, src_addr, iface->l2addr_len);
}
static void _receive_announce(gnrc_pktsnip_t *pkt, uint8_t *subnets, uint8_t *idx_start)
{
_auto_subnets_request_v0_t *request = pkt->data;
/* Check if we already got an announcement from that host, */
/* in this case, res will be 0. */
int res = _get_my_l2addr_rank(_upstream, pkt);
if (res) {
/* calculate total number of subnets */
*subnets += request->num_subnets;
DEBUG("auto_subnets: %u new remote subnets, total %u\n",
request->num_subnets, *subnets);
/* If other host is before us in order of MAC addresses, add
* their subnets to our offset */
if (res > 0) {
*idx_start += request->num_subnets;
}
}
gnrc_pktbuf_release(pkt);
}
static void _send_announce(uint8_t local_subnets, xtimer_t *timer, msg_t *msg)
{
uint32_t timeout_us;
_auto_subnets_request_v0_t request = {
.num_subnets = local_subnets,
};
/* broadcast the number of subnets we want to create */
_send_udp(_upstream, &ipv6_addr_all_routers_link_local,
CONFIG_GNRC_IPV6_AUTO_SUBNETS_PORT,
&request, sizeof(request));
/* configure timeout for resend */
timeout_us = random_uint32_range(
CONFIG_GNRC_IPV6_AUTO_SUBNETS_TIMEOUT_MS * US_PER_MS / 2,
CONFIG_GNRC_IPV6_AUTO_SUBNETS_TIMEOUT_MS * US_PER_MS);
xtimer_set_msg(timer, timeout_us, msg, _server_pid);
DEBUG("auto_subnets: announce sent, next timeout in %" PRIu32 " µs\n", timeout_us);
}
static void *_eventloop(void *arg)
{
(void)arg;
xtimer_t timeout_timer;
msg_t msg, timeout_msg = { .type = SERVER_MSG_TYPE_TIMEOUT };
gnrc_netreg_entry_t server = GNRC_NETREG_ENTRY_INIT_PID(0, KERNEL_PID_UNDEF);
const uint8_t local_subnets = gnrc_netif_numof() - 1;
uint8_t idx_start = 0;
uint8_t subnets = local_subnets;
uint8_t tx_period = CONFIG_GNRC_IPV6_AUTO_SUBNETS_TX_PER_PERIOD;
DEBUG("auto_subnets: %u local subnets\n", subnets);
if (subnets == 0) {
return NULL;
}
/* setup the message queue */
msg_init_queue(server_queue, SERVER_MSG_QUEUE_SIZE);
/* register server to receive messages from given port */
gnrc_netreg_entry_init_pid(&server, CONFIG_GNRC_IPV6_AUTO_SUBNETS_PORT, thread_getpid());
gnrc_netreg_register(GNRC_NETTYPE_UDP, &server);
while (1) {
msg_receive(&msg);
switch (msg.type) {
case GNRC_NETAPI_MSG_TYPE_RCV:
_receive_announce(msg.content.ptr, &subnets, &idx_start);
break;
case SERVER_MSG_TYPE_TIMEOUT:
if (tx_period--) {
/* send subnet announcement */
_send_announce(local_subnets, &timeout_timer, &timeout_msg);
} else {
/* config round done, configure subnets */
_configure_subnets(subnets, idx_start, _upstream, &_pio_cache);
/* start a new round of counting */
tx_period = CONFIG_GNRC_IPV6_AUTO_SUBNETS_TX_PER_PERIOD;
memset(l2addrs, 0, sizeof(l2addrs));
idx_start = 0;
subnets = local_subnets;
}
break;
}
}
/* never reached */
return NULL;
}
void gnrc_ipv6_auto_subnets_init(void)
{
/* initiate auto_subnets thread */
_server_pid = thread_create(auto_subnets_stack, sizeof(auto_subnets_stack),
THREAD_PRIORITY_MAIN - 1, THREAD_CREATE_STACKTEST,
_eventloop, NULL, "auto_subnets");
}
#endif /* !IS_USED(MODULE_GNRC_IPV6_AUTO_SUBNETS_SIMPLE) */
/** @} */