blob: 3afae81d3ec4173192aac9ff8b4137c797b826d1 [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <ztest.h>
#include <dns_pack.h>
#define MAX_BUF_SIZE 512
/* RFC 1035, 4.1.1. Header section format */
#define DNS_HEADER_SIZE 12
static u8_t buf[MAX_BUF_SIZE];
static u16_t buf_len;
static u8_t qname[MAX_BUF_SIZE];
static u16_t qname_len;
/* Domain: www.zephyrproject.org
* Type: standard query (IPv4)
* Transaction ID: 0xda0f
* Recursion desired
*/
static u8_t query_ipv4[] = { 0xda, 0x0f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
0x0d, 0x7a, 0x65, 0x70, 0x68, 0x79, 0x72, 0x70,
0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x03, 0x6f,
0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01 };
#define DNAME1 "www.zephyrproject.org"
static u16_t tid1 = 0xda0f;
static int eval_query(const char *dname, u16_t tid, enum dns_rr_type type,
u8_t *expected, u16_t expected_len)
{
u8_t *question;
int rc;
rc = dns_msg_pack_qname(&qname_len, qname, MAX_BUF_SIZE, dname);
if (rc != 0) {
goto lb_exit;
}
rc = dns_msg_pack_query(buf, &buf_len, MAX_BUF_SIZE, qname, qname_len,
tid, type);
if (rc != 0) {
goto lb_exit;
}
if (dns_unpack_header_id(buf) != tid) {
rc = -EINVAL;
goto lb_exit;
}
/* This is a query */
if (dns_header_qr(buf) != DNS_QUERY) {
rc = -EINVAL;
goto lb_exit;
}
/* This is a query (standard query) */
if (dns_header_opcode(buf) != DNS_QUERY) {
rc = -EINVAL;
goto lb_exit;
}
/* Authoritative Answer must be 0 for a Query */
if (dns_header_aa(buf) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* TrunCation is always 0 */
if (dns_header_tc(buf) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Recursion Desired is always 1 */
if (dns_header_rd(buf) != 1) {
rc = -EINVAL;
goto lb_exit;
}
/* Recursion Available is always 0 */
if (dns_header_ra(buf) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Z is always 0 */
if (dns_header_z(buf) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Response code must be 0 (no error) */
if (dns_header_rcode(buf) != DNS_HEADER_NOERROR) {
rc = -EINVAL;
goto lb_exit;
}
/* Question counter must be 1 */
if (dns_header_qdcount(buf) != 1) {
rc = -EINVAL;
goto lb_exit;
}
/* Answer counter must be 0 */
if (dns_header_ancount(buf) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Name server resource records counter must be 0 */
if (dns_header_nscount(buf) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Additional records counter must be 0 */
if (dns_header_arcount(buf) != 0) {
rc = -EINVAL;
goto lb_exit;
}
question = buf + DNS_HEADER_SIZE;
/* QClass */
if (dns_unpack_query_qclass(question + qname_len) != DNS_CLASS_IN) {
rc = -EINVAL;
goto lb_exit;
}
/* QType */
if (dns_unpack_query_qtype(question + qname_len) != type) {
rc = -EINVAL;
goto lb_exit;
}
/* compare with the expected result */
if (buf_len != expected_len) {
rc = -EINVAL;
goto lb_exit;
}
if (memcmp(expected, buf, buf_len) != 0) {
rc = -EINVAL;
goto lb_exit;
}
lb_exit:
return rc;
}
/* The DNS response min size is computed as follows:
* (hdr size) + (question min size) + (RR min size)
*/
#define RESPONSE_MIN_SIZE (DNS_HEADER_SIZE + 6 + 14)
/* DNS QNAME size here is 2 because we use DNS pointers */
#define NAME_PTR_SIZE 2
/* DNS integer size */
#define INT_SIZE 2
/* DNS answer TTL size */
#define ANS_TTL_SIZE 4
struct dns_response_test {
/* domain name: example.com */
const char *dname;
/* expected result */
u8_t *res;
/* expected result length */
u16_t res_len;
/* transaction id */
u16_t tid;
/* A, AAAA */
u8_t answer_type;
/* answer counter */
u8_t ancount;
/* answer TTL */
u32_t ttl;
/* recursion available */
u8_t ra;
/* data len */
u8_t rdlen;
/* data */
const u8_t *rdata;
};
/* This routine evaluates DNS responses with one RR, and assumes that the
* RR's name points to the DNS question's qname.
*/
static int eval_response1(struct dns_response_test *resp)
{
u8_t *ptr = resp->res;
u16_t offset;
int rc;
if (resp->res_len < RESPONSE_MIN_SIZE) {
rc = -EINVAL;
goto lb_exit;
}
if (dns_unpack_header_id(resp->res) != resp->tid) {
rc = -EINVAL;
goto lb_exit;
}
/* This is a response */
if (dns_header_qr(resp->res) != DNS_RESPONSE) {
rc = -EINVAL;
goto lb_exit;
}
/* For the DNS response, this value is standard query */
if (dns_header_opcode(resp->res) != DNS_QUERY) {
rc = -EINVAL;
goto lb_exit;
}
/* Authoritative Answer */
if (dns_header_aa(resp->res) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* TrunCation is always 0 */
if (dns_header_tc(resp->res) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Recursion Desired is always 1 in the Zephyr DNS implementation */
if (dns_header_rd(resp->res) != 1) {
rc = -EINVAL;
goto lb_exit;
}
/* Recursion Available */
if (dns_header_ra(resp->res) != resp->ra) {
rc = -EINVAL;
goto lb_exit;
}
/* Z is always 0 */
if (dns_header_z(resp->res) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Response code must be 0 (no error) */
if (dns_header_rcode(resp->res) != DNS_HEADER_NOERROR) {
rc = -EINVAL;
goto lb_exit;
}
/* Question counter must be 1 */
if (dns_header_qdcount(resp->res) != 1) {
rc = -EINVAL;
goto lb_exit;
}
/* Answer counter */
if (dns_header_ancount(resp->res) != resp->ancount) {
rc = -EINVAL;
goto lb_exit;
}
/* Name server resource records counter must be 0 */
if (dns_header_nscount(resp->res) != 0) {
rc = -EINVAL;
goto lb_exit;
}
/* Additional records counter must be 0 */
if (dns_header_arcount(resp->res) != 0) {
rc = -EINVAL;
goto lb_exit;
}
rc = dns_msg_pack_qname(&qname_len, qname, MAX_BUF_SIZE, resp->dname);
if (rc != 0) {
goto lb_exit;
}
offset = DNS_HEADER_SIZE;
/* DNS header + qname + qtype (int size) + qclass (int size) */
if (offset + qname_len + 2 * INT_SIZE >= resp->res_len) {
rc = -EINVAL;
goto lb_exit;
}
if (memcmp(qname, resp->res + offset, qname_len) != 0) {
rc = -EINVAL;
goto lb_exit;
}
offset += qname_len;
if (dns_unpack_query_qtype(resp->res + offset) != resp->answer_type) {
rc = -EINVAL;
goto lb_exit;
}
if (dns_unpack_query_qclass(resp->res + offset) != DNS_CLASS_IN) {
rc = -EINVAL;
goto lb_exit;
}
/* we add here qtype and qclass */
offset += INT_SIZE + INT_SIZE;
/* 0xc0 and 0x0c are derived from RFC 1035 4.1.4. Message compression.
* 0x0c is the DNS Header Size (fixed size) and 0xc0 is the
* DNS pointer marker.
*/
if (resp->res[offset + 0] != 0xc0 || resp->res[offset + 1] != 0x0c) {
rc = -EINVAL;
goto lb_exit;
}
/* simplify the following lines by applying the offset here */
resp->res += offset;
offset = NAME_PTR_SIZE;
if (dns_answer_type(NAME_PTR_SIZE, resp->res) != resp->answer_type) {
rc = -EINVAL;
goto lb_exit;
}
offset += INT_SIZE;
if (dns_answer_class(NAME_PTR_SIZE, resp->res) != DNS_CLASS_IN) {
rc = -EINVAL;
goto lb_exit;
}
offset += INT_SIZE;
if (dns_answer_ttl(NAME_PTR_SIZE, resp->res) != resp->ttl) {
rc = -EINVAL;
goto lb_exit;
}
offset += ANS_TTL_SIZE;
if (dns_answer_rdlength(NAME_PTR_SIZE, resp->res) != resp->rdlen) {
rc = -EINVAL;
goto lb_exit;
}
offset += INT_SIZE;
if (resp->rdlen + offset > resp->res_len) {
rc = -EINVAL;
goto lb_exit;
}
if (memcmp(resp->res + offset, resp->rdata, resp->rdlen) != 0) {
rc = -EINVAL;
}
lb_exit:
resp->res = ptr;
return rc;
}
void test_dns_query(void)
{
int rc;
rc = eval_query(DNAME1, tid1, DNS_RR_TYPE_A,
query_ipv4, sizeof(query_ipv4));
zassert_equal(rc, 0, "Query test failed for domain: "DNAME1);
rc = eval_query(NULL, tid1, DNS_RR_TYPE_A,
query_ipv4, sizeof(query_ipv4));
zassert_not_equal(rc, 0, "Query test with invalid domain name failed");
rc = eval_query(DNAME1, tid1, DNS_RR_TYPE_AAAA,
query_ipv4, sizeof(query_ipv4));
zassert_not_equal(rc, 0, "Query test for IPv4 with RR type AAAA failed");
rc = eval_query(DNAME1, tid1 + 1, DNS_RR_TYPE_A,
query_ipv4, sizeof(query_ipv4));
zassert_not_equal(rc, 0, "Query test with invalid ID failed");
}
/* DNS response for www.zephyrproject.org with the following parameters:
* Transaction ID: 0xb041
* Answer type: RR A
* Answer counter: 1
* TTL: 3028
* Recursion Available: 1
* RD len: 4 (IPv4 Address)
* RData: 140.211.169.8
*/
static u8_t resp_ipv4[] = { 0xb0, 0x41, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
0x0d, 0x7a, 0x65, 0x70, 0x68, 0x79, 0x72, 0x70,
0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x03, 0x6f,
0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0,
0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0b,
0xd4, 0x00, 0x04, 0x8c, 0xd3, 0xa9, 0x08 };
static const u8_t resp_ipv4_addr[] = {140, 211, 169, 8};
void test_dns_response(void)
{
struct dns_response_test test1 = { .dname = DNAME1,
.res = resp_ipv4,
.res_len = sizeof(resp_ipv4),
.tid = 0xb041,
.answer_type = DNS_RR_TYPE_A,
.ancount = 1,
.ttl = 3028,
.ra = 1,
.rdlen = 4, /* IPv4 test */
.rdata = resp_ipv4_addr };
int rc;
rc = eval_response1(&test1);
zassert_equal(rc, 0, "Response test failed for domain: "DNAME1);
}
void test_main(void)
{
ztest_test_suite(dns_tests,
ztest_unit_test(test_dns_query),
ztest_unit_test(test_dns_response));
ztest_run_test_suite(dns_tests);
/* TODO:
* 1) add malformed DNS data
* 2) add validations against buffer overflows
* 3) add debug info to detect the exit point (or split the tests)
* 4) add test data with CNAME and more RR
*/
}