| /** @file |
| * @brief IPv6 MLD related functions |
| */ |
| |
| /* |
| * Copyright (c) 2018 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <logging/log.h> |
| LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL); |
| |
| #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_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_LEN (MLDv2_MCAST_RECORD_LEN + sizeof(struct in6_addr)) |
| |
| static int mld_create(struct net_pkt *pkt, |
| const struct in6_addr *addr, |
| uint8_t record_type, |
| uint16_t num_sources) |
| { |
| 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 = htons(num_sources); |
| |
| net_ipaddr_copy(&mld->mcast_address, addr); |
| |
| if (net_pkt_set_data(pkt, &mld_access)) { |
| return -ENOBUFS; |
| } |
| |
| if (num_sources > 0) { |
| /* All source addresses, RFC 3810 ch 3 */ |
| if (net_pkt_write(pkt, |
| net_ipv6_unspecified_address()->s6_addr, |
| sizeof(struct in6_addr))) { |
| 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) |
| { |
| net_pkt_cursor_init(pkt); |
| net_ipv6_finalize(pkt, IPPROTO_ICMPV6); |
| |
| if (net_send_data(pkt) < 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 -1; |
| } |
| |
| net_stats_update_icmp_sent(net_pkt_iface(pkt)); |
| net_stats_update_ipv6_mld_sent(net_pkt_iface(pkt)); |
| |
| return 0; |
| } |
| |
| static int mld_send_generic(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, 1)) { |
| 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; |
| } |
| } |
| |
| ret = mld_send_generic(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_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; |
| } |
| |
| ret = mld_send_generic(iface, addr, NET_IPV6_MLDv2_MODE_IS_INCLUDE); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| net_if_mcast_monitor(iface, addr, false); |
| |
| net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_LEAVE, iface, |
| &maddr->address.in6_addr, |
| sizeof(struct in6_addr)); |
| |
| 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, count = 0; |
| |
| 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++; |
| } |
| |
| 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; |
| } |
| |
| if (mld_create_packet(pkt, count)) { |
| goto drop; |
| } |
| |
| for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) { |
| if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) { |
| continue; |
| } |
| |
| if (!mld_create(pkt, &ipv6->mcast[i].address.in6_addr, |
| NET_IPV6_MLDv2_MODE_IS_EXCLUDE, 0)) { |
| goto drop; |
| } |
| } |
| |
| if (!mld_send(pkt)) { |
| return; |
| } |
| |
| drop: |
| net_pkt_unref(pkt); |
| } |
| |
| #define dbg_addr(action, pkt_str, src, dst) \ |
| do { \ |
| NET_DBG("%s %s from %s to %s", action, pkt_str, \ |
| log_strdup(net_sprint_ipv6_addr(src)), \ |
| log_strdup(net_sprint_ipv6_addr(dst))); \ |
| } while (0) |
| |
| #define dbg_addr_recv(pkt_str, src, dst) \ |
| dbg_addr("Received", pkt_str, src, dst) |
| |
| static enum net_verdict handle_mld_query(struct net_pkt *pkt, |
| struct net_ipv6_hdr *ip_hdr, |
| struct net_icmp_hdr *icmp_hdr) |
| { |
| NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(mld_access, |
| struct net_icmpv6_mld_query); |
| uint16_t length = net_pkt_get_len(pkt); |
| struct net_icmpv6_mld_query *mld_query; |
| uint16_t pkt_len; |
| |
| 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(&mld_query->mcast_address, |
| net_ipv6_unspecified_address())) { |
| NET_DBG("DROP: only supporting unspecified address query"); |
| goto drop; |
| } |
| |
| send_mld_report(net_pkt_iface(pkt)); |
| |
| net_pkt_unref(pkt); |
| |
| return NET_OK; |
| |
| 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); |
| } |