xref: /linux/net/ipv6/exthdrs_core.c (revision fcee7d82f27d6a8b1ddc5bbefda59b4e441e9bc0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * IPv6 library code, needed by static components when full IPv6 support is
4  * not configured or static.
5  */
6 #include <linux/export.h>
7 #include <net/ipv6.h>
8 
9 /*
10  * find out if nexthdr is a well-known extension header or a protocol
11  */
12 
ipv6_ext_hdr(u8 nexthdr)13 bool ipv6_ext_hdr(u8 nexthdr)
14 {
15 	/*
16 	 * find out if nexthdr is an extension header or a protocol
17 	 */
18 	return   (nexthdr == NEXTHDR_HOP)	||
19 		 (nexthdr == NEXTHDR_ROUTING)	||
20 		 (nexthdr == NEXTHDR_FRAGMENT)	||
21 		 (nexthdr == NEXTHDR_AUTH)	||
22 		 (nexthdr == NEXTHDR_NONE)	||
23 		 (nexthdr == NEXTHDR_DEST);
24 }
25 EXPORT_SYMBOL(ipv6_ext_hdr);
26 
27 /*
28  * Skip any extension headers. This is used by the ICMP module.
29  *
30  * Note that strictly speaking this conflicts with RFC 2460 4.0:
31  * ...The contents and semantics of each extension header determine whether
32  * or not to proceed to the next header.  Therefore, extension headers must
33  * be processed strictly in the order they appear in the packet; a
34  * receiver must not, for example, scan through a packet looking for a
35  * particular kind of extension header and process that header prior to
36  * processing all preceding ones.
37  *
38  * We do exactly this. This is a protocol bug. We can't decide after a
39  * seeing an unknown discard-with-error flavour TLV option if it's a
40  * ICMP error message or not (errors should never be send in reply to
41  * ICMP error messages).
42  *
43  * But I see no other way to do this. This might need to be reexamined
44  * when Linux implements ESP (and maybe AUTH) headers.
45  * --AK
46  *
47  * This function parses (probably truncated) exthdr set "hdr".
48  * "nexthdrp" initially points to some place,
49  * where type of the first header can be found.
50  *
51  * It skips all well-known exthdrs, and returns pointer to the start
52  * of unparsable area i.e. the first header with unknown type.
53  * If it is not NULL *nexthdr is updated by type/protocol of this header.
54  *
55  * NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.
56  *        - it may return pointer pointing beyond end of packet,
57  *	    if the last recognized header is truncated in the middle.
58  *        - if packet is truncated, so that all parsed headers are skipped,
59  *	    it returns NULL.
60  *	  - First fragment header is skipped, not-first ones
61  *	    are considered as unparsable.
62  *	  - Reports the offset field of the final fragment header so it is
63  *	    possible to tell whether this is a first fragment, later fragment,
64  *	    or not fragmented.
65  *	  - ESP is unparsable for now and considered like
66  *	    normal payload protocol.
67  *	  - Note also special handling of AUTH header. Thanks to IPsec wizards.
68  *
69  * --ANK (980726)
70  */
71 
ipv6_skip_exthdr(const struct sk_buff * skb,int start,u8 * nexthdrp,__be16 * frag_offp)72 int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
73 		     __be16 *frag_offp)
74 {
75 	u8 nexthdr = *nexthdrp;
76 	int exthdr_cnt = 0;
77 
78 	*frag_offp = 0;
79 
80 	while (ipv6_ext_hdr(nexthdr)) {
81 		struct ipv6_opt_hdr _hdr, *hp;
82 		int hdrlen;
83 
84 		if (nexthdr == NEXTHDR_NONE)
85 			return -1;
86 		if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
87 			return -1;
88 		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
89 		if (!hp)
90 			return -1;
91 		if (nexthdr == NEXTHDR_FRAGMENT) {
92 			__be16 _frag_off, *fp;
93 			fp = skb_header_pointer(skb,
94 						start+offsetof(struct frag_hdr,
95 							       frag_off),
96 						sizeof(_frag_off),
97 						&_frag_off);
98 			if (!fp)
99 				return -1;
100 
101 			*frag_offp = *fp;
102 			if (ntohs(*frag_offp) & ~0x7)
103 				break;
104 			hdrlen = 8;
105 		} else if (nexthdr == NEXTHDR_AUTH)
106 			hdrlen = ipv6_authlen(hp);
107 		else
108 			hdrlen = ipv6_optlen(hp);
109 
110 		nexthdr = hp->nexthdr;
111 		start += hdrlen;
112 	}
113 
114 	*nexthdrp = nexthdr;
115 	return start;
116 }
117 EXPORT_SYMBOL(ipv6_skip_exthdr);
118 
ipv6_find_tlv(const struct sk_buff * skb,int offset,int type)119 int ipv6_find_tlv(const struct sk_buff *skb, int offset, int type)
120 {
121 	const unsigned char *nh = skb_network_header(skb);
122 	int packet_len = skb_tail_pointer(skb) - skb_network_header(skb);
123 	struct ipv6_opt_hdr *hdr;
124 	int len;
125 
126 	if (offset + 2 > packet_len)
127 		goto bad;
128 	hdr = (struct ipv6_opt_hdr *)(nh + offset);
129 	len = ((hdr->hdrlen + 1) << 3);
130 
131 	if (offset + len > packet_len)
132 		goto bad;
133 
134 	offset += 2;
135 	len -= 2;
136 
137 	while (len > 0) {
138 		int opttype = nh[offset];
139 		int optlen;
140 
141 		if (opttype == type)
142 			return offset;
143 
144 		switch (opttype) {
145 		case IPV6_TLV_PAD1:
146 			optlen = 1;
147 			break;
148 		default:
149 			if (len < 2)
150 				goto bad;
151 			optlen = nh[offset + 1] + 2;
152 			if (optlen > len)
153 				goto bad;
154 			break;
155 		}
156 		offset += optlen;
157 		len -= optlen;
158 	}
159 	/* not_found */
160  bad:
161 	return -1;
162 }
163 EXPORT_SYMBOL_GPL(ipv6_find_tlv);
164 
165 /*
166  * find the offset to specified header or the protocol number of last header
167  * if target < 0. "last header" is transport protocol header, ESP, or
168  * "No next header".
169  *
170  * Note that *offset is used as input/output parameter, and if it is not zero,
171  * then it must be a valid offset to an inner IPv6 header. This can be used
172  * to explore inner IPv6 header, eg. ICMPv6 error messages.
173  *
174  * If target header is found, its offset is set in *offset and return protocol
175  * number. Otherwise, return -1.
176  *
177  * If the first fragment doesn't contain the final protocol header or
178  * NEXTHDR_NONE it is considered invalid.
179  *
180  * Note that non-1st fragment is special case that "the protocol number
181  * of last header" is "next header" field in Fragment header. In this case,
182  * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
183  * isn't NULL.
184  *
185  * if flags is not NULL and it's a fragment, then the frag flag
186  * IP6_FH_F_FRAG will be set. If it's an AH header, the
187  * IP6_FH_F_AUTH flag is set and target < 0, then this function will
188  * stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this
189  * function will skip all those routing headers, where segements_left was 0.
190  */
ipv6_find_hdr(const struct sk_buff * skb,unsigned int * offset,int target,unsigned short * fragoff,int * flags)191 int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
192 		  int target, unsigned short *fragoff, int *flags)
193 {
194 	unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
195 	u8 nexthdr = ipv6_hdr(skb)->nexthdr;
196 	int exthdr_cnt = 0;
197 	bool found;
198 
199 	if (fragoff)
200 		*fragoff = 0;
201 
202 	if (*offset) {
203 		struct ipv6hdr _ip6, *ip6;
204 
205 		ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
206 		if (!ip6 || (ip6->version != 6))
207 			return -EBADMSG;
208 		start = *offset + sizeof(struct ipv6hdr);
209 		nexthdr = ip6->nexthdr;
210 	}
211 
212 	do {
213 		struct ipv6_opt_hdr _hdr, *hp;
214 		unsigned int hdrlen;
215 		found = (nexthdr == target);
216 
217 		if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
218 			if (target < 0 || found)
219 				break;
220 			return -ENOENT;
221 		}
222 
223 		if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
224 			return -EBADMSG;
225 
226 		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
227 		if (!hp)
228 			return -EBADMSG;
229 
230 		if (nexthdr == NEXTHDR_ROUTING) {
231 			struct ipv6_rt_hdr _rh, *rh;
232 
233 			rh = skb_header_pointer(skb, start, sizeof(_rh),
234 						&_rh);
235 			if (!rh)
236 				return -EBADMSG;
237 
238 			if (flags && (*flags & IP6_FH_F_SKIP_RH) &&
239 			    rh->segments_left == 0)
240 				found = false;
241 		}
242 
243 		if (nexthdr == NEXTHDR_FRAGMENT) {
244 			unsigned short _frag_off;
245 			__be16 *fp;
246 
247 			if (flags)	/* Indicate that this is a fragment */
248 				*flags |= IP6_FH_F_FRAG;
249 			fp = skb_header_pointer(skb,
250 						start+offsetof(struct frag_hdr,
251 							       frag_off),
252 						sizeof(_frag_off),
253 						&_frag_off);
254 			if (!fp)
255 				return -EBADMSG;
256 
257 			_frag_off = ntohs(*fp) & ~0x7;
258 			if (_frag_off) {
259 				if (target < 0 &&
260 				    ((!ipv6_ext_hdr(hp->nexthdr)) ||
261 				     hp->nexthdr == NEXTHDR_NONE)) {
262 					if (fragoff)
263 						*fragoff = _frag_off;
264 					return hp->nexthdr;
265 				}
266 				if (!found)
267 					return -ENOENT;
268 				if (fragoff)
269 					*fragoff = _frag_off;
270 				break;
271 			}
272 			hdrlen = 8;
273 		} else if (nexthdr == NEXTHDR_AUTH) {
274 			if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))
275 				break;
276 			hdrlen = ipv6_authlen(hp);
277 		} else
278 			hdrlen = ipv6_optlen(hp);
279 
280 		if (!found) {
281 			nexthdr = hp->nexthdr;
282 			start += hdrlen;
283 		}
284 	} while (!found);
285 
286 	*offset = start;
287 	return nexthdr;
288 }
289 EXPORT_SYMBOL(ipv6_find_hdr);
290