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