162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* Copyright (C) 2010: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
362306a36Sopenharmony_ci * Copyright (C) 2015: Linus Lüssing <linus.luessing@c0d3.blue>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/skbuff.h>
962306a36Sopenharmony_ci#include <net/ipv6.h>
1062306a36Sopenharmony_ci#include <net/mld.h>
1162306a36Sopenharmony_ci#include <net/addrconf.h>
1262306a36Sopenharmony_ci#include <net/ip6_checksum.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistatic int ipv6_mc_check_ip6hdr(struct sk_buff *skb)
1562306a36Sopenharmony_ci{
1662306a36Sopenharmony_ci	const struct ipv6hdr *ip6h;
1762306a36Sopenharmony_ci	unsigned int len;
1862306a36Sopenharmony_ci	unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h);
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci	if (!pskb_may_pull(skb, offset))
2162306a36Sopenharmony_ci		return -EINVAL;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	ip6h = ipv6_hdr(skb);
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	if (ip6h->version != 6)
2662306a36Sopenharmony_ci		return -EINVAL;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	len = offset + ntohs(ip6h->payload_len);
2962306a36Sopenharmony_ci	if (skb->len < len || len <= offset)
3062306a36Sopenharmony_ci		return -EINVAL;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	skb_set_transport_header(skb, offset);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	return 0;
3562306a36Sopenharmony_ci}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic int ipv6_mc_check_exthdrs(struct sk_buff *skb)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	const struct ipv6hdr *ip6h;
4062306a36Sopenharmony_ci	int offset;
4162306a36Sopenharmony_ci	u8 nexthdr;
4262306a36Sopenharmony_ci	__be16 frag_off;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	ip6h = ipv6_hdr(skb);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	if (ip6h->nexthdr != IPPROTO_HOPOPTS)
4762306a36Sopenharmony_ci		return -ENOMSG;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	nexthdr = ip6h->nexthdr;
5062306a36Sopenharmony_ci	offset = skb_network_offset(skb) + sizeof(*ip6h);
5162306a36Sopenharmony_ci	offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (offset < 0)
5462306a36Sopenharmony_ci		return -EINVAL;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	if (nexthdr != IPPROTO_ICMPV6)
5762306a36Sopenharmony_ci		return -ENOMSG;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	skb_set_transport_header(skb, offset);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	return 0;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic int ipv6_mc_check_mld_reportv2(struct sk_buff *skb)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	unsigned int len = skb_transport_offset(skb);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	len += sizeof(struct mld2_report);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int ipv6_mc_check_mld_query(struct sk_buff *skb)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	unsigned int transport_len = ipv6_transport_len(skb);
7662306a36Sopenharmony_ci	struct mld_msg *mld;
7762306a36Sopenharmony_ci	unsigned int len;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	/* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */
8062306a36Sopenharmony_ci	if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL))
8162306a36Sopenharmony_ci		return -EINVAL;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/* MLDv1? */
8462306a36Sopenharmony_ci	if (transport_len != sizeof(struct mld_msg)) {
8562306a36Sopenharmony_ci		/* or MLDv2? */
8662306a36Sopenharmony_ci		if (transport_len < sizeof(struct mld2_query))
8762306a36Sopenharmony_ci			return -EINVAL;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		len = skb_transport_offset(skb) + sizeof(struct mld2_query);
9062306a36Sopenharmony_ci		if (!ipv6_mc_may_pull(skb, len))
9162306a36Sopenharmony_ci			return -EINVAL;
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	mld = (struct mld_msg *)skb_transport_header(skb);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/* RFC2710+RFC3810 (MLDv1+MLDv2) require the multicast link layer
9762306a36Sopenharmony_ci	 * all-nodes destination address (ff02::1) for general queries
9862306a36Sopenharmony_ci	 */
9962306a36Sopenharmony_ci	if (ipv6_addr_any(&mld->mld_mca) &&
10062306a36Sopenharmony_ci	    !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr))
10162306a36Sopenharmony_ci		return -EINVAL;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	return 0;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int ipv6_mc_check_mld_msg(struct sk_buff *skb)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg);
10962306a36Sopenharmony_ci	struct mld_msg *mld;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (!ipv6_mc_may_pull(skb, len))
11262306a36Sopenharmony_ci		return -ENODATA;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	mld = (struct mld_msg *)skb_transport_header(skb);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	switch (mld->mld_type) {
11762306a36Sopenharmony_ci	case ICMPV6_MGM_REDUCTION:
11862306a36Sopenharmony_ci	case ICMPV6_MGM_REPORT:
11962306a36Sopenharmony_ci		return 0;
12062306a36Sopenharmony_ci	case ICMPV6_MLD2_REPORT:
12162306a36Sopenharmony_ci		return ipv6_mc_check_mld_reportv2(skb);
12262306a36Sopenharmony_ci	case ICMPV6_MGM_QUERY:
12362306a36Sopenharmony_ci		return ipv6_mc_check_mld_query(skb);
12462306a36Sopenharmony_ci	default:
12562306a36Sopenharmony_ci		return -ENODATA;
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo);
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic int ipv6_mc_check_icmpv6(struct sk_buff *skb)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
13762306a36Sopenharmony_ci	unsigned int transport_len = ipv6_transport_len(skb);
13862306a36Sopenharmony_ci	struct sk_buff *skb_chk;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!ipv6_mc_may_pull(skb, len))
14162306a36Sopenharmony_ci		return -EINVAL;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	skb_chk = skb_checksum_trimmed(skb, transport_len,
14462306a36Sopenharmony_ci				       ipv6_mc_validate_checksum);
14562306a36Sopenharmony_ci	if (!skb_chk)
14662306a36Sopenharmony_ci		return -EINVAL;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	if (skb_chk != skb)
14962306a36Sopenharmony_ci		kfree_skb(skb_chk);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return 0;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci/**
15562306a36Sopenharmony_ci * ipv6_mc_check_mld - checks whether this is a sane MLD packet
15662306a36Sopenharmony_ci * @skb: the skb to validate
15762306a36Sopenharmony_ci *
15862306a36Sopenharmony_ci * Checks whether an IPv6 packet is a valid MLD packet. If so sets
15962306a36Sopenharmony_ci * skb transport header accordingly and returns zero.
16062306a36Sopenharmony_ci *
16162306a36Sopenharmony_ci * -EINVAL: A broken packet was detected, i.e. it violates some internet
16262306a36Sopenharmony_ci *  standard
16362306a36Sopenharmony_ci * -ENOMSG: IP header validation succeeded but it is not an ICMPv6 packet
16462306a36Sopenharmony_ci *  with a hop-by-hop option.
16562306a36Sopenharmony_ci * -ENODATA: IP+ICMPv6 header with hop-by-hop option validation succeeded
16662306a36Sopenharmony_ci *  but it is not an MLD packet.
16762306a36Sopenharmony_ci * -ENOMEM: A memory allocation failure happened.
16862306a36Sopenharmony_ci *
16962306a36Sopenharmony_ci * Caller needs to set the skb network header and free any returned skb if it
17062306a36Sopenharmony_ci * differs from the provided skb.
17162306a36Sopenharmony_ci */
17262306a36Sopenharmony_ciint ipv6_mc_check_mld(struct sk_buff *skb)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	int ret;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	ret = ipv6_mc_check_ip6hdr(skb);
17762306a36Sopenharmony_ci	if (ret < 0)
17862306a36Sopenharmony_ci		return ret;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	ret = ipv6_mc_check_exthdrs(skb);
18162306a36Sopenharmony_ci	if (ret < 0)
18262306a36Sopenharmony_ci		return ret;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	ret = ipv6_mc_check_icmpv6(skb);
18562306a36Sopenharmony_ci	if (ret < 0)
18662306a36Sopenharmony_ci		return ret;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	return ipv6_mc_check_mld_msg(skb);
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ciEXPORT_SYMBOL(ipv6_mc_check_mld);
191