diff --git a/doc/doxygen/src/gnrc_ipv6_auto_subnets-flow.puml b/doc/doxygen/src/gnrc_ipv6_auto_subnets-flow.puml
new file mode 100644
index 0000000000..ff38f72f09
--- /dev/null
+++ b/doc/doxygen/src/gnrc_ipv6_auto_subnets-flow.puml
@@ -0,0 +1,66 @@
+' to generate SVG run plantuml -tsvg gnrc_ipv6_auto_subnets-flow.puml
+@startuml
+
+
+
+skinparam sequence {
+ LifeLineBorderColor #275a4b
+ LifeLineBorderThickness 2
+}
+
+participant "**A**\n2e:a3:9e:a9:68:23" as A
+participant "**B**\n2e:a3:9e:a9:68:42" as B
+participant "**C**\n2e:a3:9e:a9:68:f6" as C
+
+note across: Address of **A** < Address of **B** < Address of **C**
+
+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
diff --git a/doc/doxygen/src/gnrc_ipv6_auto_subnets-flow.svg b/doc/doxygen/src/gnrc_ipv6_auto_subnets-flow.svg
new file mode 100644
index 0000000000..94a0795ecd
--- /dev/null
+++ b/doc/doxygen/src/gnrc_ipv6_auto_subnets-flow.svg
@@ -0,0 +1 @@
+
diff --git a/doc/doxygen/src/gnrc_ipv6_auto_subnets.puml b/doc/doxygen/src/gnrc_ipv6_auto_subnets.puml
new file mode 100644
index 0000000000..89cea9c86e
--- /dev/null
+++ b/doc/doxygen/src/gnrc_ipv6_auto_subnets.puml
@@ -0,0 +1,80 @@
+' to generate SVG run plantuml -tsvg gnrc_ipv6_auto_subnets.puml
+@startuml
+
+
+nwdiag {
+
+ network level1 {
+ address = "2001:db8::/60";
+
+ router_a [address = "2001:db8::c8f4:13ff:fece:3f43", description = "1st level router #1"];
+ router_b [address = "2001:db8::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:2ca3:9eff:fea9:68f7"];
+ router_e [address = "2001:db8:0:4:5075:35ff:fefa:30bb", description = "2nd level router #3"];
+ router_f [address = "2001:db8:0:4: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:3c27:6dff:fe25:e95d"];
+ router_c [address = "2001:db8:0:8:c8f4:13ff:fece:3f43", description = "2nd level router #1"];
+ router_d [address = "2001:db8:0:8: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:48f7:1cf:74cc:3f13"];
+ }
+
+ network level3_2 {
+ address = "2001:db8:0:a::/64";
+ description = "level 3.2"
+
+ router_d [address = "2001:db8:0:a:a8d9:e1ff:feab:d543"];
+ }
+
+ network level3_3 {
+ address = "2001:db8:0:5::/64";
+ description = "level 3.3"
+
+ router_e [address = "2001:db8:0:5:1848:79ff:fe20:cf59"];
+ }
+
+ network level3_4 {
+ address = "2001:db8:0:6::/64";
+ description = "level 3.4"
+
+ router_f [address = "2001:db8:0:6:8cbf:adff:fef0:4092"];
+ }
+}
+@enduml
diff --git a/doc/doxygen/src/gnrc_ipv6_auto_subnets.svg b/doc/doxygen/src/gnrc_ipv6_auto_subnets.svg
new file mode 100644
index 0000000000..04af60b666
--- /dev/null
+++ b/doc/doxygen/src/gnrc_ipv6_auto_subnets.svg
@@ -0,0 +1 @@
+
diff --git a/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.puml b/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.puml
index 18c16e51d6..71ed26f217 100644
--- a/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.puml
+++ b/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.puml
@@ -1,5 +1,27 @@
' to generate SVG run plantuml -tsvg gnrc_ipv6_auto_subnets_simple.puml
@startuml
+
+
nwdiag {
network level1 {
diff --git a/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.svg b/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.svg
index 1ec775bdd1..74131592bd 100644
--- a/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.svg
+++ b/doc/doxygen/src/gnrc_ipv6_auto_subnets_simple.svg
@@ -1 +1 @@
-
+
diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk
index c9f315e9a6..f7836c5459 100644
--- a/makefiles/pseudomodules.inc.mk
+++ b/makefiles/pseudomodules.inc.mk
@@ -38,6 +38,8 @@ PSEUDOMODULES += evtimer_on_ztimer
PSEUDOMODULES += fmt_%
PSEUDOMODULES += gcoap_dtls
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
diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c
index fdc1b0b1b0..456e55051d 100644
--- a/sys/auto_init/auto_init.c
+++ b/sys/auto_init/auto_init.c
@@ -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)) {
diff --git a/sys/net/gnrc/Makefile.dep b/sys/net/gnrc/Makefile.dep
index 05c501eabd..26e85de56b 100644
--- a/sys/net/gnrc/Makefile.dep
+++ b/sys/net/gnrc/Makefile.dep
@@ -113,10 +113,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)))
diff --git a/sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c b/sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c
index 635892f78c..6628fb8037 100644
--- a/sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c
+++ b/sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c
@@ -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
*/
+#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) */
/** @} */