| /** @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); |
| } |