// SPDX-License-Identifier: GPL-2.0-only1/*2* IPv6 library code, needed by static components when full IPv6 support is3* not configured or static.4*/5#include <linux/export.h>6#include <net/ipv6.h>78/*9* find out if nexthdr is a well-known extension header or a protocol10*/1112bool ipv6_ext_hdr(u8 nexthdr)13{14/*15* find out if nexthdr is an extension header or a protocol16*/17return (nexthdr == NEXTHDR_HOP) ||18(nexthdr == NEXTHDR_ROUTING) ||19(nexthdr == NEXTHDR_FRAGMENT) ||20(nexthdr == NEXTHDR_AUTH) ||21(nexthdr == NEXTHDR_NONE) ||22(nexthdr == NEXTHDR_DEST);23}24EXPORT_SYMBOL(ipv6_ext_hdr);2526/*27* Skip any extension headers. This is used by the ICMP module.28*29* Note that strictly speaking this conflicts with RFC 2460 4.0:30* ...The contents and semantics of each extension header determine whether31* or not to proceed to the next header. Therefore, extension headers must32* be processed strictly in the order they appear in the packet; a33* receiver must not, for example, scan through a packet looking for a34* particular kind of extension header and process that header prior to35* processing all preceding ones.36*37* We do exactly this. This is a protocol bug. We can't decide after a38* seeing an unknown discard-with-error flavour TLV option if it's a39* ICMP error message or not (errors should never be send in reply to40* ICMP error messages).41*42* But I see no other way to do this. This might need to be reexamined43* when Linux implements ESP (and maybe AUTH) headers.44* --AK45*46* This function parses (probably truncated) exthdr set "hdr".47* "nexthdrp" initially points to some place,48* where type of the first header can be found.49*50* It skips all well-known exthdrs, and returns pointer to the start51* of unparsable area i.e. the first header with unknown type.52* If it is not NULL *nexthdr is updated by type/protocol of this header.53*54* NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.55* - it may return pointer pointing beyond end of packet,56* if the last recognized header is truncated in the middle.57* - if packet is truncated, so that all parsed headers are skipped,58* it returns NULL.59* - First fragment header is skipped, not-first ones60* are considered as unparsable.61* - Reports the offset field of the final fragment header so it is62* possible to tell whether this is a first fragment, later fragment,63* or not fragmented.64* - ESP is unparsable for now and considered like65* normal payload protocol.66* - Note also special handling of AUTH header. Thanks to IPsec wizards.67*68* --ANK (980726)69*/7071int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,72__be16 *frag_offp)73{74u8 nexthdr = *nexthdrp;7576*frag_offp = 0;7778while (ipv6_ext_hdr(nexthdr)) {79struct ipv6_opt_hdr _hdr, *hp;80int hdrlen;8182if (nexthdr == NEXTHDR_NONE)83return -1;84hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);85if (!hp)86return -1;87if (nexthdr == NEXTHDR_FRAGMENT) {88__be16 _frag_off, *fp;89fp = skb_header_pointer(skb,90start+offsetof(struct frag_hdr,91frag_off),92sizeof(_frag_off),93&_frag_off);94if (!fp)95return -1;9697*frag_offp = *fp;98if (ntohs(*frag_offp) & ~0x7)99break;100hdrlen = 8;101} else if (nexthdr == NEXTHDR_AUTH)102hdrlen = ipv6_authlen(hp);103else104hdrlen = ipv6_optlen(hp);105106nexthdr = hp->nexthdr;107start += hdrlen;108}109110*nexthdrp = nexthdr;111return start;112}113EXPORT_SYMBOL(ipv6_skip_exthdr);114115int ipv6_find_tlv(const struct sk_buff *skb, int offset, int type)116{117const unsigned char *nh = skb_network_header(skb);118int packet_len = skb_tail_pointer(skb) - skb_network_header(skb);119struct ipv6_opt_hdr *hdr;120int len;121122if (offset + 2 > packet_len)123goto bad;124hdr = (struct ipv6_opt_hdr *)(nh + offset);125len = ((hdr->hdrlen + 1) << 3);126127if (offset + len > packet_len)128goto bad;129130offset += 2;131len -= 2;132133while (len > 0) {134int opttype = nh[offset];135int optlen;136137if (opttype == type)138return offset;139140switch (opttype) {141case IPV6_TLV_PAD1:142optlen = 1;143break;144default:145if (len < 2)146goto bad;147optlen = nh[offset + 1] + 2;148if (optlen > len)149goto bad;150break;151}152offset += optlen;153len -= optlen;154}155/* not_found */156bad:157return -1;158}159EXPORT_SYMBOL_GPL(ipv6_find_tlv);160161/*162* find the offset to specified header or the protocol number of last header163* if target < 0. "last header" is transport protocol header, ESP, or164* "No next header".165*166* Note that *offset is used as input/output parameter, and if it is not zero,167* then it must be a valid offset to an inner IPv6 header. This can be used168* to explore inner IPv6 header, eg. ICMPv6 error messages.169*170* If target header is found, its offset is set in *offset and return protocol171* number. Otherwise, return -1.172*173* If the first fragment doesn't contain the final protocol header or174* NEXTHDR_NONE it is considered invalid.175*176* Note that non-1st fragment is special case that "the protocol number177* of last header" is "next header" field in Fragment header. In this case,178* *offset is meaningless and fragment offset is stored in *fragoff if fragoff179* isn't NULL.180*181* if flags is not NULL and it's a fragment, then the frag flag182* IP6_FH_F_FRAG will be set. If it's an AH header, the183* IP6_FH_F_AUTH flag is set and target < 0, then this function will184* stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this185* function will skip all those routing headers, where segements_left was 0.186*/187int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,188int target, unsigned short *fragoff, int *flags)189{190unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);191u8 nexthdr = ipv6_hdr(skb)->nexthdr;192bool found;193194if (fragoff)195*fragoff = 0;196197if (*offset) {198struct ipv6hdr _ip6, *ip6;199200ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);201if (!ip6 || (ip6->version != 6))202return -EBADMSG;203start = *offset + sizeof(struct ipv6hdr);204nexthdr = ip6->nexthdr;205}206207do {208struct ipv6_opt_hdr _hdr, *hp;209unsigned int hdrlen;210found = (nexthdr == target);211212if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {213if (target < 0 || found)214break;215return -ENOENT;216}217218hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);219if (!hp)220return -EBADMSG;221222if (nexthdr == NEXTHDR_ROUTING) {223struct ipv6_rt_hdr _rh, *rh;224225rh = skb_header_pointer(skb, start, sizeof(_rh),226&_rh);227if (!rh)228return -EBADMSG;229230if (flags && (*flags & IP6_FH_F_SKIP_RH) &&231rh->segments_left == 0)232found = false;233}234235if (nexthdr == NEXTHDR_FRAGMENT) {236unsigned short _frag_off;237__be16 *fp;238239if (flags) /* Indicate that this is a fragment */240*flags |= IP6_FH_F_FRAG;241fp = skb_header_pointer(skb,242start+offsetof(struct frag_hdr,243frag_off),244sizeof(_frag_off),245&_frag_off);246if (!fp)247return -EBADMSG;248249_frag_off = ntohs(*fp) & ~0x7;250if (_frag_off) {251if (target < 0 &&252((!ipv6_ext_hdr(hp->nexthdr)) ||253hp->nexthdr == NEXTHDR_NONE)) {254if (fragoff)255*fragoff = _frag_off;256return hp->nexthdr;257}258if (!found)259return -ENOENT;260if (fragoff)261*fragoff = _frag_off;262break;263}264hdrlen = 8;265} else if (nexthdr == NEXTHDR_AUTH) {266if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))267break;268hdrlen = ipv6_authlen(hp);269} else270hdrlen = ipv6_optlen(hp);271272if (!found) {273nexthdr = hp->nexthdr;274start += hdrlen;275}276} while (!found);277278*offset = start;279return nexthdr;280}281EXPORT_SYMBOL(ipv6_find_hdr);282283284