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