Line data Source code
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 :
13 0 : bool ipv6_ext_hdr(u8 nexthdr)
14 : {
15 : /*
16 : * find out if nexthdr is an extension header or a protocol
17 : */
18 0 : return (nexthdr == NEXTHDR_HOP) ||
19 0 : (nexthdr == NEXTHDR_ROUTING) ||
20 0 : (nexthdr == NEXTHDR_FRAGMENT) ||
21 0 : (nexthdr == NEXTHDR_AUTH) ||
22 0 : (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 :
72 0 : int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
73 : __be16 *frag_offp)
74 : {
75 0 : u8 nexthdr = *nexthdrp;
76 :
77 0 : *frag_offp = 0;
78 :
79 0 : while (ipv6_ext_hdr(nexthdr)) {
80 0 : struct ipv6_opt_hdr _hdr, *hp;
81 0 : int hdrlen;
82 :
83 0 : if (nexthdr == NEXTHDR_NONE)
84 0 : return -1;
85 0 : hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
86 0 : if (!hp)
87 : return -1;
88 0 : if (nexthdr == NEXTHDR_FRAGMENT) {
89 0 : __be16 _frag_off, *fp;
90 0 : fp = skb_header_pointer(skb,
91 0 : start+offsetof(struct frag_hdr,
92 : frag_off),
93 : sizeof(_frag_off),
94 : &_frag_off);
95 0 : if (!fp)
96 0 : return -1;
97 :
98 0 : *frag_offp = *fp;
99 0 : if (ntohs(*frag_offp) & ~0x7)
100 : break;
101 0 : hdrlen = 8;
102 0 : } else if (nexthdr == NEXTHDR_AUTH)
103 0 : hdrlen = ipv6_authlen(hp);
104 : else
105 0 : hdrlen = ipv6_optlen(hp);
106 :
107 0 : nexthdr = hp->nexthdr;
108 0 : start += hdrlen;
109 : }
110 :
111 0 : *nexthdrp = nexthdr;
112 0 : return start;
113 : }
114 : EXPORT_SYMBOL(ipv6_skip_exthdr);
115 :
116 0 : int ipv6_find_tlv(const struct sk_buff *skb, int offset, int type)
117 : {
118 0 : const unsigned char *nh = skb_network_header(skb);
119 0 : int packet_len = skb_tail_pointer(skb) - skb_network_header(skb);
120 0 : struct ipv6_opt_hdr *hdr;
121 0 : int len;
122 :
123 0 : if (offset + 2 > packet_len)
124 0 : goto bad;
125 0 : hdr = (struct ipv6_opt_hdr *)(nh + offset);
126 0 : len = ((hdr->hdrlen + 1) << 3);
127 :
128 0 : if (offset + len > packet_len)
129 0 : goto bad;
130 :
131 0 : offset += 2;
132 0 : len -= 2;
133 :
134 0 : while (len > 0) {
135 0 : int opttype = nh[offset];
136 0 : int optlen;
137 :
138 0 : if (opttype == type)
139 0 : return offset;
140 :
141 0 : switch (opttype) {
142 : case IPV6_TLV_PAD1:
143 : optlen = 1;
144 : break;
145 0 : default:
146 0 : optlen = nh[offset + 1] + 2;
147 0 : if (optlen > len)
148 0 : goto bad;
149 : break;
150 : }
151 0 : offset += optlen;
152 0 : len -= optlen;
153 : }
154 : /* not_found */
155 0 : bad:
156 : return -1;
157 : }
158 : EXPORT_SYMBOL_GPL(ipv6_find_tlv);
159 :
160 : /*
161 : * find the offset to specified header or the protocol number of last header
162 : * if target < 0. "last header" is transport protocol header, ESP, or
163 : * "No next header".
164 : *
165 : * Note that *offset is used as input/output parameter, and if it is not zero,
166 : * then it must be a valid offset to an inner IPv6 header. This can be used
167 : * to explore inner IPv6 header, eg. ICMPv6 error messages.
168 : *
169 : * If target header is found, its offset is set in *offset and return protocol
170 : * number. Otherwise, return -1.
171 : *
172 : * If the first fragment doesn't contain the final protocol header or
173 : * NEXTHDR_NONE it is considered invalid.
174 : *
175 : * Note that non-1st fragment is special case that "the protocol number
176 : * of last header" is "next header" field in Fragment header. In this case,
177 : * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
178 : * isn't NULL.
179 : *
180 : * if flags is not NULL and it's a fragment, then the frag flag
181 : * IP6_FH_F_FRAG will be set. If it's an AH header, the
182 : * IP6_FH_F_AUTH flag is set and target < 0, then this function will
183 : * stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this
184 : * function will skip all those routing headers, where segements_left was 0.
185 : */
186 0 : int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
187 : int target, unsigned short *fragoff, int *flags)
188 : {
189 0 : unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
190 0 : u8 nexthdr = ipv6_hdr(skb)->nexthdr;
191 0 : bool found;
192 :
193 0 : if (fragoff)
194 0 : *fragoff = 0;
195 :
196 0 : if (*offset) {
197 0 : struct ipv6hdr _ip6, *ip6;
198 :
199 0 : ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
200 0 : if (!ip6 || (ip6->version != 6))
201 0 : return -EBADMSG;
202 0 : start = *offset + sizeof(struct ipv6hdr);
203 0 : nexthdr = ip6->nexthdr;
204 : }
205 :
206 0 : do {
207 0 : struct ipv6_opt_hdr _hdr, *hp;
208 0 : unsigned int hdrlen;
209 0 : found = (nexthdr == target);
210 :
211 0 : if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
212 0 : if (target < 0 || found)
213 : break;
214 0 : return -ENOENT;
215 : }
216 :
217 0 : hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
218 0 : if (!hp)
219 : return -EBADMSG;
220 :
221 0 : if (nexthdr == NEXTHDR_ROUTING) {
222 0 : struct ipv6_rt_hdr _rh, *rh;
223 :
224 0 : rh = skb_header_pointer(skb, start, sizeof(_rh),
225 : &_rh);
226 0 : if (!rh)
227 0 : return -EBADMSG;
228 :
229 0 : if (flags && (*flags & IP6_FH_F_SKIP_RH) &&
230 0 : rh->segments_left == 0)
231 0 : found = false;
232 : }
233 :
234 0 : if (nexthdr == NEXTHDR_FRAGMENT) {
235 0 : unsigned short _frag_off;
236 0 : __be16 *fp;
237 :
238 0 : if (flags) /* Indicate that this is a fragment */
239 0 : *flags |= IP6_FH_F_FRAG;
240 0 : fp = skb_header_pointer(skb,
241 0 : start+offsetof(struct frag_hdr,
242 : frag_off),
243 : sizeof(_frag_off),
244 : &_frag_off);
245 0 : if (!fp)
246 0 : return -EBADMSG;
247 :
248 0 : _frag_off = ntohs(*fp) & ~0x7;
249 0 : if (_frag_off) {
250 0 : if (target < 0 &&
251 0 : ((!ipv6_ext_hdr(hp->nexthdr)) ||
252 : hp->nexthdr == NEXTHDR_NONE)) {
253 0 : if (fragoff)
254 0 : *fragoff = _frag_off;
255 0 : return hp->nexthdr;
256 : }
257 0 : if (!found)
258 : return -ENOENT;
259 0 : if (fragoff)
260 0 : *fragoff = _frag_off;
261 0 : break;
262 : }
263 0 : hdrlen = 8;
264 0 : } else if (nexthdr == NEXTHDR_AUTH) {
265 0 : if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))
266 : break;
267 0 : hdrlen = ipv6_authlen(hp);
268 : } else
269 0 : hdrlen = ipv6_optlen(hp);
270 :
271 0 : if (!found) {
272 0 : nexthdr = hp->nexthdr;
273 0 : start += hdrlen;
274 : }
275 0 : } while (!found);
276 :
277 0 : *offset = start;
278 0 : return nexthdr;
279 : }
280 : EXPORT_SYMBOL(ipv6_find_hdr);
|