/*
This file is part of GNUnet.
Copyright (C) 2012-2013 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet 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
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @file gnunet-dns2gns.c
* @brief DNS server that translates DNS requests to GNS
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include
#include "gns.h"
/**
* Timeout for DNS requests.
*/
#define TIMEOUT GNUNET_TIME_UNIT_MINUTES
/**
* Data kept per request.
*/
struct Request
{
/**
* Socket to use for sending the reply.
*/
struct GNUNET_NETWORK_Handle *lsock;
/**
* Destination address to use.
*/
const void *addr;
/**
* Initially, this is the DNS request, it will then be
* converted to the DNS response.
*/
struct GNUNET_DNSPARSER_Packet *packet;
/**
* Our GNS request handle.
*/
struct GNUNET_GNS_LookupWithTldRequest *lookup;
/**
* Our DNS request handle
*/
struct GNUNET_DNSSTUB_RequestSocket *dns_lookup;
/**
* Task run on timeout or shutdown to clean up without
* response.
*/
struct GNUNET_SCHEDULER_Task *timeout_task;
/**
* Original UDP request message.
*/
char *udp_msg;
/**
* Number of bytes in @e addr.
*/
size_t addr_len;
/**
* Number of bytes in @e udp_msg.
*/
size_t udp_msg_size;
/**
* ID of the original request.
*/
uint16_t original_request_id;
};
/**
* The address to bind to
*/
static in_addr_t address;
/**
* The IPv6 address to bind to
*/
static struct in6_addr address6;
/**
* Handle to GNS resolver.
*/
struct GNUNET_GNS_Handle *gns;
/**
* Stub resolver
*/
struct GNUNET_DNSSTUB_Context *dns_stub;
/**
* Listen socket for IPv4.
*/
static struct GNUNET_NETWORK_Handle *listen_socket4;
/**
* Listen socket for IPv6.
*/
static struct GNUNET_NETWORK_Handle *listen_socket6;
/**
* Task for IPv4 socket.
*/
static struct GNUNET_SCHEDULER_Task *t4;
/**
* Task for IPv6 socket.
*/
static struct GNUNET_SCHEDULER_Task *t6;
/**
* IP of DNS server
*/
static char *dns_ip;
/**
* UDP Port we listen on for inbound DNS requests.
*/
static unsigned int listen_port = 53;
/**
* Configuration to use.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Task run on shutdown. Cleans up everything.
*
* @param cls unused
*/
static void
do_shutdown (void *cls)
{
(void) cls;
if (NULL != t4)
{
GNUNET_SCHEDULER_cancel (t4);
t4 = NULL;
}
if (NULL != t6)
{
GNUNET_SCHEDULER_cancel (t6);
t6 = NULL;
}
if (NULL != listen_socket4)
{
GNUNET_NETWORK_socket_close (listen_socket4);
listen_socket4 = NULL;
}
if (NULL != listen_socket6)
{
GNUNET_NETWORK_socket_close (listen_socket6);
listen_socket6 = NULL;
}
if (NULL != gns)
{
GNUNET_GNS_disconnect (gns);
gns = NULL;
}
if (NULL != dns_stub)
{
GNUNET_DNSSTUB_stop (dns_stub);
dns_stub = NULL;
}
}
/**
* Shuffle answers
* Fisher-Yates (aka Knuth) Shuffle
*
* @param request context for the request (with answers)
*/
static void
shuffle_answers (struct Request *request)
{
unsigned int idx = request->packet->num_answers;
unsigned int r_idx;
struct GNUNET_DNSPARSER_Record tmp_answer;
while (0 != idx)
{
r_idx = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
request->packet->num_answers);
idx--;
tmp_answer = request->packet->answers[idx];
memcpy (&request->packet->answers[idx], &request->packet->answers[r_idx],
sizeof (struct GNUNET_DNSPARSER_Record));
memcpy (&request->packet->answers[r_idx], &tmp_answer,
sizeof (struct GNUNET_DNSPARSER_Record));
}
}
/**
* Send the response for the given request and clean up.
*
* @param request context for the request.
*/
static void
send_response (struct Request *request)
{
char *buf;
size_t size;
ssize_t sret;
shuffle_answers (request);
if (GNUNET_SYSERR ==
GNUNET_DNSPARSER_pack (request->packet,
UINT16_MAX /* is this not too much? */,
&buf,
&size))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Failed to pack DNS response into UDP packet!\n"));
}
else
{
sret = GNUNET_NETWORK_socket_sendto (request->lsock,
buf,
size,
request->addr,
request->addr_len);
if ((sret < 0) ||
(size != (size_t) sret))
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
"sendto");
GNUNET_free (buf);
}
GNUNET_SCHEDULER_cancel (request->timeout_task);
GNUNET_DNSPARSER_free_packet (request->packet);
GNUNET_free (request->udp_msg);
GNUNET_free (request);
}
/**
* Task run on timeout. Cleans up request.
*
* @param cls `struct Request *` of the request to clean up
*/
static void
do_timeout (void *cls)
{
struct Request *request = cls;
if (NULL != request->packet)
GNUNET_DNSPARSER_free_packet (request->packet);
if (NULL != request->lookup)
GNUNET_GNS_lookup_with_tld_cancel (request->lookup);
if (NULL != request->dns_lookup)
GNUNET_DNSSTUB_resolve_cancel (request->dns_lookup);
GNUNET_free (request->udp_msg);
GNUNET_free (request);
}
/**
* Iterator called on obtained result for a DNS lookup
*
* @param cls closure
* @param dns the DNS udp payload
* @param r size of the DNS payload
*/
static void
dns_result_processor (void *cls,
const struct GNUNET_TUN_DnsHeader *dns,
size_t r)
{
struct Request *request = cls;
if (NULL == dns)
{
/* DNSSTUB gave up, so we trigger timeout early */
GNUNET_SCHEDULER_cancel (request->timeout_task);
do_timeout (request);
return;
}
if (request->original_request_id != dns->id)
{
/* for a another query, ignore */
return;
}
request->packet = GNUNET_DNSPARSER_parse ((char *) dns,
r);
if (NULL == request->packet)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Failed to parse DNS response!\n"));
GNUNET_SCHEDULER_cancel (request->timeout_task);
do_timeout (request);
return;
}
GNUNET_DNSSTUB_resolve_cancel (request->dns_lookup);
send_response (request);
}
/**
* Iterator called on obtained result for a GNS lookup.
*
* @param cls closure
* @param was_gns #GNUNET_NO if the TLD is not configured for GNS
* @param rd_count number of records in @a rd
* @param rd the records in reply
*/
static void
result_processor (void *cls,
int was_gns,
uint32_t rd_count,
const struct GNUNET_GNSRECORD_Data *rd)
{
struct Request *request = cls;
struct GNUNET_DNSPARSER_Packet *packet;
struct GNUNET_DNSPARSER_Record rec;
request->lookup = NULL;
if (GNUNET_NO == was_gns)
{
/* TLD not configured for GNS, fall back to DNS */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Using DNS resolver IP `%s' to resolve `%s'\n",
dns_ip,
request->packet->queries[0].name);
request->original_request_id = request->packet->id;
GNUNET_DNSPARSER_free_packet (request->packet);
request->packet = NULL;
request->dns_lookup = GNUNET_DNSSTUB_resolve (dns_stub,
request->udp_msg,
request->udp_msg_size,
&dns_result_processor,
request);
return;
}
packet = request->packet;
packet->flags.query_or_response = 1;
packet->flags.return_code = GNUNET_TUN_DNS_RETURN_CODE_NO_ERROR;
packet->flags.checking_disabled = 0;
packet->flags.authenticated_data = 1;
packet->flags.zero = 0;
packet->flags.recursion_available = 1;
packet->flags.message_truncated = 0;
packet->flags.authoritative_answer = 0;
// packet->flags.opcode = GNUNET_TUN_DNS_OPCODE_STATUS; // ???
for (uint32_t i = 0; i < rd_count; i++)
{
rec.expiration_time.abs_value_us = rd[i].expiration_time;
switch (rd[i].record_type)
{
case GNUNET_DNSPARSER_TYPE_A:
GNUNET_assert (sizeof(struct in_addr) == rd[i].data_size);
rec.name = GNUNET_strdup (packet->queries[0].name);
rec.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
rec.type = GNUNET_DNSPARSER_TYPE_A;
rec.data.raw.data = GNUNET_new (struct in_addr);
GNUNET_memcpy (rec.data.raw.data,
rd[i].data,
rd[i].data_size);
rec.data.raw.data_len = sizeof(struct in_addr);
GNUNET_array_append (packet->answers,
packet->num_answers,
rec);
break;
case GNUNET_DNSPARSER_TYPE_AAAA:
GNUNET_assert (sizeof(struct in6_addr) == rd[i].data_size);
rec.name = GNUNET_strdup (packet->queries[0].name);
rec.data.raw.data = GNUNET_new (struct in6_addr);
rec.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
rec.type = GNUNET_DNSPARSER_TYPE_AAAA;
GNUNET_memcpy (rec.data.raw.data,
rd[i].data,
rd[i].data_size);
rec.data.raw.data_len = sizeof(struct in6_addr);
GNUNET_array_append (packet->answers,
packet->num_answers,
rec);
break;
case GNUNET_DNSPARSER_TYPE_CNAME:
rec.name = GNUNET_strdup (packet->queries[0].name);
rec.data.hostname = GNUNET_strdup (rd[i].data);
rec.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
rec.type = GNUNET_DNSPARSER_TYPE_CNAME;
GNUNET_memcpy (rec.data.hostname,
rd[i].data,
rd[i].data_size);
GNUNET_array_append (packet->answers,
packet->num_answers,
rec);
break;
default:
/* skip */
break;
}
}
send_response (request);
}
/**
* Handle DNS request.
*
* @param lsock socket to use for sending the reply
* @param addr address to use for sending the reply
* @param addr_len number of bytes in @a addr
* @param udp_msg DNS request payload
* @param udp_msg_size number of bytes in @a udp_msg
*/
static void
handle_request (struct GNUNET_NETWORK_Handle *lsock,
const void *addr,
size_t addr_len,
const char *udp_msg,
size_t udp_msg_size)
{
struct Request *request;
struct GNUNET_DNSPARSER_Packet *packet;
packet = GNUNET_DNSPARSER_parse (udp_msg,
udp_msg_size);
if (NULL == packet)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Cannot parse DNS request from %s\n"),
GNUNET_a2s (addr, addr_len));
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received request for `%s' with flags %u, #answers %d, #auth %d, #additional %d\n",
packet->queries[0].name,
(unsigned int) packet->flags.query_or_response,
(int) packet->num_answers,
(int) packet->num_authority_records,
(int) packet->num_additional_records);
if ((0 != packet->flags.query_or_response) ||
(0 != packet->num_answers) ||
(0 != packet->num_authority_records))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Received malformed DNS request from %s\n"),
GNUNET_a2s (addr, addr_len));
GNUNET_DNSPARSER_free_packet (packet);
return;
}
if ((1 != packet->num_queries))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Received unsupported DNS request from %s\n"),
GNUNET_a2s (addr,
addr_len));
GNUNET_DNSPARSER_free_packet (packet);
return;
}
request = GNUNET_malloc (sizeof(struct Request) + addr_len);
request->lsock = lsock;
request->packet = packet;
request->addr = &request[1];
request->addr_len = addr_len;
GNUNET_memcpy (&request[1],
addr,
addr_len);
request->udp_msg_size = udp_msg_size;
request->udp_msg = GNUNET_memdup (udp_msg,
udp_msg_size);
request->timeout_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
&do_timeout,
request);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Calling GNS on `%s'\n",
packet->queries[0].name);
request->lookup = GNUNET_GNS_lookup_with_tld (gns,
packet->queries[0].name,
packet->queries[0].type,
GNUNET_GNS_LO_DEFAULT,
&result_processor,
request);
}
/**
* Task to read IPv4 DNS packets.
*
* @param cls the 'listen_socket4'
*/
static void
read_dns4 (void *cls)
{
struct sockaddr_in v4;
socklen_t addrlen;
ssize_t size;
const struct GNUNET_SCHEDULER_TaskContext *tc;
GNUNET_assert (listen_socket4 == cls);
t4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
listen_socket4,
&read_dns4,
listen_socket4);
tc = GNUNET_SCHEDULER_get_task_context ();
if (0 == (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason))
return; /* shutdown? */
size = GNUNET_NETWORK_socket_recvfrom_amount (listen_socket4);
if (0 > size)
{
GNUNET_break (0);
return; /* read error!? */
}
{
char buf[size + 1];
ssize_t sret;
addrlen = sizeof(v4);
sret = GNUNET_NETWORK_socket_recvfrom (listen_socket4,
buf,
size + 1,
(struct sockaddr *) &v4,
&addrlen);
if (0 > sret)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
"recvfrom");
return;
}
GNUNET_break (size == sret);
handle_request (listen_socket4,
&v4,
addrlen,
buf,
size);
}
}
/**
* Task to read IPv6 DNS packets.
*
* @param cls the 'listen_socket6'
*/
static void
read_dns6 (void *cls)
{
struct sockaddr_in6 v6;
socklen_t addrlen;
ssize_t size;
const struct GNUNET_SCHEDULER_TaskContext *tc;
GNUNET_assert (listen_socket6 == cls);
t6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
listen_socket6,
&read_dns6,
listen_socket6);
tc = GNUNET_SCHEDULER_get_task_context ();
if (0 == (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason))
return; /* shutdown? */
size = GNUNET_NETWORK_socket_recvfrom_amount (listen_socket6);
if (0 > size)
{
GNUNET_break (0);
return; /* read error!? */
}
{
char buf[size];
ssize_t sret;
addrlen = sizeof(v6);
sret = GNUNET_NETWORK_socket_recvfrom (listen_socket6,
buf,
size,
(struct sockaddr *) &v6,
&addrlen);
if (0 > sret)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
"recvfrom");
return;
}
GNUNET_break (size == sret);
handle_request (listen_socket6,
&v6,
addrlen,
buf,
size);
}
}
/**
* Main function that will be run.
*
* @param cls closure
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param c configuration
*/
static void
run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
char *addr_str;
(void) cls;
(void) args;
(void) cfgfile;
cfg = c;
if (NULL == dns_ip)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ ("No DNS server specified!\n"));
return;
}
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
if (NULL == (gns = GNUNET_GNS_connect (cfg)))
return;
GNUNET_assert (NULL != (dns_stub = GNUNET_DNSSTUB_start (128)));
if (GNUNET_OK !=
GNUNET_DNSSTUB_add_dns_ip (dns_stub,
dns_ip))
{
GNUNET_DNSSTUB_stop (dns_stub);
GNUNET_GNS_disconnect (gns);
gns = NULL;
return;
}
/* Get address to bind to */
if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (c, "dns2gns",
"BIND_TO",
&addr_str))
{
// No address specified
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Don't know what to bind to...\n");
GNUNET_free (addr_str);
GNUNET_SCHEDULER_shutdown ();
return;
}
if (1 != inet_pton (AF_INET, addr_str, &address))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unable to parse address %s\n",
addr_str);
GNUNET_free (addr_str);
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_free (addr_str);
/* Get address to bind to */
if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (c, "dns2gns",
"BIND_TO6",
&addr_str))
{
// No address specified
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Don't know what to bind6 to...\n");
GNUNET_free (addr_str);
GNUNET_SCHEDULER_shutdown ();
return;
}
if (1 != inet_pton (AF_INET6, addr_str, &address6))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unable to parse IPv6 address %s\n",
addr_str);
GNUNET_free (addr_str);
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_free (addr_str);
listen_socket4 = GNUNET_NETWORK_socket_create (PF_INET,
SOCK_DGRAM,
IPPROTO_UDP);
if (NULL != listen_socket4)
{
struct sockaddr_in v4;
memset (&v4, 0, sizeof(v4));
v4.sin_family = AF_INET;
v4.sin_addr.s_addr = address;
#if HAVE_SOCKADDR_IN_SIN_LEN
v4.sin_len = sizeof(v4);
#endif
v4.sin_port = htons (listen_port);
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (listen_socket4,
(struct sockaddr *) &v4,
sizeof(v4)))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind");
GNUNET_NETWORK_socket_close (listen_socket4);
listen_socket4 = NULL;
}
}
listen_socket6 = GNUNET_NETWORK_socket_create (PF_INET6,
SOCK_DGRAM,
IPPROTO_UDP);
if (NULL != listen_socket6)
{
struct sockaddr_in6 v6;
memset (&v6, 0, sizeof(v6));
v6.sin6_family = AF_INET6;
v6.sin6_addr = address6;
#if HAVE_SOCKADDR_IN_SIN_LEN
v6.sin6_len = sizeof(v6);
#endif
v6.sin6_port = htons (listen_port);
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (listen_socket6,
(struct sockaddr *) &v6,
sizeof(v6)))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind");
GNUNET_NETWORK_socket_close (listen_socket6);
listen_socket6 = NULL;
}
}
if ((NULL == listen_socket4) &&
(NULL == listen_socket6))
{
GNUNET_GNS_disconnect (gns);
gns = NULL;
GNUNET_DNSSTUB_stop (dns_stub);
dns_stub = NULL;
return;
}
if (NULL != listen_socket4)
t4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
listen_socket4,
&read_dns4,
listen_socket4);
if (NULL != listen_socket6)
t6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
listen_socket6,
&read_dns6,
listen_socket6);
}
/**
* The main function for the dns2gns daemon.
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, 1 on error
*/
int
main (int argc,
char *const *argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_string ('d',
"dns",
"IP",
gettext_noop (
"IP of recursive DNS resolver to use (required)"),
&dns_ip),
GNUNET_GETOPT_option_uint ('p',
"port",
"UDPPORT",
gettext_noop (
"UDP port to listen on for inbound DNS requests; default: 2853"),
&listen_port),
GNUNET_GETOPT_OPTION_END
};
int ret;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
return 2;
GNUNET_log_setup ("gnunet-dns2gns",
"WARNING",
NULL);
ret =
(GNUNET_OK ==
GNUNET_PROGRAM_run (argc, argv,
"gnunet-dns2gns",
_ ("GNUnet DNS-to-GNS proxy (a DNS server)"),
options,
&run, NULL)) ? 0 : 1;
GNUNET_free_nz ((void *) argv);
return ret;
}
/* end of gnunet-dns2gns.c */