1b7c320ffSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 29afd85c9SLinus Lüssing /* Copyright (C) 2010: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> 39afd85c9SLinus Lüssing * Copyright (C) 2015: Linus Lüssing <linus.luessing@c0d3.blue> 49afd85c9SLinus Lüssing * 59afd85c9SLinus Lüssing * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki. 69afd85c9SLinus Lüssing */ 79afd85c9SLinus Lüssing 89afd85c9SLinus Lüssing #include <linux/skbuff.h> 99afd85c9SLinus Lüssing #include <net/ipv6.h> 109afd85c9SLinus Lüssing #include <net/mld.h> 119afd85c9SLinus Lüssing #include <net/addrconf.h> 129afd85c9SLinus Lüssing #include <net/ip6_checksum.h> 139afd85c9SLinus Lüssing 149afd85c9SLinus Lüssing static int ipv6_mc_check_ip6hdr(struct sk_buff *skb) 159afd85c9SLinus Lüssing { 169afd85c9SLinus Lüssing const struct ipv6hdr *ip6h; 179afd85c9SLinus Lüssing unsigned int len; 189afd85c9SLinus Lüssing unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h); 199afd85c9SLinus Lüssing 209afd85c9SLinus Lüssing if (!pskb_may_pull(skb, offset)) 219afd85c9SLinus Lüssing return -EINVAL; 229afd85c9SLinus Lüssing 239afd85c9SLinus Lüssing ip6h = ipv6_hdr(skb); 249afd85c9SLinus Lüssing 259afd85c9SLinus Lüssing if (ip6h->version != 6) 269afd85c9SLinus Lüssing return -EINVAL; 279afd85c9SLinus Lüssing 289afd85c9SLinus Lüssing len = offset + ntohs(ip6h->payload_len); 299afd85c9SLinus Lüssing if (skb->len < len || len <= offset) 309afd85c9SLinus Lüssing return -EINVAL; 319afd85c9SLinus Lüssing 324b3087c7SLinus Lüssing skb_set_transport_header(skb, offset); 334b3087c7SLinus Lüssing 349afd85c9SLinus Lüssing return 0; 359afd85c9SLinus Lüssing } 369afd85c9SLinus Lüssing 379afd85c9SLinus Lüssing static int ipv6_mc_check_exthdrs(struct sk_buff *skb) 389afd85c9SLinus Lüssing { 399afd85c9SLinus Lüssing const struct ipv6hdr *ip6h; 40fcba67c9SLinus Lüssing int offset; 419afd85c9SLinus Lüssing u8 nexthdr; 429afd85c9SLinus Lüssing __be16 frag_off; 439afd85c9SLinus Lüssing 449afd85c9SLinus Lüssing ip6h = ipv6_hdr(skb); 459afd85c9SLinus Lüssing 469afd85c9SLinus Lüssing if (ip6h->nexthdr != IPPROTO_HOPOPTS) 479afd85c9SLinus Lüssing return -ENOMSG; 489afd85c9SLinus Lüssing 499afd85c9SLinus Lüssing nexthdr = ip6h->nexthdr; 509afd85c9SLinus Lüssing offset = skb_network_offset(skb) + sizeof(*ip6h); 519afd85c9SLinus Lüssing offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off); 529afd85c9SLinus Lüssing 539afd85c9SLinus Lüssing if (offset < 0) 549afd85c9SLinus Lüssing return -EINVAL; 559afd85c9SLinus Lüssing 569afd85c9SLinus Lüssing if (nexthdr != IPPROTO_ICMPV6) 579afd85c9SLinus Lüssing return -ENOMSG; 589afd85c9SLinus Lüssing 599afd85c9SLinus Lüssing skb_set_transport_header(skb, offset); 609afd85c9SLinus Lüssing 619afd85c9SLinus Lüssing return 0; 629afd85c9SLinus Lüssing } 639afd85c9SLinus Lüssing 649afd85c9SLinus Lüssing static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb) 659afd85c9SLinus Lüssing { 669afd85c9SLinus Lüssing unsigned int len = skb_transport_offset(skb); 679afd85c9SLinus Lüssing 689afd85c9SLinus Lüssing len += sizeof(struct mld2_report); 699afd85c9SLinus Lüssing 70a2e2ca3bSLinus Lüssing return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL; 719afd85c9SLinus Lüssing } 729afd85c9SLinus Lüssing 739afd85c9SLinus Lüssing static int ipv6_mc_check_mld_query(struct sk_buff *skb) 749afd85c9SLinus Lüssing { 75a2e2ca3bSLinus Lüssing unsigned int transport_len = ipv6_transport_len(skb); 769afd85c9SLinus Lüssing struct mld_msg *mld; 77a2e2ca3bSLinus Lüssing unsigned int len; 789afd85c9SLinus Lüssing 799afd85c9SLinus Lüssing /* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */ 809afd85c9SLinus Lüssing if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) 819afd85c9SLinus Lüssing return -EINVAL; 829afd85c9SLinus Lüssing 83a2e2ca3bSLinus Lüssing /* MLDv1? */ 84a2e2ca3bSLinus Lüssing if (transport_len != sizeof(struct mld_msg)) { 85a2e2ca3bSLinus Lüssing /* or MLDv2? */ 86a2e2ca3bSLinus Lüssing if (transport_len < sizeof(struct mld2_query)) 879afd85c9SLinus Lüssing return -EINVAL; 889afd85c9SLinus Lüssing 89a2e2ca3bSLinus Lüssing len = skb_transport_offset(skb) + sizeof(struct mld2_query); 90a2e2ca3bSLinus Lüssing if (!ipv6_mc_may_pull(skb, len)) 919afd85c9SLinus Lüssing return -EINVAL; 929afd85c9SLinus Lüssing } 939afd85c9SLinus Lüssing 949afd85c9SLinus Lüssing mld = (struct mld_msg *)skb_transport_header(skb); 959afd85c9SLinus Lüssing 969afd85c9SLinus Lüssing /* RFC2710+RFC3810 (MLDv1+MLDv2) require the multicast link layer 979afd85c9SLinus Lüssing * all-nodes destination address (ff02::1) for general queries 989afd85c9SLinus Lüssing */ 999afd85c9SLinus Lüssing if (ipv6_addr_any(&mld->mld_mca) && 1009afd85c9SLinus Lüssing !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) 1019afd85c9SLinus Lüssing return -EINVAL; 1029afd85c9SLinus Lüssing 1039afd85c9SLinus Lüssing return 0; 1049afd85c9SLinus Lüssing } 1059afd85c9SLinus Lüssing 1069afd85c9SLinus Lüssing static int ipv6_mc_check_mld_msg(struct sk_buff *skb) 1079afd85c9SLinus Lüssing { 108a2e2ca3bSLinus Lüssing unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); 109a2e2ca3bSLinus Lüssing struct mld_msg *mld; 110a2e2ca3bSLinus Lüssing 111a2e2ca3bSLinus Lüssing if (!ipv6_mc_may_pull(skb, len)) 112a2e2ca3bSLinus Lüssing return -EINVAL; 113a2e2ca3bSLinus Lüssing 114a2e2ca3bSLinus Lüssing mld = (struct mld_msg *)skb_transport_header(skb); 1159afd85c9SLinus Lüssing 1169afd85c9SLinus Lüssing switch (mld->mld_type) { 1179afd85c9SLinus Lüssing case ICMPV6_MGM_REDUCTION: 1189afd85c9SLinus Lüssing case ICMPV6_MGM_REPORT: 1199afd85c9SLinus Lüssing return 0; 1209afd85c9SLinus Lüssing case ICMPV6_MLD2_REPORT: 1219afd85c9SLinus Lüssing return ipv6_mc_check_mld_reportv2(skb); 1229afd85c9SLinus Lüssing case ICMPV6_MGM_QUERY: 1239afd85c9SLinus Lüssing return ipv6_mc_check_mld_query(skb); 1249afd85c9SLinus Lüssing default: 1259afd85c9SLinus Lüssing return -ENOMSG; 1269afd85c9SLinus Lüssing } 1279afd85c9SLinus Lüssing } 1289afd85c9SLinus Lüssing 1299afd85c9SLinus Lüssing static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) 1309afd85c9SLinus Lüssing { 1319afd85c9SLinus Lüssing return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); 1329afd85c9SLinus Lüssing } 1339afd85c9SLinus Lüssing 1344b3087c7SLinus Lüssing int ipv6_mc_check_icmpv6(struct sk_buff *skb) 1359afd85c9SLinus Lüssing { 136a2e2ca3bSLinus Lüssing unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); 137a2e2ca3bSLinus Lüssing unsigned int transport_len = ipv6_transport_len(skb); 138a2e2ca3bSLinus Lüssing struct sk_buff *skb_chk; 1399afd85c9SLinus Lüssing 140a2e2ca3bSLinus Lüssing if (!ipv6_mc_may_pull(skb, len)) 141a2e2ca3bSLinus Lüssing return -EINVAL; 1429afd85c9SLinus Lüssing 1439afd85c9SLinus Lüssing skb_chk = skb_checksum_trimmed(skb, transport_len, 1449afd85c9SLinus Lüssing ipv6_mc_validate_checksum); 1459afd85c9SLinus Lüssing if (!skb_chk) 146a2e2ca3bSLinus Lüssing return -EINVAL; 1479afd85c9SLinus Lüssing 148a2e2ca3bSLinus Lüssing if (skb_chk != skb) 149a516993fSLinus Lüssing kfree_skb(skb_chk); 150a516993fSLinus Lüssing 151a2e2ca3bSLinus Lüssing return 0; 1529afd85c9SLinus Lüssing } 1534b3087c7SLinus Lüssing EXPORT_SYMBOL(ipv6_mc_check_icmpv6); 1549afd85c9SLinus Lüssing 1559afd85c9SLinus Lüssing /** 1569afd85c9SLinus Lüssing * ipv6_mc_check_mld - checks whether this is a sane MLD packet 1579afd85c9SLinus Lüssing * @skb: the skb to validate 1589afd85c9SLinus Lüssing * 1599afd85c9SLinus Lüssing * Checks whether an IPv6 packet is a valid MLD packet. If so sets 160a516993fSLinus Lüssing * skb transport header accordingly and returns zero. 1619afd85c9SLinus Lüssing * 1629afd85c9SLinus Lüssing * -EINVAL: A broken packet was detected, i.e. it violates some internet 1639afd85c9SLinus Lüssing * standard 1649afd85c9SLinus Lüssing * -ENOMSG: IP header validation succeeded but it is not an MLD packet. 1659afd85c9SLinus Lüssing * -ENOMEM: A memory allocation failure happened. 1669afd85c9SLinus Lüssing * 167a516993fSLinus Lüssing * Caller needs to set the skb network header and free any returned skb if it 168a516993fSLinus Lüssing * differs from the provided skb. 1699afd85c9SLinus Lüssing */ 170ba5ea614SLinus Lüssing int ipv6_mc_check_mld(struct sk_buff *skb) 1719afd85c9SLinus Lüssing { 1729afd85c9SLinus Lüssing int ret; 1739afd85c9SLinus Lüssing 1749afd85c9SLinus Lüssing ret = ipv6_mc_check_ip6hdr(skb); 1759afd85c9SLinus Lüssing if (ret < 0) 1769afd85c9SLinus Lüssing return ret; 1779afd85c9SLinus Lüssing 1789afd85c9SLinus Lüssing ret = ipv6_mc_check_exthdrs(skb); 1799afd85c9SLinus Lüssing if (ret < 0) 1809afd85c9SLinus Lüssing return ret; 1819afd85c9SLinus Lüssing 182a2e2ca3bSLinus Lüssing ret = ipv6_mc_check_icmpv6(skb); 183a2e2ca3bSLinus Lüssing if (ret < 0) 184a2e2ca3bSLinus Lüssing return ret; 185a2e2ca3bSLinus Lüssing 186a2e2ca3bSLinus Lüssing return ipv6_mc_check_mld_msg(skb); 1879afd85c9SLinus Lüssing } 1889afd85c9SLinus Lüssing EXPORT_SYMBOL(ipv6_mc_check_mld); 189