pkt.c 20 KB
Newer Older
Daniel Salzman's avatar
Daniel Salzman committed
1
/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
Lubos Slovak's avatar
Lubos Slovak committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

Lubos Slovak's avatar
Lubos Slovak committed
17
#include <assert.h>
18
#include <stdlib.h>
19
#include <stdbool.h>
Lubos Slovak's avatar
Lubos Slovak committed
20

21
#include "libknot/attribute.h"
Marek Vavrusa's avatar
Marek Vavrusa committed
22
#include "libknot/packet/pkt.h"
23
#include "libknot/codes.h"
24
#include "libknot/descriptor.h"
25
#include "libknot/errcode.h"
26
#include "libknot/rrtype/tsig.h"
27
#include "libknot/tsig-op.h"
Daniel Salzman's avatar
Daniel Salzman committed
28
#include "libknot/packet/wire.h"
Lubos Slovak's avatar
Lubos Slovak committed
29
#include "libknot/packet/rrset-wire.h"
30
#include "contrib/mempattern.h"
31
#include "contrib/wire.h"
32
#include "contrib/wire_ctx.h"
Lubos Slovak's avatar
Lubos Slovak committed
33

34 35 36 37
/*! \brief Packet RR array growth step. */
#define NEXT_RR_ALIGN 16
#define NEXT_RR_COUNT(count) (((count) / NEXT_RR_ALIGN + 1) * NEXT_RR_ALIGN)

38
/*! \brief Scan packet for RRSet existence. */
39
static bool pkt_contains(const knot_pkt_t *packet,
40
			 const knot_rrset_t *rrset)
41
{
42 43
	assert(packet);
	assert(rrset);
44

45
	for (int i = 0; i < packet->rrset_count; ++i) {
46 47 48
		const uint16_t type = packet->rr[i].type;
		const uint8_t *data = packet->rr[i].rrs.data;
		if (type == rrset->type && data == rrset->rrs.data) {
49
			return true;
50 51
		}
	}
52

53
	return false;
54 55
}

56
/*! \brief Free all RRSets and reset RRSet count. */
57
static void pkt_free_data(knot_pkt_t *pkt)
58
{
59 60
	assert(pkt);

61 62 63
	/* Free RRSets if applicable. */
	for (uint16_t i = 0; i < pkt->rrset_count; ++i) {
		if (pkt->rr_info[i].flags & KNOT_PF_FREE) {
64
			knot_rrset_clear(&pkt->rr[i], &pkt->mm);
65
		}
66
	}
Jan Včelák's avatar
Jan Včelák committed
67

68 69
	/* Reset RR count. */
	pkt->rrset_count = 0;
70 71 72 73

	/* Reset special types. */
	pkt->opt_rr = NULL;
	pkt->tsig_rr = NULL;
74 75 76 77

	/* Reset TSIG wire reference. */
	pkt->tsig_wire.pos = NULL;
	pkt->tsig_wire.len = 0;
78
}
Jan Včelák's avatar
Jan Včelák committed
79

80
/*! \brief Allocate new wireformat of given length. */
81 82
static int pkt_wire_alloc(knot_pkt_t *pkt, uint16_t len)
{
83
	assert(pkt);
Marek Vavruša's avatar
Marek Vavruša committed
84
	assert(len >= KNOT_WIRE_HEADER_SIZE);
85

86 87 88
	pkt->wire = pkt->mm.alloc(pkt->mm.ctx, len);
	if (pkt->wire == NULL) {
		return KNOT_ENOMEM;
89
	}
Jan Včelák's avatar
Jan Včelák committed
90

91 92 93
	pkt->flags |= KNOT_PF_FREE;
	pkt->max_size = len;
	knot_pkt_clear(pkt);
94
	return KNOT_EOK;
95 96
}

97
/*! \brief Set packet wireformat to an existing memory. */
98 99
static void pkt_wire_set(knot_pkt_t *pkt, void *wire, uint16_t len)
{
100 101
	assert(pkt);

102 103 104 105
	pkt->wire = wire;
	pkt->size = pkt->max_size = len;
	pkt->parsed = 0;
}
106

107
/*! \brief Calculate remaining size in the packet. */
108
static uint16_t pkt_remaining(knot_pkt_t *pkt)
109
{
110 111
	assert(pkt);

112
	return pkt->max_size - pkt->size - pkt->reserved;
113
}
114

