1 // SPDX-License-Identifier: GPL-2.0-only 1 // SPDX-License-Identifier: GPL-2.0-only 2 /* Copyright (C) 2010: YOSHIFUJI Hideaki <yosh 2 /* Copyright (C) 2010: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> 3 * Copyright (C) 2015: Linus Lüssing <linus.l 3 * Copyright (C) 2015: Linus Lüssing <linus.luessing@c0d3.blue> 4 * 4 * 5 * Based on the MLD support added to br_multic 5 * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki. 6 */ 6 */ 7 7 8 #include <linux/skbuff.h> 8 #include <linux/skbuff.h> 9 #include <net/ipv6.h> 9 #include <net/ipv6.h> 10 #include <net/mld.h> 10 #include <net/mld.h> 11 #include <net/addrconf.h> 11 #include <net/addrconf.h> 12 #include <net/ip6_checksum.h> 12 #include <net/ip6_checksum.h> 13 13 14 static int ipv6_mc_check_ip6hdr(struct sk_buff 14 static int ipv6_mc_check_ip6hdr(struct sk_buff *skb) 15 { 15 { 16 const struct ipv6hdr *ip6h; 16 const struct ipv6hdr *ip6h; 17 unsigned int len; 17 unsigned int len; 18 unsigned int offset = skb_network_offs 18 unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h); 19 19 20 if (!pskb_may_pull(skb, offset)) 20 if (!pskb_may_pull(skb, offset)) 21 return -EINVAL; 21 return -EINVAL; 22 22 23 ip6h = ipv6_hdr(skb); 23 ip6h = ipv6_hdr(skb); 24 24 25 if (ip6h->version != 6) 25 if (ip6h->version != 6) 26 return -EINVAL; 26 return -EINVAL; 27 27 28 len = offset + ntohs(ip6h->payload_len 28 len = offset + ntohs(ip6h->payload_len); 29 if (skb->len < len || len <= offset) 29 if (skb->len < len || len <= offset) 30 return -EINVAL; 30 return -EINVAL; 31 31 32 skb_set_transport_header(skb, offset); 32 skb_set_transport_header(skb, offset); 33 33 34 return 0; 34 return 0; 35 } 35 } 36 36 37 static int ipv6_mc_check_exthdrs(struct sk_buf 37 static int ipv6_mc_check_exthdrs(struct sk_buff *skb) 38 { 38 { 39 const struct ipv6hdr *ip6h; 39 const struct ipv6hdr *ip6h; 40 int offset; 40 int offset; 41 u8 nexthdr; 41 u8 nexthdr; 42 __be16 frag_off; 42 __be16 frag_off; 43 43 44 ip6h = ipv6_hdr(skb); 44 ip6h = ipv6_hdr(skb); 45 45 46 if (ip6h->nexthdr != IPPROTO_HOPOPTS) 46 if (ip6h->nexthdr != IPPROTO_HOPOPTS) 47 return -ENOMSG; 47 return -ENOMSG; 48 48 49 nexthdr = ip6h->nexthdr; 49 nexthdr = ip6h->nexthdr; 50 offset = skb_network_offset(skb) + siz 50 offset = skb_network_offset(skb) + sizeof(*ip6h); 51 offset = ipv6_skip_exthdr(skb, offset, 51 offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off); 52 52 53 if (offset < 0) 53 if (offset < 0) 54 return -EINVAL; 54 return -EINVAL; 55 55 56 if (nexthdr != IPPROTO_ICMPV6) 56 if (nexthdr != IPPROTO_ICMPV6) 57 return -ENOMSG; 57 return -ENOMSG; 58 58 59 skb_set_transport_header(skb, offset); 59 skb_set_transport_header(skb, offset); 60 60 61 return 0; 61 return 0; 62 } 62 } 63 63 64 static int ipv6_mc_check_mld_reportv2(struct s 64 static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb) 65 { 65 { 66 unsigned int len = skb_transport_offse 66 unsigned int len = skb_transport_offset(skb); 67 67 68 len += sizeof(struct mld2_report); 68 len += sizeof(struct mld2_report); 69 69 70 return ipv6_mc_may_pull(skb, len) ? 0 70 return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL; 71 } 71 } 72 72 73 static int ipv6_mc_check_mld_query(struct sk_b 73 static int ipv6_mc_check_mld_query(struct sk_buff *skb) 74 { 74 { 75 unsigned int transport_len = ipv6_tran 75 unsigned int transport_len = ipv6_transport_len(skb); 76 struct mld_msg *mld; 76 struct mld_msg *mld; 77 unsigned int len; 77 unsigned int len; 78 78 79 /* RFC2710+RFC3810 (MLDv1+MLDv2) requi 79 /* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */ 80 if (!(ipv6_addr_type(&ipv6_hdr(skb)->s 80 if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) 81 return -EINVAL; 81 return -EINVAL; 82 82 83 /* MLDv1? */ 83 /* MLDv1? */ 84 if (transport_len != sizeof(struct mld 84 if (transport_len != sizeof(struct mld_msg)) { 85 /* or MLDv2? */ 85 /* or MLDv2? */ 86 if (transport_len < sizeof(str 86 if (transport_len < sizeof(struct mld2_query)) 87 return -EINVAL; 87 return -EINVAL; 88 88 89 len = skb_transport_offset(skb 89 len = skb_transport_offset(skb) + sizeof(struct mld2_query); 90 if (!ipv6_mc_may_pull(skb, len 90 if (!ipv6_mc_may_pull(skb, len)) 91 return -EINVAL; 91 return -EINVAL; 92 } 92 } 93 93 94 mld = (struct mld_msg *)skb_transport_ 94 mld = (struct mld_msg *)skb_transport_header(skb); 95 95 96 /* RFC2710+RFC3810 (MLDv1+MLDv2) requi 96 /* RFC2710+RFC3810 (MLDv1+MLDv2) require the multicast link layer 97 * all-nodes destination address (ff02 97 * all-nodes destination address (ff02::1) for general queries 98 */ 98 */ 99 if (ipv6_addr_any(&mld->mld_mca) && 99 if (ipv6_addr_any(&mld->mld_mca) && 100 !ipv6_addr_is_ll_all_nodes(&ipv6_h 100 !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) 101 return -EINVAL; 101 return -EINVAL; 102 102 103 return 0; 103 return 0; 104 } 104 } 105 105 106 static int ipv6_mc_check_mld_msg(struct sk_buf 106 static int ipv6_mc_check_mld_msg(struct sk_buff *skb) 107 { 107 { 108 unsigned int len = skb_transport_offse 108 unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); 109 struct mld_msg *mld; 109 struct mld_msg *mld; 110 110 111 if (!ipv6_mc_may_pull(skb, len)) 111 if (!ipv6_mc_may_pull(skb, len)) 112 return -ENODATA; 112 return -ENODATA; 113 113 114 mld = (struct mld_msg *)skb_transport_ 114 mld = (struct mld_msg *)skb_transport_header(skb); 115 115 116 switch (mld->mld_type) { 116 switch (mld->mld_type) { 117 case ICMPV6_MGM_REDUCTION: 117 case ICMPV6_MGM_REDUCTION: 118 case ICMPV6_MGM_REPORT: 118 case ICMPV6_MGM_REPORT: 119 return 0; 119 return 0; 120 case ICMPV6_MLD2_REPORT: 120 case ICMPV6_MLD2_REPORT: 121 return ipv6_mc_check_mld_repor 121 return ipv6_mc_check_mld_reportv2(skb); 122 case ICMPV6_MGM_QUERY: 122 case ICMPV6_MGM_QUERY: 123 return ipv6_mc_check_mld_query 123 return ipv6_mc_check_mld_query(skb); 124 default: 124 default: 125 return -ENODATA; 125 return -ENODATA; 126 } 126 } 127 } 127 } 128 128 129 static inline __sum16 ipv6_mc_validate_checksu 129 static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) 130 { 130 { 131 return skb_checksum_validate(skb, IPPR 131 return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); 132 } 132 } 133 133 134 static int ipv6_mc_check_icmpv6(struct sk_buff 134 static int ipv6_mc_check_icmpv6(struct sk_buff *skb) 135 { 135 { 136 unsigned int len = skb_transport_offse 136 unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); 137 unsigned int transport_len = ipv6_tran 137 unsigned int transport_len = ipv6_transport_len(skb); 138 struct sk_buff *skb_chk; 138 struct sk_buff *skb_chk; 139 139 140 if (!ipv6_mc_may_pull(skb, len)) 140 if (!ipv6_mc_may_pull(skb, len)) 141 return -EINVAL; 141 return -EINVAL; 142 142 143 skb_chk = skb_checksum_trimmed(skb, tr 143 skb_chk = skb_checksum_trimmed(skb, transport_len, 144 ipv6_mc 144 ipv6_mc_validate_checksum); 145 if (!skb_chk) 145 if (!skb_chk) 146 return -EINVAL; 146 return -EINVAL; 147 147 148 if (skb_chk != skb) 148 if (skb_chk != skb) 149 kfree_skb(skb_chk); 149 kfree_skb(skb_chk); 150 150 151 return 0; 151 return 0; 152 } 152 } 153 153 154 /** 154 /** 155 * ipv6_mc_check_mld - checks whether this is 155 * ipv6_mc_check_mld - checks whether this is a sane MLD packet 156 * @skb: the skb to validate 156 * @skb: the skb to validate 157 * 157 * 158 * Checks whether an IPv6 packet is a valid ML 158 * Checks whether an IPv6 packet is a valid MLD packet. If so sets 159 * skb transport header accordingly and return 159 * skb transport header accordingly and returns zero. 160 * 160 * 161 * -EINVAL: A broken packet was detected, i.e. 161 * -EINVAL: A broken packet was detected, i.e. it violates some internet 162 * standard 162 * standard 163 * -ENOMSG: IP header validation succeeded but 163 * -ENOMSG: IP header validation succeeded but it is not an ICMPv6 packet 164 * with a hop-by-hop option. 164 * with a hop-by-hop option. 165 * -ENODATA: IP+ICMPv6 header with hop-by-hop 165 * -ENODATA: IP+ICMPv6 header with hop-by-hop option validation succeeded 166 * but it is not an MLD packet. 166 * but it is not an MLD packet. 167 * -ENOMEM: A memory allocation failure happen 167 * -ENOMEM: A memory allocation failure happened. 168 * 168 * 169 * Caller needs to set the skb network header 169 * Caller needs to set the skb network header and free any returned skb if it 170 * differs from the provided skb. 170 * differs from the provided skb. 171 */ 171 */ 172 int ipv6_mc_check_mld(struct sk_buff *skb) 172 int ipv6_mc_check_mld(struct sk_buff *skb) 173 { 173 { 174 int ret; 174 int ret; 175 175 176 ret = ipv6_mc_check_ip6hdr(skb); 176 ret = ipv6_mc_check_ip6hdr(skb); 177 if (ret < 0) 177 if (ret < 0) 178 return ret; 178 return ret; 179 179 180 ret = ipv6_mc_check_exthdrs(skb); 180 ret = ipv6_mc_check_exthdrs(skb); 181 if (ret < 0) 181 if (ret < 0) 182 return ret; 182 return ret; 183 183 184 ret = ipv6_mc_check_icmpv6(skb); 184 ret = ipv6_mc_check_icmpv6(skb); 185 if (ret < 0) 185 if (ret < 0) 186 return ret; 186 return ret; 187 187 188 return ipv6_mc_check_mld_msg(skb); 188 return ipv6_mc_check_mld_msg(skb); 189 } 189 } 190 EXPORT_SYMBOL(ipv6_mc_check_mld); 190 EXPORT_SYMBOL(ipv6_mc_check_mld); 191 191
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.