diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c index 9b9dab4c14..0d42695336 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c @@ -25,6 +25,7 @@ #include "net/gnrc/ipv6/nib/nc.h" #include "net/gnrc/ipv6/nib.h" #include "net/gnrc/netif/internal.h" +#include "net/ipv6/addr.h" #include "random.h" #include "_nib-internal.h" @@ -378,6 +379,20 @@ void _nib_drl_remove(_nib_dr_entry_t *nib_dr) if (nib_dr->next_hop != NULL) { _evtimer_del(&nib_dr->rtr_timeout); nib_dr->next_hop->mode &= ~(_DRL); +#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_DC) + /* When removing a router from the Default + Router list, the node MUST update the Destination Cache in such a way + that all entries using the router perform next-hop determination + again rather than continue sending traffic to the (deleted) router. + (https://datatracker.ietf.org/doc/html/rfc4861#section-6.3.5) + */ + _nib_offl_entry_t *dc = NULL; + while ((dc = _nib_offl_iter(dc))) { + if ((dc->mode & _DC) && dc->next_hop == nib_dr->next_hop) { + _nib_dc_remove(dc); + } + } +#endif _nib_onl_clear(nib_dr->next_hop); memset(nib_dr, 0, sizeof(_nib_dr_entry_t)); } @@ -538,21 +553,28 @@ static inline bool _in_abrs(const _nib_abr_entry_t *abr) void _nib_offl_clear(_nib_offl_entry_t *dst) { - if (dst->next_hop != NULL) { - _nib_offl_entry_t *ptr; - for (ptr = _dsts; _in_dsts(ptr); ptr++) { - /* there is another dst pointing to next-hop => only remove dst */ - if ((dst != ptr) && (dst->next_hop == ptr->next_hop)) { - break; + if (dst->mode == _EMPTY) { + if (dst->next_hop != NULL) { + _nib_offl_entry_t *ptr; + for (ptr = _dsts; _in_dsts(ptr); ptr++) { + /* there is another dst pointing to next-hop => only remove dst */ + if ((dst != ptr) && (dst->next_hop == ptr->next_hop)) { + break; + } + } + /* we iterated and found no further dst pointing to next-hop */ + if (!_in_dsts(ptr)) { + dst->next_hop->mode &= ~(_DST); + _nib_onl_clear(dst->next_hop); } } - /* we iterated and found no further dst pointing to next-hop */ - if (!_in_dsts(ptr)) { - dst->next_hop->mode &= ~(_DST); - _nib_onl_clear(dst->next_hop); - } memset(dst, 0, sizeof(_nib_offl_entry_t)); } + else { + DEBUG("nib: offlink entry %s/%u with mode %u not cleared\n", + ipv6_addr_to_str(addr_str, &dst->pfx, sizeof(addr_str)), + dst->pfx_len, dst->mode); + } } _nib_offl_entry_t *_nib_offl_iter(const _nib_offl_entry_t *last) diff --git a/sys/net/gnrc/network_layer/ipv6/nib/nib.c b/sys/net/gnrc/network_layer/ipv6/nib/nib.c index 6c9225018c..732e1933b8 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/nib.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/nib.c @@ -1458,17 +1458,18 @@ static void _handle_rtr_timeout(_nib_dr_entry_t *router) { if ((router->next_hop != NULL) && (router->next_hop->mode & _DRL)) { _nib_offl_entry_t *route = NULL; - unsigned iface = _nib_onl_get_if(router->next_hop); - ipv6_addr_t addr = router->next_hop->ipv6; + _nib_onl_entry_t *next_hop = router->next_hop; _nib_drl_remove(router); - /* also remove all routes to that router */ + /* The Router Lifetime applies only to + the router's usefulness as a default router; it + does not apply to information contained in other + message fields or options. Options that need time + limits for their information include their own + lifetime fields. + (https://datatracker.ietf.org/doc/html/rfc4861#section-4.2) */ while ((route = _nib_offl_iter(route))) { - if ((route->next_hop != NULL) && - (_nib_onl_get_if(route->next_hop) == iface) && - (ipv6_addr_equal(&route->next_hop->ipv6, &addr))) { - route->mode = _EMPTY; - route->next_hop->mode &= ~_DST; + if (route->next_hop == next_hop) { _nib_offl_clear(route); /* XXX routing protocol gets informed in case NUD * determines ipv6->src (still in neighbor cache) to be diff --git a/tests/net/gnrc_ipv6_nib/main.c b/tests/net/gnrc_ipv6_nib/main.c index 2694e6c0a0..6c3ea249d8 100644 --- a/tests/net/gnrc_ipv6_nib/main.c +++ b/tests/net/gnrc_ipv6_nib/main.c @@ -817,11 +817,11 @@ static void test_handle_pkt__rtr_sol(void) static size_t _set_rtr_adv(const ipv6_addr_t *ipv6_src, uint8_t ipv6_hl, uint8_t rtr_adv_code, bool set_rtr_adv_fields, - uint8_t rtr_adv_flags, + uint8_t rtr_adv_flags, uint32_t t_rtr_reachable_ms, uint16_t t_rtr_alive_s, const uint8_t *sl2ao_addr, size_t sl2ao_addr_len, uint16_t mtu, const ipv6_addr_t *pfx, unsigned pfx_len, - uint8_t pfx_flags) + uint8_t pfx_flags, uint32_t t_pfx_valid_s, uint32_t t_pfx_pref_s) { size_t icmpv6_len = sizeof(ndp_rtr_adv_t); ndp_rtr_adv_t *rtr_adv = (ndp_rtr_adv_t *)icmpv6; @@ -835,8 +835,8 @@ static size_t _set_rtr_adv(const ipv6_addr_t *ipv6_src, rtr_adv->flags = rtr_adv_flags; if (set_rtr_adv_fields) { rtr_adv->cur_hl = _CUR_HL, - rtr_adv->ltime = byteorder_htons(_RTR_LTIME); - rtr_adv->reach_time = byteorder_htonl(_REACH_TIME); + rtr_adv->ltime = byteorder_htons(t_rtr_alive_s); + rtr_adv->reach_time = byteorder_htonl(t_rtr_reachable_ms); rtr_adv->retrans_timer = byteorder_htonl(_RETRANS_TIMER); } @@ -857,8 +857,8 @@ static size_t _set_rtr_adv(const ipv6_addr_t *ipv6_src, pio->len = NDP_OPT_PI_LEN; pio->prefix_len = pfx_len; pio->flags = pfx_flags; - pio->valid_ltime = byteorder_htonl(_PIO_PFX_LTIME); - pio->pref_ltime = byteorder_htonl(_PIO_PFX_LTIME); + pio->valid_ltime = byteorder_htonl(t_pfx_valid_s); + pio->pref_ltime = byteorder_htonl(t_pfx_pref_s); ipv6_addr_init_prefix(&pio->prefix, pfx, pfx_len); icmpv6_len += sizeof(ndp_opt_pi_t); } @@ -923,9 +923,11 @@ static void test_handle_pkt__rtr_adv__invalid_src(void) void *state = NULL; size_t icmpv6_len = _set_rtr_adv(&_rem_gb, NDP_HOP_LIMIT, 0U, true, 0U, + _REACH_TIME, _RTR_LTIME, _loc_l2, sizeof(_loc_l2), 32397U, &_loc_gb, _LOC_GB_PFX_LEN, - NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A); + NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); _netif_exp_t exp_netif; _get_netif_exp(_mock_netif, &exp_netif); @@ -946,9 +948,11 @@ static void test_handle_pkt__rtr_adv__invalid_hl(void) void *state = NULL; size_t icmpv6_len = _set_rtr_adv(&_rem_ll, 194U, 0U, true, 0U, + _REACH_TIME, _RTR_LTIME, _loc_l2, sizeof(_loc_l2), 32397U, &_loc_gb, _LOC_GB_PFX_LEN, - NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A); + NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); _netif_exp_t exp_netif; _get_netif_exp(_mock_netif, &exp_netif); @@ -969,9 +973,11 @@ static void test_handle_pkt__rtr_adv__invalid_code(void) void *state = NULL; size_t icmpv6_len = _set_rtr_adv(&_rem_ll, NDP_HOP_LIMIT, 201U, true, 0U, + _REACH_TIME, _RTR_LTIME, _loc_l2, sizeof(_loc_l2), 32397U, &_loc_gb, _LOC_GB_PFX_LEN, - NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A); + NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); _netif_exp_t exp_netif; _get_netif_exp(_mock_netif, &exp_netif); @@ -994,9 +1000,11 @@ static void test_handle_pkt__rtr_adv__invalid_icmpv6_len(void) _get_netif_exp(_mock_netif, &exp_netif); _set_rtr_adv(&_rem_ll, NDP_HOP_LIMIT, 201U, true, 0U, + _REACH_TIME, _RTR_LTIME, _loc_l2, sizeof(_loc_l2), 32397U, &_loc_gb, _LOC_GB_PFX_LEN, - NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A); + NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); gnrc_ipv6_nib_handle_pkt(_mock_netif, ipv6, icmpv6, sizeof(ndp_rtr_adv_t) - 1); TEST_ASSERT_MESSAGE(!gnrc_ipv6_nib_nc_iter(0, &state, &nce), @@ -1015,9 +1023,11 @@ static void test_handle_pkt__rtr_adv__invalid_opt_len(void) void *state = NULL; size_t icmpv6_len = _set_rtr_adv(&_rem_ll, NDP_HOP_LIMIT, 201U, true, 0U, + _REACH_TIME, _RTR_LTIME, _loc_l2, sizeof(_loc_l2), 32397U, &_loc_gb, _LOC_GB_PFX_LEN, - NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A); + NDP_OPT_PI_FLAGS_L | NDP_OPT_PI_FLAGS_A, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); ndp_opt_t *opt = (ndp_opt_t *)&_buffer[icmpv6_len]; _netif_exp_t exp_netif; @@ -1047,10 +1057,11 @@ static void test_handle_pkt__rtr_adv__success(uint8_t rtr_adv_flags, void *state = NULL; size_t icmpv6_len = _set_rtr_adv(&_rem_ll, NDP_HOP_LIMIT, 0U, set_rtr_adv_fields, rtr_adv_flags, + _REACH_TIME, _RTR_LTIME, (sl2ao) ? _rem_l2 : NULL, sizeof(_rem_l2), (mtuo) ? 32397U : 0U, (pio) ? &_loc_gb : NULL, _LOC_GB_PFX_LEN, - pio_flags); + pio_flags, _PIO_PFX_LTIME, _PIO_PFX_LTIME); const unsigned exp_addr_count = _netif_addr_count(_mock_netif); _netif_exp_t exp_netif; @@ -1279,6 +1290,108 @@ static void test_change_rtr_adv_iface(void) TEST_ASSERT_EQUAL_INT(0, msg_avail()); } +static void test_handle_router_timeout(void) +{ + gnrc_ipv6_nib_ft_t route; + gnrc_ipv6_nib_pl_t prefix; + void *state = NULL; + uint32_t t_rtr_alive_s = 5; /* [0, 9000]s, 0: not a default router */ + size_t icmpv6_len = _set_rtr_adv(&_rem_ll, + NDP_HOP_LIMIT, 0U, true, 0U, + _REACH_TIME, t_rtr_alive_s, + _rem_l2, sizeof(_rem_l2), + 32397U, &_loc_gb, _LOC_GB_PFX_LEN, + NDP_OPT_PI_FLAGS_L, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); + gnrc_ipv6_nib_handle_pkt(_mock_netif, ipv6, icmpv6, icmpv6_len); + /* check that prefix has been added */ + bool pfx = false, rtr = false; + while (gnrc_ipv6_nib_pl_iter(_mock_netif->pid, &state, &prefix)) { + pfx = true; + TEST_ASSERT_MESSAGE(ipv6_addr_match_prefix(&prefix.pfx, &_loc_gb) >= _LOC_GB_PFX_LEN, + "Prefix is not the one from the RA"); + } + if (!pfx) { + TEST_ASSERT_MESSAGE(false, "Prefix not added"); + } + /* check that router has been added */ + state = NULL; + while (gnrc_ipv6_nib_ft_iter(NULL, 0, &state, &route)) { + if (ipv6_addr_is_unspecified(&route.dst)) { + rtr = true; + TEST_ASSERT_MESSAGE(ipv6_addr_equal(&_rem_ll, &route.next_hop), + "Default route is not via RA source"); + } + } + if (!rtr) { + TEST_ASSERT_MESSAGE(false, "Default route not added"); + } + /* timeout router by RA with lifetime 0 */ + icmpv6_len = _set_rtr_adv(&_rem_ll, + NDP_HOP_LIMIT, 0U, true, 0U, + _REACH_TIME, 0, + _rem_l2, sizeof(_rem_l2), + 32397U, &_loc_gb, _LOC_GB_PFX_LEN, + NDP_OPT_PI_FLAGS_L, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); + gnrc_ipv6_nib_handle_pkt(_mock_netif, ipv6, icmpv6, icmpv6_len); + /* check that default router has been timed out */ + state = NULL; + while (gnrc_ipv6_nib_ft_iter(NULL, 0, &state, &route)) { + if (ipv6_addr_is_unspecified(&route.dst)) { + TEST_ASSERT_MESSAGE(ipv6_addr_equal(&_rem_ll, &route.next_hop), + "Default route was not deleted"); + } + } +} + +static void test_handle_prefix_timeout(void) +{ + gnrc_ipv6_nib_ft_t route; + gnrc_ipv6_nib_pl_t prefix; + void *state = NULL; + uint32_t t_rtr_alive_s = 0; /* [0, 9000]s, 0: not a default router */ + size_t icmpv6_len = _set_rtr_adv(&_rem_ll, + NDP_HOP_LIMIT, 0U, true, 0U, + _REACH_TIME, t_rtr_alive_s, + _rem_l2, sizeof(_rem_l2), + 32397U, &_loc_gb, _LOC_GB_PFX_LEN, + NDP_OPT_PI_FLAGS_L, + _PIO_PFX_LTIME, _PIO_PFX_LTIME); + gnrc_ipv6_nib_handle_pkt(_mock_netif, ipv6, icmpv6, icmpv6_len); + /* check that prefix has been added */ + bool pfx = false; + while (gnrc_ipv6_nib_pl_iter(_mock_netif->pid, &state, &prefix)) { + pfx = true; + TEST_ASSERT_MESSAGE(ipv6_addr_match_prefix(&prefix.pfx, &_loc_gb) >= _LOC_GB_PFX_LEN, + "Prefix is not the one from the RA"); + } + if (!pfx) { + TEST_ASSERT_MESSAGE(false, "Prefix not added"); + } + /* check that router has not been added as default router */ + state = NULL; + while (gnrc_ipv6_nib_ft_iter(NULL, 0, &state, &route)) { + if (ipv6_addr_is_unspecified(&route.dst)) { + TEST_ASSERT_MESSAGE(false, "Router should not have been added as a default router"); + } + } + /* timeout router by RA with lifetime 0 */ + icmpv6_len = _set_rtr_adv(&_rem_ll, + NDP_HOP_LIMIT, 0U, true, 0U, + _REACH_TIME, 0, + _rem_l2, sizeof(_rem_l2), + 32397U, &_loc_gb, _LOC_GB_PFX_LEN, + NDP_OPT_PI_FLAGS_L, + 0, 0); + gnrc_ipv6_nib_handle_pkt(_mock_netif, ipv6, icmpv6, icmpv6_len); + /* check that prefix has been timed out */ + state = NULL; + if (gnrc_ipv6_nib_pl_iter(_mock_netif->pid, &state, &prefix)) { + TEST_ASSERT_MESSAGE(false, "Prefix has been timed out but is still in use"); + } +} + static Test *tests_gnrc_ipv6_nib(void) { EMB_UNIT_TESTFIXTURES(fixtures) { @@ -1341,6 +1454,8 @@ static Test *tests_gnrc_ipv6_nib(void) * we do not have access to the (internally defined) contexts required * for it */ new_TestFixture(test_change_rtr_adv_iface), + new_TestFixture(test_handle_router_timeout), + new_TestFixture(test_handle_prefix_timeout), }; EMB_UNIT_TESTCALLER(tests, _set_up, NULL, fixtures);