115 116 117 118 119 120 121 122
/*! \brief Return RR count for given section (from wire xxCOUNT in header). */
static uint16_t pkt_rr_wirecount(knot_pkt_t *pkt, knot_section_t section_id)
{
	assert(pkt);
	switch (section_id) {
	case KNOT_ANSWER:     return knot_wire_get_ancount(pkt->wire);
	case KNOT_AUTHORITY:  return knot_wire_get_nscount(pkt->wire);
	case KNOT_ADDITIONAL: return knot_wire_get_arcount(pkt->wire);
123
	default: assert(0);   return 0;
124
	}
125
}
Lubos Slovak's avatar
Lubos Slovak committed
126

127 128 129 130 131 132
/*! \brief Update RR count for given section (wire xxCOUNT in header). */
static void pkt_rr_wirecount_add(knot_pkt_t *pkt, knot_section_t section_id,
                                 int16_t val)
{
	assert(pkt);
	switch (section_id) {
133 134 135
	case KNOT_ANSWER:     knot_wire_add_ancount(pkt->wire, val); break;
	case KNOT_AUTHORITY:  knot_wire_add_nscount(pkt->wire, val); break;
	case KNOT_ADDITIONAL: knot_wire_add_arcount(pkt->wire, val); break;
Lubos Slovak's avatar
Lubos Slovak committed
136
	}
137
}
Lubos Slovak's avatar
Lubos Slovak committed
138

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
/*! \brief Reserve enough space in the RR arrays. */
static int pkt_rr_array_alloc(knot_pkt_t *pkt, uint16_t count)
{
	/* Enough space. */
	if (pkt->rrset_allocd >= count) {
		return KNOT_EOK;
	}

	/* Allocate rr_info and rr fields to next size. */
	size_t next_size = NEXT_RR_COUNT(count);
	knot_rrinfo_t *rr_info = mm_alloc(&pkt->mm, sizeof(knot_rrinfo_t) * next_size);
	if (rr_info == NULL) {
		return KNOT_ENOMEM;
	}

	knot_rrset_t *rr = mm_alloc(&pkt->mm, sizeof(knot_rrset_t) * next_size);
	if (rr == NULL) {
		mm_free(&pkt->mm, rr_info);
		return KNOT_ENOMEM;
	}

	/* Copy the old data. */
	memcpy(rr_info, pkt->rr_info, pkt->rrset_allocd * sizeof(knot_rrinfo_t));
	memcpy(rr, pkt->rr, pkt->rrset_allocd * sizeof(knot_rrset_t));

	/* Reassign and free old data. */
	mm_free(&pkt->mm, pkt->rr);
	mm_free(&pkt->mm, pkt->rr_info);
	pkt->rr = rr;
	pkt->rr_info = rr_info;
	pkt->rrset_allocd = next_size;

	return KNOT_EOK;
}

174
/*! \brief Clear the packet and switch wireformat pointers (possibly allocate new). */
175
static int pkt_init(knot_pkt_t *pkt, void *wire, uint16_t len, knot_mm_t *mm)
176
{
177
	assert(pkt);
178

179
	memset(pkt, 0, sizeof(knot_pkt_t));
180 181

	/* No data to free, set memory context. */
182
	memcpy(&pkt->mm, mm, sizeof(knot_mm_t));
183 184

	/* Initialize wire. */
185
	int ret = KNOT_EOK;
186 187 188 189 190 191 192 193 194
	if (wire == NULL) {
		ret = pkt_wire_alloc(pkt, len);
	} else {
		pkt_wire_set(pkt, wire, len);
	}

	return ret;
}

195
/*! \brief Reset packet parse state. */
196 197 198 199 200
static int pkt_reset_sections(knot_pkt_t *pkt)
{
	pkt->parsed  = 0;
	pkt->current = KNOT_ANSWER;
	memset(pkt->sections, 0, sizeof(pkt->sections));
Marek Vavruša's avatar
Marek Vavruša committed
201
	return knot_pkt_begin(pkt, KNOT_ANSWER);
202 203
}

204 205
_public_
void knot_pkt_clear_payload(knot_pkt_t *pkt)
206
{
207 208 209
	if (!pkt) {
		return;
	}
210 211 212

	/* Keep question. */
	pkt->parsed = 0;
213
	pkt->size = KNOT_WIRE_HEADER_SIZE + knot_pkt_question_size(pkt);
214 215 216 217 218 219 220
	knot_wire_set_ancount(pkt->wire, 0);
	knot_wire_set_nscount(pkt->wire, 0);
	knot_wire_set_arcount(pkt->wire, 0);

	/* Free RRSets if applicable. */
	pkt_free_data(pkt);

221
	/* Reset sections. */
222
	pkt_reset_sections(pkt);
223 224 225
}

