18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* Copyright (C) 2010: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> 38c2ecf20Sopenharmony_ci * Copyright (C) 2015: Linus Lüssing <linus.luessing@c0d3.blue> 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 98c2ecf20Sopenharmony_ci#include <net/ipv6.h> 108c2ecf20Sopenharmony_ci#include <net/mld.h> 118c2ecf20Sopenharmony_ci#include <net/addrconf.h> 128c2ecf20Sopenharmony_ci#include <net/ip6_checksum.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cistatic int ipv6_mc_check_ip6hdr(struct sk_buff *skb) 158c2ecf20Sopenharmony_ci{ 168c2ecf20Sopenharmony_ci const struct ipv6hdr *ip6h; 178c2ecf20Sopenharmony_ci unsigned int len; 188c2ecf20Sopenharmony_ci unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h); 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci if (!pskb_may_pull(skb, offset)) 218c2ecf20Sopenharmony_ci return -EINVAL; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci ip6h = ipv6_hdr(skb); 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci if (ip6h->version != 6) 268c2ecf20Sopenharmony_ci return -EINVAL; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci len = offset + ntohs(ip6h->payload_len); 298c2ecf20Sopenharmony_ci if (skb->len < len || len <= offset) 308c2ecf20Sopenharmony_ci return -EINVAL; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci skb_set_transport_header(skb, offset); 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci return 0; 358c2ecf20Sopenharmony_ci} 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic int ipv6_mc_check_exthdrs(struct sk_buff *skb) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci const struct ipv6hdr *ip6h; 408c2ecf20Sopenharmony_ci int offset; 418c2ecf20Sopenharmony_ci u8 nexthdr; 428c2ecf20Sopenharmony_ci __be16 frag_off; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci ip6h = ipv6_hdr(skb); 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (ip6h->nexthdr != IPPROTO_HOPOPTS) 478c2ecf20Sopenharmony_ci return -ENOMSG; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci nexthdr = ip6h->nexthdr; 508c2ecf20Sopenharmony_ci offset = skb_network_offset(skb) + sizeof(*ip6h); 518c2ecf20Sopenharmony_ci offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci if (offset < 0) 548c2ecf20Sopenharmony_ci return -EINVAL; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci if (nexthdr != IPPROTO_ICMPV6) 578c2ecf20Sopenharmony_ci return -ENOMSG; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci skb_set_transport_header(skb, offset); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci return 0; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int ipv6_mc_check_mld_reportv2(struct sk_buff *skb) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci unsigned int len = skb_transport_offset(skb); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci len += sizeof(struct mld2_report); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int ipv6_mc_check_mld_query(struct sk_buff *skb) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci unsigned int transport_len = ipv6_transport_len(skb); 768c2ecf20Sopenharmony_ci struct mld_msg *mld; 778c2ecf20Sopenharmony_ci unsigned int len; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */ 808c2ecf20Sopenharmony_ci if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) 818c2ecf20Sopenharmony_ci return -EINVAL; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* MLDv1? */ 848c2ecf20Sopenharmony_ci if (transport_len != sizeof(struct mld_msg)) { 858c2ecf20Sopenharmony_ci /* or MLDv2? */ 868c2ecf20Sopenharmony_ci if (transport_len < sizeof(struct mld2_query)) 878c2ecf20Sopenharmony_ci return -EINVAL; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci len = skb_transport_offset(skb) + sizeof(struct mld2_query); 908c2ecf20Sopenharmony_ci if (!ipv6_mc_may_pull(skb, len)) 918c2ecf20Sopenharmony_ci return -EINVAL; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci mld = (struct mld_msg *)skb_transport_header(skb); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci /* RFC2710+RFC3810 (MLDv1+MLDv2) require the multicast link layer 978c2ecf20Sopenharmony_ci * all-nodes destination address (ff02::1) for general queries 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci if (ipv6_addr_any(&mld->mld_mca) && 1008c2ecf20Sopenharmony_ci !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) 1018c2ecf20Sopenharmony_ci return -EINVAL; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic int ipv6_mc_check_mld_msg(struct sk_buff *skb) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); 1098c2ecf20Sopenharmony_ci struct mld_msg *mld; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (!ipv6_mc_may_pull(skb, len)) 1128c2ecf20Sopenharmony_ci return -ENODATA; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci mld = (struct mld_msg *)skb_transport_header(skb); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci switch (mld->mld_type) { 1178c2ecf20Sopenharmony_ci case ICMPV6_MGM_REDUCTION: 1188c2ecf20Sopenharmony_ci case ICMPV6_MGM_REPORT: 1198c2ecf20Sopenharmony_ci return 0; 1208c2ecf20Sopenharmony_ci case ICMPV6_MLD2_REPORT: 1218c2ecf20Sopenharmony_ci return ipv6_mc_check_mld_reportv2(skb); 1228c2ecf20Sopenharmony_ci case ICMPV6_MGM_QUERY: 1238c2ecf20Sopenharmony_ci return ipv6_mc_check_mld_query(skb); 1248c2ecf20Sopenharmony_ci default: 1258c2ecf20Sopenharmony_ci return -ENODATA; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic int ipv6_mc_check_icmpv6(struct sk_buff *skb) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); 1378c2ecf20Sopenharmony_ci unsigned int transport_len = ipv6_transport_len(skb); 1388c2ecf20Sopenharmony_ci struct sk_buff *skb_chk; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (!ipv6_mc_may_pull(skb, len)) 1418c2ecf20Sopenharmony_ci return -EINVAL; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci skb_chk = skb_checksum_trimmed(skb, transport_len, 1448c2ecf20Sopenharmony_ci ipv6_mc_validate_checksum); 1458c2ecf20Sopenharmony_ci if (!skb_chk) 1468c2ecf20Sopenharmony_ci return -EINVAL; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci if (skb_chk != skb) 1498c2ecf20Sopenharmony_ci kfree_skb(skb_chk); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return 0; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci/** 1558c2ecf20Sopenharmony_ci * ipv6_mc_check_mld - checks whether this is a sane MLD packet 1568c2ecf20Sopenharmony_ci * @skb: the skb to validate 1578c2ecf20Sopenharmony_ci * 1588c2ecf20Sopenharmony_ci * Checks whether an IPv6 packet is a valid MLD packet. If so sets 1598c2ecf20Sopenharmony_ci * skb transport header accordingly and returns zero. 1608c2ecf20Sopenharmony_ci * 1618c2ecf20Sopenharmony_ci * -EINVAL: A broken packet was detected, i.e. it violates some internet 1628c2ecf20Sopenharmony_ci * standard 1638c2ecf20Sopenharmony_ci * -ENOMSG: IP header validation succeeded but it is not an ICMPv6 packet 1648c2ecf20Sopenharmony_ci * with a hop-by-hop option. 1658c2ecf20Sopenharmony_ci * -ENODATA: IP+ICMPv6 header with hop-by-hop option validation succeeded 1668c2ecf20Sopenharmony_ci * but it is not an MLD packet. 1678c2ecf20Sopenharmony_ci * -ENOMEM: A memory allocation failure happened. 1688c2ecf20Sopenharmony_ci * 1698c2ecf20Sopenharmony_ci * Caller needs to set the skb network header and free any returned skb if it 1708c2ecf20Sopenharmony_ci * differs from the provided skb. 1718c2ecf20Sopenharmony_ci */ 1728c2ecf20Sopenharmony_ciint ipv6_mc_check_mld(struct sk_buff *skb) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci int ret; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci ret = ipv6_mc_check_ip6hdr(skb); 1778c2ecf20Sopenharmony_ci if (ret < 0) 1788c2ecf20Sopenharmony_ci return ret; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci ret = ipv6_mc_check_exthdrs(skb); 1818c2ecf20Sopenharmony_ci if (ret < 0) 1828c2ecf20Sopenharmony_ci return ret; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci ret = ipv6_mc_check_icmpv6(skb); 1858c2ecf20Sopenharmony_ci if (ret < 0) 1868c2ecf20Sopenharmony_ci return ret; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci return ipv6_mc_check_mld_msg(skb); 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ipv6_mc_check_mld); 191