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 @@ +A2e:a3:9e:a9:68:23A2e:a3:9e:a9:68:23B2e:a3:9e:a9:68:42B2e:a3:9e:a9:68:42C2e:a3:9e:a9:68:f6C2e:a3:9e:a9:68:f6Address ofA< Address ofB< Address ofCindex: 0local subnets: 2index: 0local subnets: 1index: 0local subnets: 1I want to create2local subnetsI want to create2local subnetsindex:2total subnets:3index:2total subnets:3I want to create1local subnetI want to create1local subnetindex: 0total subnets:3index: 2total subnets:4I want to create1local subnetI want to create1local subnetindex: 0 local: 2total subnets:4index:3local: 1total subnets:4index: 2 local: 1total subnets: 4 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 @@ +level12001:db8::/60level 2.12001:db8:0:4::/62level 2.22001:db8:0:8::/62level 3.12001:db8:0:9::/64level 3.22001:db8:0:a::/64level 3.32001:db8:0:5::/64level 3.42001:db8:0:6::/642001:db8::c8f4:13ff:fece:3f432001:db8:0:8:3c27:6dff:fe25:e95d2001:db8::804b:fcff:feb6:43fb2001:db8:0:4:2ca3:9eff:fea9:68f72001:db8:0:4:5075:35ff:fefa:30bb2001:db8:0:5:1848:79ff:fe20:cf592001:db8:0:4:14c4:7bff:fe63:c4492001:db8:0:6:8cbf:adff:fef0:40922001:db8:0:8:c8f4:13ff:fece:3f432001:db8:0:9:48f7:1cf:74cc:3f132001:db8:0:8:a440:e4ff:fe55:a0592001:db8:0:a:a8d9:e1ff:feab:d5431st level router #11st level router #22nd level router #32nd level router #42nd level router #12nd level router #2 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 @@ -level12001:db8::/60level22001:db8:0:8::/61level32001:db8:0:c::/62level42001:db8:0:e::/632001:db8::c8f4:13ff:fece:3f432001:db8:0:8:3c27:6dff:fe25:e95d2001:db8::804b:fcff:feb6:43fb2001:db8:0:8:5075:35ff:fefa:30bc2001:db8:0:c:2ca3:9eff:fea9:68f72001:db8:0:c:fc33:13ff:fe93:5ae42001:db8:0:e:a8d9:e1ff:feab:d5442001:db8:0:c:209e:deff:fea9:fd1b2001:db8:0:c:5491:a2ff:fe98:61a22001:db8:0:e:1cf5:33ff:fe7c:c70c1st level router1st level leaf node2nd level router3rd level router3rd level leaf node3rd level leaf node4th level leaf node +level12001:db8::/60level22001:db8:0:8::/61level32001:db8:0:c::/62level42001:db8:0:e::/632001:db8::c8f4:13ff:fece:3f432001:db8:0:8:3c27:6dff:fe25:e95d2001:db8::804b:fcff:feb6:43fb2001:db8:0:8:5075:35ff:fefa:30bc2001:db8:0:c:2ca3:9eff:fea9:68f72001:db8:0:c:fc33:13ff:fe93:5ae42001:db8:0:e:a8d9:e1ff:feab:d5442001:db8:0:c:209e:deff:fea9:fd1b2001:db8:0:c:5491:a2ff:fe98:61a22001:db8:0:e:1cf5:33ff:fe7c:c70c1st level router1st level leaf node2nd level router3rd level router3rd level leaf node3rd level leaf node4th level leaf node 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) */ /** @} */