Пример #1
0
static void _test_nsec_next(const char *msg,
                            const knot_dname_t *input,
                            const knot_dname_t *apex,
                            const knot_dname_t *expected)
{
	knot_dname_t *next = online_nsec_next(input, apex);
	ok(next != NULL && knot_dname_cmp(next, expected) == 0, "nsec_next, %s", msg);
	knot_dname_free(&next, NULL);
}
Пример #2
0
static int ztree_tests_run(int argc, char *argv[])
{
	ztree_init_data();

	/* 1. create test */
	knot_zone_tree_t* t = knot_zone_tree_create();
	ok(t != NULL, "ztree: created");

	/* 2. insert test */
	unsigned passed = 1;
	for (unsigned i = 0; i < NCOUNT; ++i) {
		if (knot_zone_tree_insert(t, NODE + i) != KNOT_EOK) {
			passed = 0;
			break;
		}
	}
	ok(passed, "ztree: insertion");

	/* 3. check data test */
	passed = 1;
	const knot_node_t *node = NULL;
	for (unsigned i = 0; i < NCOUNT; ++i) {
		int r = knot_zone_tree_find(t, NAME[i], &node);
		if (r != KNOT_EOK || node != NODE + i) {
			passed = 0;
			break;
		}
	}
	ok(passed, "ztree: lookup");

	/* heal index for ordered lookup */
	hattrie_build_index(t);

	/* 4. ordered lookup */
	passed = 1;
	node = NULL;
	const knot_node_t *prev = NULL;
	knot_dname_t *tmp_dn = knot_dname_new_from_str("z.ac.", 5, NULL);
	knot_zone_tree_find_less_or_equal(t, tmp_dn, &node, &prev);
	knot_dname_free(&tmp_dn);
	ok(prev == NODE + 1, "ztree: ordered lookup");

	/* 5. ordered traversal */
	struct ztree_iter it = { KNOT_EOK, 0 };
	knot_zone_tree_apply_inorder(t, ztree_iter_data, &it);
	ok (it.ret == KNOT_EOK, "ztree: ordered traversal");

	knot_zone_tree_free(&t);
	ztree_free_data();
	return 0;
}
Пример #3
0
static int reverse_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, knot_rrset_t *rr)
{
	/* Synthetize PTR record data. */
	knot_dname_t *ptrname = synth_ptrname(addr_str, tpl);
	if (ptrname == NULL) {
		return KNOT_ENOMEM;
	}

	rr->type = KNOT_RRTYPE_PTR;
	knot_rrset_add_rdata(rr, ptrname, knot_dname_size(ptrname), tpl->ttl, &pkt->mm);
	knot_dname_free(&ptrname, NULL);

	return KNOT_EOK;
}
Пример #4
0
/*----------------------------------------------------------------------------*/
_public_
knot_dname_t *knot_dname_cat(knot_dname_t *d1, const knot_dname_t *d2)
{
	if (d1 == NULL || d2 == NULL)
		return NULL;

	/* This is problem equal to replacing last \x00 from d1 with d2. */
	knot_dname_t *ret = knot_dname_replace_suffix(d1, 0, d2);

	/* Like if we are reallocating d1. */
	if (ret != NULL)
		knot_dname_free(&d1, NULL);

	return ret;
}
Пример #5
0
static int adjust_nsec3_pointers(zone_node_t **tnode, void *data)
{
	assert(data != NULL);
	assert(tnode != NULL);
	zone_adjust_arg_t *args = (zone_adjust_arg_t *)data;
	zone_node_t *node = *tnode;
	// Connect to NSEC3 node (only if NSEC3 tree is not empty)
	zone_node_t *nsec3 = NULL;
	knot_dname_t *nsec3_name = NULL;
	int ret = zone_contents_nsec3_name(args->zone, node->owner, &nsec3_name);
	if (ret == KNOT_EOK) {
		assert(nsec3_name);
		zone_tree_get(args->zone->nsec3_nodes, nsec3_name, &nsec3);
		node->nsec3_node = nsec3;
	} else if (ret == KNOT_ENSEC3PAR) {
		node->nsec3_node = NULL;
		ret = KNOT_EOK;
	}

	knot_dname_free(&nsec3_name, NULL);
	return ret;
}
Пример #6
0
Файл: pkt.c Проект: idtek/knot
int main(int argc, char *argv[])
{
	plan(25);

	/* Create memory pool context. */
	int ret = 0;
	knot_mm_t mm;
	mm_ctx_mempool(&mm, MM_DEFAULT_BLKSIZE);

	/* Create names and data. */
	knot_dname_t* dnames[NAMECOUNT] = {0};
	knot_rrset_t* rrsets[NAMECOUNT] = {0};
	for (unsigned i = 0; i < NAMECOUNT; ++i) {
		dnames[i] = knot_dname_from_str_alloc(g_names[i]);
	}

	uint8_t *edns_str = (uint8_t *)"ab";
	/* Create OPT RR. */
	knot_rrset_t opt_rr;
	ret = knot_edns_init(&opt_rr, 1024, 0, 0, &mm);
	if (ret != KNOT_EOK) {
		skip_block(25, "Failed to initialize OPT RR.");
		return 0;
	}
	/* Add NSID */
	ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_NSID,
	                           strlen((char *)edns_str), edns_str, &mm);
	if (ret != KNOT_EOK) {
		knot_rrset_clear(&opt_rr, &mm);
		skip_block(25, "Failed to add NSID to OPT RR.");
		return 0;
	}

	/*
	 * Packet writer tests.
	 */

	/* Create packet. */
	knot_pkt_t *out = knot_pkt_new(NULL, MM_DEFAULT_BLKSIZE, &mm);
	ok(out != NULL, "pkt: new");

	/* Mark as response (not part of the test). */
	knot_wire_set_qr(out->wire);

	/* Secure packet. */
	const char *tsig_secret = "abcd";
	knot_tsig_key_t tsig_key;
	tsig_key.algorithm = DNSSEC_TSIG_HMAC_MD5;
	tsig_key.name = dnames[0];
	tsig_key.secret.data = (uint8_t *)strdup(tsig_secret);
	tsig_key.secret.size = strlen(tsig_secret);
	ret = knot_pkt_reserve(out, knot_tsig_wire_maxsize(&tsig_key));
	ok(ret == KNOT_EOK, "pkt: set TSIG key");

	/* Write question. */
	ret = knot_pkt_put_question(out, dnames[0], KNOT_CLASS_IN, KNOT_RRTYPE_A);
	ok(ret == KNOT_EOK, "pkt: put question");

	/* Add OPT to packet (empty NSID). */
	ret = knot_pkt_reserve(out, knot_edns_wire_size(&opt_rr));
	ok(ret == KNOT_EOK, "pkt: reserve OPT RR");

	/* Begin ANSWER section. */
	ret = knot_pkt_begin(out, KNOT_ANSWER);
	ok(ret == KNOT_EOK, "pkt: begin ANSWER");

	/* Write ANSWER section. */
	rrsets[0] = knot_rrset_new(dnames[0], KNOT_RRTYPE_A, KNOT_CLASS_IN, NULL);
	knot_dname_free(&dnames[0], NULL);
	knot_rrset_add_rdata(rrsets[0], RDVAL(0), RDLEN(0), TTL, NULL);
	ret = knot_pkt_put(out, KNOT_COMPR_HINT_QNAME, rrsets[0], 0);
	ok(ret == KNOT_EOK, "pkt: write ANSWER");

	/* Begin AUTHORITY. */
	ret = knot_pkt_begin(out, KNOT_AUTHORITY);
	ok(ret == KNOT_EOK, "pkt: begin AUTHORITY");

	/* Write rest to AUTHORITY. */
	ret = KNOT_EOK;
	for (unsigned i = 1; i < NAMECOUNT; ++i) {
		rrsets[i] = knot_rrset_new(dnames[i], KNOT_RRTYPE_NS, KNOT_CLASS_IN, NULL);
		knot_dname_free(&dnames[i], NULL);
		knot_rrset_add_rdata(rrsets[i], RDVAL(i), RDLEN(i), TTL, NULL);
		ret |= knot_pkt_put(out, KNOT_COMPR_HINT_NONE, rrsets[i], 0);
	}
	ok(ret == KNOT_EOK, "pkt: write AUTHORITY(%u)", NAMECOUNT - 1);

	/* Begin ADDITIONALS */
	ret = knot_pkt_begin(out, KNOT_ADDITIONAL);
	ok(ret == KNOT_EOK, "pkt: begin ADDITIONALS");

	/* Encode OPT RR. */
	ret = knot_pkt_put(out, KNOT_COMPR_HINT_NONE, &opt_rr, 0);
	ok(ret == KNOT_EOK, "pkt: write OPT RR");

	/*
	 * Packet reader tests.
	 */

	/* Create new packet from query packet. */
	knot_pkt_t *in = knot_pkt_new(out->wire, out->size, &out->mm);
	ok(in != NULL, "pkt: create packet for parsing");

	/* Read packet header. */
	ret = knot_pkt_parse_question(in);
	ok(ret == KNOT_EOK, "pkt: read header");

	/* Read packet payload. */
	ret = knot_pkt_parse_payload(in, 0);
	ok(ret == KNOT_EOK, "pkt: read payload");

	/* Compare parsed packet to written packet. */
	packet_match(in, out);

	/*
	 * Copied packet tests.
	 */
	knot_pkt_t *copy = knot_pkt_new(NULL, in->max_size, &in->mm);
	ret = knot_pkt_copy(copy, in);
	ok(ret == KNOT_EOK, "pkt: create packet copy");

	/* Compare copied packet to original. */
	packet_match(in, copy);

	/* Free packets. */
	knot_pkt_free(&copy);
	knot_pkt_free(&out);
	knot_pkt_free(&in);
	ok(in == NULL && out == NULL && copy == NULL, "pkt: free");

	/* Free extra data. */
	for (unsigned i = 0; i < NAMECOUNT; ++i) {
		knot_rrset_free(&rrsets[i], NULL);
	}
	free(tsig_key.secret.data);
	mp_delete((struct mempool *)mm.ctx);

	return 0;
}
Пример #7
0
Файл: rrl.c Проект: dnstap/knot
int main(int argc, char *argv[])
{
	plan(10);

	/* Prepare query. */
	knot_pkt_t *query = knot_pkt_new(NULL, 512, NULL);
	if (query == NULL) {
		return KNOT_ERROR; /* Fatal */
	}

	knot_dname_t *qname = knot_dname_from_str("beef.");
	int ret = knot_pkt_put_question(query, qname, KNOT_CLASS_IN, KNOT_RRTYPE_A);
	knot_dname_free(&qname, NULL);
	if (ret != KNOT_EOK) {
		knot_pkt_free(&query);
		return KNOT_ERROR; /* Fatal */
	}

	/* Prepare response */
	uint8_t rbuf[65535];
	size_t rlen = sizeof(rbuf);
	memcpy(rbuf, query->wire, query->size);
	knot_wire_flags_set_qr(rbuf);

	rrl_req_t rq;
	rq.w = rbuf;
	rq.len = rlen;
	rq.query = query;
	rq.flags = 0;

	/* 1. create rrl table */
	rrl_table_t *rrl = rrl_create(RRL_SIZE);
	ok(rrl != NULL, "rrl: create");

	/* 2. set rate limit */
	uint32_t rate = 10;
	rrl_setrate(rrl, rate);
	is_int(rate, rrl_rate(rrl), "rrl: setrate");

	/* 3. setlocks */
	ret = rrl_setlocks(rrl, RRL_LOCKS);
	is_int(KNOT_EOK, ret, "rrl: setlocks");

	/* 4. N unlimited requests. */
	conf_zone_t *zone_conf = malloc(sizeof(conf_zone_t));
	conf_init_zone(zone_conf);
	zone_conf->name = strdup("rrl.");
	zone_t *zone = zone_new(zone_conf);

	struct sockaddr_storage addr;
	struct sockaddr_storage addr6;
	sockaddr_set(&addr, AF_INET, "1.2.3.4", 0);
	sockaddr_set(&addr6, AF_INET6, "1122:3344:5566:7788::aabb", 0);
	ret = 0;
	for (unsigned i = 0; i < rate; ++i) {
		if (rrl_query(rrl, &addr, &rq, zone) != KNOT_EOK ||
		    rrl_query(rrl, &addr6, &rq, zone) != KNOT_EOK) {
			ret = KNOT_ELIMIT;
			break;
		}
	}
	is_int(0, ret, "rrl: unlimited IPv4/v6 requests");

#ifdef ENABLE_TIMED_TESTS
	/* 5. limited request */
	ret = rrl_query(rrl, &addr, &rq, zone);
	is_int(0, ret, "rrl: throttled IPv4 request");

	/* 6. limited IPv6 request */
	ret = rrl_query(rrl, &addr6, &rq, zone);
	is_int(0, ret, "rrl: throttled IPv6 request");
#else
	skip_block(2, "Timed tests not enabled");
#endif

	/* 7. invalid values. */
	ret = 0;
	rrl_create(0);            // NULL
	ret += rrl_setrate(0, 0); // 0
	ret += rrl_rate(0);       // 0
	ret += rrl_setlocks(0,0); // -1
	ret += rrl_query(0, 0, 0, 0); // -1
	ret += rrl_query(rrl, 0, 0, 0); // -1
	ret += rrl_query(rrl, (void*)0x1, 0, 0); // -1
	ret += rrl_destroy(0); // -1
	is_int(-488, ret, "rrl: not crashed while executing functions on NULL context");

#ifdef ENABLE_TIMED_TESTS
	/* 8. hopscotch test */
	struct runnable_data rd = {
		1, rrl, &addr, &rq, zone
	};
	rrl_hopscotch(&rd);
	ok(rd.passed, "rrl: hashtable is ~ consistent");

	/* 9. reseed */
	is_int(0, rrl_reseed(rrl), "rrl: reseed");

	/* 10. hopscotch after reseed. */
	rrl_hopscotch(&rd);
	ok(rd.passed, "rrl: hashtable is ~ consistent");
#else
	skip_block(3, "Timed tests not enabled");
#endif

	zone_free(&zone);
	knot_pkt_free(&query);
	rrl_destroy(rrl);
	return 0;
}
Пример #8
0
int synth_record_load(struct query_plan *plan, struct query_module *self)
{
	/* Parse first token. */
	char *saveptr = NULL;
	char *token = strtok_r(self->param, " ", &saveptr);
	if (token == NULL) {
		return KNOT_EFEWDATA;
	}

	/* Create synthesis template. */
	struct synth_template *tpl = mm_alloc(self->mm, sizeof(struct synth_template));
	if (tpl == NULL) {
		return KNOT_ENOMEM;
	}

	/* Save in query module, it takes ownership from now on. */
	self->ctx = tpl;

	/* Supported types: reverse, forward */
	if (strcmp(token, "reverse") == 0) {
		tpl->type = SYNTH_REVERSE;
	} else if (strcmp(token, "forward") == 0) {
		tpl->type = SYNTH_FORWARD;
	} else {
		MODULE_ERR("invalid type '%s'.\n", token);
		return KNOT_ENOTSUP;
	}

	/* Parse format string. */
	tpl->prefix = strtok_r(NULL, " ", &saveptr);
	if (strchr(tpl->prefix, '.') != NULL) {
		MODULE_ERR("dots '.' are not allowed in the prefix.\n");
		return KNOT_EMALF;
	}

	/* Parse zone if generating reverse record. */
	if (tpl->type == SYNTH_REVERSE) {
		tpl->zone = strtok_r(NULL, " ", &saveptr);
		knot_dname_t *check_name = knot_dname_from_str(tpl->zone);
		if (check_name == NULL) {
			MODULE_ERR("invalid zone '%s'.\n", tpl->zone);
			return KNOT_EMALF;
		}
		knot_dname_free(&check_name, NULL);
	}

	/* Parse TTL. */
	tpl->ttl = strtol(strtok_r(NULL, " ", &saveptr), NULL, 10);

	/* Parse address. */
	token = strtok_r(NULL, " ", &saveptr);
	char *subnet = strchr(token, '/');
	if (subnet) {
		subnet[0] = '\0';
		tpl->subnet.prefix = strtol(subnet + 1, NULL, 10);
	}

	/* Estimate family. */
	int family = AF_INET;
	int prefix_max = IPV4_PREFIXLEN;
	if (strchr(token, ':') != NULL) {
		family = AF_INET6;
		prefix_max = IPV6_PREFIXLEN;
	}

	/* Check subnet. */
	if (tpl->subnet.prefix > prefix_max) {
		MODULE_ERR("invalid address prefix '%s'.\n", subnet);
		return KNOT_EMALF;
	}

	int ret = sockaddr_set(&tpl->subnet.ss, family, token, 0);
	if (ret != KNOT_EOK) {
		MODULE_ERR("invalid address '%s'.\n", token);
		return KNOT_EMALF;
	}

	return query_plan_step(plan, QPLAN_ANSWER, solve_synth_record, tpl);
}
Пример #9
0
static void ztree_free_data()
{
	for (unsigned i = 0; i < NCOUNT; ++i)
		knot_dname_free(NAME + i);
}
Пример #10
0
/*! Run all scheduled tests for given parameters.
 */
