-
7a29321c
dap_chain_node_dns_server.c 11.85 KiB
/*
* Authors:
* Roman Khlopkov <roman.khlopkov@demlabs.net>
* DeM Labs Inc. https://demlabs.net
* DeM Labs Open source community https://gitlab.demlabs.net
* Copyright (c) 2017-2020
* All rights reserved.
This file is part of DAP (Deus Applications Prototypes) the open source project
DAP (Deus Applicaions Prototypes) 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.
DAP 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 any DAP based project. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include "dap_chain_node_dns_client.h"
#include "dap_chain_node_dns_server.h"
#include "dap_events.h"
#include "dap_events_socket.h"
#include "dap_common.h"
#include "dap_chain_net.h"
#include "dap_chain_node.h"
#include "dap_string.h"
#include "dap_global_db.h"
#include "dap_chain_net_balancer.h"
#define LOG_TAG "dap_chain_node_dns_server"
#define BUF_SIZE 1024
static dap_dns_server_t *s_dns_server;
static char s_root_alias[] = "dnsroot";
/**
* @brief dap_dns_zone_register Register DNS zone and set callback to handle it
* @param zone Name of zone to register
* @param callback Callback to handle DNS zone
* @return 0 if success, else return error code
*/
int dap_dns_zone_register(char *zone, dap_dns_zone_callback_t callback) {
dap_dns_zone_hash_t *new_zone;
HASH_FIND_STR(s_dns_server->hash_table, zone, new_zone);
if (new_zone == NULL) { // zone is not present
new_zone = DAP_NEW(dap_dns_zone_hash_t);
if (!new_zone) {
log_it(L_CRITICAL, "Memory allocation error");
return DNS_ERROR_FAILURE;
}
new_zone->zone = dap_strdup(zone);
HASH_ADD_KEYPTR(hh, s_dns_server->hash_table, new_zone->zone, strlen(new_zone->zone), new_zone);
} // if zone present, just reassign callback
new_zone->callback = callback;
return DNS_ERROR_NONE;
}
/**
* @brief dap_dns_zone_unregister Unregister DNS zone
* @param zone Name of zone to unregister
* @return 0 if success, else return error code
*/
int dap_dns_zone_unregister(char *zone) {
dap_dns_zone_hash_t *asked_zone;
HASH_FIND_STR(s_dns_server->hash_table, zone, asked_zone);
if (asked_zone == NULL) {
return DNS_ERROR_NAME;
}
HASH_DEL(s_dns_server->hash_table, asked_zone);
DAP_DELETE(asked_zone->zone);
DAP_DELETE(asked_zone);
return DNS_ERROR_NONE;
}
/**
* @brief dap_dns_zone_find Find callback to registered DNS zone
* @param hostname Name of host for which the zone callback being searched
* @return Callback for registered DNS zone, else return NULL
*/
dap_dns_zone_callback_t dap_dns_zone_find(char *hostname) {
dap_dns_zone_hash_t *asked_zone;
HASH_FIND_STR(s_dns_server->hash_table, hostname, asked_zone);
if (asked_zone == NULL) {
if (!strcmp(hostname, &s_root_alias[0])) {
return NULL;
}
char *zone_up = strchr(hostname, '.');
if (zone_up++ == NULL) {
zone_up = &s_root_alias[0];
}
return dap_dns_zone_find(zone_up);
} else {
return asked_zone->callback;
}
return NULL;
}
/**
* @brief dap_dns_client_read Read and parse incoming DNS message, send reply to it
* @param client DAP client remote structure
* @param arg Unused
* @return none
*/
void dap_dns_client_read(dap_events_socket_t *a_es, void *a_arg) {
UNUSED(a_arg);
if (a_es->buf_in_size < DNS_HEADER_SIZE) { // Bad request
return;
}
dap_dns_buf_t *dns_message = DAP_NEW(dap_dns_buf_t);
if (!dns_message) {
log_it(L_CRITICAL, "Memory allocation error");
return;
}
dap_dns_buf_t *dns_reply = DAP_NEW(dap_dns_buf_t);
if (!dns_reply) {
log_it(L_CRITICAL, "Memory allocation error");
DAP_DELETE(dns_message);
return;
}
dns_message->data = DAP_NEW_SIZE(char, a_es->buf_in_size + 1);
if (!dns_message->data) {
log_it(L_CRITICAL, "Memory allocation error");
DAP_DELETE(dns_message);
DAP_DELETE(dns_reply);
return;
}
dns_message->data[a_es->buf_in_size] = 0;
dap_events_socket_pop_from_buf_in(a_es, dns_message->data, a_es->buf_in_size);
dns_message->size = 0;
// Parse incoming DNS message
int block_len = DNS_HEADER_SIZE;
dns_reply->data = DAP_NEW_SIZE(char, block_len);
if (!dns_reply->data) {
log_it(L_CRITICAL, "Memory allocation error");
return;
}
dns_reply->size = 0;
uint16_t val = dap_dns_buf_get_uint16(dns_message); // ID
dap_dns_buf_put_uint16(dns_reply, val);
val = dap_dns_buf_get_uint16(dns_message); // Flags
dns_reply->size += sizeof(uint16_t); // Put flags later
dap_dns_message_flags_t msg_flags;
msg_flags.val = val;
dap_dns_message_flags_bits_t *flags = &msg_flags.flags;
if (flags->qr) { // It's not request
goto cleanup;
}
flags->rcode = DNS_ERROR_NONE;
flags->qr = 1; // Response bit set
if (flags->tc) { // Truncated messages not supported yet
flags->rcode = DNS_ERROR_NOT_SUPPORTED;
}
flags->ra = 0; // Recursion not supported yet
flags->aa = 1; // Authoritative answer
uint16_t qdcount = dap_dns_buf_get_uint16(dns_message);
dap_dns_buf_put_uint16(dns_reply, qdcount);
val = dap_dns_buf_get_uint16(dns_message); // AN count
if (val) { // No other sections should present
goto cleanup;
}
dap_dns_buf_put_uint16(dns_reply, 1); // 1 answer section
val = dap_dns_buf_get_uint16(dns_message); // NS count
if (val) { // No other sections should present
goto cleanup;
}
dap_dns_buf_put_uint16(dns_reply, val);
val = dap_dns_buf_get_uint16(dns_message); // AR count
if (val) { // No other sections should present
goto cleanup;
}
dap_dns_buf_put_uint16(dns_reply, 1); // 1 aditional section
int dot_count = 0;
dap_string_t *dns_hostname = dap_string_new("");
for (int i = 0; i < qdcount; i++) {
block_len = strlen(&dns_message->data[dns_message->size]) + 1 + 2 * sizeof(uint16_t);
dns_reply->data = DAP_REALLOC(dns_reply->data, dns_reply->size + block_len);
memcpy(&dns_reply->data[dns_reply->size], &dns_message->data[dns_message->size], block_len);
dns_reply->size += block_len;
if (flags->rcode)
break;
while (dns_message->size < dns_reply->size - 2 * sizeof(uint16_t)) {
uint8_t len = dns_message->data[dns_message->size++];
if (len > DNS_MAX_DOMAIN_NAME_LEN) {
flags->rcode = DNS_ERROR_NAME;
break;
}
if (!len) {
break;
}
if (dot_count) {
if (dot_count > 3) { // Max three dots allowed
flags->rcode = DNS_ERROR_NAME;
break;
}
dap_string_append(dns_hostname, ".");
}
dap_string_append_len(dns_hostname, &dns_message->data[dns_message->size], len);
dns_message->size += len;
dot_count++;
if (dns_hostname->len >= DNS_MAX_HOSTNAME_LEN) {
flags->rcode = DNS_ERROR_NAME;
break;
}
}
val = dap_dns_buf_get_uint16(dns_message); // DNS record type
if (val != DNS_RECORD_TYPE_A) { // Only host address ipv4
flags->rcode = DNS_ERROR_NOT_SUPPORTED;
break;
}
val = dap_dns_buf_get_uint16(dns_message); // DNS class type
if (val != DNS_CLASS_TYPE_IN) { // Internet only
flags->rcode = DNS_ERROR_NOT_SUPPORTED;
break;
}
if (dns_message->size != dns_reply->size) {
log_it(L_ERROR, "DNS parser pointer unequal, mptr = %u, rptr = %u", dns_message->size, dns_reply->size);
}
}
// Find ip addr
dap_chain_node_info_t *l_node_info = NULL;
if (flags->rcode == DNS_ERROR_NONE) {
dap_dns_zone_callback_t callback = dap_dns_zone_find(dns_hostname->str);
if (callback) {
l_node_info = callback(dns_hostname->str);
}
}
if (l_node_info) {
// Compose DNS answer
block_len = DNS_ANSWER_SIZE * 2 - sizeof(uint16_t) + sizeof(uint64_t);
dns_reply->data = DAP_REALLOC(dns_reply->data, dns_reply->size + block_len);
val = 0xc000 | DNS_HEADER_SIZE; // Link to host name
dap_dns_buf_put_uint16(dns_reply, val);
val = DNS_RECORD_TYPE_A;
dap_dns_buf_put_uint16(dns_reply, val);
val = DNS_CLASS_TYPE_IN;
dap_dns_buf_put_uint16(dns_reply, val);
uint32_t ttl = DNS_TIME_TO_LIVE;
dap_dns_buf_put_uint32(dns_reply, ttl);
dap_dns_buf_put_uint16(dns_reply, 4); // RD len for ipv4
dap_dns_buf_put_uint32(dns_reply, l_node_info->hdr.ext_addr_v4.s_addr);
val = 0xc000 | DNS_HEADER_SIZE; // Link to host name
dap_dns_buf_put_uint16(dns_reply, val);
val = DNS_RECORD_TYPE_TXT;
dap_dns_buf_put_uint16(dns_reply, val);
val = DNS_CLASS_TYPE_IN;
dap_dns_buf_put_uint16(dns_reply, val);
dap_dns_buf_put_uint32(dns_reply, ttl);
val = sizeof(uint16_t) + sizeof(uint64_t);
dap_dns_buf_put_uint16(dns_reply, val);
dap_dns_buf_put_uint16(dns_reply, l_node_info->hdr.ext_port);
dap_dns_buf_put_uint64(dns_reply, l_node_info->hdr.address.uint64);
DAP_DELETE(l_node_info);
} else if (flags->rcode == DNS_ERROR_NONE) {
flags->rcode = DNS_ERROR_NAME;
}
if (flags->rcode) {
dns_reply->data[7] = 0; // No answer section
}
// Set reply flags
dns_reply->data[2] = msg_flags.val >> 8;
dns_reply->data[3] = msg_flags.val;
// Send DNS reply
dap_events_socket_write_unsafe(a_es, dns_reply->data, dns_reply->size);
dap_string_free(dns_hostname, true);
cleanup:
DAP_DELETE(dns_reply->data);
DAP_DELETE(dns_message->data);
DAP_DELETE(dns_reply);
DAP_DELETE(dns_message);
return;
}
void dap_dns_server_start(char *a_port)
{
s_dns_server = DAP_NEW_Z(dap_dns_server_t);
if (!s_dns_server) {
log_it(L_CRITICAL, "Memory allocation error");
return;
}
dap_events_socket_callbacks_t l_cb = {};
l_cb.read_callback = dap_dns_client_read;
s_dns_server->instance = dap_server_new(&a_port, 1, DAP_SERVER_UDP, &l_cb);
if (!s_dns_server->instance) {
log_it(L_ERROR, "Can't start DNS server");
return;
}
dap_dns_zone_register(&s_root_alias[0], dap_chain_net_balancer_dns_issue_link); // root resolver
log_it(L_NOTICE,"DNS server started");
}
void dap_dns_server_stop() {
if(!s_dns_server)
return;
dap_dns_zone_hash_t *current_zone, *tmp;
HASH_ITER(hh, s_dns_server->hash_table, current_zone, tmp) {
// Clang bug at this, current_zone should change at every loop cycle
HASH_DEL(s_dns_server->hash_table, current_zone);
DAP_DELETE(current_zone->zone);
DAP_DELETE(current_zone);
}
dap_server_delete(s_dns_server->instance);
DAP_DELETE(s_dns_server);
}