libdnsc/src/dns2.c

775 lines
19 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include "dns2.h"
#include "utils.h"
#include "packet.h"
#include "constants.h"
#include "glist.h"
#include "rr.h"
#include "question.h"
#include "dns2-private.h"
typedef struct {
char name[512];
struct sockaddr_storage server_addr;
socklen_t addr_size;
int socket;
} NameServer;
/* Prototipos de función */
int _dns2_internal_tcp_start_connect (DNS2 *obj);
int _dns2_internal_udp_create (DNS2 *obj);
int _dns2_internal_tcp_connect_continue (DNS2 *obj);
int _dns2_internal_udp_send (DNS2 *obj);
int _dns2_internal_tcp_send (DNS2 *obj);
int _dns2_internal_udp_recv (DNS2 *obj);
int _dns2_internal_tcp_recv (DNS2 *obj);
int _dns2_internal_tcp_next_or_error (DNS2 *obj);
void dns2_clean_network (DNS2 *obj);
double _dns2_diff_timespec (const struct timespec *time1, const struct timespec *time0) {
return (time1->tv_sec - time0->tv_sec)
+ (time1->tv_nsec - time0->tv_nsec) / 1000000000.0;
}
int dns2_async (DNS2 *obj) {
int length;
// checkServers
DNSRR *signature;
DNSQuestion *question;
int max_udp_size;
if (obj->state != DNS_STATE_FREE) {
return DNS_COMMAND_ERROR;
}
if (obj->packet == NULL) {
return DNS_COMMAND_ERROR;
}
/* Resetear el estado del paquete */
dns_packet_reset (obj->packet);
obj->current_ns = g_list_first (obj->nameservers);
obj->retry_count = 0;
if (obj->current_ns == NULL) {
return DNS_COMMAND_ERROR;
}
/* Actualizar el contador de paquetes */
dns_packet_recount_items (obj->packet);
if (obj->type == DNS_UPDATER && (obj->packet->header.qdcount == 0 || obj->packet->header.nscount == 0)) {
/* Error, nada que mandar en el update */
return DNS_COMMAND_ERROR;
}
max_udp_size = DNS_MAX_UDP_SIZE;
/* Si tiene una firma, generar la firma */
if (obj->auth_signature != NULL) {
if (obj->auth_signature->type == RR_TYPE_TSIG /*||
obj->auth_signature->tipo == RR_TYPE_SIG*/) {
signature = dns2_rr_dup (obj->auth_signature);
dns_rr_pack_generate_signature (signature, obj->packet);
obj->packet->additional = g_list_append (obj->packet->additional, signature);
obj->packet->header.arcount = g_list_length (obj->packet->additional);
}
max_udp_size = 4000;
}
dns_packet_pack_data (obj->packet);
/* ------ Para depuración ------
unsigned char *data;
length = dns_packet_get_data (obj->packet, &data);
for (int g = 0; g<length; g++) {
printf ("%02x", (unsigned int) data[g]);
}
printf ("\nEnd data.\n");
------ FIN de la depuración ------ */
/* Revisar el tipo de pregunta, algunos tipos de pregunta, como los ANY requieren TCP */
question = (DNSQuestion *) obj->packet->questions->data;
if (question->qtype == RR_TYPE_ANY || question->qtype == RR_TYPE_AXFR) {
obj->force_use_tcp = 1;
}
length = dns_packet_get_data (obj->packet, NULL);
if (length > max_udp_size || obj->force_use_tcp == 1) {
obj->state = DNS_STATE_TCP_CONNECT;
return _dns2_internal_tcp_start_connect (obj);
} else {
obj->state = DNS_STATE_UDP;
return _dns2_internal_udp_create (obj);
}
}
int dns2_async_continue (DNS2 *obj, int happen) {
if (obj->state == DNS_STATE_FREE) {
return DNS_COMMAND_ERROR;
}
if (obj->direction == DNS_POLL_WANT_READ) {
if ((happen & DNS_POLL_WANT_READ) == 0) {
return DNS_COMMAND_POLL;
}
switch (obj->state) {
case DNS_STATE_UDP:
return _dns2_internal_udp_recv (obj);
case DNS_STATE_TCP:
return _dns2_internal_tcp_recv (obj);
}
} else if (obj->direction == DNS_POLL_WANT_WRITE) {
if ((happen & DNS_POLL_WANT_WRITE) == 0) {
return DNS_COMMAND_POLL;
}
switch (obj->state) {
case DNS_STATE_TCP_CONNECT:
return _dns2_internal_tcp_connect_continue (obj);
case DNS_STATE_UDP:
return _dns2_internal_udp_send (obj);
case DNS_STATE_TCP:
return _dns2_internal_tcp_send (obj);
}
}
return DNS_COMMAND_ERROR;
}
int dns2_sync (DNS2 *obj) {
int res, res_poll;
struct pollfd poller;
int wanted_events;
int events;
res = dns2_async (obj);
while (res == DNS_COMMAND_POLL) {
poller.fd = dns2_get_socket (obj);
poller.revents = 0;
poller.events = 0;
/* FIXME AQUI */
wanted_events = dns2_get_socket_poll_flags (obj);
if (wanted_events == DNS_POLL_WANT_READ) {
poller.events = POLLIN;
} else if (wanted_events == DNS_POLL_WANT_WRITE) {
poller.events = POLLOUT;
}
res_poll = poll (&poller, 1, 30);
if (res_poll == 0) {
/* Si no ocurrió evento, mandar a llamar por timeouts: */
res = dns2_check_timeout (obj);
continue;
} else if (res_poll < 0) {
if (errno == EINTR) continue;
/* Caso contrario, salir */
res = DNS_COMMAND_ERROR;
break;
}
/* Ocurrió un evento, revisar cuál se quería y retornarlo para el continue */
if (poller.revents & POLLOUT) {
events = DNS_POLL_WANT_WRITE;
} else if (poller.revents & POLLIN) {
events = DNS_POLL_WANT_READ;
}
res = dns2_async_continue (obj, events);
}
/* Leer el paquete respuesta y regresarlo */
return res;
}
int _dns2_internal_tcp_start_connect (DNS2 *obj) {
NameServer *ns;
int flags;
int res;
ns = (NameServer *) obj->current_ns->data;
ns->socket = socket (ns->server_addr.ss_family, SOCK_STREAM, 0);
/* Si no pude crear un socket, ni siquiera intentar con el siguiente nameserver */
if (ns->socket < 0) {
obj->state = DNS_STATE_FREE;
obj->direction = DNS_POLL_WANT_NONE;
return DNS_COMMAND_ERROR;
}
/* Aplicar el no-bloqueante */
flags = fcntl (ns->socket, F_GETFL, 0);
flags = flags | O_NONBLOCK;
fcntl (ns->socket, F_SETFL, flags);
/* Ejecutar el connect */
res = connect (ns->socket, (struct sockaddr *) &ns->server_addr, ns->addr_size);
if (res < 0) {
if (errno == EINPROGRESS) {
obj->direction = DNS_POLL_WANT_WRITE;
return DNS_COMMAND_POLL;
}
return _dns2_internal_tcp_next_or_error (obj);
}
/* Tengo conexión, pasar de estado */
obj->state = DNS_STATE_TCP;
obj->direction = DNS_POLL_WANT_WRITE;
return dns2_async_continue (obj, DNS_POLL_WANT_WRITE);
}
int _dns2_internal_tcp_connect_continue (DNS2 *obj) {
int optval;
socklen_t optlen;
int res;
NameServer *ns;
ns = (NameServer *) obj->current_ns->data;
optlen = sizeof (optval);
res = getsockopt (ns->socket, SOL_SOCKET, SO_ERROR, &optval, &optlen);
if (res < 0 || optval != 0) {
return _dns2_internal_tcp_next_or_error (obj);
}
/* Pasar de estado */
obj->state = DNS_STATE_TCP;
obj->direction = DNS_POLL_WANT_WRITE;
return dns2_async_continue (obj, DNS_POLL_WANT_WRITE);
}
int _dns2_internal_tcp_next_or_error (DNS2 *obj) {
NameServer *ns;
ns = (NameServer *) obj->current_ns->data;
close (ns->socket);
ns->socket = -1;
obj->current_ns = g_list_next (obj->current_ns);
if (obj->current_ns != NULL) {
obj->state = DNS_STATE_TCP_CONNECT;
return _dns2_internal_tcp_start_connect (obj);
} else {
obj->state = DNS_STATE_FREE;
obj->direction = DNS_POLL_WANT_NONE;
/* Como ya no hay más nameservers en la lista, entregar error */
return DNS_COMMAND_ERROR;
}
}
int _dns2_internal_udp_next_or_error (DNS2 *obj) {
/* Los sockets UDP no se cierran, se dejan para usar después */
obj->current_ns = g_list_next (obj->current_ns);
if (obj->current_ns != NULL) {
/* Aún hay siguiente servidor, intentarlo con éste */
obj->state = DNS_STATE_UDP;
return _dns2_internal_udp_create (obj);
} else {
/* Como ya no hay mas en la lista, esperar al timeout */
obj->direction = DNS_POLL_WANT_READ;
return DNS_COMMAND_POLL;
}
}
int _dns2_internal_udp_send (DNS2 *obj) {
int res;
void *data;
int data_length;
NameServer *ns;
ns = (NameServer *) obj->current_ns->data;
data_length = dns_packet_get_data (obj->packet, (unsigned char **) &data);
clock_gettime (CLOCK_MONOTONIC, &obj->last_time_sent);
res = sendto (ns->socket, data, data_length, 0, (struct sockaddr *) &ns->server_addr, ns->addr_size);
if (res < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) {
obj->direction = DNS_POLL_WANT_WRITE;
return DNS_COMMAND_POLL;
}
return _dns2_internal_udp_next_or_error (obj);
} else if (res < data_length) {
/* Envié la petición incompleta, manejar este error */
return _dns2_internal_udp_next_or_error (obj);
}
/* Cambio de estado, esperar la lectura */
obj->direction = DNS_POLL_WANT_READ;
return dns2_async_continue (obj, DNS_POLL_WANT_READ);
}
int _dns2_internal_tcp_send (DNS2 *obj) {
int res;
void *data;
int data_length;
unsigned char data_plus[8192 + 2];
uint16_t t16;
NameServer *ns;
ns = (NameServer *) obj->current_ns->data;
data_length = dns_packet_get_data (obj->packet, (unsigned char **) &data);
/* En TCP, agregar la longitud previa a los datos */
t16 = htons (data_length);
memcpy (data_plus, &t16, 2);
memcpy (&data_plus[2], data, data_length);
res = write (ns->socket, data_plus, data_length + 2);
if (res < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) {
obj->direction = DNS_POLL_WANT_WRITE;
return DNS_COMMAND_POLL;
}
return _dns2_internal_tcp_next_or_error (obj);
} else if (res < data_length) {
/* Envié la petición incompleta, manejar este error */
return _dns2_internal_tcp_next_or_error (obj);
}
/* Cambio de estado, esperar la lectura */
obj->direction = DNS_POLL_WANT_READ;
return dns2_async_continue (obj, DNS_POLL_WANT_READ);
}
int _dns2_internal_udp_recv (DNS2 *obj) {
int res;
struct sockaddr_storage origen;
socklen_t origen_size;
unsigned char buffer[8192];
NameServer *ns;
ns = (NameServer *) obj->current_ns->data;
origen_size = sizeof (origen);
res = recvfrom (ns->socket, buffer, sizeof (buffer), 0, (struct sockaddr *) &origen, &origen_size);
if (res < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) {
obj->direction = DNS_POLL_WANT_READ;
return DNS_COMMAND_POLL;
}
/* ¿Otro error en un socket UDP? -> Mejor seguir esperando una lectura */
obj->direction = DNS_POLL_WANT_READ;
return DNS_COMMAND_POLL;
} /* Revisar que el paquete venga del mismo origen a donde envié la petición original */
else if (sockaddr_cmp ((struct sockaddr *) &origen, (struct sockaddr *) &ns->server_addr) == 0) {
/* Por el momento, dumpear la información
int g;
printf ("Respuesta recibida:\n");
for (g = 0; g < res; g++) {
printf ("%02x", ((unsigned int) buffer[g]));
}
printf ("\nEnd response\n");*/
if (obj->packet_response != NULL) {
dns_packet_free_full (obj->packet_response);
obj->packet_response = NULL;
}
obj->packet_response = dns_packet_create_response (SOCK_DGRAM, buffer, res);
if (obj->packet_response == NULL) {
/* Como no es válida la respuesta, pasar al siguiente servidor */
return _dns2_internal_udp_next_or_error (obj);
} else {
if (obj->packet_response->header.id != obj->packet->header.id) {
/* Esta no es la respuesta que esperaba */
obj->direction = DNS_POLL_WANT_READ;
return DNS_COMMAND_POLL;
}
/* Finalizar la petición */
dns2_clean_network (obj);
obj->state = DNS_STATE_FREE;
obj->direction = DNS_POLL_WANT_NONE;
return DNS_COMMAND_SUCCESS;
}
}
obj->direction = DNS_POLL_WANT_READ;
return DNS_COMMAND_POLL;
}
int _dns2_internal_tcp_recv (DNS2 *obj) {
int res;
uint16_t t16;
NameServer *ns;
ns = (NameServer *) obj->current_ns->data;
res = read (ns->socket, &obj->buffer_read[obj->buffer_read_pos], sizeof (obj->buffer_read) - obj->buffer_read_pos);
if (res < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) {
obj->direction = DNS_POLL_WANT_READ;
return DNS_COMMAND_POLL;
}
/* Si obtuvimos un error, pasar al siguiente servidor */
return _dns2_internal_tcp_next_or_error (obj);
} else if (res == 0) {
/* ¿Socket cerrado? -> Pasar al siguiente servidor */
return _dns2_internal_tcp_next_or_error (obj);
} else {
/* Por el momento, dumpear la información
int g;
printf ("Bytes recibidos por TCP:\n");
for (g = obj->buffer_read_pos; g < obj->buffer_read_pos + res; g++) {
printf ("%02x", ((unsigned int) obj->buffer_read[g]));
}
printf ("\nFin bytes\n");*/
/* Incrementar el buffer */
obj->buffer_read_pos = obj->buffer_read_pos + res;
/* Validar la longitud de los datos TCP aquí */
memcpy (&t16, obj->buffer_read, 2);
t16 = ntohs (t16);
if (t16 + 2 < obj->buffer_read_pos) {
/* Datos incompletos TCP, seguir haciendo POLL */
obj->direction = DNS_POLL_WANT_READ;
return DNS_COMMAND_POLL;
}
if (obj->packet_response != NULL) {
dns_packet_free_full (obj->packet_response);
obj->packet_response = NULL;
}
obj->packet_response = dns_packet_create_response (SOCK_STREAM, &obj->buffer_read[2], res - 2);
if (obj->packet_response == NULL) {
/* Error de parseo */
return _dns2_internal_tcp_next_or_error (obj);
} else {
/* If packet == good */
//dns2_updater_packet_has_valid_tsig (obj, packet);
/* Pasar de estado */
close (ns->socket);
ns->socket = -1;
obj->state = DNS_STATE_FREE;
obj->direction = DNS_POLL_WANT_NONE;
return DNS_COMMAND_SUCCESS;
}
}
}
int _dns2_internal_udp_create (DNS2 *obj) {
NameServer *ns;
int flags;
obj->direction = DNS_POLL_WANT_WRITE;
ns = (NameServer *) obj->current_ns->data;
/* Crear el socket UDP de una buena vez */
if (ns->socket < 0) {
ns->socket = socket (ns->server_addr.ss_family, SOCK_DGRAM, 0);
/* Aplicar el no-bloqueante */
flags = fcntl (ns->socket, F_GETFL, 0);
flags = flags | O_NONBLOCK;
fcntl (ns->socket, F_SETFL, flags);
}
/* Si no pude crear un socket, tenemos un serio problema */
if (ns->socket < 0) {
obj->state = DNS_STATE_FREE;
obj->direction = DNS_POLL_WANT_NONE;
return DNS_COMMAND_ERROR;
}
return dns2_async_continue (obj, DNS_POLL_WANT_WRITE);
}
void dns2_sign_tsig (DNS2 *obj, const char *keyname, const char *signature, const char *algorithm) {
DNSRR *rr;
/* TODO: Revisar si deberíamos liberar el auth_signature anterior */
rr = dns2_rr_create_tsig (keyname, signature, algorithm);
if (rr != NULL) {
obj->auth_signature = rr;
}
}
/* Crear un objeto */
DNS2 *dns2_new (void) {
DNS2 *obj;
obj = (DNS2 *) malloc (sizeof (DNS2));
if (obj == NULL) {
return NULL;
}
obj->packet = NULL;
obj->state = DNS_STATE_FREE;
obj->direction = DNS_POLL_WANT_NONE;
obj->nameservers = NULL;
obj->timeout = 5;
obj->current_ns = NULL;
obj->zone = NULL;
obj->force_use_tcp = 0;
obj->want_recursion = 0;
obj->buffer_read_pos = 0;
obj->auth_signature = NULL;
obj->packet_response = NULL;
return obj;
}
void dns2_add_nserver (DNS2 *obj, const char *ip) {
int res;
struct sockaddr_in v4;
struct sockaddr_in6 v6;
NameServer *ns;
memset (&v4, 0, sizeof (v4));
res = inet_pton (AF_INET, ip, &v4.sin_addr);
if (res > 0) {
/* Es una IP de 4 */
ns = (NameServer *) malloc (sizeof (NameServer));
if (ns == NULL) return;
v4.sin_family = AF_INET;
v4.sin_port = htons (53);
memcpy (&ns->server_addr, &v4, sizeof (v4));
ns->addr_size = sizeof (v4);
strncpy (ns->name, ip, sizeof (ns->name));
ns->socket = -1;
obj->nameservers = g_list_append (obj->nameservers, ns);
return;
}
memset (&v6, 0, sizeof (v6));
res = inet_pton (AF_INET6, ip, &v6.sin6_addr);
if (res > 0) {
ns = (NameServer *) malloc (sizeof (NameServer));
if (ns == NULL) return;
v6.sin6_family = AF_INET6;
v6.sin6_port = htons (53);
memcpy (&ns->server_addr, &v6, sizeof (v6));
ns->addr_size = sizeof (v6);
strncpy (ns->name, ip, sizeof (ns->name));
obj->nameservers = g_list_append (obj->nameservers, ns);
return;
}
}
void dns2_clean_network (DNS2 *obj) {
GList *g;
NameServer *ns;
for (g = obj->nameservers; g != NULL; g = g->next) {
ns = (NameServer *) g->data;
if (ns->socket >= 0) {
close (ns->socket);
ns->socket = -1;
}
}
}
void dns2_free (DNS2 *obj) {
/* Cerrar el socket, si es que hay alguno abierto */
dns2_clean_network (obj);
/* Liberar la lista de NS */
g_list_free_full (obj->nameservers, (GDestroyNotify) free);
if (obj->zone != NULL) {
free (obj->zone);
}
/* Ahora, buscar si el auth_signature ya está en la lista de additionals. Si lo está, no hay necesidad de liberarlo, se liberará cuando se libere el paquete */
if (obj->auth_signature != NULL) {
/* Liberar la firma, porque está fuera del paquete */
dns2_rr_free (obj->auth_signature);
obj->auth_signature = NULL;
}
if (obj->packet != NULL) {
dns_packet_free_full (obj->packet);
}
if (obj->packet_response != NULL) {
dns_packet_free_full (obj->packet_response);
}
free (obj);
}
int dns2_check_timeout (DNS2 *obj) {
struct timespec now;
double d;
if (obj->state != DNS_STATE_UDP) return DNS_COMMAND_POLL;
clock_gettime (CLOCK_MONOTONIC, &now);
d = _dns2_diff_timespec (&now, &obj->last_time_sent);
if (obj->current_ns->next == NULL) {
/* Si estamos al borde del último servidor, necesitamos el timeout largo */
if (d > (double) obj->timeout) {
/* Como cayó el timeout largo, incrementar los retries y regresar al primer servidor */
obj->retry_count++;
if (obj->retry_count >= 3) {
/* Ya agoté todos los reintentos y servidores, bye */
dns2_clean_network (obj);
obj->current_ns = NULL;
return DNS_COMMAND_ERROR;
}
obj->current_ns = obj->nameservers;
return _dns2_internal_udp_create (obj);
}
} else if (d > 1.0) {
/* 1 segundo entre servidor y servidor */
return _dns2_internal_udp_next_or_error (obj);
}
return DNS_COMMAND_POLL;
}
int dns2_get_socket (DNS2 *obj) {
NameServer *ns;
if (obj == NULL) return -1;
if (obj->state == DNS_STATE_FREE) return -1;
ns = (NameServer *) obj->current_ns->data;
return ns->socket;
}
int dns2_get_socket_poll_flags (DNS2 *obj) {
if (obj == NULL) return -1;
if (obj->direction == DNS_POLL_WANT_READ) {
return DNS_POLL_WANT_READ;
} else if (obj->direction == DNS_POLL_WANT_WRITE) {
return DNS_POLL_WANT_WRITE;
}
return 0;
}
void dns2_set_use_tcp (DNS2 *obj) {
if (obj == NULL) return;
obj->force_use_tcp = 1;
}
DNSRR **dns2_get_answers_from_response (DNS2 *obj) {
if (obj == NULL) return NULL;
if (obj->packet_response == NULL) return NULL;
return dns_packet_get_answers (obj->packet_response);
}
DNSRR **dns2_get_authority_from_response (DNS2 *obj) {
if (obj == NULL) return NULL;
if (obj->packet_response == NULL) return NULL;
return dns_packet_get_authority (obj->packet_response);
}
DNSRR **dns2_get_additional_from_response (DNS2 *obj) {
if (obj == NULL) return NULL;
if (obj->packet_response == NULL) return NULL;
return dns_packet_get_additional (obj->packet_response);
}
int dns2_get_rcode (DNS2 *obj) {
if (obj == NULL) return -1;
if (obj->packet_response == NULL) return -1;
return obj->packet_response->header.rcode;
}