19afd85c9SLinus Lüssing /* Copyright (C) 2010: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> 29afd85c9SLinus Lüssing * Copyright (C) 2015: Linus Lüssing <linus.luessing@c0d3.blue> 39afd85c9SLinus Lüssing * 49afd85c9SLinus Lüssing * This program is free software; you can redistribute it and/or 59afd85c9SLinus Lüssing * modify it under the terms of version 2 of the GNU General Public 69afd85c9SLinus Lüssing * License as published by the Free Software Foundation. 79afd85c9SLinus Lüssing * 89afd85c9SLinus Lüssing * This program is distributed in the hope that it will be useful, but 99afd85c9SLinus Lüssing * WITHOUT ANY WARRANTY; without even the implied warranty of 109afd85c9SLinus Lüssing * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 119afd85c9SLinus Lüssing * General Public License for more details. 129afd85c9SLinus Lüssing * 139afd85c9SLinus Lüssing * You should have received a copy of the GNU General Public License 149afd85c9SLinus Lüssing * along with this program; if not, see <http://www.gnu.org/licenses/>. 159afd85c9SLinus Lüssing * 169afd85c9SLinus Lüssing * 179afd85c9SLinus Lüssing * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki. 189afd85c9SLinus Lüssing */ 199afd85c9SLinus Lüssing 209afd85c9SLinus Lüssing #include <linux/skbuff.h> 219afd85c9SLinus Lüssing #include <net/ipv6.h> 229afd85c9SLinus Lüssing #include <net/mld.h> 239afd85c9SLinus Lüssing #include <net/addrconf.h> 249afd85c9SLinus Lüssing #include <net/ip6_checksum.h> 259afd85c9SLinus Lüssing 269afd85c9SLinus Lüssing static int ipv6_mc_check_ip6hdr(struct sk_buff *skb) 279afd85c9SLinus Lüssing { 289afd85c9SLinus Lüssing const struct ipv6hdr *ip6h; 299afd85c9SLinus Lüssing unsigned int len; 309afd85c9SLinus Lüssing unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h); 319afd85c9SLinus Lüssing 329afd85c9SLinus Lüssing if (!pskb_may_pull(skb, offset)) 339afd85c9SLinus Lüssing return -EINVAL; 349afd85c9SLinus Lüssing 359afd85c9SLinus Lüssing ip6h = ipv6_hdr(skb); 369afd85c9SLinus Lüssing 379afd85c9SLinus Lüssing if (ip6h->version != 6) 389afd85c9SLinus Lüssing return -EINVAL; 399afd85c9SLinus Lüssing 409afd85c9SLinus Lüssing len = offset + ntohs(ip6h->payload_len); 419afd85c9SLinus Lüssing if (skb->len < len || len <= offset) 429afd85c9SLinus Lüssing return -EINVAL; 439afd85c9SLinus Lüssing 449afd85c9SLinus Lüssing return 0; 459afd85c9SLinus Lüssing } 469afd85c9SLinus Lüssing 479afd85c9SLinus Lüssing static int ipv6_mc_check_exthdrs(struct sk_buff *skb) 489afd85c9SLinus Lüssing { 499afd85c9SLinus Lüssing const struct ipv6hdr *ip6h; 50fcba67c9SLinus Lüssing int offset; 519afd85c9SLinus Lüssing u8 nexthdr; 529afd85c9SLinus Lüssing __be16 frag_off; 539afd85c9SLinus Lüssing 549afd85c9SLinus Lüssing ip6h = ipv6_hdr(skb); 559afd85c9SLinus Lüssing 569afd85c9SLinus Lüssing if (ip6h->nexthdr != IPPROTO_HOPOPTS) 579afd85c9SLinus Lüssing return -ENOMSG; 589afd85c9SLinus Lüssing 599afd85c9SLinus Lüssing nexthdr = ip6h->nexthdr; 609afd85c9SLinus Lüssing offset = skb_network_offset(skb) + sizeof(*ip6h); 619afd85c9SLinus Lüssing offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off); 629afd85c9SLinus Lüssing 639afd85c9SLinus Lüssing if (offset < 0) 649afd85c9SLinus Lüssing return -EINVAL; 659afd85c9SLinus Lüssing 669afd85c9SLinus Lüssing if (nexthdr != IPPROTO_ICMPV6) 679afd85c9SLinus Lüssing return -ENOMSG; 689afd85c9SLinus Lüssing 699afd85c9SLinus Lüssing skb_set_transport_header(skb, offset); 709afd85c9SLinus Lüssing 719afd85c9SLinus Lüssing return 0; 729afd85c9SLinus Lüssing } 739afd85c9SLinus Lüssing 749afd85c9SLinus Lüssing static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb) 759afd85c9SLinus Lüssing { 769afd85c9SLinus Lüssing unsigned int len = skb_transport_offset(skb); 779afd85c9SLinus Lüssing 789afd85c9SLinus Lüssing len += sizeof(struct mld2_report); 799afd85c9SLinus Lüssing 809afd85c9SLinus Lüssing return pskb_may_pull(skb, len) ? 0 : -EINVAL; 819afd85c9SLinus Lüssing } 829afd85c9SLinus Lüssing 839afd85c9SLinus Lüssing static int ipv6_mc_check_mld_query(struct sk_buff *skb) 849afd85c9SLinus Lüssing { 859afd85c9SLinus Lüssing struct mld_msg *mld; 869afd85c9SLinus Lüssing unsigned int len = skb_transport_offset(skb); 879afd85c9SLinus Lüssing 889afd85c9SLinus Lüssing /* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */ 899afd85c9SLinus Lüssing if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) 909afd85c9SLinus Lüssing return -EINVAL; 919afd85c9SLinus Lüssing 929afd85c9SLinus Lüssing len += sizeof(struct mld_msg); 939afd85c9SLinus Lüssing if (skb->len < len) 949afd85c9SLinus Lüssing return -EINVAL; 959afd85c9SLinus Lüssing 969afd85c9SLinus Lüssing /* MLDv1? */ 979afd85c9SLinus Lüssing if (skb->len != len) { 989afd85c9SLinus Lüssing /* or MLDv2? */ 999afd85c9SLinus Lüssing len += sizeof(struct mld2_query) - sizeof(struct mld_msg); 1009afd85c9SLinus Lüssing if (skb->len < len || !pskb_may_pull(skb, len)) 1019afd85c9SLinus Lüssing return -EINVAL; 1029afd85c9SLinus Lüssing } 1039afd85c9SLinus Lüssing 1049afd85c9SLinus Lüssing mld = (struct mld_msg *)skb_transport_header(skb); 1059afd85c9SLinus Lüssing 1069afd85c9SLinus Lüssing /* RFC2710+RFC3810 (MLDv1+MLDv2) require the multicast link layer 1079afd85c9SLinus Lüssing * all-nodes destination address (ff02::1) for general queries 1089afd85c9SLinus Lüssing */ 1099afd85c9SLinus Lüssing if (ipv6_addr_any(&mld->mld_mca) && 1109afd85c9SLinus Lüssing !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) 1119afd85c9SLinus Lüssing return -EINVAL; 1129afd85c9SLinus Lüssing 1139afd85c9SLinus Lüssing return 0; 1149afd85c9SLinus Lüssing } 1159afd85c9SLinus Lüssing 1169afd85c9SLinus Lüssing static int ipv6_mc_check_mld_msg(struct sk_buff *skb) 1179afd85c9SLinus Lüssing { 1189afd85c9SLinus Lüssing struct mld_msg *mld = (struct mld_msg *)skb_transport_header(skb); 1199afd85c9SLinus Lüssing 1209afd85c9SLinus Lüssing switch (mld->mld_type) { 1219afd85c9SLinus Lüssing case ICMPV6_MGM_REDUCTION: 1229afd85c9SLinus Lüssing case ICMPV6_MGM_REPORT: 1239afd85c9SLinus Lüssing /* fall through */ 1249afd85c9SLinus Lüssing return 0; 1259afd85c9SLinus Lüssing case ICMPV6_MLD2_REPORT: 1269afd85c9SLinus Lüssing return ipv6_mc_check_mld_reportv2(skb); 1279afd85c9SLinus Lüssing case ICMPV6_MGM_QUERY: 1289afd85c9SLinus Lüssing return ipv6_mc_check_mld_query(skb); 1299afd85c9SLinus Lüssing default: 1309afd85c9SLinus Lüssing return -ENOMSG; 1319afd85c9SLinus Lüssing } 1329afd85c9SLinus Lüssing } 1339afd85c9SLinus Lüssing 1349afd85c9SLinus Lüssing static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) 1359afd85c9SLinus Lüssing { 1369afd85c9SLinus Lüssing return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); 1379afd85c9SLinus Lüssing } 1389afd85c9SLinus Lüssing 1399afd85c9SLinus Lüssing static int __ipv6_mc_check_mld(struct sk_buff *skb, 1409afd85c9SLinus Lüssing struct sk_buff **skb_trimmed) 1419afd85c9SLinus Lüssing 1429afd85c9SLinus Lüssing { 1439afd85c9SLinus Lüssing struct sk_buff *skb_chk = NULL; 1449afd85c9SLinus Lüssing unsigned int transport_len; 1459afd85c9SLinus Lüssing unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); 146a516993fSLinus Lüssing int ret = -EINVAL; 1479afd85c9SLinus Lüssing 1489afd85c9SLinus Lüssing transport_len = ntohs(ipv6_hdr(skb)->payload_len); 1499afd85c9SLinus Lüssing transport_len -= skb_transport_offset(skb) - sizeof(struct ipv6hdr); 1509afd85c9SLinus Lüssing 1519afd85c9SLinus Lüssing skb_chk = skb_checksum_trimmed(skb, transport_len, 1529afd85c9SLinus Lüssing ipv6_mc_validate_checksum); 1539afd85c9SLinus Lüssing if (!skb_chk) 154a516993fSLinus Lüssing goto err; 1559afd85c9SLinus Lüssing 156a516993fSLinus Lüssing if (!pskb_may_pull(skb_chk, len)) 157a516993fSLinus Lüssing goto err; 1589afd85c9SLinus Lüssing 1599afd85c9SLinus Lüssing ret = ipv6_mc_check_mld_msg(skb_chk); 160a516993fSLinus Lüssing if (ret) 161a516993fSLinus Lüssing goto err; 1629afd85c9SLinus Lüssing 1639afd85c9SLinus Lüssing if (skb_trimmed) 1649afd85c9SLinus Lüssing *skb_trimmed = skb_chk; 165a516993fSLinus Lüssing /* free now unneeded clone */ 166a516993fSLinus Lüssing else if (skb_chk != skb) 1679afd85c9SLinus Lüssing kfree_skb(skb_chk); 1689afd85c9SLinus Lüssing 169a516993fSLinus Lüssing ret = 0; 170a516993fSLinus Lüssing 171a516993fSLinus Lüssing err: 172a516993fSLinus Lüssing if (ret && skb_chk && skb_chk != skb) 173a516993fSLinus Lüssing kfree_skb(skb_chk); 174a516993fSLinus Lüssing 175a516993fSLinus Lüssing return ret; 1769afd85c9SLinus Lüssing } 1779afd85c9SLinus Lüssing 1789afd85c9SLinus Lüssing /** 1799afd85c9SLinus Lüssing * ipv6_mc_check_mld - checks whether this is a sane MLD packet 1809afd85c9SLinus Lüssing * @skb: the skb to validate 1819afd85c9SLinus Lüssing * @skb_trimmed: to store an skb pointer trimmed to IPv6 packet tail (optional) 1829afd85c9SLinus Lüssing * 1839afd85c9SLinus Lüssing * Checks whether an IPv6 packet is a valid MLD packet. If so sets 184a516993fSLinus Lüssing * skb transport header accordingly and returns zero. 1859afd85c9SLinus Lüssing * 1869afd85c9SLinus Lüssing * -EINVAL: A broken packet was detected, i.e. it violates some internet 1879afd85c9SLinus Lüssing * standard 1889afd85c9SLinus Lüssing * -ENOMSG: IP header validation succeeded but it is not an MLD packet. 1899afd85c9SLinus Lüssing * -ENOMEM: A memory allocation failure happened. 1909afd85c9SLinus Lüssing * 1919afd85c9SLinus Lüssing * Optionally, an skb pointer might be provided via skb_trimmed (or set it 1929afd85c9SLinus Lüssing * to NULL): After parsing an MLD packet successfully it will point to 1939afd85c9SLinus Lüssing * an skb which has its tail aligned to the IP packet end. This might 1949afd85c9SLinus Lüssing * either be the originally provided skb or a trimmed, cloned version if 1959afd85c9SLinus Lüssing * the skb frame had data beyond the IP packet. A cloned skb allows us 1969afd85c9SLinus Lüssing * to leave the original skb and its full frame unchanged (which might be 1979afd85c9SLinus Lüssing * desirable for layer 2 frame jugglers). 1989afd85c9SLinus Lüssing * 199a516993fSLinus Lüssing * Caller needs to set the skb network header and free any returned skb if it 200a516993fSLinus Lüssing * differs from the provided skb. 2019afd85c9SLinus Lüssing */ 2029afd85c9SLinus Lüssing int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed) 2039afd85c9SLinus Lüssing { 2049afd85c9SLinus Lüssing int ret; 2059afd85c9SLinus Lüssing 2069afd85c9SLinus Lüssing ret = ipv6_mc_check_ip6hdr(skb); 2079afd85c9SLinus Lüssing if (ret < 0) 2089afd85c9SLinus Lüssing return ret; 2099afd85c9SLinus Lüssing 2109afd85c9SLinus Lüssing ret = ipv6_mc_check_exthdrs(skb); 2119afd85c9SLinus Lüssing if (ret < 0) 2129afd85c9SLinus Lüssing return ret; 2139afd85c9SLinus Lüssing 2149afd85c9SLinus Lüssing return __ipv6_mc_check_mld(skb, skb_trimmed); 2159afd85c9SLinus Lüssing } 2169afd85c9SLinus Lüssing EXPORT_SYMBOL(ipv6_mc_check_mld); 217