source: mainline/uspace/srv/net/dnsrsrv/dns_msg.c@ 08e103d4

Last change on this file since 08e103d4 was 08e103d4, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

Use clearer naming for string length functions

This and the following commit change the names of functions, as well as
their documentation, to use unambiguous terms "bytes" and "code points"
instead of ambiguous terms "size", "length", and "characters".

  • Property mode set to 100644
File size: 16.1 KB
Line 
1/*
2 * Copyright (c) 2013 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup dnsrsrv
30 * @{
31 */
32/**
33 * @file
34 */
35
36#include <bitops.h>
37#include <byteorder.h>
38#include <errno.h>
39#include <io/log.h>
40#include <macros.h>
41#include <stdint.h>
42#include <stdlib.h>
43#include <str.h>
44
45#include "dns_msg.h"
46#include "dns_std.h"
47
48#define NAME "dnsres"
49
50static uint16_t dns_uint16_t_decode(uint8_t *, size_t);
51
52/** Extend dynamically allocated string with suffix.
53 *
54 * @a *dstr points to a dynamically allocated buffer containing a string.
55 * Reallocate this buffer so that concatenation of @a *dstr and @a suff can
56 * fit in and append @a suff.
57 */
58static errno_t dns_dstr_ext(char **dstr, const char *suff)
59{
60 size_t s1, s2;
61 size_t nsize;
62 char *nstr;
63
64 if (*dstr == NULL) {
65 *dstr = str_dup(suff);
66 if (*dstr == NULL)
67 return ENOMEM;
68 return EOK;
69 }
70
71 s1 = str_bytes(*dstr);
72 s2 = str_bytes(suff);
73 nsize = s1 + s2 + 1;
74
75 nstr = realloc(*dstr, nsize);
76 if (nstr == NULL)
77 return ENOMEM;
78
79 str_cpy(nstr + s1, nsize - s1, suff);
80
81 *dstr = nstr;
82 return EOK;
83}
84
85/** Encode DNS name.
86 *
87 * Encode DNS name or measure the size of encoded name (with @a buf NULL,
88 * and @a buf_size 0).
89 *
90 * @param name String to encode
91 * @param buf Buffer or NULL
92 * @param buf_size Buffer size or 0 if @a buf is NULL
93 * @param act_size Place to store actual encoded size
94 */
95static errno_t dns_name_encode(char *name, uint8_t *buf, size_t buf_size,
96 size_t *act_size)
97{
98 size_t off;
99 wchar_t c;
100 size_t lsize;
101 size_t pi, di;
102
103 pi = 0;
104 di = 1;
105 off = 0;
106
107 lsize = 0;
108 while (true) {
109 c = str_decode(name, &off, STR_NO_LIMIT);
110 if (c >= 127) {
111 /* Non-ASCII character */
112 log_msg(LOG_DEFAULT, LVL_DEBUG, "Non-ascii character");
113 return EINVAL;
114 }
115
116 if (c == '.' || c == '\0') {
117 /* Empty string, starting with period or two consecutive periods. */
118 if (lsize == 0) {
119 log_msg(LOG_DEFAULT, LVL_DEBUG, "Empty token");
120 return EINVAL;
121 }
122
123 if (lsize > DNS_LABEL_MAX_SIZE) {
124 /* Label too long */
125 log_msg(LOG_DEFAULT, LVL_DEBUG, "Label too long");
126 return EINVAL;
127 }
128
129 if (buf != NULL && pi < buf_size)
130 buf[pi] = (uint8_t)lsize;
131
132 lsize = 0;
133 pi = di;
134 ++di;
135
136 if (c == '\0')
137 break;
138 } else {
139 if (buf != NULL && di < buf_size)
140 buf[di] = c;
141 ++di;
142 ++lsize;
143 }
144 }
145
146 if (buf != NULL && pi < buf_size)
147 buf[pi] = 0;
148
149 *act_size = di;
150 return EOK;
151}
152
153/** Decode DNS name.
154 *
155 * @param pdu PDU from which we are decoding
156 * @param boff Starting offset within PDU
157 * @param rname Place to return dynamically allocated string
158 * @param eoff Place to store end offset (offset after last decoded byte)
159 */
160errno_t dns_name_decode(dns_pdu_t *pdu, size_t boff, char **rname,
161 size_t *eoff)
162{
163 uint8_t *bp;
164 size_t bsize;
165 size_t lsize;
166 size_t i;
167 size_t ptr;
168 size_t eptr;
169 char *name;
170 char dbuf[2];
171 errno_t rc;
172 bool first;
173
174 name = NULL;
175
176 if (boff > pdu->size)
177 return EINVAL;
178
179 bp = pdu->data + boff;
180 bsize = min(pdu->size - boff, DNS_NAME_MAX_SIZE);
181 first = true;
182 *eoff = 0;
183
184 while (true) {
185 if (bsize == 0) {
186 rc = EINVAL;
187 goto error;
188 }
189
190 lsize = *bp;
191 ++bp;
192 --bsize;
193
194 if (lsize == 0)
195 break;
196
197 if ((lsize & 0xc0) == 0xc0) {
198 /* Pointer */
199 if (bsize < 1) {
200 log_msg(LOG_DEFAULT, LVL_DEBUG, "Pointer- bsize < 1");
201 rc = EINVAL;
202 goto error;
203 }
204
205 ptr = dns_uint16_t_decode(bp - 1, bsize) & 0x3fff;
206 ++bp;
207 --bsize;
208
209 if (ptr >= (size_t)(bp - pdu->data)) {
210 log_msg(LOG_DEFAULT, LVL_DEBUG,
211 "Pointer- forward ref %zu, pos=%zu",
212 ptr, (size_t)(bp - pdu->data));
213 /* Forward reference */
214 rc = EINVAL;
215 goto error;
216 }
217
218 /*
219 * Make sure we will not decode any byte twice.
220 * XXX Is assumption correct?
221 */
222 eptr = bp - pdu->data;
223 /*
224 * This is where encoded name ends in terms where
225 * the message continues
226 */
227 if (*eoff == 0)
228 *eoff = eptr;
229
230 bp = pdu->data + ptr;
231 bsize = eptr - ptr;
232 continue;
233 }
234
235 if (lsize > bsize) {
236 rc = EINVAL;
237 goto error;
238 }
239
240 if (!first) {
241 rc = dns_dstr_ext(&name, ".");
242 if (rc != EOK) {
243 rc = ENOMEM;
244 goto error;
245 }
246 }
247
248 for (i = 0; i < lsize; i++) {
249 if (*bp < 32 || *bp >= 127) {
250 rc = EINVAL;
251 goto error;
252 }
253
254 dbuf[0] = *bp;
255 dbuf[1] = '\0';
256
257 rc = dns_dstr_ext(&name, dbuf);
258 if (rc != EOK) {
259 rc = ENOMEM;
260 goto error;
261 }
262 ++bp;
263 --bsize;
264 }
265
266 first = false;
267 }
268
269 *rname = name;
270 if (*eoff == 0)
271 *eoff = bp - pdu->data;
272 return EOK;
273error:
274 free(name);
275 return rc;
276}
277
278/** Decode unaligned big-endian 16-bit integer */
279static uint16_t dns_uint16_t_decode(uint8_t *buf, size_t buf_size)
280{
281 assert(buf_size >= 2);
282
283 return ((uint16_t)buf[0] << 8) + buf[1];
284}
285
286/** Encode unaligned big-endian 16-bit integer */
287static void dns_uint16_t_encode(uint16_t w, uint8_t *buf, size_t buf_size)
288{
289 if (buf != NULL && buf_size >= 1)
290 buf[0] = w >> 8;
291
292 if (buf != NULL && buf_size >= 2)
293 buf[1] = w & 0xff;
294}
295
296/** Decode unaligned big-endian 32-bit integer */
297uint32_t dns_uint32_t_decode(uint8_t *buf, size_t buf_size)
298{
299 assert(buf_size >= 4);
300
301 uint32_t w = ((uint32_t) buf[0] << 24) +
302 ((uint32_t) buf[1] << 16) +
303 ((uint32_t) buf[2] << 8) +
304 buf[3];
305
306 return w;
307}
308
309/** Decode unaligned big-endian 128-bit integer */
310void dns_addr128_t_decode(uint8_t *buf, size_t buf_size, addr128_t addr)
311{
312 assert(buf_size >= 16);
313
314 addr128_t_be2host(buf, addr);
315}
316
317/** Encode DNS question.
318 *
319 * Encode DNS question or measure the size of encoded question (with @a buf NULL,
320 * and @a buf_size 0).
321 *
322 * @param question Question to encode
323 * @param buf Buffer or NULL
324 * @param buf_size Buffer size or 0 if @a buf is NULL
325 * @param act_size Place to store actual encoded size
326 */
327static errno_t dns_question_encode(dns_question_t *question, uint8_t *buf,
328 size_t buf_size, size_t *act_size)
329{
330 size_t name_size;
331 size_t di;
332 errno_t rc;
333
334 rc = dns_name_encode(question->qname, buf, buf_size, &name_size);
335 if (rc != EOK)
336 return rc;
337
338 *act_size = name_size + sizeof(uint16_t) + sizeof(uint16_t);
339 if (buf == NULL)
340 return EOK;
341
342 di = name_size;
343
344 dns_uint16_t_encode(question->qtype, buf + di, buf_size - di);
345 di += sizeof(uint16_t);
346
347 dns_uint16_t_encode(question->qclass, buf + di, buf_size - di);
348 di += sizeof(uint16_t);
349
350 return EOK;
351}
352
353/** Decode DNS question.
354 *
355 * @param pdu PDU from which we are decoding
356 * @param boff Starting offset within PDU
357 * @param rquestion Place to return dynamically allocated question
358 * @param eoff Place to store end offset (offset after last decoded byte)
359 */
360static errno_t dns_question_decode(dns_pdu_t *pdu, size_t boff,
361 dns_question_t **rquestion, size_t *eoff)
362{
363 dns_question_t *question;
364 size_t name_eoff;
365 errno_t rc;
366
367 question = calloc(1, sizeof (dns_question_t));
368 if (question == NULL)
369 return ENOMEM;
370
371 rc = dns_name_decode(pdu, boff, &question->qname, &name_eoff);
372 if (rc != EOK) {
373 log_msg(LOG_DEFAULT, LVL_DEBUG, "Error decoding name");
374 free(question);
375 return ENOMEM;
376 }
377
378 if (name_eoff + 2 * sizeof(uint16_t) > pdu->size) {
379 free(question);
380 return EINVAL;
381 }
382
383 question->qtype = dns_uint16_t_decode(pdu->data + name_eoff,
384 pdu->size - name_eoff);
385 question->qclass = dns_uint16_t_decode(pdu->data + sizeof(uint16_t) +
386 name_eoff, pdu->size - sizeof(uint16_t) - name_eoff);
387 *eoff = name_eoff + 2 * sizeof(uint16_t);
388
389 *rquestion = question;
390 return EOK;
391}
392
393/** Decode DNS resource record.
394 *
395 * @param pdu PDU from which we are decoding
396 * @param boff Starting offset within PDU
397 * @param retrr Place to return dynamically allocated resource record
398 * @param eoff Place to store end offset (offset after last decoded byte)
399 */
400static errno_t dns_rr_decode(dns_pdu_t *pdu, size_t boff, dns_rr_t **retrr,
401 size_t *eoff)
402{
403 dns_rr_t *rr;
404 size_t name_eoff;
405 uint8_t *bp;
406 size_t bsz;
407 size_t rdlength;
408 errno_t rc;
409
410 rr = calloc(1, sizeof(dns_rr_t));
411 if (rr == NULL)
412 return ENOMEM;
413
414 rc = dns_name_decode(pdu, boff, &rr->name, &name_eoff);
415 if (rc != EOK) {
416 log_msg(LOG_DEFAULT, LVL_DEBUG, "dns_rr_decode: error decoding name");
417 free(rr);
418 return ENOMEM;
419 }
420
421 if (name_eoff + 2 * sizeof(uint16_t) > pdu->size) {
422 free(rr->name);
423 free(rr);
424 log_msg(LOG_DEFAULT, LVL_DEBUG, "dns_rr_decode: error name_off");
425 return EINVAL;
426 }
427
428 bp = pdu->data + name_eoff;
429 bsz = pdu->size - name_eoff;
430
431 if (bsz < 3 * sizeof(uint16_t) + sizeof(uint32_t)) {
432 free(rr->name);
433 free(rr);
434 log_msg(LOG_DEFAULT, LVL_DEBUG, "dns_rr_decode: error bsz");
435 return EINVAL;
436 }
437
438 rr->rtype = dns_uint16_t_decode(bp, bsz);
439 bp += sizeof(uint16_t);
440 bsz -= sizeof(uint16_t);
441
442 rr->rclass = dns_uint16_t_decode(bp, bsz);
443 bp += sizeof(uint16_t);
444 bsz -= sizeof(uint16_t);
445
446 rr->ttl = dns_uint32_t_decode(bp, bsz);
447 bp += sizeof(uint32_t);
448 bsz -= sizeof(uint32_t);
449
450 log_msg(LOG_DEFAULT, LVL_DEBUG2, "dns_rr_decode: rtype=0x%x, rclass=0x%x, ttl=0x%x",
451 rr->rtype, rr->rclass, rr->ttl);
452
453 rdlength = dns_uint16_t_decode(bp, bsz);
454 bp += sizeof(uint16_t);
455 bsz -= sizeof(uint16_t);
456
457 if (rdlength > bsz) {
458 free(rr->name);
459 free(rr);
460 log_msg(LOG_DEFAULT, LVL_DEBUG, "dns_rr_decode: Error rdlength %zu > bsz %zu", rdlength, bsz);
461 return EINVAL;
462 }
463
464 rr->rdata_size = rdlength;
465 log_msg(LOG_DEFAULT, LVL_DEBUG2, "dns_rr_decode: rdlength=%zu", rdlength);
466 rr->rdata = calloc(1, rdlength);
467 if (rr->rdata == NULL) {
468 free(rr->name);
469 free(rr);
470 log_msg(LOG_DEFAULT, LVL_DEBUG, "dns_rr_decode: Error memory");
471 return ENOMEM;
472 }
473
474 memcpy(rr->rdata, bp, rdlength);
475 rr->roff = bp - pdu->data;
476 bp += rdlength;
477 bsz -= rdlength;
478
479 *eoff = bp - pdu->data;
480 *retrr = rr;
481 log_msg(LOG_DEFAULT, LVL_DEBUG2, "dns_rr_decode: done");
482 return EOK;
483}
484
485/** Encode DNS message.
486 *
487 * @param msg Message
488 * @param rdata Place to store encoded data pointer
489 * @param rsize Place to store encoded data size
490 *
491 * @return EOK on success, EINVAL if message contains invalid data,
492 * ENOMEM if out of memory
493 */
494errno_t dns_message_encode(dns_message_t *msg, void **rdata, size_t *rsize)
495{
496 uint8_t *data;
497 size_t size;
498 dns_header_t hdr;
499 size_t q_size = 0;
500 size_t di;
501 errno_t rc;
502
503 hdr.id = host2uint16_t_be(msg->id);
504
505 hdr.opbits = host2uint16_t_be(
506 (msg->qr << OPB_QR) |
507 (msg->opcode << OPB_OPCODE_l) |
508 (msg->aa ? BIT_V(uint16_t, OPB_AA) : 0) |
509 (msg->tc ? BIT_V(uint16_t, OPB_TC) : 0) |
510 (msg->rd ? BIT_V(uint16_t, OPB_RD) : 0) |
511 (msg->ra ? BIT_V(uint16_t, OPB_RA) : 0) |
512 msg->rcode);
513
514 hdr.qd_count = host2uint16_t_be(list_count(&msg->question));
515 hdr.an_count = 0;
516 hdr.ns_count = 0;
517 hdr.ar_count = 0;
518
519 size = sizeof(dns_header_t);
520
521 list_foreach(msg->question, msg, dns_question_t, q) {
522 rc = dns_question_encode(q, NULL, 0, &q_size);
523 if (rc != EOK)
524 return rc;
525
526 assert(q_size > 0);
527
528 size += q_size;
529 }
530
531 data = calloc(1, size);
532 if (data == NULL)
533 return ENOMEM;
534
535 memcpy(data, &hdr, sizeof(dns_header_t));
536 di = sizeof(dns_header_t);
537
538 list_foreach(msg->question, msg, dns_question_t, q) {
539 rc = dns_question_encode(q, data + di, size - di, &q_size);
540 if (rc != EOK) {
541 assert(rc == ENOMEM || rc == EINVAL);
542 free(data);
543 return rc;
544 }
545
546 assert(q_size > 0);
547
548 di += q_size;
549 }
550
551 *rdata = data;
552 *rsize = size;
553 return EOK;
554}
555
556/** Decode DNS message.
557 *
558 * @param data Encoded PDU data
559 * @param size Encoded PDU size
560 * @param rmsg Place to store pointer to decoded message
561 *
562 * @return EOK on success, EINVAL if message contains invalid data,
563 * ENOMEM if out of memory
564 */
565errno_t dns_message_decode(void *data, size_t size, dns_message_t **rmsg)
566{
567 dns_message_t *msg;
568 dns_header_t *hdr;
569 size_t doff;
570 size_t field_eoff;
571 dns_question_t *question;
572 dns_rr_t *rr;
573 size_t qd_count;
574 size_t an_count;
575 size_t i;
576 errno_t rc;
577
578 msg = dns_message_new();
579 if (msg == NULL)
580 return ENOMEM;
581
582 if (size < sizeof(dns_header_t)) {
583 rc = EINVAL;
584 goto error;
585 }
586
587 /* Store a copy of raw message data for string decompression */
588
589 msg->pdu.data = malloc(size);
590 if (msg->pdu.data == NULL) {
591 rc = ENOMEM;
592 goto error;
593 }
594
595 memcpy(msg->pdu.data, data, size);
596 msg->pdu.size = size;
597 log_msg(LOG_DEFAULT, LVL_DEBUG2, "dns_message_decode: pdu->data = %p, "
598 "pdu->size=%zu", msg->pdu.data, msg->pdu.size);
599
600 hdr = data;
601
602 msg->id = uint16_t_be2host(hdr->id);
603 msg->qr = BIT_RANGE_EXTRACT(uint16_t, OPB_QR, OPB_QR, hdr->opbits);
604 msg->opcode = BIT_RANGE_EXTRACT(uint16_t, OPB_OPCODE_h, OPB_OPCODE_l,
605 hdr->opbits);
606 msg->aa = BIT_RANGE_EXTRACT(uint16_t, OPB_AA, OPB_AA, hdr->opbits);
607 msg->tc = BIT_RANGE_EXTRACT(uint16_t, OPB_TC, OPB_TC, hdr->opbits);
608 msg->rd = BIT_RANGE_EXTRACT(uint16_t, OPB_RD, OPB_RD, hdr->opbits);
609 msg->ra = BIT_RANGE_EXTRACT(uint16_t, OPB_RA, OPB_RA, hdr->opbits);
610 msg->rcode = BIT_RANGE_EXTRACT(uint16_t, OPB_RCODE_h, OPB_RCODE_l,
611 hdr->opbits);
612
613 doff = sizeof(dns_header_t);
614
615 qd_count = uint16_t_be2host(hdr->qd_count);
616 log_msg(LOG_DEFAULT, LVL_DEBUG2, "qd_count=%zu", qd_count);
617
618 for (i = 0; i < qd_count; i++) {
619 rc = dns_question_decode(&msg->pdu, doff, &question, &field_eoff);
620 if (rc != EOK) {
621 log_msg(LOG_DEFAULT, LVL_DEBUG, "error decoding question");
622 goto error;
623 }
624
625 list_append(&question->msg, &msg->question);
626 doff = field_eoff;
627 }
628
629 an_count = uint16_t_be2host(hdr->an_count);
630 log_msg(LOG_DEFAULT, LVL_DEBUG2, "an_count=%zu", an_count);
631
632 for (i = 0; i < an_count; i++) {
633 rc = dns_rr_decode(&msg->pdu, doff, &rr, &field_eoff);
634 if (rc != EOK) {
635 log_msg(LOG_DEFAULT, LVL_DEBUG, "Error decoding answer");
636 goto error;
637 }
638
639 list_append(&rr->msg, &msg->answer);
640 doff = field_eoff;
641 }
642
643 *rmsg = msg;
644 return EOK;
645error:
646 dns_message_destroy(msg);
647 return rc;
648}
649
650/** Destroy question. */
651static void dns_question_destroy(dns_question_t *question)
652{
653 free(question->qname);
654 free(question);
655}
656
657/** Destroy resource record. */
658static void dns_rr_destroy(dns_rr_t *rr)
659{
660 free(rr->name);
661 free(rr->rdata);
662 free(rr);
663}
664
665/** Create new empty message. */
666dns_message_t *dns_message_new(void)
667{
668 dns_message_t *msg;
669
670 msg = calloc(1, sizeof(dns_message_t));
671 if (msg == NULL)
672 return NULL;
673
674 list_initialize(&msg->question);
675 list_initialize(&msg->answer);
676 list_initialize(&msg->authority);
677 list_initialize(&msg->additional);
678
679 return msg;
680}
681
682/** Destroy message. */
683void dns_message_destroy(dns_message_t *msg)
684{
685 link_t *link;
686 dns_question_t *question;
687 dns_rr_t *rr;
688
689 while (!list_empty(&msg->question)) {
690 link = list_first(&msg->question);
691 question = list_get_instance(link, dns_question_t, msg);
692 list_remove(&question->msg);
693 dns_question_destroy(question);
694 }
695
696 while (!list_empty(&msg->answer)) {
697 link = list_first(&msg->answer);
698 rr = list_get_instance(link, dns_rr_t, msg);
699 list_remove(&rr->msg);
700 dns_rr_destroy(rr);
701 }
702
703 while (!list_empty(&msg->authority)) {
704 link = list_first(&msg->authority);
705 rr = list_get_instance(link, dns_rr_t, msg);
706 list_remove(&rr->msg);
707 dns_rr_destroy(rr);
708 }
709
710 while (!list_empty(&msg->additional)) {
711 link = list_first(&msg->additional);
712 rr = list_get_instance(link, dns_rr_t, msg);
713 list_remove(&rr->msg);
714 dns_rr_destroy(rr);
715 }
716
717 free(msg->pdu.data);
718 free(msg);
719}
720
721/** @}
722 */
Note: See TracBrowser for help on using the repository browser.