/*! \brief Allocate new packet using memory context. */
226
static knot_pkt_t *pkt_new_mm(void *wire, uint16_t len, knot_mm_t *mm)
227
{
228 229
	assert(mm);

230
	knot_pkt_t *pkt = mm_alloc(mm, sizeof(knot_pkt_t));
231
	if (pkt == NULL) {
232 233 234
		return NULL;
	}

235
	if (pkt_init(pkt, wire, len, mm) != KNOT_EOK) {
236
		mm_free(mm, pkt);
237
		return NULL;
Lubos Slovak's avatar
Lubos Slovak committed
238
	}
Jan Včelák's avatar
Jan Včelák committed
239

240 241
	return pkt;
}
Jan Včelák's avatar
Jan Včelák committed
242

243
_public_
244
knot_pkt_t *knot_pkt_new(void *wire, uint16_t len, knot_mm_t *mm)
245 246
{
	/* Default memory allocator if NULL. */
247
	knot_mm_t _mm;
248
	if (mm == NULL) {
249
		mm_ctx_init(&_mm);
250
		mm = &_mm;
251 252
	}

253
	return pkt_new_mm(wire, len, mm);
254 255
}

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
static int append_tsig(knot_pkt_t *dst, const knot_pkt_t *src)
{
	/* Check if a wire TSIG is available. */
	if (src->tsig_wire.pos != NULL) {
		if (dst->max_size < src->size + src->tsig_wire.len) {
			return KNOT_ESPACE;
		}
		memcpy(dst->wire + dst->size, src->tsig_wire.pos,
		       src->tsig_wire.len);
		dst->size += src->tsig_wire.len;

		/* Increment arcount. */
		knot_wire_set_arcount(dst->wire,
		                      knot_wire_get_arcount(dst->wire) + 1);
	} else {
		return knot_tsig_append(dst->wire, &dst->size, dst->max_size,
		                        src->tsig_rr);
	}

	return KNOT_EOK;
}

278
_public_
Marek Vavruša's avatar
Marek Vavruša committed
279
int knot_pkt_copy(knot_pkt_t *dst, const knot_pkt_t *src)
280
{
Marek Vavruša's avatar
Marek Vavruša committed
281 282
	if (dst == NULL || src == NULL) {
		return KNOT_EINVAL;
283 284
	}

Marek Vavruša's avatar
Marek Vavruša committed
285 286
	if (dst->max_size < src->size) {
		return KNOT_ESPACE;
287
	}
288
	memcpy(dst->wire, src->wire, src->size);
Marek Vavruša's avatar
Marek Vavruša committed
289
	dst->size = src->size;
290

291
	/* Append TSIG record. */
Marek Vavruša's avatar
Marek Vavruša committed
292
	if (src->tsig_rr) {
293
		int ret = append_tsig(dst, src);
294
		if (ret != KNOT_EOK) {
Marek Vavruša's avatar
Marek Vavruša committed
295
			return ret;
296 297 298
		}
	}

299
	/* Invalidate arrays. */
300 301 302 303 304
	dst->rr = NULL;
	dst->rr_info = NULL;
	dst->rrset_count = 0;
	dst->rrset_allocd = 0;

305
	/* @note This could be done more effectively if needed. */
Marek Vavruša's avatar
Marek Vavruša committed
306
	return knot_pkt_parse(dst, 0);
307 308
}

