diff options
Diffstat (limited to 'src/utils/dig/dig_exec.c')
-rw-r--r-- | src/utils/dig/dig_exec.c | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/src/utils/dig/dig_exec.c b/src/utils/dig/dig_exec.c new file mode 100644 index 0000000..8a13e96 --- /dev/null +++ b/src/utils/dig/dig_exec.c @@ -0,0 +1,732 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <config.h> +#include "utils/dig/dig_exec.h" + +#include <stdlib.h> // free +#include <sys/time.h> // gettimeofday +#include <sys/socket.h> // AF_INET +#include <netinet/in.h> // sockaddr_in (BSD) + +#include "libknot/libknot.h" +#include "common/lists.h" // list +#include "common/print.h" // time_diff +#include "common/errcode.h" // KNOT_EOK +#include "common/descriptor.h" // KNOT_RRTYPE_ +#include "utils/common/msg.h" // WARN +#include "utils/common/netio.h" // get_socktype +#include "utils/common/exec.h" // print_packet + +static knot_packet_t* create_query_packet(const query_t *query, + uint8_t **data, + size_t *data_len) +{ + knot_question_t q; + knot_packet_t *packet; + + // Set packet buffer size. + int max_size = query->udp_size; + if (max_size < 0) { + if (get_socktype(query->protocol, query->type_num) + == SOCK_STREAM) { + max_size = MAX_PACKET_SIZE; + } else if (query->flags.do_flag || query->nsid) { + max_size = DEFAULT_EDNS_SIZE; + } else { + max_size = DEFAULT_UDP_SIZE; + } + } + + // Create packet skeleton. + packet = create_empty_packet(KNOT_PACKET_PREALLOC_NONE, max_size); + if (packet == NULL) { + return NULL; + } + + // Set flags to wireformat. + if (query->flags.aa_flag == true) { + knot_wire_set_aa(packet->wireformat); + } + if (query->flags.tc_flag == true) { + knot_wire_set_tc(packet->wireformat); + } + if (query->flags.rd_flag == true) { + knot_wire_set_rd(packet->wireformat); + } + if (query->flags.ra_flag == true) { + knot_wire_set_ra(packet->wireformat); + } + if (query->flags.z_flag == true) { + knot_wire_set_z(packet->wireformat); + } + if (query->flags.ad_flag == true) { + knot_wire_set_ad(packet->wireformat); + } + if (query->flags.cd_flag == true) { + knot_wire_set_cd(packet->wireformat); + } + + // Fill auxiliary question structure. + q.qclass = query->class_num; + q.qtype = query->type_num; + q.qname = knot_dname_new_from_str(query->owner, strlen(query->owner), 0); + if (q.qname == NULL) { + knot_packet_free(&packet); + return NULL; + } + + // Set packet question. + if (knot_query_set_question(packet, &q) != KNOT_EOK) { + // It's necessary to release q.qname by hand as it isn't + // connected with the packet yet. + knot_dname_release(q.qname); + knot_packet_free(&packet); + return NULL; + } + + // For IXFR query add authority section. + if (query->type_num == KNOT_RRTYPE_IXFR) { + // SOA rdata in wireformat. + uint8_t wire[22] = { 0x0 }; + size_t pos = 0; + int ret; + + // Create rrset with SOA record. + knot_rrset_t *soa = knot_rrset_new(q.qname, + KNOT_RRTYPE_SOA, + query->class_num, + 0); + if (soa == NULL) { + knot_packet_free(&packet); + return NULL; + } + + // Fill in blank SOA rdata to rrset. + ret = knot_rrset_rdata_from_wire_one(soa, wire, &pos, + sizeof(wire), sizeof(wire)); + if (ret != KNOT_EOK) { + free(soa); + knot_packet_free(&packet); + return NULL; + } + + // Set SOA serial. + knot_rrset_rdata_soa_serial_set(soa, query->xfr_serial); + + // Add authority section. + ret = knot_query_add_rrset_authority(packet, soa); + if (ret != KNOT_EOK) { + free(soa); + knot_packet_free(&packet); + return NULL; + } + } + + // Create EDNS section if required. + if (query->udp_size > 0 || query->flags.do_flag || query->nsid) { + knot_opt_rr_t *opt_rr = knot_edns_new(); + if (opt_rr == NULL) { + ERR("can't create EDNS section\n"); + knot_edns_free(&opt_rr); + knot_packet_free(&packet); + return NULL; + } + + knot_edns_set_version(opt_rr, 0); + knot_edns_set_payload(opt_rr, max_size); + + if (knot_response_add_opt(packet, opt_rr, 0, 0) != KNOT_EOK) { + ERR("can't set EDNS section\n"); + knot_edns_free(&opt_rr); + knot_packet_free(&packet); + return NULL; + } + + // Set DO flag to EDNS section. + if (query->flags.do_flag) { + knot_edns_set_do(&packet->opt_rr); + } + + if (query->nsid) { + if (knot_edns_add_option(&packet->opt_rr, + EDNS_OPTION_NSID, + 0, NULL) != KNOT_EOK) { + ERR("can't set NSID query\n"); + knot_edns_free(&opt_rr); + knot_packet_free(&packet); + return NULL; + } + } + + knot_edns_free(&opt_rr); + } + + // Create wire query. + if (knot_packet_to_wire(packet, data, data_len) != KNOT_EOK) { + ERR("can't create wire query packet\n"); + knot_packet_free(&packet); + return NULL; + } + + // Sign the packet if a key was specified. + if (query->key_params.name != NULL) { + int ret = sign_packet(packet, (sign_context_t *)&query->sign_ctx, + &query->key_params); + if (ret != KNOT_EOK) { + ERR("failed to sign query packet (%s)\n", + knot_strerror(ret)); + knot_packet_free(&packet); + return NULL; + } + + *data_len = packet->size; + } + + return packet; +} + +static bool check_reply_id(const knot_packet_t *reply, + const knot_packet_t *query) +{ + uint16_t query_id = knot_wire_get_id(query->wireformat); + uint16_t reply_id = knot_wire_get_id(reply->wireformat); + + if (reply_id != query_id) { + WARN("reply ID (%u) is different from query ID (%u)\n", + reply_id, query_id); + return false; + } + + return true; +} + +static void check_reply_question(const knot_packet_t *reply, + const knot_packet_t *query) +{ + if (reply->header.qdcount < 1) { + WARN("response doesn't have question section\n"); + return; + } + + int name_diff = knot_dname_compare_cs(reply->question.qname, + query->question.qname); + + if (reply->question.qclass != query->question.qclass || + reply->question.qtype != query->question.qtype || + name_diff != 0) { + WARN("query/response question sections are different\n"); + return; + } +} + +static int64_t first_serial_check(const knot_packet_t *reply) +{ + if (reply->header.ancount <= 0) { + return -1; + } + + const knot_rrset_t *first = *(reply->answer); + + if (first->type != KNOT_RRTYPE_SOA) { + return -1; + } else { + return knot_rrset_rdata_soa_serial(first); + } +} + +static bool last_serial_check(const uint32_t serial, const knot_packet_t *reply) +{ + if (reply->header.ancount <= 0) { + return false; + } + + const knot_rrset_t *last = *(reply->answer + reply->header.ancount - 1); + + if (last->type != KNOT_RRTYPE_SOA) { + return false; + } else { + int64_t last_serial = knot_rrset_rdata_soa_serial(last); + + if (last_serial == serial) { + return true; + } else { + return false; + } + } +} + +static int process_query_packet(const knot_packet_t *query, + net_t *net, + const bool ignore_tc, + const sign_context_t *sign_ctx, + const knot_key_params_t *key_params, + const style_t *style) +{ + struct timeval t_start, t_query, t_end; + knot_packet_t *reply; + uint8_t in[MAX_PACKET_SIZE]; + int in_len; + int ret; + + // Get start query time. + gettimeofday(&t_start, NULL); + + // Connect to the server. + ret = net_connect(net); + if (ret != KNOT_EOK) { + return -1; + } + + // Send query packet. + ret = net_send(net, query->wireformat, query->size); + if (ret != KNOT_EOK) { + net_close(net); + return -1; + } + + // Get stop query time and start reply time. + gettimeofday(&t_query, NULL); + + // Print query packet if required. + if (style->show_query) { + print_packet(query, query->size, net, + time_diff(&t_start, &t_query), + false, style); + } + + // Loop over incoming messages, unless reply id is correct or timeout. + while (true) { + // Receive a reply message. + in_len = net_receive(net, in, sizeof(in)); + if (in_len <= 0) { + net_close(net); + return -1; + } + + // Get stop reply time. + gettimeofday(&t_end, NULL); + + // Create reply packet structure to fill up. + reply = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (reply == NULL) { + net_close(net); + return -1; + } + + // Parse reply to the packet structure. + if (knot_packet_parse_from_wire(reply, in, in_len, 0, + KNOT_PACKET_DUPL_NO_MERGE) + != KNOT_EOK) { + ERR("malformed reply packet from %s\n", net->remote_str); + knot_packet_free(&reply); + net_close(net); + return -1; + } + + // Compare reply header id. + if (check_reply_id(reply, query)) { + break; + // Check for timeout. + } else if (time_diff(&t_query, &t_end) > 1000 * net->wait) { + knot_packet_free(&reply); + net_close(net); + return -1; + } + + knot_packet_free(&reply); + } + + // Check for TC bit and repeat query with TCP if required. + if (knot_wire_get_tc(reply->wireformat) != 0 && + ignore_tc == false && net->socktype == SOCK_DGRAM) { + WARN("truncated reply from %s, retrying over TCP\n", + net->remote_str); + knot_packet_free(&reply); + net_close(net); + + net->socktype = SOCK_STREAM; + + return process_query_packet(query, net, true, sign_ctx, + key_params, style); + } + + // Check for question sections equality. + if (knot_wire_get_rcode(in) == KNOT_RCODE_NOERROR) { + check_reply_question(reply, query); + } + + // Verify signature if a key was specified. + if (key_params->name != NULL) { + ret = verify_packet(reply, sign_ctx, key_params); + if (ret != KNOT_EOK) { + ERR("reply verification for %s (%s)\n", + net->remote_str, knot_strerror(ret)); + knot_packet_free(&reply); + net_close(net); + return -1; + } + } + + // Print reply packet. + print_packet(reply, in_len, net, time_diff(&t_query, &t_end), + true, style); + + knot_packet_free(&reply); + net_close(net); + + // Check for SERVFAIL. + if (knot_wire_get_rcode(in) == KNOT_RCODE_SERVFAIL) { + return 1; + } + + return 0; +} + +static void process_query(const query_t *query) +{ + node *server = NULL; + knot_packet_t *out_packet; + uint8_t *out = NULL; + size_t out_len = 0; + net_t net; + int ret; + + if (query == NULL) { + DBG_NULL; + return; + } + + // Create query packet. + out_packet = create_query_packet(query, &out, &out_len); + if (out_packet == NULL) { + ERR("can't create query packet\n"); + return; + } + + // Get connection parameters. + int iptype = get_iptype(query->ip); + int socktype = get_socktype(query->protocol, query->type_num); + + // Loop over server list to process query. + WALK_LIST(server, query->servers) { + server_t *remote = (server_t *)server; + + DBG("Quering for owner(%s), class(%u), type(%u), server(%s), " + "port(%s), protocol(%s)\n", query->owner, query->class_num, + query->type_num, remote->name, remote->service, + get_sockname(socktype)); + + // Loop over the number of retries. + for (size_t i = 0; i <= query->retries; i++) { + // Initialize network structure for current server. + ret = net_init(query->local, remote, iptype, socktype, + query->wait, &net); + if (ret != KNOT_EOK) { + continue; + } + + // Loop over all resolved addresses for remote. + while (net.srv != NULL) { + ret = process_query_packet(out_packet, &net, + query->ignore_tc, + &query->sign_ctx, + &query->key_params, + &query->style); + // If error try next resolved address. + if (ret != 0) { + net.srv = (net.srv)->ai_next; + continue; + } + + break; + } + + // Success. + if (ret == 0) { + net_clean(&net); + knot_packet_free(&out_packet); + return; + // SERVFAIL. + } else if (ret == 1 && query->servfail_stop == true) { + net_clean(&net); + knot_packet_free(&out_packet); + return; + } + + if (i < query->retries) { + DBG("retrying server %s#%s(%s)\n", + remote->name, remote->service, + get_sockname(socktype)); + } + + net_clean(&net); + } + + WARN("failed to query server %s#%s(%s)\n", + remote->name, remote->service, get_sockname(socktype)); + } + + knot_packet_free(&out_packet); +} + +static int process_packet_xfr(const knot_packet_t *query, + net_t *net, + const sign_context_t *sign_ctx, + const knot_key_params_t *key_params, + const style_t *style) +{ + struct timeval t_start, t_query, t_end; + knot_packet_t *reply; + uint8_t in[MAX_PACKET_SIZE]; + int in_len; + int ret; + int64_t serial = 0; + size_t total_len = 0; + size_t msg_count = 0; + size_t rr_count = 0; + + // Get start query time. + gettimeofday(&t_start, NULL); + + // Connect to the server. + ret = net_connect(net); + if (ret != KNOT_EOK) { + return -1; + } + + // Send query packet. + ret = net_send(net, query->wireformat, query->size); + if (ret != KNOT_EOK) { + net_close(net); + return -1; + } + + // Get stop query time and start reply time. + gettimeofday(&t_query, NULL); + + + // Print query packet if required. + if (style->show_query) { + print_packet(query, query->size, net, + time_diff(&t_start, &t_query), + false, style); + } + + // Print leading transfer information. + print_header_xfr(&query->question, style); + + // Loop over reply messages unless first and last SOA serials differ. + while (true) { + // Receive a reply message. + in_len = net_receive(net, in, sizeof(in)); + if (in_len <= 0) { + net_close(net); + return -1; + } + + // Create reply packet structure to fill up. + reply = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (reply == NULL) { + net_close(net); + return -1; + } + + // Parse reply to the packet structure. + if (knot_packet_parse_from_wire(reply, in, in_len, 0, + KNOT_PACKET_DUPL_NO_MERGE) + != KNOT_EOK) { + ERR("malformed reply packet from %s\n", net->remote_str); + knot_packet_free(&reply); + net_close(net); + return -1; + } + + // Compare reply header id. + if (check_reply_id(reply, query) == false) { + knot_packet_free(&reply); + net_close(net); + return -1; + } + + // Check for reply error. + uint8_t rcode_id = knot_wire_get_rcode(in); + if (rcode_id != KNOT_RCODE_NOERROR) { + knot_lookup_table_t *rcode = + knot_lookup_by_id(knot_rcode_names, rcode_id); + if (rcode != NULL) { + ERR("server %s respond %s\n", + net->remote_str, rcode->name); + } else { + ERR("server %s respond %i\n", + net->remote_str, rcode_id); + } + + knot_packet_free(&reply); + net_close(net); + return -1; + } + + // The first message has a special treatment. + if (msg_count == 0) { + // Verify 1. signature if a key was specified. + if (key_params->name != NULL) { + ret = verify_packet(reply, sign_ctx, key_params); + if (ret != KNOT_EOK) { + ERR("reply verification for %s (%s)\n", + net->remote_str, knot_strerror(ret)); + knot_packet_free(&reply); + net_close(net); + return -1; + } + } + + // Read first SOA serial. + serial = first_serial_check(reply); + + if (serial < 0) { + ERR("first answer record isn't SOA\n"); + knot_packet_free(&reply); + net_close(net); + return -1; + } + + // Check for question sections equality. + check_reply_question(reply, query); + } + + msg_count++; + rr_count += reply->header.ancount; + total_len += in_len; + + // Print reply packet. + print_data_xfr(reply, style); + + // Stop if last SOA record has correct serial. + if (last_serial_check(serial, reply)) { + knot_packet_free(&reply); + break; + } + + knot_packet_free(&reply); + } + + // Get stop reply time. + gettimeofday(&t_end, NULL); + + // Print trailing transfer information. + print_footer_xfr(total_len, msg_count, rr_count, net, + time_diff(&t_query, &t_end), style); + + net_close(net); + + return 0; +} + +static void process_query_xfr(const query_t *query) +{ + knot_packet_t *out_packet; + uint8_t *out = NULL; + size_t out_len = 0; + net_t net; + int ret; + + if (query == NULL) { + DBG_NULL; + return; + } + + // Create query packet. + out_packet = create_query_packet(query, &out, &out_len); + if (out_packet == NULL) { + ERR("can't create query packet\n"); + return; + } + + // Get connection parameters. + int iptype = get_iptype(query->ip); + int socktype = get_socktype(query->protocol, query->type_num); + + // Use the first nameserver from the list. + server_t *remote = HEAD(query->servers); + + DBG("Quering for owner(%s), class(%u), type(%u), server(%s), " + "port(%s), protocol(%s)\n", query->owner, query->class_num, + query->type_num, remote->name, remote->service, + get_sockname(socktype)); + + // Initialize network structure. + ret = net_init(query->local, remote, iptype, socktype, + query->wait, &net); + if (ret != KNOT_EOK) { + knot_packet_free(&out_packet); + return; + } + + // Loop over all resolved addresses for remote. + while (net.srv != NULL) { + ret = process_packet_xfr(out_packet, &net, + &query->sign_ctx, + &query->key_params, + &query->style); + // If error try next resolved address. + if (ret != 0) { + net.srv = (net.srv)->ai_next; + continue; + } + + break; + } + + if (ret != 0) { + ERR("failed to query server %s#%s(%s)\n", + remote->name, remote->service, get_sockname(socktype)); + } + + net_clean(&net); + knot_packet_free(&out_packet); +} + +int dig_exec(const dig_params_t *params) +{ + node *n = NULL; + + if (params == NULL) { + DBG_NULL; + return KNOT_EINVAL; + } + + // Loop over query list. + WALK_LIST(n, params->queries) { + query_t *query = (query_t *)n; + + switch (query->operation) { + case OPERATION_QUERY: + process_query(query); + break; + case OPERATION_XFR: + process_query_xfr(query); + break; + case OPERATION_LIST_SOA: + break; + default: + ERR("unsupported operation\n"); + break; + } + } + + return KNOT_EOK; +} |