int main(int argc, char *argv[])
{
	plan(19);

	// Test 1: Allocate new config
	const char *config_fn = "rc:/sample_conf";
	conf_t *conf = conf_new(strdup(config_fn));
	ok(conf != 0, "config_new()");

	// Test 2: Parse config
	int ret = conf_parse_str(conf, sample_conf_rc);
	is_int(0, ret, "parsing configuration file %s", config_fn);
	if (ret != 0) {
		skip_block(19, "Parse err");
		goto skip_all;
	}

	// Test 3: Test server version (0-level depth)
	is_string("Infinitesimal", conf->version, "server version loaded ok");

	// Test 4: Test interfaces (1-level depth)
	ok(!EMPTY_LIST(conf->ifaces), "configured interfaces exist");

	// Test 5,6: Interfaces content (2-level depth)
	struct node *n = HEAD(conf->ifaces);
	conf_iface_t *iface = (conf_iface_t*)n;
	struct sockaddr_storage addr_ref;
	sockaddr_set(&addr_ref, AF_INET, "10.10.1.1", 53531);
	is_int(0, sockaddr_cmp(&iface->addr, &addr_ref), "interface0 address check");

	n = n->next;
	iface = (conf_iface_t*)n;
	sockaddr_set(&addr_ref, AF_INET6, "::0", 53);
	is_int(0, sockaddr_cmp(&iface->addr, &addr_ref), "interface1 address check");

	// Test 9,10: Check server key
	if (EMPTY_LIST(conf->keys)) {
		ok(0, "TSIG key algorithm check - NO KEY FOUND");
		ok(0, "TSIG key secret check - NO KEY FOUND");
	} else {
		knot_tsig_key_t *k = &((conf_key_t *)HEAD(conf->keys))->k;
		uint8_t decoded_secret[] = { 0x5a };

		ok(k->algorithm == KNOT_TSIG_ALG_HMAC_MD5,
		       "TSIG key algorithm check");
		ok(k->secret.size == sizeof(decoded_secret)
		   && memcmp(k->secret.data, decoded_secret,
			     sizeof(decoded_secret)) == 0,
		   "TSIG key secret check");
	}

	// Test 11,12,13,14,15,16,17,18: Check logging facilities
	ok(list_size(&conf->logs) == 4, "log facilites count check");
	n = HEAD(conf->logs);
	ok(!EMPTY_LIST(conf->logs), "log facilities not empty");

	conf_log_t *log = (conf_log_t*)n;
	node_t *nm = HEAD(log->map);
	conf_log_map_t *m = (conf_log_map_t*)nm;
	ok(log->type == LOGT_SYSLOG, "log0 is syslog");

	if (EMPTY_LIST(log->map)) {
		skip_block(5, "Empty list");
	} else {
		ok(m->source == LOG_ANY, "syslog first rule is ANY");
		int mask = LOG_UPTO(LOG_NOTICE);
		ok(m->prios == mask, "syslog mask is equal");
		nm = nm->next;
		m = (conf_log_map_t*)nm;
		ok(m != 0, "syslog has more than 1 rule");
		if (m == 0) {
			skip_block(2, "No mapping");
		} else {
			ok(m->source == LOG_ZONE, "syslog next rule is for zone");
			ok(m->prios == LOG_UPTO(LOG_INFO), "rule for zone is: info level");
		}
	}

	// Test 19,20: File facility checks
	n = n->next;
	log = (conf_log_t*)n;
	ok(n != 0, "log has next facility");
	if (n == 0) {
		skip("No mapping");
	} else {
		is_string("/var/log/knot/server.err", log->file, "log file matches");
	}

	// Test 21: Load key dname
	const char *sample_str = "key0.example.net";
	knot_dname_t *sample = knot_dname_from_str_alloc(sample_str);
	if (list_size(&conf->keys) > 0) {
		knot_tsig_key_t *k = &((conf_key_t *)HEAD(conf->keys))->k;
		ok(knot_dname_cmp(sample, k->name) == 0,
		   "TSIG key dname check");
	} else {
		ok(0, "TSIG key dname check - NO KEY FOUND");
	}
	knot_dname_free(&sample, NULL);

skip_all:

	// Deallocating config
	conf_free(conf);

	return 0;
}
Пример #11
0
/*!
 * \brief Entry point of 'knsec3hash'.
 */