309
_public_
310
int knot_pkt_init_response(knot_pkt_t *pkt, const knot_pkt_t *query)
311
{
312 313
	if (pkt == NULL || query == NULL) {
		return KNOT_EINVAL;
314
	}
315

316
	/* Header + question size. */
317 318
	size_t base_size = KNOT_WIRE_HEADER_SIZE + knot_pkt_question_size(query);
	if (base_size > pkt->max_size) {
319
		return KNOT_ESPACE;
320
	}
321

322 323
	pkt->size = base_size;
	memcpy(pkt->wire, query->wire, base_size);
324

325 326 327 328 329 330
	pkt->qname_size = query->qname_size;
	if (query->qname_size == 0) {
		/* Reset question count if malformed. */
		knot_wire_set_qdcount(pkt->wire, 0);
	}

331 332 333 334 335
	/* Update size and flags. */
	knot_wire_set_qr(pkt->wire);
	knot_wire_clear_tc(pkt->wire);
	knot_wire_clear_ad(pkt->wire);
	knot_wire_clear_ra(pkt->wire);
336
	knot_wire_clear_aa(pkt->wire);
337
	knot_wire_clear_z(pkt->wire);
338

339
	/* Clear payload. */
340
	knot_pkt_clear_payload(pkt);
Daniel Salzman's avatar
Daniel Salzman committed
341

Lubos Slovak's avatar
Lubos Slovak committed
342
	return KNOT_EOK;
343 344
}

345
_public_
346
void knot_pkt_clear(knot_pkt_t *pkt)
347
{
348 349 350
	if (pkt == NULL) {
		return;
	}
351

352
	/* Clear payload. */
353
	knot_pkt_clear_payload(pkt);
Lubos Slovak's avatar
Lubos Slovak committed
354

355 356
	/* Reset to header size. */
	pkt->size = KNOT_WIRE_HEADER_SIZE;
357
	memset(pkt->wire, 0, pkt->size);
358
}
Lubos Slovak's avatar
Lubos Slovak committed
359

360
_public_
361
void knot_pkt_free(knot_pkt_t **pkt)
362
{
363
	if (pkt == NULL || *pkt == NULL) {
364
		return;
Lubos Slovak's avatar
Lubos Slovak committed
365 366
	}

367
	/* Free temporary RRSets. */
368
	pkt_free_data(*pkt);
369

370 371 372 373
	/* Free RR/RR info arrays. */
	mm_free(&(*pkt)->mm, (*pkt)->rr);
	mm_free(&(*pkt)->mm, (*pkt)->rr_info);

374
	// free the space for wireformat
375 376
	if ((*pkt)->flags & KNOT_PF_FREE) {
		(*pkt)->mm.free((*pkt)->wire);
Lubos Slovak's avatar
Lubos Slovak committed
377 378
	}

379 380
	(*pkt)->mm.free(*pkt);
	*pkt = NULL;
381
}
Lubos Slovak's avatar
Lubos Slovak committed
382

383
_public_
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size)
{
	if (pkt == NULL) {
		return KNOT_EINVAL;
	}

	/* Reserve extra space (if possible). */
	if (pkt_remaining(pkt) >= size) {
		pkt->reserved += size;
		return KNOT_EOK;
	} else {
		return KNOT_ERANGE;
	}
}

399
_public_
400 401 402 403 404 405 406 407 408 409 410 411 412
int knot_pkt_reclaim(knot_pkt_t *pkt, uint16_t size)
{
	if (pkt == NULL) {
		return KNOT_EINVAL;
	}

	if (pkt->reserved >= size) {
		pkt->reserved -= size;
		return KNOT_EOK;
	} else {
		return KNOT_ERANGE;
	}
}
413

414
_public_
415
uint16_t knot_pkt_question_size(const knot_pkt_t *pkt)
416
{
417
	if (pkt == NULL || pkt->qname_size == 0) {
418 419 420
		return 0;
	}

421
	return pkt->qname_size + 2 * sizeof(uint16_t);
422 423
}

424
_public_
425
const knot_dname_t *knot_pkt_qname(const knot_pkt_t *pkt)
426
{
427
	if (pkt == NULL || pkt->qname_size == 0) {
428 429
		return NULL;
	}
430

431
	return pkt->wire + KNOT_WIRE_HEADER_SIZE;
432 433
}

434
_public_
435
uint16_t knot_pkt_qtype(const knot_pkt_t *pkt)
436
{
437
	if (pkt == NULL || pkt->qname_size == 0) {
438
		return 0;
439 440
	}

441
	unsigned off = KNOT_WIRE_HEADER_SIZE + pkt->qname_size;
442
	return wire_read_u16(pkt->wire + off);
443 444
}

445
_public_
446
uint16_t knot_pkt_qclass(const knot_pkt_t *pkt)
447
{
448
	if (pkt == NULL || pkt->qname_size == 0) {
449
		return 0;
Lubos Slovak's avatar
Lubos Slovak committed
450
	}
Lubos Slovak's avatar
Lubos Slovak committed
451

452
	unsigned off = KNOT_WIRE_HEADER_SIZE + pkt->qname_size + sizeof(uint16_t);
453
	return wire_read_u16(pkt->wire + off);
454 455
}

