xref: /dragonfly/contrib/ldns/edns.c (revision 7733acb50455a11cc2ee36edd926ff0fa3361e9a)
1 /*
2  * edns.c
3  *
4  * edns implementation
5  *
6  * a Net::DNS like library for C
7  *
8  * (c) NLnet Labs, 2004-2022
9  *
10  * See the file LICENSE for the license
11  */
12 
13 #include <ldns/ldns.h>
14 
15 #define LDNS_OPTIONLIST_INIT 8
16 
17 /*
18  * Access functions
19  * functions to get and set type checking
20  */
21 
22 /* read */
23 size_t
ldns_edns_get_size(const ldns_edns_option * edns)24 ldns_edns_get_size(const ldns_edns_option *edns)
25 {
26           assert(edns != NULL);
27           return edns->_size;
28 }
29 
30 ldns_edns_option_code
ldns_edns_get_code(const ldns_edns_option * edns)31 ldns_edns_get_code(const ldns_edns_option *edns)
32 {
33           assert(edns != NULL);
34           return edns->_code;
35 }
36 
37 uint8_t *
ldns_edns_get_data(const ldns_edns_option * edns)38 ldns_edns_get_data(const ldns_edns_option *edns)
39 {
40           assert(edns != NULL);
41           return edns->_data;
42 }
43 
44 ldns_buffer *
ldns_edns_get_wireformat_buffer(const ldns_edns_option * edns)45 ldns_edns_get_wireformat_buffer(const ldns_edns_option *edns)
46 {
47           uint16_t option;
48           size_t size;
49           uint8_t* data;
50           ldns_buffer* buffer;
51 
52           if (edns == NULL) {
53                     return NULL;
54           }
55 
56           option = ldns_edns_get_code(edns);
57           size = ldns_edns_get_size(edns);
58           data = ldns_edns_get_data(edns);
59 
60           buffer = ldns_buffer_new(size + 4);
61 
62           if (buffer == NULL) {
63                     return NULL;
64           }
65 
66           ldns_buffer_write_u16(buffer, option);
67           ldns_buffer_write_u16(buffer, size);
68           ldns_buffer_write(buffer, data, size);
69 
70           ldns_buffer_flip(buffer);
71 
72           return buffer;
73 }
74 
75 /* write */
76 void
ldns_edns_set_size(ldns_edns_option * edns,size_t size)77 ldns_edns_set_size(ldns_edns_option *edns, size_t size)
78 {
79           assert(edns != NULL);
80           edns->_size = size;
81 }
82 
83 void
ldns_edns_set_code(ldns_edns_option * edns,ldns_edns_option_code code)84 ldns_edns_set_code(ldns_edns_option *edns, ldns_edns_option_code code)
85 {
86           assert(edns != NULL);
87           edns->_code = code;
88 }
89 
90 void
ldns_edns_set_data(ldns_edns_option * edns,void * data)91 ldns_edns_set_data(ldns_edns_option *edns, void *data)
92 {
93           /* only copy the pointer */
94           assert(edns != NULL);
95           edns->_data = data;
96 }
97 
98 /* note: data must be allocated memory */
99 ldns_edns_option *
ldns_edns_new(ldns_edns_option_code code,size_t size,void * data)100 ldns_edns_new(ldns_edns_option_code code, size_t size, void *data)
101 {
102           ldns_edns_option *edns;
103           edns = LDNS_MALLOC(ldns_edns_option);
104           if (!edns) {
105                     return NULL;
106           }
107           ldns_edns_set_code(edns, code);
108           ldns_edns_set_size(edns, size);
109           ldns_edns_set_data(edns, data);
110 
111           return edns;
112 }
113 
114 ldns_edns_option *
ldns_edns_new_from_data(ldns_edns_option_code code,size_t size,const void * data)115 ldns_edns_new_from_data(ldns_edns_option_code code, size_t size, const void *data)
116 {
117           ldns_edns_option *edns;
118           edns = LDNS_MALLOC(ldns_edns_option);
119           if (!edns) {
120                     return NULL;
121           }
122           edns->_data = LDNS_XMALLOC(uint8_t, size);
123           if (!edns->_data) {
124                     LDNS_FREE(edns);
125                     return NULL;
126           }
127 
128           /* set the values */
129           ldns_edns_set_code(edns, code);
130           ldns_edns_set_size(edns, size);
131           memcpy(edns->_data, data, size);
132 
133           return edns;
134 }
135 
136 ldns_edns_option *
ldns_edns_clone(ldns_edns_option * edns)137 ldns_edns_clone(ldns_edns_option *edns)
138 {
139           ldns_edns_option *new_option;
140 
141           assert(edns != NULL);
142 
143           new_option = ldns_edns_new_from_data(ldns_edns_get_code(edns),
144                     ldns_edns_get_size(edns),
145                     ldns_edns_get_data(edns));
146 
147           return new_option;
148 }
149 
150 void
ldns_edns_deep_free(ldns_edns_option * edns)151 ldns_edns_deep_free(ldns_edns_option *edns)
152 {
153           if (edns) {
154                     if (edns->_data) {
155                               LDNS_FREE(edns->_data);
156                     }
157                     LDNS_FREE(edns);
158           }
159 }
160 
161 void
ldns_edns_free(ldns_edns_option * edns)162 ldns_edns_free(ldns_edns_option *edns)
163 {
164           if (edns) {
165                     LDNS_FREE(edns);
166           }
167 }
168 
169 ldns_edns_option_list*
ldns_edns_option_list_new()170 ldns_edns_option_list_new()
171 {
172           ldns_edns_option_list *option_list = LDNS_MALLOC(ldns_edns_option_list);
173           if(!option_list) {
174                     return NULL;
175           }
176 
177           option_list->_option_count = 0;
178           option_list->_option_capacity = 0;
179           option_list->_options_size = 0;
180           option_list->_options = NULL;
181           return option_list;
182 }
183 
184 ldns_edns_option_list *
ldns_edns_option_list_clone(ldns_edns_option_list * old_list)185 ldns_edns_option_list_clone(ldns_edns_option_list *old_list)
186 {
187           size_t i;
188           ldns_edns_option_list *new_list;
189 
190           if (!old_list) {
191                     return NULL;
192           }
193 
194           new_list = ldns_edns_option_list_new();
195           if (!new_list) {
196                     return NULL;
197           }
198 
199           if (old_list->_option_count == 0) {
200                     return new_list;
201           }
202 
203           /* adding options also updates the total options size */
204           for (i = 0; i < old_list->_option_count; i++) {
205                     ldns_edns_option *option = ldns_edns_clone(ldns_edns_option_list_get_option(old_list, i));
206                     if (!ldns_edns_option_list_push(new_list, option)) {
207                               ldns_edns_deep_free(option);
208                               ldns_edns_option_list_deep_free(new_list);
209                               return NULL;
210                     }
211           }
212           return new_list;
213 }
214 
215 void
ldns_edns_option_list_free(ldns_edns_option_list * option_list)216 ldns_edns_option_list_free(ldns_edns_option_list *option_list)
217 {
218           if (option_list) {
219                     LDNS_FREE(option_list->_options);
220                     LDNS_FREE(option_list);
221           }
222 }
223 
224 void
ldns_edns_option_list_deep_free(ldns_edns_option_list * option_list)225 ldns_edns_option_list_deep_free(ldns_edns_option_list *option_list)
226 {
227           size_t i;
228 
229           if (option_list) {
230                     for (i=0; i < ldns_edns_option_list_get_count(option_list); i++) {
231                               ldns_edns_deep_free(ldns_edns_option_list_get_option(option_list, i));
232                     }
233                     ldns_edns_option_list_free(option_list);
234           }
235 }
236 
237 size_t
ldns_edns_option_list_get_count(const ldns_edns_option_list * option_list)238 ldns_edns_option_list_get_count(const ldns_edns_option_list *option_list)
239 {
240           if (option_list) {
241                     return option_list->_option_count;
242           } else {
243                     return 0;
244           }
245 }
246 
247 ldns_edns_option *
ldns_edns_option_list_get_option(const ldns_edns_option_list * option_list,size_t index)248 ldns_edns_option_list_get_option(const ldns_edns_option_list *option_list, size_t index)
249 {
250           if (option_list && index < ldns_edns_option_list_get_count(option_list)) {
251                     assert(option_list->_options[index]);
252                     return option_list->_options[index];
253           } else {
254                     return NULL;
255           }
256 }
257 
258 size_t
ldns_edns_option_list_get_options_size(const ldns_edns_option_list * option_list)259 ldns_edns_option_list_get_options_size(const ldns_edns_option_list *option_list)
260 {
261           if (option_list) {
262                     return option_list->_options_size;
263           } else {
264                     return 0;
265           }
266 }
267 
268 
269 ldns_edns_option *
ldns_edns_option_list_set_option(ldns_edns_option_list * option_list,ldns_edns_option * option,size_t index)270 ldns_edns_option_list_set_option(ldns_edns_option_list *option_list,
271           ldns_edns_option *option, size_t index)
272 {
273           ldns_edns_option* old;
274 
275           assert(option_list != NULL);
276 
277           if (index > ldns_edns_option_list_get_count(option_list)) {
278                     return NULL;
279           }
280 
281           if (option == NULL) {
282                     return NULL;
283           }
284 
285           old = ldns_edns_option_list_get_option(option_list, index);
286 
287           /* shrink the total EDNS size if the old EDNS option exists */
288           if (old != NULL) {
289                     option_list->_options_size -= (ldns_edns_get_size(old) + 4);
290           }
291 
292           option_list->_options_size += (ldns_edns_get_size(option) + 4);
293 
294           option_list->_options[index] = option;
295           return old;
296 }
297 
298 bool
ldns_edns_option_list_push(ldns_edns_option_list * option_list,ldns_edns_option * option)299 ldns_edns_option_list_push(ldns_edns_option_list *option_list,
300           ldns_edns_option *option)
301 {
302           size_t cap;
303           size_t option_count;
304 
305           assert(option_list != NULL);
306 
307           if (option == NULL) {
308                     return false;
309           }
310 
311           cap = option_list->_option_capacity;
312           option_count = ldns_edns_option_list_get_count(option_list);
313 
314           /* verify we need to grow the array to fit the new option */
315           if (option_count+1 > cap) {
316                     ldns_edns_option **new_list;
317 
318                     /* initialize the capacity if needed, otherwise grow by doubling */
319                     if (cap == 0) {
320                               cap = LDNS_OPTIONLIST_INIT; /* initial list size */
321                     } else {
322                               cap *= 2;
323                     }
324 
325                     new_list = LDNS_XREALLOC(option_list->_options,
326                               ldns_edns_option *, cap);
327 
328                     if (!new_list) {
329                               return false;
330                     }
331 
332                     option_list->_options = new_list;
333                     option_list->_option_capacity = cap;
334           }
335 
336           /* add the new option */
337           ldns_edns_option_list_set_option(option_list, option,
338                     option_list->_option_count);
339           option_list->_option_count += 1;
340 
341           return true;
342 }
343 
344 ldns_edns_option *
ldns_edns_option_list_pop(ldns_edns_option_list * option_list)345 ldns_edns_option_list_pop(ldns_edns_option_list *option_list)
346 {
347           ldns_edns_option* pop;
348           size_t count;
349           size_t cap;
350 
351           assert(option_list != NULL);
352 
353           cap = option_list->_option_capacity;
354           count = ldns_edns_option_list_get_count(option_list);
355 
356           if (count == 0) {
357                     return NULL;
358           }
359           /* get the last option from the list */
360           pop = ldns_edns_option_list_get_option(option_list, count-1);
361 
362           /* shrink the array */
363           if (cap > LDNS_OPTIONLIST_INIT && count-1 <= cap/2) {
364                     ldns_edns_option **new_list;
365 
366                     cap /= 2;
367 
368                     new_list = LDNS_XREALLOC(option_list->_options,
369                               ldns_edns_option *, cap);
370                     if (new_list) {
371                               option_list->_options = new_list;
372                     }
373                     /* if the realloc fails, the capacity for the list remains unchanged */
374           }
375 
376           /* shrink the total EDNS size of the options if the popped EDNS option exists */
377           if (pop != NULL) {
378                     option_list->_options_size -= (ldns_edns_get_size(pop) + 4);
379           }
380 
381           option_list->_option_count = count - 1;
382 
383           return pop;
384 }
385 
386 ldns_buffer *
ldns_edns_option_list2wireformat_buffer(const ldns_edns_option_list * option_list)387 ldns_edns_option_list2wireformat_buffer(const ldns_edns_option_list *option_list)
388 {
389           size_t i, list_size, options_size, option, size;
390           ldns_buffer* buffer;
391           ldns_edns_option *edns;
392           uint8_t* data = NULL;
393 
394           if (!option_list) {
395                     return NULL;
396           }
397 
398           /* get the number of EDNS options in the list*/
399           list_size = ldns_edns_option_list_get_count(option_list);
400 
401           /* create buffer the size of the total EDNS wireformat options */
402           options_size = ldns_edns_option_list_get_options_size(option_list);
403           buffer = ldns_buffer_new(options_size);
404 
405           if (!buffer) {
406                     return NULL;
407           }
408 
409           /* write individual serialized EDNS options to final buffer*/
410           for (i = 0; i < list_size; i++) {
411                     edns = ldns_edns_option_list_get_option(option_list, i);
412 
413                     if (edns == NULL) {
414                               /* this shouldn't be possible */
415                               return NULL;
416                     }
417 
418                     option = ldns_edns_get_code(edns);
419                     size = ldns_edns_get_size(edns);
420                     data = ldns_edns_get_data(edns);
421 
422                     /* make sure the option fits */
423                     if (!(ldns_buffer_available(buffer, size + 4))) {
424                               ldns_buffer_free(buffer);
425                               return NULL;
426                     }
427 
428                     ldns_buffer_write_u16(buffer, option);
429                     ldns_buffer_write_u16(buffer, size);
430                     ldns_buffer_write(buffer, data, size);
431           }
432 
433           ldns_buffer_flip(buffer);
434 
435           return buffer;
436 }
437