1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/tests/net/gcoap_dns/main.c

570 lines
16 KiB
C

/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "fmt.h"
#include "net/af.h"
#include "net/coap.h"
#include "net/credman.h"
#include "net/dns/msg.h"
#include "net/ipv4/addr.h"
#include "net/ipv6/addr.h"
#include "od.h"
#include "embUnit.h"
#include "shell.h"
#if IS_USED(MODULE_GCOAP_DTLS)
#include "net/sock/dtls.h"
#endif
#include "net/gcoap/dns.h"
#define PSK_ID_LEN 32U
#define PSK_LEN 32U
#define TEST_TAG 2599U
#define TEST_PSK_ID "client_identity"
#define TEST_PSK "secretPSK"
static char line_buf[SHELL_DEFAULT_BUFSIZE];
static char _psk_id[PSK_ID_LEN];
static char _psk[PSK_LEN];
static credman_credential_t _credential = {
.type = CREDMAN_TYPE_PSK,
.params = {
.psk = {
.id = { .s = _psk_id, .len = 0U, },
.key = { .s = _psk, .len = 0U, },
}
},
};
static uint8_t _mock_response[CONFIG_DNS_MSG_LEN];
static size_t _mock_response_len = 0U;
static uint8_t _resp_code = COAP_CODE_EMPTY;
#if IS_USED(MODULE_GCOAP_DTLS)
static_assert(CONFIG_GCOAP_DNS_CREDS_MAX == CONFIG_DTLS_CREDENTIALS_MAX,
"CONFIG_GCOAP_DNS_CREDS_MAX and CONFIG_DTLS_CREDENTIALS_MAX "
"must be equal for this test");
#endif
static_assert(!IS_USED(MODULE_GCOAP_DTLS) ||
(CONFIG_GCOAP_DNS_CREDS_MAX == CONFIG_CREDMAN_MAX_CREDENTIALS),
"CONFIG_GCOAP_DNS_CREDS_MAX and CONFIG_CREDMAN_MAX_CREDENTIALS "
"must be equal for this test");
#define INIT_TEST_PSK(t) \
_credential.type = CREDMAN_TYPE_PSK; \
_credential.tag = (t); \
_credential.params.psk.id.len = sizeof(TEST_PSK_ID); \
strcpy((char *)_credential.params.psk.id.s, TEST_PSK_ID); \
_credential.params.psk.key.len = sizeof(TEST_PSK); \
strcpy((char *)_credential.params.psk.key.s, TEST_PSK)
static void setup(void)
{
gcoap_dns_server_uri_set(NULL);
gcoap_dns_cred_reset();
_credential.tag = CREDMAN_TAG_EMPTY;
_credential.params.psk.id.len = 0U;
_credential.params.psk.key.len = 0U;
}
static void test_server_uri_set__success_1(void)
{
static const char uri[] = "coap://example.org/";
char res[sizeof(uri)];
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_set(uri));
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_get(res, sizeof(res)));
TEST_ASSERT_EQUAL_STRING(uri, res);
TEST_ASSERT(gcoap_dns_server_uri_is_set());
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_server_uri_set(NULL));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
static void test_server_uri_set__success_2(void)
{
static const char uri[] = "coaps://example.org/";
if (IS_USED(MODULE_GCOAP_DTLS)) {
char res[sizeof(uri)];
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_set(uri));
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_get(res, sizeof(res)));
TEST_ASSERT_EQUAL_STRING(uri, res);
TEST_ASSERT(gcoap_dns_server_uri_is_set());
}
else {
TEST_ASSERT_EQUAL_INT(-EINVAL, gcoap_dns_server_uri_set(uri));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
}
static void test_server_uri_set__uri_too_long(void)
{
static const char uri[] = "coap://a.very.long.host-name.org"
"/this/is/a/very/long/path/to/dns";
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
/* would not fit trailing \0 */
TEST_ASSERT_EQUAL_INT(CONFIG_GCOAP_DNS_SERVER_URI_LEN,
strlen(uri));
INIT_TEST_PSK(TEST_TAG);
TEST_ASSERT_EQUAL_INT(-ENOBUFS, gcoap_dns_server_uri_set(uri));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
static void test_server_uri_set__not_coap(void)
{
static const char uri[] = "https://example.org/";
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
INIT_TEST_PSK(TEST_TAG);
TEST_ASSERT_EQUAL_INT(-EINVAL, gcoap_dns_server_uri_set(uri));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
static void test_server_uri_get__buf_too_short(void)
{
static const char uri[] = "coap://example.org/";
char res[sizeof(uri) - 1];
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_set(uri));
TEST_ASSERT_EQUAL_INT(-ENOBUFS, gcoap_dns_server_uri_get(res, sizeof(res)));
}
static void test_cred_add__success(void)
{
INIT_TEST_PSK(TEST_TAG);
if (IS_USED(MODULE_GCOAP_DTLS)) {
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_cred_add(&_credential));
}
else {
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
}
}
static void test_cred_add__no_mem(void)
{
#if IS_USED(MODULE_GCOAP_DTLS)
for (unsigned i = 0; i < CONFIG_GCOAP_DNS_CREDS_MAX; i++) {
INIT_TEST_PSK(TEST_TAG + i);
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_cred_add(&_credential));
}
INIT_TEST_PSK(TEST_TAG + CONFIG_GCOAP_DNS_CREDS_MAX);
TEST_ASSERT_EQUAL_INT(-ENOMEM, gcoap_dns_cred_add(&_credential));
#else
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
#endif
}
static void test_cred_add__credman_error(void)
{
if (IS_USED(MODULE_GCOAP_DTLS)) {
INIT_TEST_PSK(TEST_TAG);
_credential.type = CREDMAN_TYPE_EMPTY;
TEST_ASSERT_EQUAL_INT(-EBADF, gcoap_dns_cred_add(&_credential));
}
else {
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
}
}
static void test_cred_remove__success(void)
{
INIT_TEST_PSK(TEST_TAG);
if (IS_USED(MODULE_GCOAP_DTLS)) {
TEST_ASSERT_EQUAL_INT(CREDMAN_NOT_FOUND, credman_get(&_credential,
_credential.tag, _credential.type));
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_cred_add(&_credential));
TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_get(&_credential,
_credential.tag, _credential.type));
gcoap_dns_cred_remove(_credential.tag, _credential.type);
TEST_ASSERT_EQUAL_INT(CREDMAN_NOT_FOUND, credman_get(&_credential,
_credential.tag, _credential.type));
}
else {
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
}
}
static int _unittests(int argc, char **argv)
{
(void)argc;
(void)argv;
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_server_uri_set__success_1),
new_TestFixture(test_server_uri_set__success_2),
new_TestFixture(test_server_uri_set__uri_too_long),
new_TestFixture(test_server_uri_set__not_coap),
new_TestFixture(test_server_uri_get__buf_too_short),
new_TestFixture(test_cred_add__success),
new_TestFixture(test_cred_add__no_mem),
new_TestFixture(test_cred_add__credman_error),
new_TestFixture(test_cred_remove__success),
};
EMB_UNIT_TESTCALLER(gcoap_dns_tests, setup, NULL, fixtures);
TESTS_START();
TESTS_RUN((Test *)&gcoap_dns_tests);
TESTS_END();
puts("Be aware that URI and credentials are now reset.");
return 0;
}
static ssize_t _mock_dns_server(coap_pkt_t *pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx)
{
(void)ctx;
if ((_resp_code >> 5) == COAP_CLASS_SUCCESS) {
gcoap_resp_init(pdu, buf, len, _resp_code);
coap_opt_add_format(pdu, COAP_FORMAT_DNS_MESSAGE);
size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
/* write the RIOT board name in the response buffer */
if (pdu->payload_len >= _mock_response_len) {
memcpy(pdu->payload, _mock_response, _mock_response_len);
return resp_len + _mock_response_len;
}
else {
puts("gcoap_cli: msg buffer too small");
return gcoap_response(pdu, buf, len, COAP_CODE_INTERNAL_SERVER_ERROR);
}
}
else {
return gcoap_response(pdu, buf, len, _resp_code);
}
}
static void _uri_usage(const char *cmd)
{
printf("usage: %s -d\n", cmd);
printf(" %s <uri>\n", cmd);
}
static int _set_uri(int argc, char **argv)
{
int res;
if ((argc > 1) && (strcmp(argv[1], "-d") == 0)) {
gcoap_dns_server_uri_set(NULL);
puts("Successfully reset URI\n");
return 0;
}
else if (argc == 1) {
static char uri[CONFIG_GCOAP_DNS_SERVER_URI_LEN];
ssize_t res;
if ((res = gcoap_dns_server_uri_get(uri, sizeof(uri))) < 0) {
errno = -res;
perror("Unable to get URI");
return 1;
}
else {
if (res > 0) {
puts(uri);
return 0;
}
else {
_uri_usage(argv[0]);
return 1;
}
return 0;
}
}
/* argc > 1 can be assumed since argc == 1 returns above */
res = gcoap_dns_server_uri_set(argv[1]);
if (res < 0) {
errno = -res;
perror("Unable to set URI");
return errno;
}
printf("Successfully added URI %s\n", argv[1]);
return 0;
}
static void _creds_usage(const char *cmd)
{
printf("usage: %s -d <cred_tag>\n", cmd);
printf(" %s <cred_tag> <psk_id> <psk>\n", cmd);
}
static int _creds(int argc, char **argv)
{
if ((argc > 2) && (strcmp(argv[1], "-d") == 0)) {
credman_tag_t tag = atoi(argv[2]);
if (tag == 0) {
_creds_usage(argv[0]);
return 1;
}
gcoap_dns_cred_remove(tag, CREDMAN_TYPE_PSK);
printf("Successfully removed credentials with tag %u\n", tag);
return 0;
}
if (argc < 4) {
_creds_usage(argv[0]);
return 1;
}
if ((_credential.tag = atoi(argv[1])) == 0) {
_creds_usage(argv[0]);
return 1;
}
if ((_credential.params.psk.id.len = strlen(argv[2])) > PSK_ID_LEN) {
printf("PSK ID too long (max. %u bytes allowed)\n", PSK_ID_LEN);
return 1;
}
if ((_credential.params.psk.key.len = strlen(argv[3])) > PSK_LEN) {
printf("PSK too long (max. %u bytes allowed)\n", PSK_LEN);
return 1;
}
_credential.type = CREDMAN_TYPE_PSK;
strcpy((char *)_credential.params.psk.id.s, argv[2]);
strcpy((char *)_credential.params.psk.key.s, argv[3]);
int res = gcoap_dns_cred_add(&_credential);
if (res < 0) {
errno = -res;
perror("Unable to add credential");
return errno;
}
printf("Successfully added creds: %u, %s, %s\n", _credential.tag, argv[2], argv[3]);
return 0;
}
static int _proxy(int argc, char **argv)
{
if (argc < 2) {
static char proxy[CONFIG_GCOAP_DNS_SERVER_URI_LEN];
int res;
if ((res = gcoap_dns_server_proxy_get(proxy, sizeof(proxy))) < 0) {
errno = -res;
perror("Unable to get URI");
return 1;
}
else {
if (res > 0) {
puts(proxy);
return 0;
}
else {
printf("usage: %s [<proxy URI>|-]\n", argv[0]);
return 1;
}
return 0;
}
}
else if (argv[1][0] == '-') {
gcoap_dns_server_proxy_reset();
puts("Successfully reset proxy URI\n");
}
else {
int res = gcoap_dns_server_proxy_set(argv[1]);
if (res < 0) {
errno = -res;
perror("Unable to set proxy URI");
return errno;
}
printf("Successfully added proxy URI %s\n", argv[1]);
}
return 0;
}
static void _query_usage(const char *cmd)
{
printf("usage: %s <hostname> [inet|inet6]\n", cmd);
}
int _parse_af(const char *family_name)
{
if (strcmp("inet6", family_name) == 0) {
return AF_INET6;
}
else if (strcmp("inet", family_name) == 0) {
return AF_INET;
}
else {
printf("Unexpected family %s\n", family_name);
return AF_UNSPEC;
}
}
int _print_addr(const char *hostname, const uint8_t *addr, int addr_len)
{
char addr_str[IPV6_ADDR_MAX_STR_LEN];
switch (addr_len) {
case sizeof(ipv4_addr_t):
printf("Hostname %s resolves to %s (IPv4)\n",
hostname, ipv4_addr_to_str(addr_str, (ipv4_addr_t *)addr,
sizeof(addr_str)));
break;
case sizeof(ipv6_addr_t):
printf("Hostname %s resolves to %s (IPv6)\n",
hostname, ipv6_addr_to_str(addr_str, (ipv6_addr_t *)addr,
sizeof(addr_str)));
break;
default:
printf("Unexpected address format resolved for hostname %s\n",
hostname);
return 1;
}
return 0;
}
static int _query(int argc, char **argv)
{
uint8_t addr_out[sizeof(ipv6_addr_t)];
const char *hostname;
int family = AF_INET6;
int res;
int offset = 0;
if (argc < 2) {
_query_usage(argv[0]);
return 1;
}
if (argc < (offset + 2)) {
_query_usage(argv[0]);
return 1;
}
if (argc == (offset + 3)) {
if ((family = _parse_af(argv[2 + offset])) == AF_UNSPEC) {
return 1;
}
}
hostname = argv[offset + 1];
res = gcoap_dns_query(hostname, addr_out, family);
if (res < 0) {
errno = -res;
perror("Unable to resolve query");
return errno;
}
return _print_addr(hostname, addr_out, res);
}
static ssize_t _copy_mock_response(const char *str)
{
bool msn = true; /* most significant nibble */
ssize_t start = _mock_response_len;
for (unsigned i = 0; i < strlen(str); i++) {
uint8_t nibble = scn_u32_hex(&str[i], 1);
if (_mock_response_len == CONFIG_DNS_MSG_LEN) {
break;
}
if (msn) {
_mock_response[_mock_response_len] = nibble << 4;
}
else {
_mock_response[_mock_response_len++] |= nibble;
}
msn = !msn;
}
if (!msn) {
_mock_response_len++;
}
if (_mock_response_len >= CONFIG_DNS_MSG_LEN) {
printf("Too many bytes added to response");
return -ENOBUFS;
}
return _mock_response_len - start;
}
static int _resp(int argc, char **argv)
{
if (argc < 2) {
od_hex_dump(_mock_response, _mock_response_len, OD_WIDTH_DEFAULT);
return 0;
}
if (argc > 2 && (strcmp(argv[1], "-c") == 0)) {
ssize_t size = _copy_mock_response(argv[2]);
if (size >= 0) {
printf("Successfully continued response\n");
od_hex_dump(&_mock_response[_mock_response_len - size], size,
OD_WIDTH_DEFAULT);
return 0;
}
else {
return 1;
}
}
_resp_code = atoi(argv[1]);
if ((argc > 2) && ((_resp_code >> 5) == 2)) {
_mock_response_len = 0;
if (_copy_mock_response(argv[2]) < 0) {
return 1;
}
printf("Successfully set response with code %u.%02u\n", _resp_code >> 5, _resp_code & 0x1f);
od_hex_dump(_mock_response, _mock_response_len, OD_WIDTH_DEFAULT);
}
else {
printf("Successfully set response code %u.%02u\n", _resp_code >> 5, _resp_code & 0x1f);
_mock_response_len = 0;
}
return 0;
}
static int _has_dns_cache(int argc, char **argv)
{
(void)argc;
(void)argv;
if (IS_USED(MODULE_DNS_CACHE)) {
puts("DNS cache exists");
}
else {
puts("DNS cache does not exist");
}
return 0;
}
static const coap_resource_t _resources[] = {
{ "/", COAP_FETCH, _mock_dns_server, NULL },
};
static gcoap_listener_t _listener = {
&_resources[0],
ARRAY_SIZE(_resources),
GCOAP_SOCKET_TYPE_UNDEF,
NULL,
NULL,
NULL
};
static const shell_command_t _shell_commands[] = {
{ "unittests", "Runs unittests", _unittests},
{ "uri", "Sets URI to DoC server", _set_uri},
{ "creds", "Adds/removes credentials for DoC server", _creds},
{ "proxy", "Sets proxy URI for DoC queries", _proxy},
{ "query", "Sends DoC query for a hostname", _query},
{ "resp", "Set static response for mock DoC server", _resp},
{ "has_dns_cache", "Check if DNS cache is activated", _has_dns_cache},
{ NULL, NULL, NULL }
};
int main(void)
{
gcoap_register_listener(&_listener);
shell_run(_shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}
/** @} */