456
_public_
457 458
int knot_pkt_begin(knot_pkt_t *pkt, knot_section_t section_id)
{
459
	if (pkt == NULL || section_id < pkt->current) {
460 461 462
		return KNOT_EINVAL;
	}

463
	/* Remember watermark but not on repeated calls. */
464
	pkt->sections[section_id].pkt = pkt;
465 466 467 468 469
	if (section_id > pkt->current) {
		pkt->sections[section_id].pos = pkt->rrset_count;
	}

	pkt->current = section_id;
470

Lubos Slovak's avatar
Lubos Slovak committed
471
	return KNOT_EOK;
472 473
}

474
_public_
475
int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype)
Lubos Slovak's avatar
Lubos Slovak committed
476
{
477
	if (pkt == NULL || qname == NULL) {
Marek Vavrusa's avatar
Marek Vavrusa committed
478
		return KNOT_EINVAL;
479 480
	}

481 482
	assert(pkt->size == KNOT_WIRE_HEADER_SIZE);
	assert(pkt->rrset_count == 0);
483

484
	/* Copy name wireformat. */
485 486 487 488 489
	wire_ctx_t wire = wire_ctx_init(pkt->wire, pkt->max_size);
	wire_ctx_set_offset(&wire, KNOT_WIRE_HEADER_SIZE);

	int qname_len = knot_dname_to_wire(wire.position,
	                                   qname, wire_ctx_available(&wire));
490 491 492
	if (qname_len < 0) {
		return qname_len;
	}
493
	wire_ctx_skip(&wire, qname_len);
Jan Včelák's avatar
Jan Včelák committed
494

495
	/* Copy QTYPE & QCLASS */
496 497 498 499 500 501 502
	wire_ctx_write_u16(&wire, qtype);
	wire_ctx_write_u16(&wire, qclass);

	/* Check errors. */
	if (wire.error != KNOT_EOK) {
		return wire.error;
	}
503

504 505
	/* Update question count and sizes. */
	knot_wire_set_qdcount(pkt->wire, 1);
506
	pkt->size = wire_ctx_offset(&wire);
507
	pkt->qname_size = qname_len;
508

509 510
	/* Start writing ANSWER. */
	return knot_pkt_begin(pkt, KNOT_ANSWER);
Lubos Slovak's avatar
Lubos Slovak committed
511 512
}

513
_public_
Lubos Slovak's avatar
Lubos Slovak committed
514 515
int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr,
                 uint16_t flags)
516
{
517 518 519
	if (pkt == NULL || rr == NULL) {
		return KNOT_EINVAL;
	}
520

521 522 523 524 525 526
	/* Reserve memory for RR descriptors. */
	int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1);
	if (ret != KNOT_EOK) {
		return ret;
	}

527 528 529 530
	knot_rrinfo_t *rrinfo = &pkt->rr_info[pkt->rrset_count];
	memset(rrinfo, 0, sizeof(knot_rrinfo_t));
	rrinfo->pos = pkt->size;
	rrinfo->flags = flags;
531
	rrinfo->compress_ptr[0] = compr_hint;
532
	memcpy(pkt->rr + pkt->rrset_count, rr, sizeof(knot_rrset_t));
533

534
	/* Check for double insertion. */
Daniel Salzman's avatar
Daniel Salzman committed
535
	if ((flags & KNOT_PF_CHECKDUP) && pkt_contains(pkt, rr)) {
536
		return KNOT_EOK; /*! \todo return rather a number of added RRs */
Lubos Slovak's avatar
Lubos Slovak committed
537 538
	}

539 540
	uint8_t *pos = pkt->wire + pkt->size;
	size_t maxlen = pkt_remaining(pkt);
541

542
	/* Create compression context. */
543 544 545 546 547 548
	knot_compr_t compr;
	compr.wire = pkt->wire;
	compr.rrinfo = rrinfo;
	compr.suffix.pos = KNOT_WIRE_HEADER_SIZE;
	compr.suffix.labels = knot_dname_labels(compr.wire + compr.suffix.pos,
	                                        compr.wire);
549

550
	/* Write RRSet to wireformat. */
551
	ret = knot_rrset_to_wire(rr, pos, maxlen, &compr);
