diff --git a/sys/include/net/gnrc/netif/internal.h b/sys/include/net/gnrc/netif/internal.h index e69665a594..ef0b192038 100644 --- a/sys/include/net/gnrc/netif/internal.h +++ b/sys/include/net/gnrc/netif/internal.h @@ -693,12 +693,36 @@ static inline int gnrc_netif_ndp_addr_len_from_l2ao(gnrc_netif_t *netif, assert(netif->flags & GNRC_NETIF_FLAGS_HAS_L2ADDR); return l2util_ndp_addr_len_from_l2ao(netif->device_type, opt); } + +/** + * @brief Converts an IPv6 multicast address to a multicast address + * of the respective link layer. + * + * @pre There is enough allocated space in @p l2_group for an address for a + * device of type @p dev_type (e.g. 6 bytes for an ethernet address). + * + * @param[in] dev_type The network interface @p l2_addr should be generated + * for. + * @param[in] ipv6_group An IPv6 multicast address. + * @param[out] l2_group A link layer multicast address + * + * @return Length of @p l2_group in bytes + * @return `-ENOTSUP` if link layer does not support multicast. + */ +static inline int gnrc_netif_ipv6_group_to_l2_group(gnrc_netif_t *netif, + const ipv6_addr_t *ipv6_group, + uint8_t *l2_group) +{ + return l2util_ipv6_group_to_l2_group(netif->device_type, ipv6_group, + l2_group); +} #else /* IS_USED(MODULE_GNRC_NETIF_IPV6) || defined(DOXYGEN) */ #define gnrc_netif_ipv6_init_mtu(netif) (void)netif #define gnrc_netif_ipv6_iid_from_addr(netif, addr, addr_len, iid) (-ENOTSUP) #define gnrc_netif_ipv6_iid_to_addr(netif, iid, addr) (-ENOTSUP) #define gnrc_netif_ndp_addr_len_from_l2ao(netif, opt) (-ENOTSUP) #define gnrc_netif_ipv6_get_iid(netif, iid) (-ENOTSUP) +#define gnrc_netif_ipv6_group_to_l2_group(netif, ipv6_group, l2_group) (-ENOTSUP) #endif /* IS_USED(MODULE_GNRC_NETIF_IPV6) || defined(DOXYGEN) */ /** @} */ diff --git a/sys/net/gnrc/netif/gnrc_netif.c b/sys/net/gnrc/netif/gnrc_netif.c index 54ebcf8713..2aece9fe1d 100644 --- a/sys/net/gnrc/netif/gnrc_netif.c +++ b/sys/net/gnrc/netif/gnrc_netif.c @@ -717,12 +717,44 @@ gnrc_netif_t *gnrc_netif_get_by_prefix(const ipv6_addr_t *prefix) return best_netif; } +static int _netif_ops_set_helper(gnrc_netif_t *netif, netopt_t opt, + void *data, uint16_t data_len) +{ + gnrc_netapi_opt_t netapi_opt = { + .opt = opt, + .data = data, + .data_len = data_len, + }; + return netif->ops->set(netif, &netapi_opt); +} + int gnrc_netif_ipv6_group_join_internal(gnrc_netif_t *netif, const ipv6_addr_t *addr) { + uint8_t l2_group_data[GNRC_NETIF_L2ADDR_MAXLEN]; unsigned idx = UINT_MAX; + int l2_group_len; + /* can be called out of lock */ + l2_group_len = gnrc_netif_ipv6_group_to_l2_group(netif, addr, + l2_group_data); gnrc_netif_acquire(netif); + if (l2_group_len > 0) { + int res = _netif_ops_set_helper(netif, NETOPT_L2_GROUP, + l2_group_data, (uint16_t)l2_group_len); + /* link layer does not support multicast, but we can still use + * broadcast */ + if ((res != -ENOTSUP) && (res < 0)) { + gnrc_netif_release(netif); + return res; + } + } + /* link layer does not support multicast, but we can still use + * broadcast */ + else if (l2_group_len != -ENOTSUP) { + gnrc_netif_release(netif); + return l2_group_len; + } for (unsigned i = 0; i < GNRC_NETIF_IPV6_GROUPS_NUMOF; i++) { if (ipv6_addr_equal(&netif->ipv6.groups[i], addr)) { gnrc_netif_release(netif); @@ -748,11 +780,56 @@ int gnrc_netif_ipv6_group_join_internal(gnrc_netif_t *netif, void gnrc_netif_ipv6_group_leave_internal(gnrc_netif_t *netif, const ipv6_addr_t *addr) { - int idx; + uint8_t l2_group_data[GNRC_NETIF_L2ADDR_MAXLEN]; + int idx = -1, l2_group_len; + /* IPv6 addresses that correspond to the same L2 address */ + unsigned l2_groups = 0; assert((netif != NULL) && (addr != NULL)); + /* can be called out of lock */ + l2_group_len = gnrc_netif_ipv6_group_to_l2_group(netif, addr, + l2_group_data); + /* link layer does not support multicast, but might still have used + * broadcast */ + if ((l2_group_len < 0) && (l2_group_len != -ENOTSUP)) { + return; + } gnrc_netif_acquire(netif); - idx = _group_idx(netif, addr); + for (unsigned i = 0; i < GNRC_NETIF_IPV6_GROUPS_NUMOF; i++) { + if (l2_group_len > 0) { + uint8_t tmp[GNRC_NETIF_L2ADDR_MAXLEN]; + if (!ipv6_addr_is_unspecified(&netif->ipv6.groups[i]) && + (gnrc_netif_ipv6_group_to_l2_group(netif, + &netif->ipv6.groups[i], + tmp) == l2_group_len)) { + if (memcmp(tmp, l2_group_data, l2_group_len) == 0) { + l2_groups++; + } + } + } + if (ipv6_addr_equal(&netif->ipv6.groups[i], addr)) { + idx = i; + } + } + if (idx < 0) { + gnrc_netif_release(netif); + return; + } + /* we need to have found at least one corresponding group for the IPv6 + * group we want to leave when link layer supports multicast */ + assert((l2_group_len == -ENOTSUP) || (l2_groups > 0)); + /* we only found exactly IPv6 multicast address that corresponds to + * `l2_group_data`, so we can remove it, if there would be more, we need + * to stay in the group */ + if (l2_groups == 1) { + int res = _netif_ops_set_helper(netif, NETOPT_L2_GROUP_LEAVE, + l2_group_data, (uint16_t)l2_group_len); + /* link layer does not support multicast, but might still have used + * broadcast */ + if ((res != -ENOTSUP) && (res < 0)) { + DEBUG("gnrc_netif: error leaving link layer group\n"); + } + } if (idx >= 0) { ipv6_addr_set_unspecified(&netif->ipv6.groups[idx]); /* TODO: