162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * IPv6 library code, needed by static components when full IPv6 support is
462306a36Sopenharmony_ci * not configured or static.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/export.h>
762306a36Sopenharmony_ci#include <net/ipv6.h>
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci/*
1062306a36Sopenharmony_ci * find out if nexthdr is a well-known extension header or a protocol
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_cibool ipv6_ext_hdr(u8 nexthdr)
1462306a36Sopenharmony_ci{
1562306a36Sopenharmony_ci	/*
1662306a36Sopenharmony_ci	 * find out if nexthdr is an extension header or a protocol
1762306a36Sopenharmony_ci	 */
1862306a36Sopenharmony_ci	return   (nexthdr == NEXTHDR_HOP)	||
1962306a36Sopenharmony_ci		 (nexthdr == NEXTHDR_ROUTING)	||
2062306a36Sopenharmony_ci		 (nexthdr == NEXTHDR_FRAGMENT)	||
2162306a36Sopenharmony_ci		 (nexthdr == NEXTHDR_AUTH)	||
2262306a36Sopenharmony_ci		 (nexthdr == NEXTHDR_NONE)	||
2362306a36Sopenharmony_ci		 (nexthdr == NEXTHDR_DEST);
2462306a36Sopenharmony_ci}
2562306a36Sopenharmony_ciEXPORT_SYMBOL(ipv6_ext_hdr);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/*
2862306a36Sopenharmony_ci * Skip any extension headers. This is used by the ICMP module.
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * Note that strictly speaking this conflicts with RFC 2460 4.0:
3162306a36Sopenharmony_ci * ...The contents and semantics of each extension header determine whether
3262306a36Sopenharmony_ci * or not to proceed to the next header.  Therefore, extension headers must
3362306a36Sopenharmony_ci * be processed strictly in the order they appear in the packet; a
3462306a36Sopenharmony_ci * receiver must not, for example, scan through a packet looking for a
3562306a36Sopenharmony_ci * particular kind of extension header and process that header prior to
3662306a36Sopenharmony_ci * processing all preceding ones.
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci * We do exactly this. This is a protocol bug. We can't decide after a
3962306a36Sopenharmony_ci * seeing an unknown discard-with-error flavour TLV option if it's a
4062306a36Sopenharmony_ci * ICMP error message or not (errors should never be send in reply to
4162306a36Sopenharmony_ci * ICMP error messages).
4262306a36Sopenharmony_ci *
4362306a36Sopenharmony_ci * But I see no other way to do this. This might need to be reexamined
4462306a36Sopenharmony_ci * when Linux implements ESP (and maybe AUTH) headers.
4562306a36Sopenharmony_ci * --AK
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci * This function parses (probably truncated) exthdr set "hdr".
4862306a36Sopenharmony_ci * "nexthdrp" initially points to some place,
4962306a36Sopenharmony_ci * where type of the first header can be found.
5062306a36Sopenharmony_ci *
5162306a36Sopenharmony_ci * It skips all well-known exthdrs, and returns pointer to the start
5262306a36Sopenharmony_ci * of unparsable area i.e. the first header with unknown type.
5362306a36Sopenharmony_ci * If it is not NULL *nexthdr is updated by type/protocol of this header.
5462306a36Sopenharmony_ci *
5562306a36Sopenharmony_ci * NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.
5662306a36Sopenharmony_ci *        - it may return pointer pointing beyond end of packet,
5762306a36Sopenharmony_ci *	    if the last recognized header is truncated in the middle.
5862306a36Sopenharmony_ci *        - if packet is truncated, so that all parsed headers are skipped,
5962306a36Sopenharmony_ci *	    it returns NULL.
6062306a36Sopenharmony_ci *	  - First fragment header is skipped, not-first ones
6162306a36Sopenharmony_ci *	    are considered as unparsable.
6262306a36Sopenharmony_ci *	  - Reports the offset field of the final fragment header so it is
6362306a36Sopenharmony_ci *	    possible to tell whether this is a first fragment, later fragment,
6462306a36Sopenharmony_ci *	    or not fragmented.
6562306a36Sopenharmony_ci *	  - ESP is unparsable for now and considered like
6662306a36Sopenharmony_ci *	    normal payload protocol.
6762306a36Sopenharmony_ci *	  - Note also special handling of AUTH header. Thanks to IPsec wizards.
6862306a36Sopenharmony_ci *
6962306a36Sopenharmony_ci * --ANK (980726)
7062306a36Sopenharmony_ci */
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ciint ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
7362306a36Sopenharmony_ci		     __be16 *frag_offp)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	u8 nexthdr = *nexthdrp;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	*frag_offp = 0;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	while (ipv6_ext_hdr(nexthdr)) {
8062306a36Sopenharmony_ci		struct ipv6_opt_hdr _hdr, *hp;
8162306a36Sopenharmony_ci		int hdrlen;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		if (nexthdr == NEXTHDR_NONE)
8462306a36Sopenharmony_ci			return -1;
8562306a36Sopenharmony_ci		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
8662306a36Sopenharmony_ci		if (!hp)
8762306a36Sopenharmony_ci			return -1;
8862306a36Sopenharmony_ci		if (nexthdr == NEXTHDR_FRAGMENT) {
8962306a36Sopenharmony_ci			__be16 _frag_off, *fp;
9062306a36Sopenharmony_ci			fp = skb_header_pointer(skb,
9162306a36Sopenharmony_ci						start+offsetof(struct frag_hdr,
9262306a36Sopenharmony_ci							       frag_off),
9362306a36Sopenharmony_ci						sizeof(_frag_off),
9462306a36Sopenharmony_ci						&_frag_off);
9562306a36Sopenharmony_ci			if (!fp)
9662306a36Sopenharmony_ci				return -1;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci			*frag_offp = *fp;
9962306a36Sopenharmony_ci			if (ntohs(*frag_offp) & ~0x7)
10062306a36Sopenharmony_ci				break;
10162306a36Sopenharmony_ci			hdrlen = 8;
10262306a36Sopenharmony_ci		} else if (nexthdr == NEXTHDR_AUTH)
10362306a36Sopenharmony_ci			hdrlen = ipv6_authlen(hp);
10462306a36Sopenharmony_ci		else
10562306a36Sopenharmony_ci			hdrlen = ipv6_optlen(hp);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci		nexthdr = hp->nexthdr;
10862306a36Sopenharmony_ci		start += hdrlen;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	*nexthdrp = nexthdr;
11262306a36Sopenharmony_ci	return start;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ciEXPORT_SYMBOL(ipv6_skip_exthdr);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ciint ipv6_find_tlv(const struct sk_buff *skb, int offset, int type)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	const unsigned char *nh = skb_network_header(skb);
11962306a36Sopenharmony_ci	int packet_len = skb_tail_pointer(skb) - skb_network_header(skb);
12062306a36Sopenharmony_ci	struct ipv6_opt_hdr *hdr;
12162306a36Sopenharmony_ci	int len;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (offset + 2 > packet_len)
12462306a36Sopenharmony_ci		goto bad;
12562306a36Sopenharmony_ci	hdr = (struct ipv6_opt_hdr *)(nh + offset);
12662306a36Sopenharmony_ci	len = ((hdr->hdrlen + 1) << 3);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (offset + len > packet_len)
12962306a36Sopenharmony_ci		goto bad;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	offset += 2;
13262306a36Sopenharmony_ci	len -= 2;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	while (len > 0) {
13562306a36Sopenharmony_ci		int opttype = nh[offset];
13662306a36Sopenharmony_ci		int optlen;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci		if (opttype == type)
13962306a36Sopenharmony_ci			return offset;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		switch (opttype) {
14262306a36Sopenharmony_ci		case IPV6_TLV_PAD1:
14362306a36Sopenharmony_ci			optlen = 1;
14462306a36Sopenharmony_ci			break;
14562306a36Sopenharmony_ci		default:
14662306a36Sopenharmony_ci			if (len < 2)
14762306a36Sopenharmony_ci				goto bad;
14862306a36Sopenharmony_ci			optlen = nh[offset + 1] + 2;
14962306a36Sopenharmony_ci			if (optlen > len)
15062306a36Sopenharmony_ci				goto bad;
15162306a36Sopenharmony_ci			break;
15262306a36Sopenharmony_ci		}
15362306a36Sopenharmony_ci		offset += optlen;
15462306a36Sopenharmony_ci		len -= optlen;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci	/* not_found */
15762306a36Sopenharmony_ci bad:
15862306a36Sopenharmony_ci	return -1;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ipv6_find_tlv);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/*
16362306a36Sopenharmony_ci * find the offset to specified header or the protocol number of last header
16462306a36Sopenharmony_ci * if target < 0. "last header" is transport protocol header, ESP, or
16562306a36Sopenharmony_ci * "No next header".
16662306a36Sopenharmony_ci *
16762306a36Sopenharmony_ci * Note that *offset is used as input/output parameter, and if it is not zero,
16862306a36Sopenharmony_ci * then it must be a valid offset to an inner IPv6 header. This can be used
16962306a36Sopenharmony_ci * to explore inner IPv6 header, eg. ICMPv6 error messages.
17062306a36Sopenharmony_ci *
17162306a36Sopenharmony_ci * If target header is found, its offset is set in *offset and return protocol
17262306a36Sopenharmony_ci * number. Otherwise, return -1.
17362306a36Sopenharmony_ci *
17462306a36Sopenharmony_ci * If the first fragment doesn't contain the final protocol header or
17562306a36Sopenharmony_ci * NEXTHDR_NONE it is considered invalid.
17662306a36Sopenharmony_ci *
17762306a36Sopenharmony_ci * Note that non-1st fragment is special case that "the protocol number
17862306a36Sopenharmony_ci * of last header" is "next header" field in Fragment header. In this case,
17962306a36Sopenharmony_ci * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
18062306a36Sopenharmony_ci * isn't NULL.
18162306a36Sopenharmony_ci *
18262306a36Sopenharmony_ci * if flags is not NULL and it's a fragment, then the frag flag
18362306a36Sopenharmony_ci * IP6_FH_F_FRAG will be set. If it's an AH header, the
18462306a36Sopenharmony_ci * IP6_FH_F_AUTH flag is set and target < 0, then this function will
18562306a36Sopenharmony_ci * stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this
18662306a36Sopenharmony_ci * function will skip all those routing headers, where segements_left was 0.
18762306a36Sopenharmony_ci */
18862306a36Sopenharmony_ciint ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
18962306a36Sopenharmony_ci		  int target, unsigned short *fragoff, int *flags)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
19262306a36Sopenharmony_ci	u8 nexthdr = ipv6_hdr(skb)->nexthdr;
19362306a36Sopenharmony_ci	bool found;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (fragoff)
19662306a36Sopenharmony_ci		*fragoff = 0;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (*offset) {
19962306a36Sopenharmony_ci		struct ipv6hdr _ip6, *ip6;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci		ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
20262306a36Sopenharmony_ci		if (!ip6 || (ip6->version != 6))
20362306a36Sopenharmony_ci			return -EBADMSG;
20462306a36Sopenharmony_ci		start = *offset + sizeof(struct ipv6hdr);
20562306a36Sopenharmony_ci		nexthdr = ip6->nexthdr;
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	do {
20962306a36Sopenharmony_ci		struct ipv6_opt_hdr _hdr, *hp;
21062306a36Sopenharmony_ci		unsigned int hdrlen;
21162306a36Sopenharmony_ci		found = (nexthdr == target);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
21462306a36Sopenharmony_ci			if (target < 0 || found)
21562306a36Sopenharmony_ci				break;
21662306a36Sopenharmony_ci			return -ENOENT;
21762306a36Sopenharmony_ci		}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
22062306a36Sopenharmony_ci		if (!hp)
22162306a36Sopenharmony_ci			return -EBADMSG;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci		if (nexthdr == NEXTHDR_ROUTING) {
22462306a36Sopenharmony_ci			struct ipv6_rt_hdr _rh, *rh;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci			rh = skb_header_pointer(skb, start, sizeof(_rh),
22762306a36Sopenharmony_ci						&_rh);
22862306a36Sopenharmony_ci			if (!rh)
22962306a36Sopenharmony_ci				return -EBADMSG;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci			if (flags && (*flags & IP6_FH_F_SKIP_RH) &&
23262306a36Sopenharmony_ci			    rh->segments_left == 0)
23362306a36Sopenharmony_ci				found = false;
23462306a36Sopenharmony_ci		}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci		if (nexthdr == NEXTHDR_FRAGMENT) {
23762306a36Sopenharmony_ci			unsigned short _frag_off;
23862306a36Sopenharmony_ci			__be16 *fp;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci			if (flags)	/* Indicate that this is a fragment */
24162306a36Sopenharmony_ci				*flags |= IP6_FH_F_FRAG;
24262306a36Sopenharmony_ci			fp = skb_header_pointer(skb,
24362306a36Sopenharmony_ci						start+offsetof(struct frag_hdr,
24462306a36Sopenharmony_ci							       frag_off),
24562306a36Sopenharmony_ci						sizeof(_frag_off),
24662306a36Sopenharmony_ci						&_frag_off);
24762306a36Sopenharmony_ci			if (!fp)
24862306a36Sopenharmony_ci				return -EBADMSG;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci			_frag_off = ntohs(*fp) & ~0x7;
25162306a36Sopenharmony_ci			if (_frag_off) {
25262306a36Sopenharmony_ci				if (target < 0 &&
25362306a36Sopenharmony_ci				    ((!ipv6_ext_hdr(hp->nexthdr)) ||
25462306a36Sopenharmony_ci				     hp->nexthdr == NEXTHDR_NONE)) {
25562306a36Sopenharmony_ci					if (fragoff)
25662306a36Sopenharmony_ci						*fragoff = _frag_off;
25762306a36Sopenharmony_ci					return hp->nexthdr;
25862306a36Sopenharmony_ci				}
25962306a36Sopenharmony_ci				if (!found)
26062306a36Sopenharmony_ci					return -ENOENT;
26162306a36Sopenharmony_ci				if (fragoff)
26262306a36Sopenharmony_ci					*fragoff = _frag_off;
26362306a36Sopenharmony_ci				break;
26462306a36Sopenharmony_ci			}
26562306a36Sopenharmony_ci			hdrlen = 8;
26662306a36Sopenharmony_ci		} else if (nexthdr == NEXTHDR_AUTH) {
26762306a36Sopenharmony_ci			if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))
26862306a36Sopenharmony_ci				break;
26962306a36Sopenharmony_ci			hdrlen = ipv6_authlen(hp);
27062306a36Sopenharmony_ci		} else
27162306a36Sopenharmony_ci			hdrlen = ipv6_optlen(hp);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci		if (!found) {
27462306a36Sopenharmony_ci			nexthdr = hp->nexthdr;
27562306a36Sopenharmony_ci			start += hdrlen;
27662306a36Sopenharmony_ci		}
27762306a36Sopenharmony_ci	} while (!found);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	*offset = start;
28062306a36Sopenharmony_ci	return nexthdr;
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ciEXPORT_SYMBOL(ipv6_find_hdr);
283