552
	if (ret < 0) {
553
		/* Truncate packet if required. */
554
		if (ret == KNOT_ESPACE && !(flags & KNOT_PF_NOTRUNC)) {
Daniel Salzman's avatar
Daniel Salzman committed
555
			knot_wire_set_tc(pkt->wire);
556 557
		}
		return ret;
558 559
	}

560 561 562
	size_t len = ret;
	uint16_t rr_added = rr->rrs.rr_count;

563 564 565 566 567
	/* Keep reference to special types. */
	if (rr->type == KNOT_RRTYPE_OPT) {
		pkt->opt_rr = &pkt->rr[pkt->rrset_count];
	}

568
	if (rr_added > 0) {
569 570
		pkt->rrset_count += 1;
		pkt->sections[pkt->current].count += 1;
571 572 573
		pkt->size += len;
		pkt_rr_wirecount_add(pkt, pkt->current, rr_added);
	}
574

575
	return KNOT_EOK;
576 577
}

578
_public_
579 580
const knot_pktsection_t *knot_pkt_section(const knot_pkt_t *pkt,
                                          knot_section_t section_id)
Lubos Slovak's avatar
Lubos Slovak committed
581
{
582 583
	if (pkt == NULL) {
		return NULL;
584 585
	}

586
	return &pkt->sections[section_id];
Lubos Slovak's avatar
Lubos Slovak committed
587 588
}

589 590 591 592 593 594 595 596 597 598
_public_
const knot_rrset_t *knot_pkt_rr(const knot_pktsection_t *section, uint16_t i)
{
	if (section == NULL) {
		return NULL;
	}

	return section->pkt->rr + section->pos + i;
}

599 600 601 602 603 604 605 606 607 608
_public_
uint16_t knot_pkt_rr_offset(const knot_pktsection_t *section, uint16_t i)
{
	if (section == NULL || section->pkt == NULL) {
		return -1;
	}

	return section->pkt->rr_info[section->pos + i].pos;
}

609
_public_
610
int knot_pkt_parse(knot_pkt_t *pkt, unsigned flags)
611
{
612
	if (pkt == NULL) {
Marek Vavrusa's avatar
Marek Vavrusa committed
613
		return KNOT_EINVAL;
614
	}
Jan Včelák's avatar
Jan Včelák committed
615

616
	/* Reset parse state. */
617
	pkt_reset_sections(pkt);
618

619 620 621
	int ret = knot_pkt_parse_question(pkt);
	if (ret == KNOT_EOK) {
		ret = knot_pkt_parse_payload(pkt, flags);
622
	}
Jan Včelák's avatar
Jan Včelák committed
623

624
	return ret;
625 626
}

627
_public_
628
int knot_pkt_parse_question(knot_pkt_t *pkt)
Lubos Slovak's avatar
Lubos Slovak committed
629
{
630 631 632
	if (pkt == NULL) {
		return KNOT_EINVAL;
	}
Jan Včelák's avatar
Jan Včelák committed
633

634 635 636 637
	/* Check at least header size. */
	if (pkt->size < KNOT_WIRE_HEADER_SIZE) {
		return KNOT_EMALF;
	}
Lubos Slovak's avatar
Lubos Slovak committed
638

639 640 641
	/* We have at least some DNS header. */
	pkt->parsed = KNOT_WIRE_HEADER_SIZE;

642 643 644 645
	/* Check QD count. */
	uint16_t qd = knot_wire_get_qdcount(pkt->wire);
	if (qd > 1) {
		return KNOT_EMALF;
646 647
	}

648 649 650 651
	/* No question. */
	if (qd == 0) {
		pkt->qname_size = 0;
		return KNOT_EOK;
652 653
	}

654 655 656
	/* Process question. */
	int len = knot_dname_wire_check(pkt->wire + pkt->parsed,
	                                pkt->wire + pkt->size,
657
	                                NULL /* No compression in QNAME. */);
658 659
	if (len <= 0) {
		return KNOT_EMALF;
660
	}
Jan Včelák's avatar
Jan Včelák committed
661

662 663 664 665 666
	/* Check QCLASS/QTYPE size. */
	uint16_t question_size = len + 2 * sizeof(uint16_t); /* QCLASS + QTYPE */
	if (pkt->parsed + question_size > pkt->size) {
		return KNOT_EMALF;
	}
667

Jan Včelák's avatar
Jan Včelák committed
668
	pkt->parsed += question_size;
669
	pkt->qname_size = len;
670

671
	return KNOT_EOK;
672 673
}

