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