1 | /* Copyright (C) 2006-2023 Free Software Foundation, Inc. |
2 | This file is part of the GNU C Library. |
3 | |
4 | The GNU C Library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2.1 of the License, or (at your option) any later version. |
8 | |
9 | The GNU C Library is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | Lesser General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Lesser General Public |
15 | License along with the GNU C Library; if not, see |
16 | <https://www.gnu.org/licenses/>. */ |
17 | |
18 | #include <string.h> |
19 | #include <netinet/in.h> |
20 | #include <netinet/ip6.h> |
21 | |
22 | |
23 | /* RFC 3542, 10.1 |
24 | |
25 | This function returns the number of bytes needed for the empty |
26 | extension header i.e., without any options. If EXTBUF is not NULL it |
27 | also initializes the extension header to have the correct length |
28 | field. In that case if the EXTLEN value is not a positive (i.e., |
29 | non-zero) multiple of 8 the function fails and returns -1. */ |
30 | int |
31 | inet6_opt_init (void *extbuf, socklen_t extlen) |
32 | { |
33 | if (extbuf != NULL) |
34 | { |
35 | if (extlen <= 0 || (extlen % 8) != 0 || extlen > 256 * 8) |
36 | return -1; |
37 | |
38 | /* Fill in the length in units of 8 octets. */ |
39 | struct ip6_hbh *extp = (struct ip6_hbh *) extbuf; |
40 | |
41 | /* RFC 2460 requires that the header extension length is the |
42 | length of the option header in 8-byte units, not including |
43 | the first 8 bytes. Hence we have to subtract one. */ |
44 | extp->ip6h_len = extlen / 8 - 1; |
45 | } |
46 | |
47 | return sizeof (struct ip6_hbh); |
48 | } |
49 | |
50 | |
51 | static void |
52 | add_padding (uint8_t *extbuf, int offset, int npad) |
53 | { |
54 | if (npad == 1) |
55 | extbuf[offset] = IP6OPT_PAD1; |
56 | else if (npad > 0) |
57 | { |
58 | struct ip6_opt *pad_opt = (struct ip6_opt *) (extbuf + offset); |
59 | |
60 | pad_opt->ip6o_type = IP6OPT_PADN; |
61 | pad_opt->ip6o_len = npad - sizeof (struct ip6_opt); |
62 | /* Clear the memory used by the padding. */ |
63 | memset (pad_opt + 1, '\0', pad_opt->ip6o_len); |
64 | } |
65 | } |
66 | |
67 | |
68 | |
69 | /* RFC 3542, 10.2 |
70 | |
71 | This function returns the updated total length taking into account |
72 | adding an option with length 'len' and alignment 'align'. If |
73 | EXTBUF is not NULL then, in addition to returning the length, the |
74 | function inserts any needed pad option, initializes the option |
75 | (setting the type and length fields) and returns a pointer to the |
76 | location for the option content in databufp. If the option does |
77 | not fit in the extension header buffer the function returns -1. */ |
78 | int |
79 | inet6_opt_append (void *extbuf, socklen_t extlen, int offset, uint8_t type, |
80 | socklen_t len, uint8_t align, void **databufp) |
81 | { |
82 | /* Check minimum offset. */ |
83 | if (offset < sizeof (struct ip6_hbh)) |
84 | return -1; |
85 | |
86 | /* One cannot add padding options. */ |
87 | if (type == IP6OPT_PAD1 || type == IP6OPT_PADN) |
88 | return -1; |
89 | |
90 | /* The option length must fit in one octet. */ |
91 | if (len > 255) |
92 | return -1; |
93 | |
94 | /* The alignment can only by 1, 2, 4, or 8 and must not exceed the |
95 | option length. */ |
96 | if (align == 0 || align > 8 || (align & (align - 1)) != 0 || align > len) |
97 | return -1; |
98 | |
99 | /* Determine the needed padding for alignment. Following the |
100 | current content of the buffer we have the is the IPv6 option type |
101 | and length, followed immediately by the data. The data has the |
102 | alignment constraints. Therefore padding must be inserted in the |
103 | form of padding options before the new option. */ |
104 | int data_offset = offset + sizeof (struct ip6_opt); |
105 | int npad = (align - data_offset % align) & (align - 1); |
106 | |
107 | if (extbuf != NULL) |
108 | { |
109 | /* Now we can check whether the buffer is large enough. */ |
110 | if (data_offset + npad + len > extlen) |
111 | return -1; |
112 | |
113 | add_padding (extbuf, offset, npad); |
114 | |
115 | offset += npad; |
116 | |
117 | /* Now prepare the option itself. */ |
118 | struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset); |
119 | |
120 | opt->ip6o_type = type; |
121 | opt->ip6o_len = len; |
122 | |
123 | *databufp = opt + 1; |
124 | } |
125 | else |
126 | offset += npad; |
127 | |
128 | return offset + sizeof (struct ip6_opt) + len; |
129 | } |
130 | |
131 | |
132 | /* RFC 3542, 10.3 |
133 | |
134 | This function returns the updated total length taking into account |
135 | the final padding of the extension header to make it a multiple of |
136 | 8 bytes. If EXTBUF is not NULL the function also initializes the |
137 | option by inserting a Pad1 or PadN option of the proper length. */ |
138 | int |
139 | inet6_opt_finish (void *extbuf, socklen_t extlen, int offset) |
140 | { |
141 | /* Check minimum offset. */ |
142 | if (offset < sizeof (struct ip6_hbh)) |
143 | return -1; |
144 | |
145 | /* Required padding at the end. */ |
146 | int npad = (8 - (offset & 7)) & 7; |
147 | |
148 | if (extbuf != NULL) |
149 | { |
150 | /* Make sure the buffer is large enough. */ |
151 | if (offset + npad > extlen) |
152 | return -1; |
153 | |
154 | add_padding (extbuf, offset, npad); |
155 | } |
156 | |
157 | return offset + npad; |
158 | } |
159 | |
160 | |
161 | /* RFC 3542, 10.4 |
162 | |
163 | This function inserts data items of various sizes in the data |
164 | portion of the option. VAL should point to the data to be |
165 | inserted. OFFSET specifies where in the data portion of the option |
166 | the value should be inserted; the first byte after the option type |
167 | and length is accessed by specifying an offset of zero. */ |
168 | int |
169 | inet6_opt_set_val (void *databuf, int offset, void *val, socklen_t vallen) |
170 | { |
171 | memcpy ((uint8_t *) databuf + offset, val, vallen); |
172 | |
173 | return offset + vallen; |
174 | } |
175 | |
176 | |
177 | /* RFC 3542, 10.5 |
178 | |
179 | This function parses received option extension headers returning |
180 | the next option. EXTBUF and EXTLEN specifies the extension header. |
181 | OFFSET should either be zero (for the first option) or the length |
182 | returned by a previous call to 'inet6_opt_next' or |
183 | 'inet6_opt_find'. It specifies the position where to continue |
184 | scanning the extension buffer. */ |
185 | int |
186 | inet6_opt_next (void *extbuf, socklen_t extlen, int offset, uint8_t *typep, |
187 | socklen_t *lenp, void **databufp) |
188 | { |
189 | if (offset == 0) |
190 | offset = sizeof (struct ip6_hbh); |
191 | else if (offset < sizeof (struct ip6_hbh)) |
192 | return -1; |
193 | |
194 | while (offset < extlen) |
195 | { |
196 | struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset); |
197 | |
198 | if (opt->ip6o_type == IP6OPT_PAD1) |
199 | /* Single byte padding. */ |
200 | ++offset; |
201 | else if (opt->ip6o_type == IP6OPT_PADN) |
202 | offset += sizeof (struct ip6_opt) + opt->ip6o_len; |
203 | else |
204 | { |
205 | /* Check whether the option is valid. */ |
206 | offset += sizeof (struct ip6_opt) + opt->ip6o_len; |
207 | if (offset > extlen) |
208 | return -1; |
209 | |
210 | *typep = opt->ip6o_type; |
211 | *lenp = opt->ip6o_len; |
212 | *databufp = opt + 1; |
213 | return offset; |
214 | } |
215 | } |
216 | |
217 | return -1; |
218 | } |
219 | |
220 | |
221 | /* RFC 3542, 10.6 |
222 | |
223 | This function is similar to the previously described |
224 | 'inet6_opt_next' function, except this function lets the caller |
225 | specify the option type to be searched for, instead of always |
226 | returning the next option in the extension header. */ |
227 | int |
228 | inet6_opt_find (void *extbuf, socklen_t extlen, int offset, uint8_t type, |
229 | socklen_t *lenp, void **databufp) |
230 | { |
231 | if (offset == 0) |
232 | offset = sizeof (struct ip6_hbh); |
233 | else if (offset < sizeof (struct ip6_hbh)) |
234 | return -1; |
235 | |
236 | while (offset < extlen) |
237 | { |
238 | struct ip6_opt *opt = (struct ip6_opt *) ((uint8_t *) extbuf + offset); |
239 | |
240 | if (opt->ip6o_type == IP6OPT_PAD1) |
241 | { |
242 | /* Single byte padding. */ |
243 | ++offset; |
244 | if (type == IP6OPT_PAD1) |
245 | { |
246 | *lenp = 0; |
247 | *databufp = (uint8_t *) extbuf + offset; |
248 | return offset; |
249 | } |
250 | } |
251 | else if (opt->ip6o_type != type) |
252 | offset += sizeof (struct ip6_opt) + opt->ip6o_len; |
253 | else |
254 | { |
255 | /* Check whether the option is valid. */ |
256 | offset += sizeof (struct ip6_opt) + opt->ip6o_len; |
257 | if (offset > extlen) |
258 | return -1; |
259 | |
260 | *lenp = opt->ip6o_len; |
261 | *databufp = opt + 1; |
262 | return offset; |
263 | } |
264 | } |
265 | |
266 | return -1; |
267 | } |
268 | |
269 | |
270 | /* RFC 3542, 10.7 |
271 | |
272 | This function extracts data items of various sizes in the data |
273 | portion of the option. */ |
274 | int |
275 | inet6_opt_get_val (void *databuf, int offset, void *val, socklen_t vallen) |
276 | { |
277 | memcpy (val, (uint8_t *) databuf + offset, vallen); |
278 | |
279 | return offset + vallen; |
280 | } |
281 | |