674 675 676 677 678 679 680 681 682 683 684 685
/* \note Private for check_rr_constraints(). */
#define CHECK_AR_CONSTRAINTS(pkt, rr, var, check_func) \
	if ((pkt)->current != KNOT_ADDITIONAL) { \
		return KNOT_EMALF; \
	} else if ((pkt)->var != NULL) { \
		return KNOT_EMALF; \
	} else if (!check_func(rr)) { \
		return KNOT_EMALF; \
	} else { \
		(pkt)->var = rr; \
	}

686 687 688 689 690
/*! \brief Check constraints (position, uniqueness, validity) for special types
 *         (TSIG, OPT).
 */
static int check_rr_constraints(knot_pkt_t *pkt, knot_rrset_t *rr, size_t rr_size,
                                unsigned flags)
691 692
{
	/* Check RR constraints. */
Daniel Salzman's avatar
Daniel Salzman committed
693
	switch (rr->type) {
694
	case KNOT_RRTYPE_TSIG:
695
		CHECK_AR_CONSTRAINTS(pkt, rr, tsig_rr, knot_tsig_rdata_is_ok);
696 697 698 699 700

		/* Strip TSIG RR from wireformat and decrease ARCOUNT. */
		if (!(flags & KNOT_PF_KEEPWIRE)) {
			pkt->parsed -= rr_size;
			pkt->size -= rr_size;
701 702
			pkt->tsig_wire.pos = pkt->wire + pkt->parsed;
			pkt->tsig_wire.len = rr_size;
703 704 705 706 707 708 709 710 711 712 713 714 715
			knot_wire_set_arcount(pkt->wire, knot_wire_get_arcount(pkt->wire) - 1);
		}
		break;
	case KNOT_RRTYPE_OPT:
		CHECK_AR_CONSTRAINTS(pkt, rr, opt_rr, knot_edns_check_record);
		break;
	default:
		break;
	}

	return KNOT_EOK;
}

Jan Včelák's avatar
Jan Včelák committed
716
#undef CHECK_AR_CONSTRAINTS
717

718
_public_
719
int knot_pkt_parse_rr(knot_pkt_t *pkt, unsigned flags)
Lubos Slovak's avatar
Lubos Slovak committed
720
{
721 722 723 724
	if (pkt == NULL) {
		return KNOT_EINVAL;
	}

725 726
	if (pkt->parsed >= pkt->size) {
		return KNOT_EFEWDATA;
727 728
	}

729 730 731 732 733 734
	/* Reserve memory for RR descriptors. */
	int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1);
	if (ret != KNOT_EOK) {
		return ret;
	}

735 736 737 738
	/* Initialize RR info. */
	memset(&pkt->rr_info[pkt->rrset_count], 0, sizeof(knot_rrinfo_t));
	pkt->rr_info[pkt->rrset_count].pos = pkt->parsed;
	pkt->rr_info[pkt->rrset_count].flags = KNOT_PF_FREE;
Lubos Slovak's avatar
Lubos Slovak committed
739

740
	/* Parse wire format. */
741
	size_t rr_size = pkt->parsed;
742
	knot_rrset_t *rr = &pkt->rr[pkt->rrset_count];
743
	ret = knot_rrset_rr_from_wire(pkt->wire, &pkt->parsed, pkt->size,
744
	                              &pkt->mm, rr, !(flags & KNOT_PF_NOCANON));
745 746
	if (ret != KNOT_EOK) {
		return ret;
Lubos Slovak's avatar
Lubos Slovak committed
747
	}
Jan Včelák's avatar
Jan Včelák committed
748

749 750
	/* Calculate parsed RR size from before/after parsing. */
	rr_size = (pkt->parsed - rr_size);
Lubos Slovak's avatar
Lubos Slovak committed
751

752
	/* Update packet RRSet count. */
753 754 755
	++pkt->rrset_count;
	++pkt->sections[pkt->current].count;

756 757
	/* Check special RRs (OPT and TSIG). */
	return check_rr_constraints(pkt, rr, rr_size, flags);
Lubos Slovak's avatar
Lubos Slovak committed
758 759
}

