0001
0002
0003
0004
0005
0006
0007
0008 #include <linux/skbuff.h>
0009 #include <net/ipv6.h>
0010 #include <net/mld.h>
0011 #include <net/addrconf.h>
0012 #include <net/ip6_checksum.h>
0013
0014 static int ipv6_mc_check_ip6hdr(struct sk_buff *skb)
0015 {
0016 const struct ipv6hdr *ip6h;
0017 unsigned int len;
0018 unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h);
0019
0020 if (!pskb_may_pull(skb, offset))
0021 return -EINVAL;
0022
0023 ip6h = ipv6_hdr(skb);
0024
0025 if (ip6h->version != 6)
0026 return -EINVAL;
0027
0028 len = offset + ntohs(ip6h->payload_len);
0029 if (skb->len < len || len <= offset)
0030 return -EINVAL;
0031
0032 skb_set_transport_header(skb, offset);
0033
0034 return 0;
0035 }
0036
0037 static int ipv6_mc_check_exthdrs(struct sk_buff *skb)
0038 {
0039 const struct ipv6hdr *ip6h;
0040 int offset;
0041 u8 nexthdr;
0042 __be16 frag_off;
0043
0044 ip6h = ipv6_hdr(skb);
0045
0046 if (ip6h->nexthdr != IPPROTO_HOPOPTS)
0047 return -ENOMSG;
0048
0049 nexthdr = ip6h->nexthdr;
0050 offset = skb_network_offset(skb) + sizeof(*ip6h);
0051 offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off);
0052
0053 if (offset < 0)
0054 return -EINVAL;
0055
0056 if (nexthdr != IPPROTO_ICMPV6)
0057 return -ENOMSG;
0058
0059 skb_set_transport_header(skb, offset);
0060
0061 return 0;
0062 }
0063
0064 static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb)
0065 {
0066 unsigned int len = skb_transport_offset(skb);
0067
0068 len += sizeof(struct mld2_report);
0069
0070 return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL;
0071 }
0072
0073 static int ipv6_mc_check_mld_query(struct sk_buff *skb)
0074 {
0075 unsigned int transport_len = ipv6_transport_len(skb);
0076 struct mld_msg *mld;
0077 unsigned int len;
0078
0079
0080 if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL))
0081 return -EINVAL;
0082
0083
0084 if (transport_len != sizeof(struct mld_msg)) {
0085
0086 if (transport_len < sizeof(struct mld2_query))
0087 return -EINVAL;
0088
0089 len = skb_transport_offset(skb) + sizeof(struct mld2_query);
0090 if (!ipv6_mc_may_pull(skb, len))
0091 return -EINVAL;
0092 }
0093
0094 mld = (struct mld_msg *)skb_transport_header(skb);
0095
0096
0097
0098
0099 if (ipv6_addr_any(&mld->mld_mca) &&
0100 !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr))
0101 return -EINVAL;
0102
0103 return 0;
0104 }
0105
0106 static int ipv6_mc_check_mld_msg(struct sk_buff *skb)
0107 {
0108 unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg);
0109 struct mld_msg *mld;
0110
0111 if (!ipv6_mc_may_pull(skb, len))
0112 return -ENODATA;
0113
0114 mld = (struct mld_msg *)skb_transport_header(skb);
0115
0116 switch (mld->mld_type) {
0117 case ICMPV6_MGM_REDUCTION:
0118 case ICMPV6_MGM_REPORT:
0119 return 0;
0120 case ICMPV6_MLD2_REPORT:
0121 return ipv6_mc_check_mld_reportv2(skb);
0122 case ICMPV6_MGM_QUERY:
0123 return ipv6_mc_check_mld_query(skb);
0124 default:
0125 return -ENODATA;
0126 }
0127 }
0128
0129 static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb)
0130 {
0131 return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo);
0132 }
0133
0134 static int ipv6_mc_check_icmpv6(struct sk_buff *skb)
0135 {
0136 unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
0137 unsigned int transport_len = ipv6_transport_len(skb);
0138 struct sk_buff *skb_chk;
0139
0140 if (!ipv6_mc_may_pull(skb, len))
0141 return -EINVAL;
0142
0143 skb_chk = skb_checksum_trimmed(skb, transport_len,
0144 ipv6_mc_validate_checksum);
0145 if (!skb_chk)
0146 return -EINVAL;
0147
0148 if (skb_chk != skb)
0149 kfree_skb(skb_chk);
0150
0151 return 0;
0152 }
0153
0154
0155
0156
0157
0158
0159
0160
0161
0162
0163
0164
0165
0166
0167
0168
0169
0170
0171
0172 int ipv6_mc_check_mld(struct sk_buff *skb)
0173 {
0174 int ret;
0175
0176 ret = ipv6_mc_check_ip6hdr(skb);
0177 if (ret < 0)
0178 return ret;
0179
0180 ret = ipv6_mc_check_exthdrs(skb);
0181 if (ret < 0)
0182 return ret;
0183
0184 ret = ipv6_mc_check_icmpv6(skb);
0185 if (ret < 0)
0186 return ret;
0187
0188 return ipv6_mc_check_mld_msg(skb);
0189 }
0190 EXPORT_SYMBOL(ipv6_mc_check_mld);