int main(int argc, char *argv[])
{
	bool enable_idn = true;

	struct option options[] = {
		{ "version", no_argument, 0, 'V' },
		{ "help",    no_argument, 0, 'h' },
		{ NULL }
	};

#ifdef LIBIDN
	// Set up localization.
	if (setlocale(LC_CTYPE, "") == NULL) {
		enable_idn = false;
	}
#endif

	int opt = 0;
	int li = 0;
	while ((opt = getopt_long(argc, argv, "hV", options, &li)) != -1) {
		switch(opt) {
		case 'V':
			printf("%s, version %s\n", PROGRAM_NAME, PACKAGE_VERSION);
			return 0;
		case 'h':
			usage(stdout);
			return 0;
		default:
			usage(stderr);
			return 1;
		}
	}

	// knsec3hash <salt> <algorithm> <iterations> <domain>
	if (argc != 5) {
		usage(stderr);
		return 1;
	}

	atexit(knot_crypto_cleanup);

	int exit_code = 1;
	knot_nsec3_params_t nsec3_params = { 0 };
	knot_dname_t *dname = NULL;
	uint8_t *digest = NULL;
	size_t digest_size = 0;
	uint8_t *b32_digest = NULL;
	int32_t b32_length = 0;
	int result = 0;

	if (!parse_nsec3_params(&nsec3_params, argv[1], argv[2], argv[3])) {
		goto fail;
	}

	if (enable_idn) {
		char *ascii_name = name_from_idn(argv[4]);
		if (ascii_name == NULL) {
			fprintf(stderr, "Cannot transform IDN domain name.\n");
			goto fail;
		}
		dname = knot_dname_from_str(ascii_name);
		free(ascii_name);
	} else {
		dname = knot_dname_from_str(argv[4]);
	}
	if (dname == NULL) {
		fprintf(stderr, "Cannot parse domain name.\n");
		goto fail;
	}

	result = knot_nsec3_hash(&nsec3_params, dname, knot_dname_size(dname),
	                         &digest, &digest_size);
	if (result != KNOT_EOK) {
		fprintf(stderr, "Cannot compute hash: %s\n",
		        knot_strerror(result));
		goto fail;
	}

	b32_length = base32hex_encode_alloc(digest, digest_size, &b32_digest);
	if (b32_length < 0) {
		fprintf(stderr, "Cannot encode computed hash: %s\n",
		        knot_strerror(b32_length));
		goto fail;
	}

	exit_code = 0;

	printf("%.*s (salt=%s, hash=%d, iterations=%d)\n", b32_length,
	       b32_digest, argv[1], nsec3_params.algorithm,
	       nsec3_params.iterations);

fail:
	knot_nsec3param_free(&nsec3_params);
	knot_dname_free(&dname, NULL);
	free(digest);
	free(b32_digest);

	return exit_code;
}
Пример #12
0
int main(int argc, char *argv[])
{
	plan(23);

	// Test with NULL changeset
	ok(changeset_size(NULL) == 0, "changeset: NULL size");
	ok(changeset_empty(NULL), "changeset: NULL empty");

	// Test creation.
	knot_dname_t *d = knot_dname_from_str_alloc("test.");
	assert(d);
	changeset_t *ch = changeset_new(d);
	knot_dname_free(&d, NULL);
	ok(ch != NULL, "changeset: new");
	ok(changeset_empty(ch), "changeset: empty");
	ch->soa_to = (knot_rrset_t *)0xdeadbeef;
	ok(!changeset_empty(ch), "changseset: empty SOA");
	ch->soa_to = NULL;
	ok(changeset_size(ch) == 0, "changeset: empty size");

	// Test additions.
	d = knot_dname_from_str_alloc("non.terminals.test.");
	assert(d);
	knot_rrset_t *apex_txt_rr = knot_rrset_new(d, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, NULL);
	assert(apex_txt_rr);
	uint8_t data[8] = "\7teststr";
	knot_rrset_add_rdata(apex_txt_rr, data, sizeof(data), 3600, NULL);

	int ret = changeset_add_rrset(ch, apex_txt_rr);
	ok(ret == KNOT_EOK, "changeset: add RRSet");
	ok(changeset_size(ch) == 1, "changeset: size add");
	ret = changeset_rem_rrset(ch, apex_txt_rr);
	ok(ret == KNOT_EOK, "changeset: rem RRSet");
	ok(changeset_size(ch) == 2, "changeset: size remove");

	ok(!changeset_empty(ch), "changeset: not empty");

	// Add another RR to node.
	knot_rrset_t *apex_spf_rr = knot_rrset_new(d, KNOT_RRTYPE_SPF, KNOT_CLASS_IN, NULL);
	assert(apex_spf_rr);
	knot_rrset_add_rdata(apex_spf_rr, data, sizeof(data), 3600, NULL);
	ret = changeset_add_rrset(ch, apex_spf_rr);
	ok(ret == KNOT_EOK, "changeset: add multiple");

	// Add another node.
	knot_dname_free(&d, NULL);
	d = knot_dname_from_str_alloc("here.come.more.non.terminals.test");
	assert(d);
	knot_rrset_t *other_rr = knot_rrset_new(d, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, NULL);
	assert(other_rr);
	knot_rrset_add_rdata(other_rr, data, sizeof(data), 3600, NULL);
	ret = changeset_add_rrset(ch, other_rr);
	ok(ret == KNOT_EOK, "changeset: remove multiple");

	// Test add traversal.
	changeset_iter_t it;
	ret = changeset_iter_add(&it, ch, true);
	ok(ret == KNOT_EOK, "changeset: create iter add");
	// Order: non.terminals.test. TXT, SPF, here.come.more.non.terminals.test. TXT.
	knot_rrset_t iter = changeset_iter_next(&it);
	bool trav_ok = knot_rrset_equal(&iter, apex_txt_rr, KNOT_RRSET_COMPARE_WHOLE);
	iter = changeset_iter_next(&it);
	trav_ok = trav_ok && knot_rrset_equal(&iter, apex_spf_rr, KNOT_RRSET_COMPARE_WHOLE);
	iter = changeset_iter_next(&it);
	trav_ok = trav_ok && knot_rrset_equal(&iter, other_rr, KNOT_RRSET_COMPARE_WHOLE);

	ok(trav_ok, "changeset: add traversal");

	iter = changeset_iter_next(&it);
	changeset_iter_clear(&it);
	ok(knot_rrset_empty(&iter), "changeset: traversal: skip non-terminals");

	// Test remove traversal.
	ret = changeset_iter_rem(&it, ch, false);
	ok(ret == KNOT_EOK, "changeset: create iter rem");
	iter = changeset_iter_next(&it);
	ok(knot_rrset_equal(&iter, apex_txt_rr, KNOT_RRSET_COMPARE_WHOLE),
	   "changeset: rem traversal");
	changeset_iter_clear(&it);

	// Test all traversal - just count.
	ret = changeset_iter_all(&it, ch, false);
	ok(ret == KNOT_EOK, "changest: create iter all");
	size_t size = 0;
	iter = changeset_iter_next(&it);
	while (!knot_rrset_empty(&iter)) {
		++size;
		iter = changeset_iter_next(&it);
	}
	changeset_iter_clear(&it);
	ok(size == 4, "changeset: iter all");

	// Create new changeset.
	knot_dname_free(&d, NULL);
	d = knot_dname_from_str_alloc("test.");
	assert(d);
	changeset_t *ch2 = changeset_new(d);
	knot_dname_free(&d, NULL);
	assert(ch2);
	// Add something to add section.
	knot_dname_free(&apex_txt_rr->owner, NULL);
	apex_txt_rr->owner = knot_dname_from_str_alloc("something.test.");
	assert(apex_txt_rr->owner);
	ret = changeset_add_rrset(ch2, apex_txt_rr);
	assert(ret == KNOT_EOK);

	// Add something to remove section.
	knot_dname_free(&apex_txt_rr->owner, NULL);
	apex_txt_rr->owner =
		knot_dname_from_str_alloc("and.now.for.something.completely.different.test.");
	assert(apex_txt_rr->owner);
	ret = changeset_rem_rrset(ch2, apex_txt_rr);
	assert(ret == KNOT_EOK);

	// Test merge.
	ret = changeset_merge(ch, ch2);
	ok(ret == KNOT_EOK && changeset_size(ch) == 6, "changeset: merge");

	// Test cleanup.
	changeset_clear(ch);
	ok(changeset_empty(ch), "changeset: clear");
	free(ch);

	list_t chgs;
	init_list(&chgs);
	add_head(&chgs, &ch2->n);
	changesets_clear(&chgs);
	ok(changeset_empty(ch2), "changeset: clear list");
	free(ch2);

	knot_rrset_free(&apex_txt_rr, NULL);
	knot_rrset_free(&apex_spf_rr, NULL);
	knot_rrset_free(&other_rr, NULL);

	return 0;
}