diff --git a/CMakeLists.txt b/CMakeLists.txt index a243eebc..bf160fde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set(SRCS src/mbuf.c src/md5.c src/mem.c + src/mock/dnssrv.c src/mock/fuzz.c src/mock/nat.c src/mock/pf.c diff --git a/src/dns.c b/src/dns.c index f3ff0d79..b5a897cc 100644 --- a/src/dns.c +++ b/src/dns.c @@ -293,3 +293,154 @@ int test_dns_dname(void) return err; } + + +struct test_dns { + int err; + uint32_t addr; + struct dnsc *dnsc; +}; + + +static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl, + struct list *authl, struct list *addl, void *arg) +{ + struct dnsrr *rr = list_ledata(list_head(ansl)); + struct test_dns *data = arg; + struct sa sa; + (void)hdr; + (void)authl; + (void)addl; + (void)arg; + + if (!data || !rr) { + re_cancel(); + return; + } + + TEST_ERR(err); + + TEST_EQUALS(DNS_TYPE_A, rr->type); + TEST_EQUALS(data->addr, rr->rdata.a.addr); + + sa_set_in(&sa, rr->rdata.a.addr, 0); + + DEBUG_INFO("%s. IN A %j\n", rr->name, &sa); + +out: + data->err = err; + re_cancel(); +} + + +static int check_dns(struct test_dns *data, const char *name, uint32_t addr, + bool main) +{ + struct dns_query *q = NULL; + int err; + + data->addr = addr; + data->err = ENODATA; + + err = dnsc_query(&q, data->dnsc, name, DNS_TYPE_A, DNS_CLASS_IN, true, + query_handler, data); + TEST_ERR(err); + + if (main) { + err = re_main_timeout(100); + TEST_ERR(err); + } + + /* check query handler result */ + err = data->err; + +out: + mem_deref(q); + return err; +} + + +int test_dns_integration(void) +{ + struct dns_server *srv = NULL; + struct test_dns data; + struct dns_query *q; + int err; + + /* Setup Mocking DNS Server */ + err = dns_server_alloc(&srv, false); + TEST_ERR(err); + + err = dns_server_add_a(srv, "test1.example.net", 0x7f000001, 1); + TEST_ERR(err); + + err = dnsc_alloc(&data.dnsc, NULL, &srv->addr, 1); + TEST_ERR(err); + + err = check_dns(&data, "test1.example.net", 0x7f000001, true); + TEST_ERR(err); + + /* Test does not exist */ + err = check_dns(&data, "test2.example.net", 0x7f000001, true); + TEST_EQUALS(ENODATA, err); + + dns_server_flush(srv); + + err = dns_server_add_a(srv, "test1.example.net", 0x7f000002, 1); + TEST_ERR(err); + + err = dns_server_add_a(srv, "test2.example.net", 0x7f000003, 1); + TEST_ERR(err); + + err = dns_server_add_a(srv, "test3.example.net", 0x7f000004, 1); + TEST_ERR(err); + + /* --- Test DNS Cache --- */ + err = check_dns(&data, "test1.example.net", 0x7f000001, true); + TEST_ERR(err); + + err = check_dns(&data, "test2.example.net", 0x7f000003, true); + TEST_ERR(err); + + err = check_dns(&data, "test2.example.net", 0x7f000003, true); + TEST_ERR(err); + + /* Check another resource record afterwards */ + err = check_dns(&data, "test3.example.net", 0x7f000004, true); + TEST_ERR(err); + + sys_msleep(100); /* wait until TTL timer expires */ + re_main_timeout(1); /* execute tmr callbacks */ + + /* --- Check expired TTL --- */ + err = check_dns(&data, "test1.example.net", 0x7f000002, true); + TEST_ERR(err); + + /* --- Test explicit DNS cache flush --- */ + dns_server_flush(srv); + err = dns_server_add_a(srv, "test1.example.net", 0x7f000005, 1); + TEST_ERR(err); + dnsc_cache_flush(data.dnsc); + err = check_dns(&data, "test1.example.net", 0x7f000005, true); + TEST_ERR(err); + + /* --- Test early query cancellation --- */ + err = dnsc_query(&q, data.dnsc, "test1.example.net", DNS_TYPE_A, + DNS_CLASS_IN, true, query_handler, &data); + TEST_ERR(err); + mem_deref(q); + + err = check_dns(&data, "test1.example.net", 0x7f000005, true); + TEST_ERR(err); + + /* --- Leave query open for cleanup test --- */ + err = dnsc_query(&q, data.dnsc, "test1.example.net", DNS_TYPE_A, + DNS_CLASS_IN, true, query_handler, &data); + TEST_ERR(err); + +out: + mem_deref(data.dnsc); + mem_deref(srv); + + return err; +} diff --git a/src/mock/dnssrv.c b/src/mock/dnssrv.c new file mode 100644 index 00000000..ddf13347 --- /dev/null +++ b/src/mock/dnssrv.c @@ -0,0 +1,284 @@ +/** + * @file mock/dnssrv.c Mock DNS server + * + * Copyright (C) 2010 - 2016 Alfred E. Heggestad + */ +#include +#include +#include "../test.h" + + +#define DEBUG_MODULE "mock/dnssrv" +#define DEBUG_LEVEL 5 +#include + + +#define LOCAL_PORT 0 + + +static void dns_server_match(struct dns_server *srv, struct list *rrl, + const char *name, uint16_t type) +{ + struct dnsrr *rr0 = NULL; + struct le *le; + + le = srv->rrl.head; + while (le) { + + struct dnsrr *rr = le->data; + le = le->next; + + if (type == rr->type && 0 == str_casecmp(name, rr->name)) { + + if (!rr0) + rr0 = rr; + list_append(rrl, &rr->le_priv, rr); + } + } + + /* If rotation is enabled, then rotate multiple entries + in a deterministic way (no randomness please) */ + if (srv->rotate && rr0) { + list_unlink(&rr0->le); + list_append(&srv->rrl, &rr0->le, rr0); + } +} + + +static void decode_dns_query(struct dns_server *srv, const struct sa *src, + struct mbuf *mb) +{ + struct list rrl = LIST_INIT; + struct dnshdr hdr; + struct le *le; + char *qname = NULL; + size_t start, end; + uint16_t type, dnsclass; + int err = 0; + + start = mb->pos; + end = mb->end; + + if (dns_hdr_decode(mb, &hdr) || hdr.qr || hdr.nq != 1) { + DEBUG_WARNING("unable to decode query header\n"); + return; + } + + err = dns_dname_decode(mb, &qname, start); + if (err) { + DEBUG_WARNING("unable to decode query name\n"); + goto out; + } + + if (mbuf_get_left(mb) < 4) { + DEBUG_WARNING("unable to decode query type/class\n"); + goto out; + } + + type = ntohs(mbuf_read_u16(mb)); + dnsclass = ntohs(mbuf_read_u16(mb)); + + DEBUG_INFO("dnssrv: type=%s query-name='%s'\n", dns_rr_typename(type), + qname); + + if (dnsclass == DNS_CLASS_IN) { + dns_server_match(srv, &rrl, qname, type); + } + + hdr.qr = true; + hdr.tc = false; + hdr.rcode = DNS_RCODE_OK; + hdr.nq = 1; + hdr.nans = list_count(&rrl); + + mb->pos = start; + + err = dns_hdr_encode(mb, &hdr); + if (err) + goto out; + + mb->pos = end; + + DEBUG_INFO("dnssrv: @@ found %u answers for %s\n", list_count(&rrl), + qname); + + for (le = rrl.head; le; le = le->next) { + struct dnsrr *rr = le->data; + + err = dns_rr_encode(mb, rr, 0, NULL, start); + if (err) + goto out; + } + + mb->pos = start; + + (void)udp_send(srv->us, src, mb); + +out: + list_clear(&rrl); + mem_deref(qname); +} + + +static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) +{ + struct dns_server *srv = arg; + + decode_dns_query(srv, src, mb); +} + + +static void destructor(void *arg) +{ + struct dns_server *srv = arg; + + list_flush(&srv->rrl); + mem_deref(srv->us); +} + + +void dns_server_flush(struct dns_server *srv) +{ + list_flush(&srv->rrl); +} + + +int dns_server_alloc(struct dns_server **srvp, bool rotate) +{ + struct dns_server *srv; + int err; + + if (!srvp) + return EINVAL; + + srv = mem_zalloc(sizeof(*srv), destructor); + if (!srv) + return ENOMEM; + + err = sa_set_str(&srv->addr, "127.0.0.1", LOCAL_PORT); + if (err) + goto out; + + err = udp_listen(&srv->us, &srv->addr, udp_recv, srv); + if (err) + goto out; + + err = udp_local_get(srv->us, &srv->addr); + if (err) + goto out; + + srv->rotate = rotate; + +out: + if (err) + mem_deref(srv); + else + *srvp = srv; + + return err; +} + + +int dns_server_add_a(struct dns_server *srv, const char *name, uint32_t addr, + int64_t ttl) +{ + struct dnsrr *rr; + int err; + + if (!srv || !name) + return EINVAL; + + rr = dns_rr_alloc(); + if (!rr) + return ENOMEM; + + err = str_dup(&rr->name, name); + if (err) + goto out; + + rr->type = DNS_TYPE_A; + rr->dnsclass = DNS_CLASS_IN; + rr->ttl = ttl; + rr->rdlen = 0; + + rr->rdata.a.addr = addr; + + list_append(&srv->rrl, &rr->le, rr); + +out: + if (err) + mem_deref(rr); + + return err; +} + + +int dns_server_add_aaaa(struct dns_server *srv, const char *name, + const uint8_t *addr) +{ + struct dnsrr *rr; + int err; + + if (!srv || !name) + return EINVAL; + + rr = dns_rr_alloc(); + if (!rr) + return ENOMEM; + + err = str_dup(&rr->name, name); + if (err) + goto out; + + rr->type = DNS_TYPE_AAAA; + rr->dnsclass = DNS_CLASS_IN; + rr->ttl = 3600; + rr->rdlen = 0; + + memcpy(rr->rdata.aaaa.addr, addr, 16); + + list_append(&srv->rrl, &rr->le, rr); + +out: + if (err) + mem_deref(rr); + + return err; +} + + +int dns_server_add_srv(struct dns_server *srv, const char *name, uint16_t pri, + uint16_t weight, uint16_t port, const char *target) +{ + struct dnsrr *rr; + int err; + + if (!srv || !name || !port || !target) + return EINVAL; + + rr = dns_rr_alloc(); + if (!rr) + return ENOMEM; + + err = str_dup(&rr->name, name); + if (err) + goto out; + + rr->type = DNS_TYPE_SRV; + rr->dnsclass = DNS_CLASS_IN; + rr->ttl = 3600; + rr->rdlen = 0; + + rr->rdata.srv.pri = pri; + rr->rdata.srv.weight = weight; + rr->rdata.srv.port = port; + str_dup(&rr->rdata.srv.target, target); + + list_append(&srv->rrl, &rr->le, rr); + +out: + if (err) + mem_deref(rr); + + return err; +} diff --git a/src/srcs.mk b/src/srcs.mk index 738e0760..6e08b44f 100644 --- a/src/srcs.mk +++ b/src/srcs.mk @@ -83,3 +83,4 @@ SRCS += mock/turnsrv.c SRCS += mock/nat.c SRCS += mock/tcpsrv.c SRCS += mock/fuzz.c +SRCS += mock/dnssrv.c diff --git a/src/test.c b/src/test.c index 9de6a2c5..ab683820 100644 --- a/src/test.c +++ b/src/test.c @@ -217,6 +217,7 @@ static const struct test tests_integration[] = { #endif TEST(test_tmr_jiffies), TEST(test_tmr_jiffies_usec), + TEST(test_dns_integration), }; diff --git a/src/test.h b/src/test.h index 9b5d3cab..5aca50f9 100644 --- a/src/test.h +++ b/src/test.h @@ -169,6 +169,7 @@ int test_bfcp_bin(void); int test_conf(void); int test_crc32(void); int test_dns_hdr(void); +int test_dns_integration(void); int test_dns_rr(void); int test_dns_dname(void); int test_dsp(void); @@ -491,3 +492,25 @@ int sip_server_uri(struct sip_server *srv, char *uri, size_t sz, struct fuzz; int fuzz_register_tcpconn(struct fuzz **fuzzp, struct tcp_conn *tc); + + +/* + * Mock DNS-Server + */ + +struct dns_server { + struct udp_sock *us; + struct sa addr; + struct list rrl; + bool rotate; +}; + +int dns_server_alloc(struct dns_server **srvp, bool rotate); +int dns_server_add_a(struct dns_server *srv, const char *name, uint32_t addr, + int64_t ttl); +int dns_server_add_aaaa(struct dns_server *srv, const char *name, + const uint8_t *addr); +int dns_server_add_srv(struct dns_server *srv, const char *name, + uint16_t pri, uint16_t weight, uint16_t port, + const char *target); +void dns_server_flush(struct dns_server *srv);