From 3149069126599133a8fe0c66734df6deb3907dfb Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Fri, 12 Dec 2014 17:10:25 +0100 Subject: RESOLV: Add an internal function to read TTL from a DNS packet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related: https://fedorahosted.org/sssd/ticket/1884 Adds an internal resolver function that reads the TTL for SRV records as specified by RFC-2181. Several internal c-ares definitions are used until c-ares contains a function that exposes all this information via a parsing function. Reviewed-by: Pavel Březina (cherry picked from commit bf54fbed126ec3d459af40ea370ffadacd31c76d) --- src/tests/cmocka/test_resolv_fake.c | 374 ++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 src/tests/cmocka/test_resolv_fake.c (limited to 'src/tests/cmocka') diff --git a/src/tests/cmocka/test_resolv_fake.c b/src/tests/cmocka/test_resolv_fake.c new file mode 100644 index 000000000..6c201e702 --- /dev/null +++ b/src/tests/cmocka/test_resolv_fake.c @@ -0,0 +1,374 @@ +/* + Authors: + Jakub Hrozek + + Copyright (C) 2014 Red Hat + + SSSD tests: Resolver tests using a fake resolver library + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "tests/cmocka/common_mock.h" +#include "tests/cmocka/common_mock_resp.h" + +#define TEST_BUFSIZE 1024 +#define TEST_DEFAULT_TIMEOUT 5 +#define TEST_SRV_QUERY "_ldap._tcp.sssd.com" + +static TALLOC_CTX *global_mock_context = NULL; + +struct srv_rrdata { + uint16_t port; + uint16_t prio; + uint16_t weight; + uint32_t ttl; + const char *hostname; +}; + +static ssize_t dns_header(unsigned char **buf, size_t ancount) +{ + uint8_t *hb; + HEADER *h; + + hb = *buf; + memset(hb, 0, NS_HFIXEDSZ); + + h = (HEADER *) hb; + h->id = res_randomid(); /* random query ID */ + h->qr = 1; /* response flag */ + h->rd = 1; /* recursion desired */ + h->ra = 1; /* resursion available */ + + h->qdcount = htons(1); /* no. of questions */ + h->ancount = htons(ancount); /* no. of answers */ + h->arcount = htons(0); /* no. of add'tl records */ + + hb += NS_HFIXEDSZ; /* move past the header */ + *buf = hb; + + return NS_HFIXEDSZ; +} + +static ssize_t dns_question(const char *question, + uint16_t type, + uint8_t **question_ptr, + size_t remaining) +{ + unsigned char *qb = *question_ptr; + int n; + + n = ns_name_compress(question, qb, remaining, NULL, NULL); + assert_true(n > 0); + + qb += n; + remaining -= n; + + NS_PUT16(type, qb); + NS_PUT16(ns_c_in, qb); + + *question_ptr = qb; + return n + 2 * sizeof(uint16_t); +} + +static ssize_t add_rr_common(uint16_t type, + uint32_t ttl, + size_t rdata_size, + const char *key, + size_t remaining, + uint8_t **rdata_ptr) +{ + uint8_t *rd = *rdata_ptr; + ssize_t written = 0; + + written = ns_name_compress(key, rd, remaining, NULL, NULL); + assert_int_not_equal(written, -1); + rd += written; + remaining -= written; + + assert_true(remaining > 3 * sizeof(uint16_t) + sizeof(uint32_t)); + NS_PUT16(type, rd); + NS_PUT16(ns_c_in, rd); + NS_PUT32(ttl, rd); + NS_PUT16(rdata_size, rd); + + assert_true(remaining > rdata_size); + *rdata_ptr = rd; + return written + 3 * sizeof(uint16_t) + sizeof(uint32_t) + rdata_size; +} + +static ssize_t add_srv_rr(struct srv_rrdata *rr, + const char *question, + uint8_t *answer, + size_t anslen) +{ + uint8_t *a = answer; + ssize_t resp_size; + size_t rdata_size; + unsigned char hostname_compressed[MAXDNAME]; + ssize_t compressed_len; + + rdata_size = 3 * sizeof(uint16_t); + + /* Prepare the data to write */ + compressed_len = ns_name_compress(rr->hostname, + hostname_compressed, MAXDNAME, + NULL, NULL); + assert_int_not_equal(compressed_len, -1); + rdata_size += compressed_len; + + resp_size = add_rr_common(ns_t_srv, rr->ttl, rdata_size, + question, anslen, &a); + + NS_PUT16(rr->prio, a); + NS_PUT16(rr->weight, a); + NS_PUT16(rr->port, a); + memcpy(a, hostname_compressed, compressed_len); + + return resp_size; +} + +unsigned char *create_srv_buffer(TALLOC_CTX *mem_ctx, + const char *question, + struct srv_rrdata *rrs, + size_t n_rrs, + size_t *_buflen) +{ + unsigned char *buf; + unsigned char *buf_head; + ssize_t len; + ssize_t i; + ssize_t total = 0; + + buf = talloc_zero_array(mem_ctx, unsigned char, TEST_BUFSIZE); + assert_non_null(buf); + buf_head = buf; + + len = dns_header(&buf, n_rrs); + assert_true(len > 0); + total += len; + + len = dns_question(question, ns_t_srv, &buf, TEST_BUFSIZE - total); + assert_true(len > 0); + total += len; + + /* answer */ + for (i = 0; i < n_rrs; i++) { + len = add_srv_rr(&rrs[i], question, buf, TEST_BUFSIZE - total); + assert_true(len > 0); + total += len; + buf += len; + } + + *_buflen = total; + return buf_head; +} + +struct fake_ares_query { + int status; + int timeouts; + unsigned char *abuf; + int alen; +}; + +void mock_ares_query(int status, int timeouts, unsigned char *abuf, int alen) +{ + will_return(__wrap_ares_query, status); + will_return(__wrap_ares_query, timeouts); + will_return(__wrap_ares_query, abuf); + will_return(__wrap_ares_query, alen); +} + +void __wrap_ares_query(ares_channel channel, const char *name, int dnsclass, + int type, ares_callback callback, void *arg) +{ + struct fake_ares_query query; + + query.status = sss_mock_type(int); + query.timeouts = sss_mock_type(int); + query.abuf = sss_mock_ptr_type(unsigned char *); + query.alen = sss_mock_type(int); + + callback(arg, query.status, query.timeouts, query.abuf, query.alen); +} + +/* The unit test */ +struct resolv_fake_ctx { + struct resolv_ctx *resolv; + struct sss_test_ctx *ctx; +}; + +void test_resolv_fake_setup(void **state) +{ + struct resolv_fake_ctx *test_ctx; + int ret; + + assert_true(leak_check_setup()); + global_mock_context = talloc_new(global_talloc_context); + assert_non_null(global_mock_context); + + test_ctx = talloc_zero(global_mock_context, + struct resolv_fake_ctx); + assert_non_null(test_ctx); + + test_ctx->ctx = create_ev_test_ctx(test_ctx); + assert_non_null(test_ctx->ctx); + + ret = resolv_init(test_ctx, test_ctx->ctx->ev, + TEST_DEFAULT_TIMEOUT, &test_ctx->resolv); + assert_int_equal(ret, EOK); + + *state = test_ctx; +} + +void test_resolv_fake_teardown(void **state) +{ + struct resolv_fake_ctx *test_ctx = + talloc_get_type(*state, struct resolv_fake_ctx); + + talloc_free(test_ctx); + talloc_free(global_mock_context); + assert_true(leak_check_teardown()); +} + +void test_resolv_fake_srv_done(struct tevent_req *req) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + int status; + uint32_t ttl; + struct ares_srv_reply *srv_replies = NULL; + struct resolv_fake_ctx *test_ctx = + tevent_req_callback_data(req, struct resolv_fake_ctx); + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + ret = resolv_getsrv_recv(tmp_ctx, req, &status, NULL, + &srv_replies, &ttl); + assert_int_equal(ret, EOK); + + assert_non_null(srv_replies); + assert_int_equal(srv_replies->priority, 1); + assert_int_equal(srv_replies->weight, 40); + assert_int_equal(srv_replies->port, 389); + assert_string_equal(srv_replies->host, "ldap.sssd.com"); + + srv_replies = srv_replies->next; + assert_non_null(srv_replies); + assert_int_equal(srv_replies->priority, 1); + assert_int_equal(srv_replies->weight, 60); + assert_int_equal(srv_replies->port, 389); + assert_string_equal(srv_replies->host, "ldap2.sssd.com"); + + srv_replies = srv_replies->next; + assert_null(srv_replies); + + assert_int_equal(ttl, 500); + + talloc_free(tmp_ctx); + test_ctx->ctx->error = EOK; + test_ctx->ctx->done = true; +} + +void test_resolv_fake_srv(void **state) +{ + int ret; + struct tevent_req *req; + struct resolv_fake_ctx *test_ctx = + talloc_get_type(*state, struct resolv_fake_ctx); + + unsigned char *buf; + size_t buflen; + + struct srv_rrdata rr[2]; + + rr[0].prio = 1; + rr[0].port = 389; + rr[0].weight = 40; + rr[0].ttl = 600; + rr[0].hostname = "ldap.sssd.com"; + + rr[1].prio = 1; + rr[1].port = 389; + rr[1].weight = 60; + rr[1].ttl = 500; + rr[1].hostname = "ldap2.sssd.com"; + + buf = create_srv_buffer(test_ctx, TEST_SRV_QUERY, rr, 2, &buflen); + assert_non_null(buf); + mock_ares_query(0, 0, buf, buflen); + + req = resolv_getsrv_send(test_ctx, test_ctx->ctx->ev, + test_ctx->resolv, TEST_SRV_QUERY); + assert_non_null(req); + tevent_req_set_callback(req, test_resolv_fake_srv_done, test_ctx); + + ret = test_ev_loop(test_ctx->ctx); + assert_int_equal(ret, ERR_OK); +} + +int main(int argc, const char *argv[]) +{ + int rv; + poptContext pc; + int opt; + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + POPT_TABLEEND + }; + + const UnitTest tests[] = { + unit_test_setup_teardown(test_resolv_fake_srv, + test_resolv_fake_setup, + test_resolv_fake_teardown), + }; + + /* Set debug level to invalid value so we can deside if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + DEBUG_CLI_INIT(debug_level); + + /* Even though normally the tests should clean up after themselves + * they might not after a failed run. Remove the old db to be sure */ + tests_set_cwd(); + + rv = run_tests(tests); + return rv; +} -- cgit