blob: cff1a2d2b73e7fc8920d883537b9d196c5c3795a [file] [log] [blame]
/** @file
* @brief IPv6 MLD related functions
*/
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_NET_DEBUG_IPV6)
#define SYS_LOG_DOMAIN "net/ipv6-mld"
#define NET_LOG_ENABLED 1
/* By default this prints too much data, set the value to 1 to see
* neighbor cache contents.
*/
#define NET_DEBUG_NBR 0
#endif
#include <errno.h>
#include <net/net_core.h>
#include <net/net_pkt.h>
#include <net/net_stats.h>
#include <net/net_context.h>
#include <net/net_mgmt.h>
#include <net/tcp.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 "rpl.h"
#include "net_stats.h"
/* Timeout for various buffer allocations in this file. */
#define NET_BUF_TIMEOUT K_MSEC(50)
#define append(pkt, type, value) \
do { \
if (!net_pkt_append_##type##_timeout(pkt, value, \
NET_BUF_TIMEOUT)) { \
ret = -ENOMEM; \
goto drop; \
} \
} while (0)
#define append_all(pkt, size, value) \
do { \
if (!net_pkt_append_all(pkt, size, value, \
NET_BUF_TIMEOUT)) { \
ret = -ENOMEM; \
goto drop; \
} \
} while (0)
#define MLDv2_LEN (2 + 1 + 1 + 2 + sizeof(struct in6_addr) * 2)
static struct net_pkt *create_mldv2(struct net_pkt *pkt,
const struct in6_addr *addr,
u16_t record_type,
u8_t num_sources)
{
int ret;
append(pkt, u8, record_type);
append(pkt, u8, 0); /* aux data len */
append(pkt, be16, num_sources); /* number of addresses */
append_all(pkt, sizeof(struct in6_addr), addr->s6_addr);
if (num_sources > 0) {
/* All source addresses, RFC 3810 ch 3 */
append_all(pkt, sizeof(struct in6_addr),
net_ipv6_unspecified_address()->s6_addr);
}
return pkt;
drop:
return NULL;
}
static int send_mldv2_raw(struct net_if *iface, struct net_buf *frags)
{
struct net_pkt *pkt;
struct in6_addr dst;
u16_t pos;
int ret;
/* Sent to all MLDv2-capable routers */
net_ipv6_addr_create(&dst, 0xff02, 0, 0, 0, 0, 0, 0, 0x0016);
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, &dst),
NET_BUF_TIMEOUT);
if (!pkt) {
return -ENOMEM;
}
if (!net_ipv6_create(pkt,
net_if_ipv6_select_src_addr(iface, &dst),
&dst,
iface,
NET_IPV6_NEXTHDR_HBHO)) {
ret = -ENOMEM;
goto drop;
}
NET_IPV6_HDR(pkt)->hop_limit = 1; /* RFC 3810 ch 7.4 */
net_pkt_set_ipv6_hdr_prev(pkt, pkt->frags->len);
/* Add hop-by-hop option and router alert option, RFC 3810 ch 5. */
append(pkt, u8, IPPROTO_ICMPV6);
append(pkt, u8, 0); /* length (0 means 8 bytes) */
/* IPv6 router alert option is described in RFC 2711. */
append(pkt, be16, 0x0502); /* RFC 2711 ch 2.1 */
append(pkt, be16, 0); /* pkt contains MLD msg */
append(pkt, u8, 0); /* padding */
append(pkt, u8, 0); /* padding */
/* ICMPv6 header */
append(pkt, u8, NET_ICMPV6_MLDv2); /* type */
append(pkt, u8, 0); /* code */
append(pkt, be16, 0); /* chksum */
append(pkt, be16, 0); /* reserved field */
#define ROUTER_ALERT_LEN 8
pkt->frags->len = NET_IPV6ICMPH_LEN + ROUTER_ALERT_LEN;
net_pkt_set_iface(pkt, iface);
/* Insert the actual multicast record(s) here */
net_pkt_frag_add(pkt, frags);
ret = net_ipv6_finalize(pkt, NET_IPV6_NEXTHDR_HBHO);
if (ret < 0) {
goto drop;
}
net_pkt_set_ipv6_ext_len(pkt, ROUTER_ALERT_LEN);
if (!net_pkt_write_be16_timeout(pkt, pkt->frags,
NET_IPV6H_LEN + ROUTER_ALERT_LEN + 2,
&pos,
ntohs(~net_calc_chksum_icmpv6(pkt)),
NET_BUF_TIMEOUT)) {
ret = -ENOMEM;
goto drop;
}
ret = net_send_data(pkt);
if (ret < 0) {
goto drop;
}
net_stats_update_icmp_sent(net_pkt_iface(pkt));
net_stats_update_ipv6_mld_sent(net_pkt_iface(pkt));
return 0;
drop:
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;
}
static int send_mldv2(struct net_if *iface, const struct in6_addr *addr,
u8_t mode)
{
struct net_pkt *pkt;
int ret;
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL),
NET_BUF_TIMEOUT);
if (!pkt) {
return -ENOMEM;
}
append(pkt, be16, 1); /* number of records */
if (!create_mldv2(pkt, addr, mode, 1)) {
ret = -ENOMEM;
goto drop;
}
ret = send_mldv2_raw(iface, pkt->frags);
pkt->frags = NULL;
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;
}
}
ret = send_mldv2(iface, addr, NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
if (ret < 0) {
return ret;
}
net_if_ipv6_maddr_join(maddr);
net_if_mcast_monitor(iface, addr, true);
net_mgmt_event_notify(NET_EVENT_IPV6_MCAST_JOIN, iface);
return ret;
}
int net_ipv6_mld_leave(struct net_if *iface, const struct in6_addr *addr)
{
int ret;
if (!net_if_ipv6_maddr_rm(iface, addr)) {
return -EINVAL;
}
ret = send_mldv2(iface, addr, NET_IPV6_MLDv2_MODE_IS_INCLUDE);
if (ret < 0) {
return ret;
}
net_if_mcast_monitor(iface, addr, false);
net_mgmt_event_notify(NET_EVENT_IPV6_MCAST_LEAVE, iface);
return ret;
}
static void send_mld_report(struct net_if *iface)
{
struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6;
struct net_pkt *pkt;
int i, ret, count = 0;
NET_ASSERT(ipv6);
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL),
NET_BUF_TIMEOUT);
if (!pkt) {
return;
}
append(pkt, u8, 0); /* This will be the record count */
for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
continue;
}
if (!create_mldv2(pkt, &ipv6->mcast[i].address.in6_addr,
NET_IPV6_MLDv2_MODE_IS_EXCLUDE, 0)) {
goto drop;
}
count++;
}
if (count > 0) {
u16_t pos;
/* Write back the record count */
if (!net_pkt_write_u8_timeout(pkt, pkt->frags, 0, &pos,
count, NET_BUF_TIMEOUT)) {
goto drop;
}
send_mldv2_raw(iface, pkt->frags);
pkt->frags = NULL;
}
drop:
net_pkt_unref(pkt);
}
#if defined(CONFIG_NET_DEBUG_IPV6)
#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)
#else
#define dbg_addr(...)
#define dbg_addr_recv(...)
#endif /* CONFIG_NET_DEBUG_IPV6 */
static enum net_verdict handle_mld_query(struct net_pkt *pkt)
{
u16_t total_len = net_pkt_get_len(pkt);
struct in6_addr mcast;
u16_t max_rsp_code, num_src, pkt_len;
u16_t offset, pos;
struct net_buf *frag;
int ret;
dbg_addr_recv("Multicast Listener Query",
&NET_IPV6_HDR(pkt)->src,
&NET_IPV6_HDR(pkt)->dst);
net_stats_update_ipv6_mld_recv(net_pkt_iface(pkt));
/* offset tells now where the ICMPv6 header is starting */
frag = net_frag_get_pos(pkt,
net_pkt_ip_hdr_len(pkt) +
net_pkt_ipv6_ext_len(pkt) +
sizeof(struct net_icmp_hdr),
&offset);
frag = net_frag_read_be16(frag, offset, &pos, &max_rsp_code);
frag = net_frag_skip(frag, pos, &pos, 2); /* two reserved bytes */
frag = net_frag_read(frag, pos, &pos, sizeof(mcast), mcast.s6_addr);
frag = net_frag_skip(frag, pos, &pos, 2); /* skip S, QRV & QQIC */
frag = net_frag_read_be16(pkt->frags, pos, &pos, &num_src);
if (!frag && pos == 0xffff) {
goto drop;
}
pkt_len = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) +
sizeof(struct net_icmp_hdr) + (2 + 2 + 16 + 2 + 2) +
sizeof(struct in6_addr) * num_src;
if ((total_len < pkt_len || pkt_len > NET_IPV6_MTU ||
(NET_IPV6_HDR(pkt)->hop_limit != 1))) {
struct net_icmp_hdr icmp_hdr;
ret = net_icmpv6_get_hdr(pkt, &icmp_hdr);
if (ret < 0 || icmp_hdr.code != 0) {
NET_DBG("Preliminary check failed %u/%u, code %u, "
"hop %u", total_len, pkt_len,
icmp_hdr.code, NET_IPV6_HDR(pkt)->hop_limit);
goto drop;
}
}
/* Currently we only support a unspecified address query. */
if (!net_ipv6_addr_cmp(&mcast, net_ipv6_unspecified_address())) {
NET_DBG("Only supporting unspecified address query (%s)",
net_sprint_ipv6_addr(&mcast));
goto drop;
}
send_mld_report(net_pkt_iface(pkt));
drop:
net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
return NET_DROP;
}
static struct net_icmpv6_handler mld_query_input_handler = {
.type = NET_ICMPV6_MLD_QUERY,
.code = 0,
.handler = handle_mld_query,
};
void net_ipv6_mld_init(void)
{
net_icmpv6_register_handler(&mld_query_input_handler);
}