/*
This file is part of GNUnet.
- (C) 2011-2013 Christian Grothoff (and other contributing authors)
+ Copyright (C) 2011-2013 GNUnet e.V.
GNUnet is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
You should have received a copy of the GNU General Public License
along with GNUnet; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- Boston, MA 02111-1307, USA.
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
*/
/**
* @brief GNU Name System resolver logic
* @author Martin Schanzenbach
* @author Christian Grothoff
- *
- * TODO:
- * - GNS: handle special SRV names --- no delegation, direct lookup;
- * can likely be done in 'resolver_lookup_get_next_label'. (#3003)
- * - revocation checks (use REVOCATION service!), (#3004)
- * - DNAME support (#3005)
*/
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_resolver_service.h"
#include "gnunet_revocation_service.h"
#include "gnunet_dnsparser_lib.h"
+#include "gnunet_tun_lib.h"
#include "gnunet_gns_service.h"
#include "gns.h"
#include "gnunet-service-gns_resolver.h"
/**
* Default timeout for DNS lookups.
*/
-#define DNS_LOOKUP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
+#define DNS_LOOKUP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
/**
* Default timeout for VPN redirections.
/**
* Expiration time for the DNS record, 0 if we didn't
- * get anything useful (i.e. 'gethostbyname' was used).
+ * get anything useful (i.e. 'gethostbyname()' was used).
*/
uint64_t expiration_time;
/**
* ID of a task associated with the resolution process.
*/
- GNUNET_SCHEDULER_TaskIdentifier task_id;
+ struct GNUNET_SCHEDULER_Task * task_id;
/**
* The name to resolve
/**
* Use only cache
*/
- int only_cached;
+ enum GNUNET_GNS_LocalOptions options;
+
+ /**
+ * For SRV and TLSA records, the number of the
+ * protocol specified in the name. 0 if no protocol was given.
+ */
+ int protocol;
+
+ /**
+ * For SRV and TLSA records, the number of the
+ * service specified in the name. 0 if no service was given.
+ */
+ int service;
/**
* Desired type for the resolution.
*/
static struct CacheOps *co_tail;
-
/**
- * Global configuration.
+ * Use namecache
*/
-static const struct GNUNET_CONFIGURATION_Handle *cfg;
+static int use_cache;
-#if 0
/**
- * Check if name is in srv format (_x._y.xxx)
- *
- * @param name
- * @return #GNUNET_YES if true
+ * Global configuration.
*/
-static int
-is_srv (const char *name)
-{
- char *ndup;
- int ret;
-
- if (*name != '_')
- return GNUNET_NO;
- if (NULL == strstr (name, "._"))
- return GNUNET_NO;
- ret = GNUNET_YES;
- ndup = GNUNET_strdup (name);
- strtok (ndup, ".");
- if (NULL == strtok (NULL, "."))
- ret = GNUNET_NO;
- if (NULL == strtok (NULL, "."))
- ret = GNUNET_NO;
- if (NULL != strtok (NULL, "."))
- ret = GNUNET_NO;
- GNUNET_free (ndup);
- return ret;
-}
-#endif
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Task scheduled to asynchronously fail a resolution.
*
* @param cls the 'struct GNS_ResolverHandle' of the resolution to fail
- * @param tc task context
*/
static void
-fail_resolution (void *cls,
- const struct GNUNET_SCHEDULER_TaskContext *tc)
+fail_resolution (void *cls)
{
struct GNS_ResolverHandle *rh = cls;
- rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+ rh->task_id = NULL;
rh->proc (rh->proc_cls, 0, NULL);
GNS_resolver_lookup_cancel (rh);
}
#if (defined WINDOWS) || (defined DARWIN)
/* Don't have this on W32, here's a naive implementation
* Was somehow removed on OS X ... */
-void *
+static void *
memrchr (const void *s,
int c,
size_t n)
/**
* Get the next, rightmost label from the name that we are trying to resolve,
- * and update the resolution position accordingly.
+ * and update the resolution position accordingly. Labels usually consist
+ * of up to 63 characters without a period ("."); however, we use a special
+ * convention to support SRV and TLSA records where the domain name
+ * includes an encoding for a service and protocol in the name. The
+ * syntax (see RFC 2782) here is "_Service._Proto.Name" and in this
+ * special case we include the "_Service._Proto" in the rightmost label.
+ * Thus, for "_443._tcp.foo.bar" we first return the label "bar" and then
+ * the label "_443._tcp.foo". The special case is detected by the
+ * presence of labels beginning with an underscore. Whenever a label
+ * begins with an underscore, it is combined with the label to its right
+ * (and the "." is preserved).
*
* @param rh handle to the resolution operation to get the next label from
* @return NULL if there are no more labels
const char *rp;
const char *dot;
size_t len;
+ char *ret;
+ char *srv_name;
+ char *proto_name;
+ struct protoent *pe;
+ struct servent *se;
if (0 == rh->name_resolution_pos)
return NULL;
- dot = memrchr (rh->name, (int) '.', rh->name_resolution_pos);
+ dot = memrchr (rh->name,
+ (int) '.',
+ rh->name_resolution_pos);
if (NULL == dot)
{
/* done, this was the last one */
rp = dot + 1;
rh->name_resolution_pos = dot - rh->name;
}
- return GNUNET_strndup (rp, len);
+ rh->protocol = 0;
+ rh->service = 0;
+ ret = GNUNET_strndup (rp, len);
+ /* If we have labels starting with underscore with label on
+ * the right (SRV/DANE/BOX case), determine port/protocol;
+ * The format of `rh->name` must be "_PORT._PROTOCOL".
+ */
+ if ( ('_' == rh->name[0]) &&
+ (NULL != (dot = memrchr (rh->name,
+ (int) '.',
+ rh->name_resolution_pos))) &&
+ ('_' == dot[1]) &&
+ (NULL == memrchr (rh->name,
+ (int) '.',
+ dot - rh->name)) )
+ {
+ srv_name = GNUNET_strndup (&rh->name[1],
+ (dot - rh->name) - 1);
+ proto_name = GNUNET_strndup (&dot[2],
+ rh->name_resolution_pos - (dot - rh->name) - 1);
+ rh->name_resolution_pos = 0;
+ pe = getprotobyname (proto_name);
+ if (NULL == pe)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ _("Protocol `%s' unknown, skipping labels.\n"),
+ proto_name);
+ GNUNET_free (proto_name);
+ GNUNET_free (srv_name);
+ return ret;
+ }
+ se = getservbyname (srv_name,
+ proto_name);
+ if (NULL == se)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ _("Service `%s' unknown for protocol `%s', skipping labels.\n"),
+ srv_name,
+ proto_name);
+ GNUNET_free (proto_name);
+ GNUNET_free (srv_name);
+ return ret;
+ }
+ rh->protocol = pe->p_proto;
+ rh->service = se->s_port;
+ }
+ return ret;
}
const struct sockaddr_in *sa4;
const struct sockaddr_in6 *sa6;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Received %u bytes of DNS IP data\n",
- addrlen);
if (NULL == addr)
{
rh->std_resolve = NULL;
transmit_lookup_dns_result (rh);
return;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received %u bytes of DNS IP data\n",
+ addrlen);
switch (addr->sa_family)
{
case AF_INET:
* @param tc task context
*/
static void
-recursive_resolution (void *cls,
- const struct GNUNET_SCHEDULER_TaskContext *tc);
+recursive_resolution (void *cls);
/**
rh->dns_request = NULL;
GNUNET_SCHEDULER_cancel (rh->task_id);
- rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+ rh->task_id = NULL;
p = GNUNET_DNSPARSER_parse ((const char *) dns,
dns_len);
if (NULL == p)
{
GNUNET_free (rh->name);
rh->name = GNUNET_strdup (p->answers[0].data.hostname);
+ rh->name_resolution_pos = strlen (rh->name);
start_resolver_lookup (rh);
GNUNET_DNSPARSER_free_packet (p);
return;
}
- /* FIXME: add DNAME support */
/* convert from (parsed) DNS to (binary) GNS format! */
rd_count = p->num_answers + p->num_authority_records + p->num_additional_records;
else if (i < p->num_answers + p->num_authority_records)
rec = &p->authority_records[i - p->num_answers];
else
- rec = &p->authority_records[i - p->num_answers - p->num_authority_records];
+ rec = &p->additional_records[i - p->num_answers - p->num_authority_records];
/* As we copied the full DNS name to 'rh->ac_tail->label', this
should be the correct check to see if this record is actually
a record for our label... */
/* name is absolute, start from the beginning */
GNUNET_free (rh->name);
rh->name = GNUNET_strdup (cname);
+ rh->name_resolution_pos = strlen (rh->name);
start_resolver_lookup (rh);
}
case GNUNET_GNSRECORD_TYPE_VPN:
{
af = (GNUNET_DNSPARSER_TYPE_A == rh->record_type) ? AF_INET : AF_INET6;
- if (sizeof (struct GNUNET_TUN_GnsVpnRecord) <
+ if (sizeof (struct GNUNET_TUN_GnsVpnRecord) >
rd[i].data_size)
{
GNUNET_break_op (0);
GNS_resolver_lookup_cancel (rh);
return;
}
- GNUNET_CRYPTO_hash (vname,
- strlen (vname), // FIXME: +1?
- &vhash);
+ GNUNET_TUN_service_name_to_hash (vname,
+ &vhash);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Attempting VPN allocation for %s-%s (AF: %d, proto %d)\n",
+ GNUNET_i2s (&vpn->peer),
+ vname,
+ (int) af,
+ (int) ntohs (vpn->proto));
vpn_ctx = GNUNET_new (struct VpnContext);
rh->vpn_ctx = vpn_ctx;
vpn_ctx->rh = rh;
vpn_ctx->rd_data_size = GNUNET_GNSRECORD_records_get_size (rd_count,
rd);
vpn_ctx->rd_data = GNUNET_malloc (vpn_ctx->rd_data_size);
- (void) GNUNET_GNSRECORD_records_serialize (rd_count,
- rd,
- vpn_ctx->rd_data_size,
- vpn_ctx->rd_data);
+ vpn_ctx->rd_count = rd_count;
+ GNUNET_assert (vpn_ctx->rd_data_size ==
+ GNUNET_GNSRECORD_records_serialize (rd_count,
+ rd,
+ vpn_ctx->rd_data_size,
+ vpn_ctx->rd_data));
vpn_ctx->vpn_request = GNUNET_VPN_redirect_to_peer (vpn_handle,
af,
ntohs (vpn->proto),
&vhash,
GNUNET_TIME_relative_to_absolute (VPN_TIMEOUT),
&vpn_allocation_cb,
- rh);
+ vpn_ctx);
return;
}
case GNUNET_GNSRECORD_TYPE_GNS2DNS:
shorten_ac = rh->ac_tail;
for (i=0;i<rd_count;i++)
{
+ if ( (0 != rh->protocol) &&
+ (0 != rh->service) &&
+ (GNUNET_GNSRECORD_TYPE_BOX != rd[i].record_type) )
+ continue; /* we _only_ care about boxed records */
+
rd_new[rd_off] = rd[i];
/* Check if the embedded name(s) end in "+", and if so,
replace the "+" with the zone at "ac_tail", changing the name
struct GNUNET_DNSPARSER_SrvRecord *srv;
off = 0;
- /* FIXME: passing rh->name here is is not necessarily what we want
- (SRV support not finished) */
- srv = GNUNET_DNSPARSER_parse_srv (rh->name,
- rd[i].data,
+ srv = GNUNET_DNSPARSER_parse_srv (rd[i].data,
rd[i].data_size,
&off);
if ( (NULL == srv) ||
}
else
{
- srv->domain_name = translate_dot_plus (rh, srv->domain_name);
srv->target = translate_dot_plus (rh, srv->target);
scratch_start = scratch_off;
if (GNUNET_OK !=
case GNUNET_GNSRECORD_TYPE_NICK:
{
const char *nick;
+
nick = rd[i].data;
if ((rd[i].data_size > 0) &&
(nick[rd[i].data_size -1] != '\0'))
case GNUNET_GNSRECORD_TYPE_GNS2DNS:
{
/* delegation to DNS */
+ if (GNUNET_GNSRECORD_TYPE_GNS2DNS == rh->record_type)
+ {
+ rd_off++;
+ break; /* do not follow to DNS, we wanted the GNS2DNS record! */
+ }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found GNS2DNS record, delegating to DNS!\n");
goto do_recurse;
}
+ case GNUNET_GNSRECORD_TYPE_BOX:
+ {
+ /* unbox SRV/TLSA records if a specific one was requested */
+ if ( (0 != rh->protocol) &&
+ (0 != rh->service) &&
+ (rd[i].data_size >= sizeof (struct GNUNET_GNSRECORD_BoxRecord)) )
+ {
+ const struct GNUNET_GNSRECORD_BoxRecord *box;
+
+ box = rd[i].data;
+ if ( (ntohs (box->protocol) == rh->protocol) &&
+ (ntohs (box->service) == rh->service) )
+ {
+ /* Box matches, unbox! */
+ rd_new[rd_off].record_type = ntohl (box->record_type);
+ rd_new[rd_off].data_size -= sizeof (struct GNUNET_GNSRECORD_BoxRecord);
+ rd_new[rd_off].data = &box[1];
+ rd_off++;
+ }
+ }
+ else
+ {
+ /* no specific protocol/service specified, preserve all BOX
+ records (for modern, GNS-enabled applications) */
+ rd_off++;
+ }
+ }
default:
rd_off++;
break;
resolver to use */
g2dc = GNUNET_new (struct Gns2DnsContext);
g2dc->ns = ns;
+
g2dc->rh = GNUNET_new (struct GNS_ResolverHandle);
g2dc->rh->authority_zone = rh->ac_tail->authority_info.gns_authority;
ip = translate_dot_plus (rh, ip);
g2dc->rh->proc = &handle_gns2dns_result;
g2dc->rh->proc_cls = rh;
g2dc->rh->record_type = GNUNET_GNSRECORD_TYPE_ANY;
- g2dc->rh->only_cached = GNUNET_NO;
+ g2dc->rh->options = GNUNET_GNS_LO_DEFAULT;
g2dc->rh->loop_limiter = rh->loop_limiter + 1;
rh->g2dc = g2dc;
start_resolver_lookup (g2dc->rh);
}
+/**
+ * Initiate a DHT query for a set of GNS records.
+ *
+ * @param rh resolution handle
+ * @param query key to use in the DHT lookup
+ */
+static void
+start_dht_request (struct GNS_ResolverHandle *rh,
+ const struct GNUNET_HashCode *query)
+{
+ struct GNS_ResolverHandle *rx;
+
+ GNUNET_assert (NULL == rh->get_handle);
+ rh->get_handle = GNUNET_DHT_get_start (dht_handle,
+ GNUNET_BLOCK_TYPE_GNS_NAMERECORD,
+ query,
+ DHT_GNS_REPLICATION_LEVEL,
+ GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
+ NULL, 0,
+ &handle_dht_response, rh);
+ rh->dht_heap_node = GNUNET_CONTAINER_heap_insert (dht_lookup_heap,
+ rh,
+ GNUNET_TIME_absolute_get ().abs_value_us);
+ if (GNUNET_CONTAINER_heap_get_size (dht_lookup_heap) > max_allowed_background_queries)
+ {
+ /* fail longest-standing DHT request */
+ rx = GNUNET_CONTAINER_heap_peek (dht_lookup_heap);
+ GNUNET_assert (NULL != rx);
+ rx->proc (rx->proc_cls, 0, NULL);
+ GNS_resolver_lookup_cancel (rx);
+ }
+}
+
+
+/**
+ * Process a records that were decrypted from a block that we got from
+ * the namecache. Simply calls #handle_gns_resolution_result().
+ *
+ * @param cls closure with the `struct GNS_ResolverHandle`
+ * @param rd_count number of entries in @a rd array
+ * @param rd array of records with data to store
+ */
+static void
+handle_gns_namecache_resolution_result (void *cls,
+ unsigned int rd_count,
+ const struct GNUNET_GNSRECORD_Data *rd)
+{
+ struct GNS_ResolverHandle *rh = cls;
+
+ handle_gns_resolution_result (rh,
+ rd_count,
+ rd);
+}
+
+
/**
* Process a record that was stored in the namecache.
*
const struct GNUNET_GNSRECORD_Block *block)
{
struct GNS_ResolverHandle *rh = cls;
- struct GNS_ResolverHandle *rx;
struct AuthorityChain *ac = rh->ac_tail;
const char *label = ac->label;
const struct GNUNET_CRYPTO_EcdsaPublicKey *auth = &ac->authority_info.gns_authority;
struct GNUNET_HashCode query;
- GNUNET_GNSRECORD_query_from_public_key (auth,
- label,
- &query);
GNUNET_assert (NULL != rh->namecache_qe);
rh->namecache_qe = NULL;
- if ( (GNUNET_NO == rh->only_cached) &&
+ if ( ( (GNUNET_GNS_LO_DEFAULT == rh->options) ||
+ ( (GNUNET_GNS_LO_LOCAL_MASTER == rh->options) &&
+ (ac != rh->ac_head) ) ) &&
( (NULL == block) ||
(0 == GNUNET_TIME_absolute_get_remaining (GNUNET_TIME_absolute_ntoh (block->expiration_time)).rel_value_us) ) )
{
/* namecache knows nothing; try DHT lookup */
+ GNUNET_GNSRECORD_query_from_public_key (auth,
+ label,
+ &query);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting DHT lookup for `%s' in zone `%s' under key `%s'\n",
- ac->label,
- GNUNET_GNSRECORD_z2s (&ac->authority_info.gns_authority),
- GNUNET_h2s (&query));
- GNUNET_assert (NULL == rh->get_handle);
- rh->get_handle = GNUNET_DHT_get_start (dht_handle,
- GNUNET_BLOCK_TYPE_GNS_NAMERECORD,
- &query,
- DHT_GNS_REPLICATION_LEVEL,
- GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
- NULL, 0,
- &handle_dht_response, rh);
- rh->dht_heap_node = GNUNET_CONTAINER_heap_insert (dht_lookup_heap,
- rh,
- GNUNET_TIME_absolute_get ().abs_value_us);
- if (GNUNET_CONTAINER_heap_get_size (dht_lookup_heap) > max_allowed_background_queries)
- {
- /* fail longest-standing DHT request */
- rx = GNUNET_CONTAINER_heap_peek (dht_lookup_heap);
- GNUNET_assert (NULL != rx);
- rx->proc (rx->proc_cls, 0, NULL);
- GNS_resolver_lookup_cancel (rx);
- }
+ "Starting DHT lookup for `%s' in zone `%s' under key `%s'\n",
+ ac->label,
+ GNUNET_GNSRECORD_z2s (&ac->authority_info.gns_authority),
+ GNUNET_h2s (&query));
+ start_dht_request (rh, &query);
return;
}
GNUNET_GNSRECORD_block_decrypt (block,
auth,
label,
- &handle_gns_resolution_result,
+ &handle_gns_namecache_resolution_result,
rh))
{
GNUNET_break_op (0); /* block was ill-formed */
- rh->proc (rh->proc_cls, 0, NULL);
- GNS_resolver_lookup_cancel (rh);
+ /* try DHT instead */
+ GNUNET_GNSRECORD_query_from_public_key (auth,
+ label,
+ &query);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting DHT lookup for `%s' in zone `%s' under key `%s'\n",
+ ac->label,
+ GNUNET_GNSRECORD_z2s (&ac->authority_info.gns_authority),
+ GNUNET_h2s (&query));
+ start_dht_request (rh, &query);
return;
}
}
GNUNET_GNSRECORD_query_from_public_key (&ac->authority_info.gns_authority,
ac->label,
&query);
- rh->namecache_qe = GNUNET_NAMECACHE_lookup_block (namecache_handle,
- &query,
- &handle_namecache_block_response,
- rh);
- GNUNET_assert (NULL != rh->namecache_qe);
+ if (GNUNET_YES == use_cache)
+ {
+ rh->namecache_qe
+ = GNUNET_NAMECACHE_lookup_block (namecache_handle,
+ &query,
+ &handle_namecache_block_response,
+ rh);
+ GNUNET_assert (NULL != rh->namecache_qe);
+ }
+ else
+ {
+ start_dht_request (rh, &query);
+ }
}
* Task scheduled to continue with the resolution process.
*
* @param cls the `struct GNS_ResolverHandle` of the resolution
- * @param tc task context
*/
static void
-recursive_resolution (void *cls,
- const struct GNUNET_SCHEDULER_TaskContext *tc)
+recursive_resolution (void *cls)
{
struct GNS_ResolverHandle *rh = cls;
- rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+ rh->task_id = NULL;
if (MAX_RECURSION < rh->loop_limiter++)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
* @param record_type the record type to look up
* @param name the name to look up
* @param shorten_key a private key for use with PSEU import (can be NULL)
- * @param only_cached #GNUNET_NO to only check locally not DHT for performance
+ * @param options local options to control local lookup
* @param proc the processor to call on result
* @param proc_cls the closure to pass to @a proc
* @return handle to cancel operation
uint32_t record_type,
const char *name,
const struct GNUNET_CRYPTO_EcdsaPrivateKey *shorten_key,
- int only_cached,
+ enum GNUNET_GNS_LocalOptions options,
GNS_ResultProcessor proc, void *proc_cls)
{
struct GNS_ResolverHandle *rh;
rh->authority_zone = *zone;
rh->proc = proc;
rh->proc_cls = proc_cls;
- rh->only_cached = only_cached;
+ rh->options = options;
rh->record_type = record_type;
rh->name = GNUNET_strdup (name);
rh->name_resolution_pos = strlen (name);
GNUNET_free (rh->g2dc);
rh->g2dc = NULL;
}
- if (GNUNET_SCHEDULER_NO_TASK != rh->task_id)
+ if (NULL != rh->task_id)
{
GNUNET_SCHEDULER_cancel (rh->task_id);
- rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+ rh->task_id = NULL;
}
if (NULL != rh->get_handle)
{
dht_lookup_heap =
GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
max_allowed_background_queries = max_bg_queries;
+ if (GNUNET_SYSERR == (use_cache = GNUNET_CONFIGURATION_get_value_yesno (c,
+ "gns",
+ "USE_CACHE")))
+ use_cache = GNUNET_YES;
+ if (GNUNET_NO == use_cache)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Namecache disabled\n");
+
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (c,
"gns",