18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * IPv6 library code, needed by static components when full IPv6 support is
48c2ecf20Sopenharmony_ci * not configured or static.
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci#include <linux/export.h>
78c2ecf20Sopenharmony_ci#include <net/ipv6.h>
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci/*
108c2ecf20Sopenharmony_ci * find out if nexthdr is a well-known extension header or a protocol
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_cibool ipv6_ext_hdr(u8 nexthdr)
148c2ecf20Sopenharmony_ci{
158c2ecf20Sopenharmony_ci	/*
168c2ecf20Sopenharmony_ci	 * find out if nexthdr is an extension header or a protocol
178c2ecf20Sopenharmony_ci	 */
188c2ecf20Sopenharmony_ci	return   (nexthdr == NEXTHDR_HOP)	||
198c2ecf20Sopenharmony_ci		 (nexthdr == NEXTHDR_ROUTING)	||
208c2ecf20Sopenharmony_ci		 (nexthdr == NEXTHDR_FRAGMENT)	||
218c2ecf20Sopenharmony_ci		 (nexthdr == NEXTHDR_AUTH)	||
228c2ecf20Sopenharmony_ci		 (nexthdr == NEXTHDR_NONE)	||
238c2ecf20Sopenharmony_ci		 (nexthdr == NEXTHDR_DEST);
248c2ecf20Sopenharmony_ci}
258c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ipv6_ext_hdr);
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci/*
288c2ecf20Sopenharmony_ci * Skip any extension headers. This is used by the ICMP module.
298c2ecf20Sopenharmony_ci *
308c2ecf20Sopenharmony_ci * Note that strictly speaking this conflicts with RFC 2460 4.0:
318c2ecf20Sopenharmony_ci * ...The contents and semantics of each extension header determine whether
328c2ecf20Sopenharmony_ci * or not to proceed to the next header.  Therefore, extension headers must
338c2ecf20Sopenharmony_ci * be processed strictly in the order they appear in the packet; a
348c2ecf20Sopenharmony_ci * receiver must not, for example, scan through a packet looking for a
358c2ecf20Sopenharmony_ci * particular kind of extension header and process that header prior to
368c2ecf20Sopenharmony_ci * processing all preceding ones.
378c2ecf20Sopenharmony_ci *
388c2ecf20Sopenharmony_ci * We do exactly this. This is a protocol bug. We can't decide after a
398c2ecf20Sopenharmony_ci * seeing an unknown discard-with-error flavour TLV option if it's a
408c2ecf20Sopenharmony_ci * ICMP error message or not (errors should never be send in reply to
418c2ecf20Sopenharmony_ci * ICMP error messages).
428c2ecf20Sopenharmony_ci *
438c2ecf20Sopenharmony_ci * But I see no other way to do this. This might need to be reexamined
448c2ecf20Sopenharmony_ci * when Linux implements ESP (and maybe AUTH) headers.
458c2ecf20Sopenharmony_ci * --AK
468c2ecf20Sopenharmony_ci *
478c2ecf20Sopenharmony_ci * This function parses (probably truncated) exthdr set "hdr".
488c2ecf20Sopenharmony_ci * "nexthdrp" initially points to some place,
498c2ecf20Sopenharmony_ci * where type of the first header can be found.
508c2ecf20Sopenharmony_ci *
518c2ecf20Sopenharmony_ci * It skips all well-known exthdrs, and returns pointer to the start
528c2ecf20Sopenharmony_ci * of unparsable area i.e. the first header with unknown type.
538c2ecf20Sopenharmony_ci * If it is not NULL *nexthdr is updated by type/protocol of this header.
548c2ecf20Sopenharmony_ci *
558c2ecf20Sopenharmony_ci * NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.
568c2ecf20Sopenharmony_ci *        - it may return pointer pointing beyond end of packet,
578c2ecf20Sopenharmony_ci *	    if the last recognized header is truncated in the middle.
588c2ecf20Sopenharmony_ci *        - if packet is truncated, so that all parsed headers are skipped,
598c2ecf20Sopenharmony_ci *	    it returns NULL.
608c2ecf20Sopenharmony_ci *	  - First fragment header is skipped, not-first ones
618c2ecf20Sopenharmony_ci *	    are considered as unparsable.
628c2ecf20Sopenharmony_ci *	  - Reports the offset field of the final fragment header so it is
638c2ecf20Sopenharmony_ci *	    possible to tell whether this is a first fragment, later fragment,
648c2ecf20Sopenharmony_ci *	    or not fragmented.
658c2ecf20Sopenharmony_ci *	  - ESP is unparsable for now and considered like
668c2ecf20Sopenharmony_ci *	    normal payload protocol.
678c2ecf20Sopenharmony_ci *	  - Note also special handling of AUTH header. Thanks to IPsec wizards.
688c2ecf20Sopenharmony_ci *
698c2ecf20Sopenharmony_ci * --ANK (980726)
708c2ecf20Sopenharmony_ci */
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ciint ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
738c2ecf20Sopenharmony_ci		     __be16 *frag_offp)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	u8 nexthdr = *nexthdrp;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	*frag_offp = 0;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	while (ipv6_ext_hdr(nexthdr)) {
808c2ecf20Sopenharmony_ci		struct ipv6_opt_hdr _hdr, *hp;
818c2ecf20Sopenharmony_ci		int hdrlen;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci		if (nexthdr == NEXTHDR_NONE)
848c2ecf20Sopenharmony_ci			return -1;
858c2ecf20Sopenharmony_ci		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
868c2ecf20Sopenharmony_ci		if (!hp)
878c2ecf20Sopenharmony_ci			return -1;
888c2ecf20Sopenharmony_ci		if (nexthdr == NEXTHDR_FRAGMENT) {
898c2ecf20Sopenharmony_ci			__be16 _frag_off, *fp;
908c2ecf20Sopenharmony_ci			fp = skb_header_pointer(skb,
918c2ecf20Sopenharmony_ci						start+offsetof(struct frag_hdr,
928c2ecf20Sopenharmony_ci							       frag_off),
938c2ecf20Sopenharmony_ci						sizeof(_frag_off),
948c2ecf20Sopenharmony_ci						&_frag_off);
958c2ecf20Sopenharmony_ci			if (!fp)
968c2ecf20Sopenharmony_ci				return -1;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci			*frag_offp = *fp;
998c2ecf20Sopenharmony_ci			if (ntohs(*frag_offp) & ~0x7)
1008c2ecf20Sopenharmony_ci				break;
1018c2ecf20Sopenharmony_ci			hdrlen = 8;
1028c2ecf20Sopenharmony_ci		} else if (nexthdr == NEXTHDR_AUTH)
1038c2ecf20Sopenharmony_ci			hdrlen = ipv6_authlen(hp);
1048c2ecf20Sopenharmony_ci		else
1058c2ecf20Sopenharmony_ci			hdrlen = ipv6_optlen(hp);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci		nexthdr = hp->nexthdr;
1088c2ecf20Sopenharmony_ci		start += hdrlen;
1098c2ecf20Sopenharmony_ci	}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	*nexthdrp = nexthdr;
1128c2ecf20Sopenharmony_ci	return start;
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ipv6_skip_exthdr);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ciint ipv6_find_tlv(const struct sk_buff *skb, int offset, int type)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	const unsigned char *nh = skb_network_header(skb);
1198c2ecf20Sopenharmony_ci	int packet_len = skb_tail_pointer(skb) - skb_network_header(skb);
1208c2ecf20Sopenharmony_ci	struct ipv6_opt_hdr *hdr;
1218c2ecf20Sopenharmony_ci	int len;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	if (offset + 2 > packet_len)
1248c2ecf20Sopenharmony_ci		goto bad;
1258c2ecf20Sopenharmony_ci	hdr = (struct ipv6_opt_hdr *)(nh + offset);
1268c2ecf20Sopenharmony_ci	len = ((hdr->hdrlen + 1) << 3);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	if (offset + len > packet_len)
1298c2ecf20Sopenharmony_ci		goto bad;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	offset += 2;
1328c2ecf20Sopenharmony_ci	len -= 2;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	while (len > 0) {
1358c2ecf20Sopenharmony_ci		int opttype = nh[offset];
1368c2ecf20Sopenharmony_ci		int optlen;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci		if (opttype == type)
1398c2ecf20Sopenharmony_ci			return offset;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		switch (opttype) {
1428c2ecf20Sopenharmony_ci		case IPV6_TLV_PAD1:
1438c2ecf20Sopenharmony_ci			optlen = 1;
1448c2ecf20Sopenharmony_ci			break;
1458c2ecf20Sopenharmony_ci		default:
1468c2ecf20Sopenharmony_ci			if (len < 2)
1478c2ecf20Sopenharmony_ci				goto bad;
1488c2ecf20Sopenharmony_ci			optlen = nh[offset + 1] + 2;
1498c2ecf20Sopenharmony_ci			if (optlen > len)
1508c2ecf20Sopenharmony_ci				goto bad;
1518c2ecf20Sopenharmony_ci			break;
1528c2ecf20Sopenharmony_ci		}
1538c2ecf20Sopenharmony_ci		offset += optlen;
1548c2ecf20Sopenharmony_ci		len -= optlen;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci	/* not_found */
1578c2ecf20Sopenharmony_ci bad:
1588c2ecf20Sopenharmony_ci	return -1;
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ipv6_find_tlv);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci/*
1638c2ecf20Sopenharmony_ci * find the offset to specified header or the protocol number of last header
1648c2ecf20Sopenharmony_ci * if target < 0. "last header" is transport protocol header, ESP, or
1658c2ecf20Sopenharmony_ci * "No next header".
1668c2ecf20Sopenharmony_ci *
1678c2ecf20Sopenharmony_ci * Note that *offset is used as input/output parameter, and if it is not zero,
1688c2ecf20Sopenharmony_ci * then it must be a valid offset to an inner IPv6 header. This can be used
1698c2ecf20Sopenharmony_ci * to explore inner IPv6 header, eg. ICMPv6 error messages.
1708c2ecf20Sopenharmony_ci *
1718c2ecf20Sopenharmony_ci * If target header is found, its offset is set in *offset and return protocol
1728c2ecf20Sopenharmony_ci * number. Otherwise, return -1.
1738c2ecf20Sopenharmony_ci *
1748c2ecf20Sopenharmony_ci * If the first fragment doesn't contain the final protocol header or
1758c2ecf20Sopenharmony_ci * NEXTHDR_NONE it is considered invalid.
1768c2ecf20Sopenharmony_ci *
1778c2ecf20Sopenharmony_ci * Note that non-1st fragment is special case that "the protocol number
1788c2ecf20Sopenharmony_ci * of last header" is "next header" field in Fragment header. In this case,
1798c2ecf20Sopenharmony_ci * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
1808c2ecf20Sopenharmony_ci * isn't NULL.
1818c2ecf20Sopenharmony_ci *
1828c2ecf20Sopenharmony_ci * if flags is not NULL and it's a fragment, then the frag flag
1838c2ecf20Sopenharmony_ci * IP6_FH_F_FRAG will be set. If it's an AH header, the
1848c2ecf20Sopenharmony_ci * IP6_FH_F_AUTH flag is set and target < 0, then this function will
1858c2ecf20Sopenharmony_ci * stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this
1868c2ecf20Sopenharmony_ci * function will skip all those routing headers, where segements_left was 0.
1878c2ecf20Sopenharmony_ci */
1888c2ecf20Sopenharmony_ciint ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
1898c2ecf20Sopenharmony_ci		  int target, unsigned short *fragoff, int *flags)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
1928c2ecf20Sopenharmony_ci	u8 nexthdr = ipv6_hdr(skb)->nexthdr;
1938c2ecf20Sopenharmony_ci	bool found;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	if (fragoff)
1968c2ecf20Sopenharmony_ci		*fragoff = 0;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	if (*offset) {
1998c2ecf20Sopenharmony_ci		struct ipv6hdr _ip6, *ip6;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci		ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
2028c2ecf20Sopenharmony_ci		if (!ip6 || (ip6->version != 6))
2038c2ecf20Sopenharmony_ci			return -EBADMSG;
2048c2ecf20Sopenharmony_ci		start = *offset + sizeof(struct ipv6hdr);
2058c2ecf20Sopenharmony_ci		nexthdr = ip6->nexthdr;
2068c2ecf20Sopenharmony_ci	}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	do {
2098c2ecf20Sopenharmony_ci		struct ipv6_opt_hdr _hdr, *hp;
2108c2ecf20Sopenharmony_ci		unsigned int hdrlen;
2118c2ecf20Sopenharmony_ci		found = (nexthdr == target);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci		if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
2148c2ecf20Sopenharmony_ci			if (target < 0 || found)
2158c2ecf20Sopenharmony_ci				break;
2168c2ecf20Sopenharmony_ci			return -ENOENT;
2178c2ecf20Sopenharmony_ci		}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
2208c2ecf20Sopenharmony_ci		if (!hp)
2218c2ecf20Sopenharmony_ci			return -EBADMSG;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci		if (nexthdr == NEXTHDR_ROUTING) {
2248c2ecf20Sopenharmony_ci			struct ipv6_rt_hdr _rh, *rh;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci			rh = skb_header_pointer(skb, start, sizeof(_rh),
2278c2ecf20Sopenharmony_ci						&_rh);
2288c2ecf20Sopenharmony_ci			if (!rh)
2298c2ecf20Sopenharmony_ci				return -EBADMSG;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci			if (flags && (*flags & IP6_FH_F_SKIP_RH) &&
2328c2ecf20Sopenharmony_ci			    rh->segments_left == 0)
2338c2ecf20Sopenharmony_ci				found = false;
2348c2ecf20Sopenharmony_ci		}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci		if (nexthdr == NEXTHDR_FRAGMENT) {
2378c2ecf20Sopenharmony_ci			unsigned short _frag_off;
2388c2ecf20Sopenharmony_ci			__be16 *fp;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci			if (flags)	/* Indicate that this is a fragment */
2418c2ecf20Sopenharmony_ci				*flags |= IP6_FH_F_FRAG;
2428c2ecf20Sopenharmony_ci			fp = skb_header_pointer(skb,
2438c2ecf20Sopenharmony_ci						start+offsetof(struct frag_hdr,
2448c2ecf20Sopenharmony_ci							       frag_off),
2458c2ecf20Sopenharmony_ci						sizeof(_frag_off),
2468c2ecf20Sopenharmony_ci						&_frag_off);
2478c2ecf20Sopenharmony_ci			if (!fp)
2488c2ecf20Sopenharmony_ci				return -EBADMSG;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci			_frag_off = ntohs(*fp) & ~0x7;
2518c2ecf20Sopenharmony_ci			if (_frag_off) {
2528c2ecf20Sopenharmony_ci				if (target < 0 &&
2538c2ecf20Sopenharmony_ci				    ((!ipv6_ext_hdr(hp->nexthdr)) ||
2548c2ecf20Sopenharmony_ci				     hp->nexthdr == NEXTHDR_NONE)) {
2558c2ecf20Sopenharmony_ci					if (fragoff)
2568c2ecf20Sopenharmony_ci						*fragoff = _frag_off;
2578c2ecf20Sopenharmony_ci					return hp->nexthdr;
2588c2ecf20Sopenharmony_ci				}
2598c2ecf20Sopenharmony_ci				if (!found)
2608c2ecf20Sopenharmony_ci					return -ENOENT;
2618c2ecf20Sopenharmony_ci				if (fragoff)
2628c2ecf20Sopenharmony_ci					*fragoff = _frag_off;
2638c2ecf20Sopenharmony_ci				break;
2648c2ecf20Sopenharmony_ci			}
2658c2ecf20Sopenharmony_ci			hdrlen = 8;
2668c2ecf20Sopenharmony_ci		} else if (nexthdr == NEXTHDR_AUTH) {
2678c2ecf20Sopenharmony_ci			if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))
2688c2ecf20Sopenharmony_ci				break;
2698c2ecf20Sopenharmony_ci			hdrlen = ipv6_authlen(hp);
2708c2ecf20Sopenharmony_ci		} else
2718c2ecf20Sopenharmony_ci			hdrlen = ipv6_optlen(hp);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci		if (!found) {
2748c2ecf20Sopenharmony_ci			nexthdr = hp->nexthdr;
2758c2ecf20Sopenharmony_ci			start += hdrlen;
2768c2ecf20Sopenharmony_ci		}
2778c2ecf20Sopenharmony_ci	} while (!found);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	*offset = start;
2808c2ecf20Sopenharmony_ci	return nexthdr;
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ipv6_find_hdr);
283