760
_public_
761
int knot_pkt_parse_section(knot_pkt_t *pkt, unsigned flags)
762
{
763 764 765
	if (pkt == NULL) {
		return KNOT_EINVAL;
	}
766

767 768 769 770 771 772 773 774
	int ret = KNOT_EOK;
	uint16_t rr_parsed = 0;
	uint16_t rr_count = pkt_rr_wirecount(pkt, pkt->current);

	/* Parse all RRs belonging to the section. */
	for (rr_parsed = 0; rr_parsed < rr_count; ++rr_parsed) {
		ret = knot_pkt_parse_rr(pkt, flags);
		if (ret != KNOT_EOK) {
775
			return ret;
776
		}
777 778
	}

Lubos Slovak's avatar
Lubos Slovak committed
779
	return KNOT_EOK;
780 781
}

782
_public_
783
int knot_pkt_parse_payload(knot_pkt_t *pkt, unsigned flags)
784
{
785 786 787 788
	if (pkt == NULL) {
		return KNOT_EINVAL;
	}

789 790
	assert(pkt->wire != NULL);
	assert(pkt->size > 0);
791

792 793 794 795
	/* Reserve memory in advance to avoid resizing. */
	size_t rr_count = knot_wire_get_ancount(pkt->wire) +
	                  knot_wire_get_nscount(pkt->wire) +
	                  knot_wire_get_arcount(pkt->wire);
796 797 798 799 800

	if (rr_count > pkt->size / KNOT_WIRE_RR_MIN_SIZE) {
		return KNOT_EMALF;
	}

801 802 803 804
	int ret = pkt_rr_array_alloc(pkt, rr_count);
	if (ret != KNOT_EOK) {
		return ret;
	}
805

806 807 808 809 810 811 812 813 814
	for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) {
		ret = knot_pkt_begin(pkt, i);
		if (ret != KNOT_EOK) {
			return ret;
		}
		ret = knot_pkt_parse_section(pkt, flags);
		if (ret != KNOT_EOK) {
			return ret;
		}
815 816
	}

817 818 819
	/* TSIG must be last record of AR if present. */
	const knot_pktsection_t *ar = knot_pkt_section(pkt, KNOT_ADDITIONAL);
	if (pkt->tsig_rr != NULL) {
820 821
		const knot_rrset_t *last_rr = knot_pkt_rr(ar, ar->count - 1);
		if (ar->count > 0 && pkt->tsig_rr->rrs.data != last_rr->rrs.data) {
822 823 824
			return KNOT_EMALF;
		}
	}
825

826 827 828 829
	/* Check for trailing garbage. */
	if (pkt->parsed < pkt->size) {
		return KNOT_EMALF;
	}
830

Lubos Slovak's avatar
Lubos Slovak committed
831
	return KNOT_EOK;
832
}
833

834
_public_
835
uint16_t knot_pkt_ext_rcode(const knot_pkt_t *pkt)
836 837 838 839 840
{
	if (pkt == NULL) {
		return 0;
	}

841 842
	/* Get header RCODE. */
	uint16_t rcode = knot_wire_get_rcode(pkt->wire);
843

844 845
	/* Update to extended RCODE if EDNS is available. */
	if (pkt->opt_rr != NULL) {
846
		uint8_t opt_rcode = knot_edns_get_ext_rcode(pkt->opt_rr);
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
		rcode = knot_edns_whole_rcode(opt_rcode, rcode);
	}

	/* Return if not NOTAUTH. */
	if (rcode != KNOT_RCODE_NOTAUTH) {
		return rcode;
	}

	/* Get TSIG RCODE. */
	uint16_t tsig_rcode = KNOT_RCODE_NOERROR;
	if (pkt->tsig_rr != NULL) {
		tsig_rcode = knot_tsig_rdata_error(pkt->tsig_rr);
	}

	/* Return proper RCODE. */
	if (tsig_rcode != KNOT_RCODE_NOERROR) {
		return tsig_rcode;
864 865 866 867
	} else {
		return rcode;
	}
}
868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887

_public_
const char *knot_pkt_ext_rcode_name(const knot_pkt_t *pkt)
{
	if (pkt == NULL) {
		return "";
	}

	uint16_t rcode = knot_pkt_ext_rcode(pkt);

	const knot_lookup_t *item = NULL;
	if (pkt->tsig_rr != NULL) {
		item = knot_lookup_by_id(knot_tsig_rcode_names, rcode);
	}
	if (item == NULL) {
		item = knot_lookup_by_id(knot_rcode_names, rcode);
	}

	return (item != NULL) ? item->name : "";
}