From 68afd9d9008b8f06f01876060edef54d761c12e2 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 16 Jun 2021 12:02:16 +0200 Subject: [PATCH 1/3] gnrc_ndp: add route information option --- sys/include/net/gnrc/ndp.h | 29 +++++++++++++++++++++++ sys/include/net/ndp.h | 27 +++++++++++++++++++++ sys/net/gnrc/network_layer/ndp/gnrc_ndp.c | 24 +++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/sys/include/net/gnrc/ndp.h b/sys/include/net/gnrc/ndp.h index 2eb8057600..f58a635854 100644 --- a/sys/include/net/gnrc/ndp.h +++ b/sys/include/net/gnrc/ndp.h @@ -232,6 +232,35 @@ gnrc_pktsnip_t *gnrc_ndp_opt_pi_build(const ipv6_addr_t *prefix, uint8_t prefix_len, uint32_t valid_ltime, uint32_t pref_ltime, uint8_t flags, gnrc_pktsnip_t *next); +/** + * @brief Builds the route information option. + * + * @pre `prefix != NULL` + * @pre `!ipv6_addr_is_link_local(prefix) && !ipv6_addr_is_multicast(prefix)` + * @pre `prefix_len <= 128` + * + * @see [RFC 4191, section 2.3](https://tools.ietf.org/html/rfc4191#section-2.3) + * + * @note Should only be used with router advertisemnents. This is not checked + * however, since nodes should silently ignore it in other NDP messages. + * + * @param[in] prefix An IPv6 address or a prefix of an IPv6 address. + * Must not be NULL or be a link-local or + * multicast address. + * @param[in] prefix_len The length of @p prefix in bits. Must be between + * 0 and 128. + * @param[in] route_ltime Length of time in seconds that @p prefix is valid. + * UINT32_MAX represents infinity. + * @param[in] flags Flags as defined in net/ndp.h. + * @param[in] next More options in the packet. NULL, if there are none. + * + * @return The packet snip list of options, on success + * @return NULL, if packet buffer is full + */ +gnrc_pktsnip_t *gnrc_ndp_opt_ri_build(const ipv6_addr_t *prefix, + uint8_t prefix_len, + uint32_t route_ltime, + uint8_t flags, gnrc_pktsnip_t *next); /** * @brief Builds the MTU option. diff --git a/sys/include/net/ndp.h b/sys/include/net/ndp.h index fd43a05cd0..5cd82ef40f 100644 --- a/sys/include/net/ndp.h +++ b/sys/include/net/ndp.h @@ -88,6 +88,7 @@ extern "C" { #define NDP_OPT_PI (3) /**< prefix information option */ #define NDP_OPT_RH (4) /**< redirected option */ #define NDP_OPT_MTU (5) /**< MTU option */ +#define NDP_OPT_RI (24) /**< Route Information Option */ #define NDP_OPT_RDNSS (25) /**< recursive DNS server option */ #define NDP_OPT_AR (33) /**< address registration option */ #define NDP_OPT_6CTX (34) /**< 6LoWPAN context option */ @@ -103,6 +104,17 @@ extern "C" { #define NDP_OPT_PI_FLAGS_A (0x40) /**< autonomous address configuration */ /** @} */ +/** + * @{ + * @name Flags for route information option + */ +#define NDP_OPT_RI_FLAGS_MASK (0x18) +#define NDP_OPT_RI_FLAGS_PRF_NONE (0x10) /**< ignore preference */ +#define NDP_OPT_RI_FLAGS_PRF_NEG (0x18) /**< negative preference */ +#define NDP_OPT_RI_FLAGS_PRF_ZERO (0x0) /**< zero preference */ +#define NDP_OPT_RI_FLAGS_PRF_POS (0x8) /**< positive preference */ +/** @} */ + /** * @{ * @name Prefix information option constants @@ -308,6 +320,21 @@ typedef struct __attribute__((packed)) { ipv6_addr_t prefix; /**< prefix */ } ndp_opt_pi_t; +/** + * @brief Route information option format + * @extends ndp_opt_t + * + * @see [RFC 4191, section 2.3](https://tools.ietf.org/html/rfc4191#section-2.3) + */ +typedef struct __attribute__((packed)) { + uint8_t type; /**< option type */ + uint8_t len; /**< length in units of 8 octets */ + uint8_t prefix_len; /**< prefix length */ + uint8_t flags; /**< flags */ + network_uint32_t route_ltime; /**< route lifetime */ + ipv6_addr_t prefix; /**< prefix */ +} ndp_opt_ri_t; + /** * @brief Redirected header option format * @extends ndp_opt_t diff --git a/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c b/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c index 2fc4d16437..7294d733e1 100644 --- a/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c +++ b/sys/net/gnrc/network_layer/ndp/gnrc_ndp.c @@ -201,6 +201,30 @@ gnrc_pktsnip_t *gnrc_ndp_opt_pi_build(const ipv6_addr_t *prefix, return pkt; } +gnrc_pktsnip_t *gnrc_ndp_opt_ri_build(const ipv6_addr_t *prefix, + uint8_t prefix_len, + uint32_t route_ltime, + uint8_t flags, gnrc_pktsnip_t *next) +{ + assert(prefix != NULL); + assert(!ipv6_addr_is_link_local(prefix) && !ipv6_addr_is_multicast(prefix)); + assert(prefix_len <= 128); + gnrc_pktsnip_t *pkt = gnrc_ndp_opt_build(NDP_OPT_RI, sizeof(ndp_opt_ri_t), + next); + + if (pkt != NULL) { + ndp_opt_ri_t *ri_opt = pkt->data; + + ri_opt->prefix_len = prefix_len; + ri_opt->flags = (flags & NDP_OPT_PI_FLAGS_MASK); + ri_opt->route_ltime = byteorder_htonl(route_ltime); + /* Bits beyond prefix_len MUST be 0 */ + ipv6_addr_set_unspecified(&ri_opt->prefix); + ipv6_addr_init_prefix(&ri_opt->prefix, prefix, prefix_len); + } + return pkt; +} + gnrc_pktsnip_t *gnrc_ndp_opt_mtu_build(uint32_t mtu, gnrc_pktsnip_t *next) { gnrc_pktsnip_t *pkt = gnrc_ndp_opt_build(NDP_OPT_MTU, From 12a0d481dc0b7edef344c4dd24bf979ab857aa47 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 16 Jun 2021 12:45:12 +0200 Subject: [PATCH 2/3] gnrc_ipv6_nib: handle route information option --- makefiles/pseudomodules.inc.mk | 1 + sys/net/gnrc/Makefile.dep | 1 + sys/net/gnrc/network_layer/ipv6/nib/nib.c | 56 +++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 3738999b53..5c4c6ef42e 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -40,6 +40,7 @@ PSEUDOMODULES += gnrc_ipv6_nib_6lbr PSEUDOMODULES += gnrc_ipv6_nib_6ln PSEUDOMODULES += gnrc_ipv6_nib_6lr PSEUDOMODULES += gnrc_ipv6_nib_dns +PSEUDOMODULES += gnrc_ipv6_nib_rio PSEUDOMODULES += gnrc_ipv6_nib_router PSEUDOMODULES += gnrc_netdev_default PSEUDOMODULES += gnrc_neterr diff --git a/sys/net/gnrc/Makefile.dep b/sys/net/gnrc/Makefile.dep index 965adcc389..6f530dc1d6 100644 --- a/sys/net/gnrc/Makefile.dep +++ b/sys/net/gnrc/Makefile.dep @@ -246,6 +246,7 @@ ifneq (,$(filter gnrc_ipv6_default,$(USEMODULE))) endif ifneq (,$(filter gnrc_ipv6_router_default,$(USEMODULE))) + USEMODULE += gnrc_ipv6_nib_rio USEMODULE += gnrc_ipv6_router USEMODULE += gnrc_icmpv6 endif diff --git a/sys/net/gnrc/network_layer/ipv6/nib/nib.c b/sys/net/gnrc/network_layer/ipv6/nib/nib.c index e2000a4086..a4fcf0ece9 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/nib.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/nib.c @@ -461,6 +461,8 @@ static uint32_t _handle_pio(gnrc_netif_t *netif, const icmpv6_hdr_t *icmpv6, static uint32_t _handle_pio(gnrc_netif_t *netif, const icmpv6_hdr_t *icmpv6, const ndp_opt_pi_t *pio); #endif /* CONFIG_GNRC_IPV6_NIB_MULTIHOP_P6C */ +static uint32_t _handle_rio(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6, + const ndp_opt_ri_t *pio); /** @} */ /* Iterator for NDP options in a packet */ @@ -738,6 +740,9 @@ static void _handle_rtr_adv(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6, _handle_mtuo(netif, (const icmpv6_hdr_t *)rtr_adv, (ndp_opt_mtu_t *)opt); break; + case NDP_OPT_RI: + _handle_rio(netif, ipv6, (ndp_opt_ri_t *)opt); + break; case NDP_OPT_PI: { uint32_t min_pfx_timeout; #if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_MULTIHOP_P6C) @@ -1570,4 +1575,55 @@ static uint32_t _handle_pio(gnrc_netif_t *netif, const icmpv6_hdr_t *icmpv6, return UINT32_MAX; } +static const char *_prio_string(uint8_t prio) +{ + switch (prio & NDP_OPT_RI_FLAGS_MASK) { + case NDP_OPT_RI_FLAGS_PRF_NONE: + return "none"; + case NDP_OPT_RI_FLAGS_PRF_NEG: + return "-1"; + case NDP_OPT_RI_FLAGS_PRF_ZERO: + return "0"; + case NDP_OPT_RI_FLAGS_PRF_POS: + return "1"; + } + + return "invalid"; +} + +static uint32_t _handle_rio(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6, + const ndp_opt_ri_t *rio) +{ + if (!IS_USED(MODULE_GNRC_IPV6_NIB_RIO)) { + return 0; + } + + uint32_t route_ltime = byteorder_ntohl(rio->route_ltime); + + if (ipv6_addr_is_link_local(&rio->prefix)) { + DEBUG("nib: ignoring RIO with invalid data\n"); + return UINT32_MAX; + } + DEBUG("nib: received valid Route Information option:\n"); + DEBUG(" - Prefix: %s/%u\n", + ipv6_addr_to_str(addr_str, &rio->prefix, sizeof(addr_str)), + rio->prefix_len); + DEBUG(" - Priority: %s\n", _prio_string(rio->flags)); + DEBUG(" - Route lifetime: %" PRIu32 "\n", + byteorder_ntohl(rio->route_ltime)); + + if (route_ltime < UINT32_MAX) { /* UINT32_MAX means infinite lifetime */ + /* the valid lifetime is given in seconds, but our timers work in + * microseconds, so we have to scale down to the smallest possible + * value (UINT32_MAX - 1). This is however alright since we ask for + * a new router advertisement before this timeout expires */ + route_ltime = (route_ltime > (UINT32_MAX / MS_PER_SEC)) ? + (UINT32_MAX - 1) : route_ltime * MS_PER_SEC; + } + + gnrc_ipv6_nib_ft_add(&rio->prefix, rio->prefix_len, &ipv6->src, + netif->pid, route_ltime); + + return route_ltime; +} /** @} */ From 71ae768e56ee8ffd164eb4829ebbeab082470b87 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Mon, 12 Jul 2021 19:16:27 +0200 Subject: [PATCH 3/3] net/gnrc/ipv6/nib: add option to include RIO with final RA Sending a RA with ltime = 0 does not get us added to the default router list, but the options (and therefore the RIO) are still parsed. This even appears to work with Linux as a receiver. --- sys/include/net/gnrc/ipv6/nib/conf.h | 11 ++++ .../gnrc/network_layer/ipv6/nib/_nib-router.c | 50 ++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/sys/include/net/gnrc/ipv6/nib/conf.h b/sys/include/net/gnrc/ipv6/nib/conf.h index a97c31ed0a..59293aa4d3 100644 --- a/sys/include/net/gnrc/ipv6/nib/conf.h +++ b/sys/include/net/gnrc/ipv6/nib/conf.h @@ -138,6 +138,17 @@ extern "C" { #endif #endif +/** + * @brief Include a Route Information Option for subnets + * on other interfaces in the last Router Advertisement + * generated by @ref gnrc_ipv6_nib_change_rtr_adv_iface + * + * Requires the `gnrc_ipv6_nib_rio` module. + */ +#ifndef CONFIG_GNRC_IPV6_NIB_ADD_RIO_IN_LAST_RA +#define CONFIG_GNRC_IPV6_NIB_ADD_RIO_IN_LAST_RA 0 +#endif + /** * @brief (de-)activate NDP address resolution state-machine */ diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c index 89956794e0..f1fa76cc6c 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-router.c @@ -215,6 +215,52 @@ static gnrc_pktsnip_t *_build_ext_opts(gnrc_netif_t *netif, return ext_opts; } +/* Sending a RA with ltime = 0 causes the router to be removed from the + * default router list, but options are still parsed. + * This allows us to add downstream subnets that should be routed through + * this router, but the router is not an upstream / default router for + * this link. */ +static gnrc_pktsnip_t *_build_final_ext_opts(gnrc_netif_t *netif) +{ + gnrc_pktsnip_t *ext_opts = NULL; + _nib_offl_entry_t *entry = NULL; + + if (!IS_USED(MODULE_GNRC_IPV6_NIB_RIO) || + !IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ADD_RIO_IN_LAST_RA)) { + return NULL; + } + + DEBUG("nib: sending final RA on interface %u\n", netif->pid); + + uint32_t now = evtimer_now_msec(); + while ((entry = _nib_offl_iter(entry))) { + + unsigned id = netif->pid; + if (_nib_onl_get_if(entry->next_hop) == id) { + continue; + } + + if ((entry->mode & _PL) && (entry->flags & _PFX_ON_LINK)) { + DEBUG("nib: adding downstream subnet to RA\n"); + uint32_t valid_ltime = (entry->valid_until == UINT32_MAX) ? UINT32_MAX : + ((entry->valid_until - now) / MS_PER_SEC); + gnrc_pktsnip_t *snip = gnrc_ndp_opt_ri_build(&entry->pfx, + entry->pfx_len, + valid_ltime, + NDP_OPT_RI_FLAGS_PRF_NONE, + ext_opts); + if (snip != NULL) { + ext_opts = snip; + } else { + DEBUG_PUTS("nib: can't add RIO to RA - out of memory"); + break; + } + } + } + + return ext_opts; +} + void _set_rtr_adv(gnrc_netif_t *netif) { DEBUG("nib: set RTR_ADV flag for interface %i\n", netif->pid); @@ -228,7 +274,9 @@ static void _snd_ra(gnrc_netif_t *netif, const ipv6_addr_t *dst, { gnrc_pktsnip_t *ext_opts = NULL; - if (!final) { + if (final) { + ext_opts = _build_final_ext_opts(netif); + } else { ext_opts = _build_ext_opts(netif, abr); }