/* * 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); }