mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-18 05:21:56 +00:00
IPv6 MLD API was so far defined in an internal header. This does not seem correct though, as application code should be able to join/leave multicast groups, hence the API should be exposed in a public header, just as it is done for its IPv4 countepart - IGMP. Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
470 lines
10 KiB
C
470 lines
10 KiB
C
/** @file
|
|
* @brief IPv6 MLD related functions
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2018 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL);
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/net/mld.h>
|
|
#include <zephyr/net/net_core.h>
|
|
#include <zephyr/net/net_pkt.h>
|
|
#include <zephyr/net/net_stats.h>
|
|
#include <zephyr/net/net_context.h>
|
|
#include <zephyr/net/net_mgmt.h>
|
|
#include <zephyr/net/icmp.h>
|
|
#include "net_private.h"
|
|
#include "connection.h"
|
|
#include "icmpv6.h"
|
|
#include "udp_internal.h"
|
|
#include "tcp_internal.h"
|
|
#include "ipv6.h"
|
|
#include "nbr.h"
|
|
#include "6lo.h"
|
|
#include "route.h"
|
|
#include "net_stats.h"
|
|
|
|
/* Timeout for various buffer allocations in this file. */
|
|
#define PKT_WAIT_TIME K_MSEC(50)
|
|
|
|
#define MLDv2_MCAST_RECORD_LEN sizeof(struct net_icmpv6_mld_mcast_record)
|
|
#define IPV6_OPT_HDR_ROUTER_ALERT_LEN 8
|
|
#define MLDV2_REPORT_RESERVED_BYTES 2
|
|
|
|
#define MLDv2_LEN (MLDv2_MCAST_RECORD_LEN + sizeof(struct in6_addr))
|
|
|
|
/* Internal structure used for appending multicast routes to MLDv2 reports */
|
|
struct mcast_route_appending_info {
|
|
int status;
|
|
struct net_pkt *pkt;
|
|
struct net_if *iface;
|
|
size_t skipped;
|
|
};
|
|
|
|
static int mld_create(struct net_pkt *pkt,
|
|
const struct in6_addr *addr,
|
|
uint8_t record_type)
|
|
{
|
|
NET_PKT_DATA_ACCESS_DEFINE(mld_access,
|
|
struct net_icmpv6_mld_mcast_record);
|
|
struct net_icmpv6_mld_mcast_record *mld;
|
|
|
|
mld = (struct net_icmpv6_mld_mcast_record *)
|
|
net_pkt_get_data(pkt, &mld_access);
|
|
if (!mld) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
mld->record_type = record_type;
|
|
mld->aux_data_len = 0U;
|
|
mld->num_sources = 0U;
|
|
|
|
net_ipv6_addr_copy_raw(mld->mcast_address, (uint8_t *)addr);
|
|
|
|
if (net_pkt_set_data(pkt, &mld_access)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mld_create_packet(struct net_pkt *pkt, uint16_t count)
|
|
{
|
|
struct in6_addr dst;
|
|
|
|
/* Sent to all MLDv2-capable routers */
|
|
net_ipv6_addr_create(&dst, 0xff02, 0, 0, 0, 0, 0, 0, 0x0016);
|
|
|
|
net_pkt_set_ipv6_hop_limit(pkt, 1); /* RFC 3810 ch 7.4 */
|
|
|
|
if (net_ipv6_create(pkt, net_if_ipv6_select_src_addr(
|
|
net_pkt_iface(pkt), &dst),
|
|
&dst)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* Add hop-by-hop option and router alert option, RFC 3810 ch 5. */
|
|
if (net_pkt_write_u8(pkt, IPPROTO_ICMPV6) ||
|
|
net_pkt_write_u8(pkt, 0)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* IPv6 router alert option is described in RFC 2711.
|
|
* - 0x0502 RFC 2711 ch 2.1
|
|
* - MLD (value 0)
|
|
* - 2 bytes of padding
|
|
*/
|
|
if (net_pkt_write_be16(pkt, 0x0502) ||
|
|
net_pkt_write_be16(pkt, 0) ||
|
|
net_pkt_write_be16(pkt, 0)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
net_pkt_set_ipv6_ext_len(pkt, IPV6_OPT_HDR_ROUTER_ALERT_LEN);
|
|
|
|
/* ICMPv6 header + reserved space + count.
|
|
* MLDv6 stuff will come right after
|
|
*/
|
|
if (net_icmpv6_create(pkt, NET_ICMPV6_MLDv2, 0) ||
|
|
net_pkt_write_be16(pkt, 0) ||
|
|
net_pkt_write_be16(pkt, count)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
net_pkt_set_ipv6_next_hdr(pkt, NET_IPV6_NEXTHDR_HBHO);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mld_send(struct net_pkt *pkt)
|
|
{
|
|
int ret;
|
|
|
|
net_pkt_cursor_init(pkt);
|
|
net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
|
|
|
|
ret = net_send_data(pkt);
|
|
if (ret < 0) {
|
|
net_stats_update_icmp_drop(net_pkt_iface(pkt));
|
|
net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
net_stats_update_icmp_sent(net_pkt_iface(pkt));
|
|
net_stats_update_ipv6_mld_sent(net_pkt_iface(pkt));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_MCAST_ROUTE_MLD_REPORTS)
|
|
static void count_mcast_routes(struct net_route_entry_mcast *entry, void *user_data)
|
|
{
|
|
(*((int *)user_data))++;
|
|
}
|
|
|
|
static void append_mcast_routes(struct net_route_entry_mcast *entry, void *user_data)
|
|
{
|
|
struct mcast_route_appending_info *info = (struct mcast_route_appending_info *)user_data;
|
|
struct net_if_mcast_addr *mcasts = info->iface->config.ip.ipv6->mcast;
|
|
|
|
if (info->status != 0 || entry->prefix_len != 128) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
|
|
if (!mcasts[i].is_used || !mcasts[i].is_joined) {
|
|
continue;
|
|
}
|
|
|
|
if (net_ipv6_addr_cmp(&entry->group, &mcasts[i].address.in6_addr)) {
|
|
/* Address was already added to the report */
|
|
info->skipped++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
info->status = mld_create(info->pkt, &entry->group, NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
|
|
}
|
|
#endif
|
|
|
|
int net_ipv6_mld_send_single(struct net_if *iface, const struct in6_addr *addr, uint8_t mode)
|
|
{
|
|
struct net_pkt *pkt;
|
|
int ret;
|
|
|
|
pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
|
|
NET_ICMPV6_UNUSED_LEN +
|
|
MLDv2_MCAST_RECORD_LEN +
|
|
sizeof(struct in6_addr),
|
|
AF_INET6, IPPROTO_ICMPV6,
|
|
PKT_WAIT_TIME);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (mld_create_packet(pkt, 1) ||
|
|
mld_create(pkt, addr, mode)) {
|
|
ret = -ENOBUFS;
|
|
goto drop;
|
|
}
|
|
|
|
ret = mld_send(pkt);
|
|
if (ret) {
|
|
goto drop;
|
|
}
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
net_pkt_unref(pkt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int net_ipv6_mld_join(struct net_if *iface, const struct in6_addr *addr)
|
|
{
|
|
struct net_if_mcast_addr *maddr;
|
|
int ret;
|
|
|
|
maddr = net_if_ipv6_maddr_lookup(addr, &iface);
|
|
if (maddr && net_if_ipv6_maddr_is_joined(maddr)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
if (!maddr) {
|
|
maddr = net_if_ipv6_maddr_add(iface, addr);
|
|
if (!maddr) {
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
if (net_if_flag_is_set(iface, NET_IF_IPV6_NO_MLD)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!net_if_is_up(iface)) {
|
|
return -ENETDOWN;
|
|
}
|
|
|
|
ret = net_ipv6_mld_send_single(iface, addr, NET_IPV6_MLDv2_CHANGE_TO_EXCLUDE_MODE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
net_if_ipv6_maddr_join(iface, maddr);
|
|
|
|
net_if_mcast_monitor(iface, &maddr->address, true);
|
|
|
|
net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_JOIN, iface,
|
|
&maddr->address.in6_addr,
|
|
sizeof(struct in6_addr));
|
|
|
|
return ret;
|
|
}
|
|
|
|
int net_ipv6_mld_leave(struct net_if *iface, const struct in6_addr *addr)
|
|
{
|
|
struct net_if_mcast_addr *maddr;
|
|
int ret;
|
|
|
|
maddr = net_if_ipv6_maddr_lookup(addr, &iface);
|
|
if (!maddr) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!net_if_ipv6_maddr_rm(iface, addr)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (net_if_flag_is_set(iface, NET_IF_IPV6_NO_MLD)) {
|
|
return 0;
|
|
}
|
|
|
|
ret = net_ipv6_mld_send_single(iface, addr, NET_IPV6_MLDv2_CHANGE_TO_INCLUDE_MODE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
net_if_mcast_monitor(iface, &maddr->address, false);
|
|
|
|
net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_LEAVE, iface,
|
|
&maddr->address.in6_addr,
|
|
sizeof(struct in6_addr));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int send_mld_report(struct net_if *iface)
|
|
{
|
|
struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6;
|
|
struct net_pkt *pkt;
|
|
int i, count = 0;
|
|
int ret;
|
|
|
|
NET_ASSERT(ipv6);
|
|
|
|
for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
|
|
if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
|
|
continue;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_MCAST_ROUTE_MLD_REPORTS)
|
|
/* Increase number of slots by a number of multicast routes that
|
|
* can be later added to the report. Checking for duplicates is done
|
|
* while appending an entry.
|
|
*/
|
|
net_route_mcast_foreach(count_mcast_routes, NULL, (void *)&count);
|
|
#endif
|
|
|
|
pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
|
|
NET_ICMPV6_UNUSED_LEN +
|
|
count * MLDv2_MCAST_RECORD_LEN,
|
|
AF_INET6, IPPROTO_ICMPV6,
|
|
PKT_WAIT_TIME);
|
|
if (!pkt) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
ret = mld_create_packet(pkt, count);
|
|
if (ret < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
|
|
if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
|
|
continue;
|
|
}
|
|
|
|
ret = mld_create(pkt, &ipv6->mcast[i].address.in6_addr,
|
|
NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
|
|
if (ret < 0) {
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_NET_MCAST_ROUTE_MLD_REPORTS)
|
|
/* Append information about multicast routes as packets will be
|
|
* forwarded to these interfaces on reception.
|
|
*/
|
|
struct mcast_route_appending_info info;
|
|
|
|
info.status = 0;
|
|
info.pkt = pkt;
|
|
info.iface = iface;
|
|
info.skipped = 0;
|
|
|
|
net_route_mcast_foreach(append_mcast_routes, NULL, &info);
|
|
|
|
ret = info.status;
|
|
if (ret < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
/* We may have skipped duplicated addresses that we reserved space for,
|
|
* modify number of records.
|
|
*/
|
|
if (info.skipped) {
|
|
net_pkt_cursor_init(pkt);
|
|
net_pkt_set_overwrite(pkt, true);
|
|
|
|
net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr) + MLDV2_REPORT_RESERVED_BYTES);
|
|
|
|
count -= info.skipped;
|
|
|
|
ret = net_pkt_write_be16(pkt, count);
|
|
if (ret < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_remove_tail(pkt, info.skipped * sizeof(struct net_icmpv6_mld_mcast_record));
|
|
}
|
|
#endif
|
|
|
|
ret = mld_send(pkt);
|
|
if (ret < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
net_pkt_unref(pkt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define dbg_addr(action, pkt_str, src, dst) \
|
|
do { \
|
|
NET_DBG("%s %s from %s to %s", action, pkt_str, \
|
|
net_sprint_ipv6_addr(src), \
|
|
net_sprint_ipv6_addr(dst)); \
|
|
} while (0)
|
|
|
|
#define dbg_addr_recv(pkt_str, src, dst) \
|
|
dbg_addr("Received", pkt_str, src, dst)
|
|
|
|
static int handle_mld_query(struct net_icmp_ctx *ctx,
|
|
struct net_pkt *pkt,
|
|
struct net_icmp_ip_hdr *hdr,
|
|
struct net_icmp_hdr *icmp_hdr,
|
|
void *user_data)
|
|
{
|
|
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(mld_access,
|
|
struct net_icmpv6_mld_query);
|
|
struct net_ipv6_hdr *ip_hdr = hdr->ipv6;
|
|
uint16_t length = net_pkt_get_len(pkt);
|
|
struct net_icmpv6_mld_query *mld_query;
|
|
uint16_t pkt_len;
|
|
int ret = -EIO;
|
|
|
|
if (net_pkt_remaining_data(pkt) < sizeof(struct net_icmpv6_mld_query)) {
|
|
/* MLDv1 query, drop. */
|
|
ret = 0;
|
|
goto drop;
|
|
}
|
|
|
|
mld_query = (struct net_icmpv6_mld_query *)
|
|
net_pkt_get_data(pkt, &mld_access);
|
|
if (!mld_query) {
|
|
NET_DBG("DROP: NULL MLD query");
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_acknowledge_data(pkt, &mld_access);
|
|
|
|
dbg_addr_recv("Multicast Listener Query", &ip_hdr->src, &ip_hdr->dst);
|
|
|
|
net_stats_update_ipv6_mld_recv(net_pkt_iface(pkt));
|
|
|
|
mld_query->num_sources = ntohs(mld_query->num_sources);
|
|
|
|
pkt_len = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_mld_query) +
|
|
sizeof(struct in6_addr) * mld_query->num_sources;
|
|
|
|
if (length < pkt_len || pkt_len > NET_IPV6_MTU ||
|
|
ip_hdr->hop_limit != 1U || icmp_hdr->code != 0U) {
|
|
goto drop;
|
|
}
|
|
|
|
/* Currently we only support an unspecified address query. */
|
|
if (!net_ipv6_addr_cmp_raw(mld_query->mcast_address,
|
|
(uint8_t *)net_ipv6_unspecified_address())) {
|
|
NET_DBG("DROP: only supporting unspecified address query");
|
|
goto drop;
|
|
}
|
|
|
|
return send_mld_report(net_pkt_iface(pkt));
|
|
|
|
drop:
|
|
net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void net_ipv6_mld_init(void)
|
|
{
|
|
static struct net_icmp_ctx ctx;
|
|
int ret;
|
|
|
|
ret = net_icmp_init_ctx(&ctx, NET_ICMPV6_MLD_QUERY, 0, handle_mld_query);
|
|
if (ret < 0) {
|
|
NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV6_MLD_QUERY),
|
|
ret);
|
|
}
|
|
}
|