xref: /freebsd/contrib/ldns/edns.c (revision 5afab0e5e56fe90a378fb57249600e7924e1cab2)
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 static 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 static 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 static 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