/** @file * @brief IPv6 MLD related functions */ /* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #define LOG_MODULE_NAME net_ipv6_mld #define NET_LOG_LEVEL CONFIG_NET_IPV6_LOG_LEVEL #include #include #include #include #include #include #include #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 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); } #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) { 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)", log_strdup(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); }