diff --git a/src/context.h b/src/context.h index 2267c06e..69eb4d21 100644 --- a/src/context.h +++ b/src/context.h @@ -351,7 +351,6 @@ struct getdns_context { int mdns_connection_nb; /* typically 0 or 2 for IPv4 and IPv6 */ struct mdns_network_connection * mdns_connection; struct lruhash * mdns_cache; - _getdns_rbtree_t mdns_continuous_queries_by_name_rrtype; #endif /* HAVE_MDNS_SUPPORT */ }; /* getdns_context */ diff --git a/src/mdns.c b/src/mdns.c index f21bea38..eec9c740 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -263,9 +263,32 @@ static void msdn_cache_delkey(void* vkey, void* vcontext) GETDNS_FREE(((struct getdns_context *) vcontext)->mf, vkey); } -/** old data is deleted. This function is called: func(data, userarg). */ +/** old data is deleted. This function is called: func(data, userarg). + * Since we use the hash table for both data and requests, need to + * terminate whatever request was ongoing. TODO: we should have some smarts + * in cache management and never drop cached entries with active requests. + */ static void msdn_cache_deldata(void* vdata, void* vcontext) { + /* need to terminate the pending queries? */ + getdns_mdns_cached_record_header* header = ((getdns_mdns_cached_record_header*)vdata); + + while (header->netreq_first) + { + /* Need to unchain the request from that entry */ + getdns_network_req* netreq = header->netreq_first; + header->netreq_first = netreq->mdns_netreq_next; + netreq->mdns_netreq_next = NULL; + + /* TODO: treating as a timeout for now, may consider treating as error */ + netreq->debug_end_time = _getdns_get_time_as_uintt64(); + netreq->state = NET_REQ_TIMED_OUT; + if (netreq->owner->user_callback) { + (void)_getdns_context_request_timed_out(netreq->owner); + } + _getdns_check_dns_req_complete(netreq->owner); + + } GETDNS_FREE(((struct getdns_context *) vcontext)->mf, vdata); } @@ -309,7 +332,7 @@ static uint8_t * mdns_cache_create_data( { getdns_mdns_cached_record_header * header; int current_index; - size_t data_size = sizeof(getdns_mdns_cached_record_header) + name_len + 4; + size_t data_size = sizeof(getdns_mdns_cached_record_header) + 12 + name_len + 4; size_t alloc_size = mdns_util_suggest_size(data_size + record_data_len + 2 + 2 + 2 + 4 + 2); uint8_t* data = GETDNS_XMALLOC(context->mf, uint8_t, alloc_size); @@ -320,7 +343,10 @@ static uint8_t * mdns_cache_create_data( header->insertion_microsec = current_time; header->content_len = data_size; header->allocated_length = alloc_size; + header->netreq_first = NULL; current_index = sizeof(getdns_mdns_cached_record_header); + memset(data + current_index, 0, 12); + current_index += 12; memcpy(data + current_index, name, name_len); current_index += name_len; data[current_index++] = (uint8_t)(record_type >> 8); @@ -553,6 +579,7 @@ mdns_propose_entry_to_cache( uint8_t * name, int name_len, int record_type, int record_class, int ttl, uint8_t * record_data, int record_data_len, + getdns_network_req * netreq, uint64_t current_time) { int ret = 0; @@ -561,6 +588,7 @@ mdns_propose_entry_to_cache( hashvalue_type hash; struct lruhash_entry *entry, *new_entry; uint8_t *key, *data; + getdns_mdns_cached_record_header * header; msdn_cache_create_key_in_buffer(temp_key, name, name_len, record_type, record_class); @@ -621,10 +649,19 @@ mdns_propose_entry_to_cache( if (entry != NULL) { - ret = mdns_update_cache_ttl_and_prune(context, - (uint8_t*)entry->data, &data, - record_type, record_class, ttl, record_data, record_data_len, - current_time); + if (record_data != NULL && record_data_len > 0) + ret = mdns_update_cache_ttl_and_prune(context, + (uint8_t*)entry->data, &data, + record_type, record_class, ttl, record_data, record_data_len, + current_time); + + if (netreq != NULL) + { + /* chain the continuous request to the cache line */ + header = (getdns_mdns_cached_record_header *) entry->data; + netreq->mdns_netreq_next = header->netreq_first; + header->netreq_first = netreq; + } /* then, unlock the entry */ lock_rw_unlock(entry->lock); @@ -635,482 +672,6 @@ mdns_propose_entry_to_cache( return ret; } -#if 0 -/* - * Add record function for the MDNS record cache. - */ -static int -mdns_add_record_to_cache(struct getdns_context *context, - uint8_t * name, int name_len, - int record_type, int record_class, int ttl, - uint8_t * record_data, int record_data_len) -{ - int ret = 0; - - /* First, format a key */ - - /* Next, get the record from the LRU cache */ - - /* If there is no record, need to create one */ - - /* Else, just do simple update */ - - return ret; -} - - -/* -* MDNS cache management. -* -* This is provisional, until we can get a proper cache by reusing the outbound code. -* -* The cache is a collection of getdns_mdns_known_record structures, each holding -* one record: name, type, class, ttl, rdata length and rdata, plus a 64 bit time stamp. -* The collection is organized as an RB tree. The comparison function in mdns_cmp_known_records -* is somewhat arbitrary: compare by numeric fields class, type, name length, and -* record length first, then by name value, and then by record value. -* -* When a message is received, it will be parsed, and each valid record in the -* ANSWER, AUTH or additional section will be added to the cache. If there is already -* a matching record, only the TTL and time stamp will be updated. If the new TTL is zero, -* the matching record will be removed from the cache. -* -* When a request is first presented, we will try to solve it from the cache. If there -* is response present, we will format a reply containing all the matching records, with -* an updated TTL. If the updated TTL is negative, the record will be deleted from the -* cache and will not be added to the query. -* -* For continuing requests, we may need to send queries with the list of known answers. -* These will be extracted from the cache. -* -* We may want to periodically check all the records in the cache and remove all those -* that have expired. This could be treated as a garbage collection task. -* -* The cache is emptied and deleted upon context deletion. -* -* In a multithreaded environment, we will assume that whoever accesses the cache holds a -* on the context. This is not ideal, but fine grain locks can lead to all kinds of -* synchronization issues. -*/ - -/* - * Compare function for the getdns_mdns_known_record type, - * used in the red-black tree of known records per query. - */ - -static int mdns_cmp_known_records(const void * nkr1, const void * nkr2) -{ - int ret = 0; - getdns_mdns_known_record * kr1 = (getdns_mdns_known_record *)nkr1; - getdns_mdns_known_record * kr2 = (getdns_mdns_known_record *)nkr2; - - if (kr1->record_class != kr2->record_class) - { - ret = (kr1->record_class < kr2->record_class) ? -1 : 1; - } - else if (kr1->record_type != kr2->record_type) - { - ret = (kr1->record_type < kr2->record_type) ? -1 : 1; - } - else if (kr1->name_len != kr2->name_len) - { - ret = (kr1->name_len < kr2->name_len) ? -1 : 1; - } - else if (kr1->record_data_len != kr2->record_data_len) - { - ret = (kr1->record_data_len < kr2->record_data_len) ? -1 : 1; - } - else if ((ret = memcmp((void*)kr1->name, (void*)kr2->name, kr2->name_len)) == 0) - { - ret = memcmp((const void*)kr1->record_data, (const void*)kr2->record_data, kr1->record_data_len); - } - - return ret; -} - -/* - * Add record function for the MDNS record cache. - */ -static int -mdns_add_known_record_to_cache( -struct getdns_context *context, - uint8_t * name, int name_len, - int record_type, int record_class, int ttl, - uint8_t * record_data, int record_data_len) -{ - int ret = 0; - getdns_mdns_known_record temp, *record, *inserted_record; - size_t required_memory = 0; - _getdns_rbnode_t * node_found, *to_delete; - - temp.name = name; - temp.name_len = name_len; - temp.record_class = record_class; - temp.record_type = record_type; - temp.record_data = record_data; - temp.record_data_len = record_data_len; - - - if (ttl == 0) - { - to_delete = _getdns_rbtree_delete( - &context->mdns_known_records_by_value, &temp); - - if (to_delete != NULL) - { - GETDNS_FREE(context->mf, to_delete->key); - } - } - else - { - node_found = _getdns_rbtree_search(&context->mdns_known_records_by_value, &temp); - - if (node_found != NULL) - { - record = (getdns_mdns_known_record *)node_found->key; - record->ttl = ttl; - record->insertion_microsec = _getdns_get_time_as_uintt64(); - } - else - { - required_memory = sizeof(getdns_mdns_known_record) + name_len + record_data_len; - - record = (getdns_mdns_known_record *) - GETDNS_XMALLOC(context->mf, uint8_t, required_memory); - - if (record == NULL) - { - ret = GETDNS_RETURN_MEMORY_ERROR; - } - else - { - record->node.parent = NULL; - record->node.left = NULL; - record->node.right = NULL; - record->node.key = (void*)record; - record->record_class = temp.record_class; - record->name = ((uint8_t*)record) + sizeof(getdns_mdns_known_record); - record->name_len = name_len; - record->record_class = record_class; - record->record_type = record_type; - record->record_data = record->name + name_len; - record->record_data_len = record_data_len; - record->insertion_microsec = _getdns_get_time_as_uintt64(); - - memcpy(record->name, name, name_len); - memcpy(record->record_data, record_data, record_data_len); - - inserted_record = (getdns_mdns_known_record *) - _getdns_rbtree_insert(&context->mdns_known_records_by_value, - &record->node); - if (inserted_record == NULL) - { - /* Weird. This can only happen in a race condition */ - GETDNS_FREE(context->mf, record); - ret = GETDNS_RETURN_GENERIC_ERROR; - } - } - } - } - - return ret; -} - -/* - * Get the position of the first matching record in the cache - */ -static _getdns_rbnode_t * -mdns_get_first_record_from_cache( -struct getdns_context *context, - uint8_t * name, int name_len, - int record_type, int record_class, - getdns_mdns_known_record* next_key) -{ - /* First, get a search key */ - getdns_mdns_known_record temp; - _getdns_rbnode_t *next_node; - - if (next_key == NULL) - { - temp.name = name; - temp.name_len = name_len; - temp.record_class = record_class; - temp.record_type = record_type; - temp.record_data = name; - temp.record_data_len = 0; - - next_key = &temp; - } - - /* - * Find the starting point - */ - if (!_getdns_rbtree_find_less_equal( - &context->mdns_known_records_by_value, - next_key, - &next_node)) - { - /* - * The key was not an exact match. Need to find the first node larger - * than the key. - */ - if (next_node == NULL) - { - /* - * The key was smallest than the smallest key in the tree, or the tree is empty - */ - next_node = _getdns_rbtree_first(&context->mdns_known_records_by_value); - } - else - { - /* - * Search retrurned a key smaller than target, so we pick the next one. - */ - next_node = _getdns_rbtree_next(next_node); - } - } - - /* - * At this point, we do not check that this is the right node - */ - - return next_node; -} - -/* - * Count the number of records for this name and type, purging those that have expired. - * Return the first valid node in the set, or NULL if there are no such nodes. - */ -static _getdns_rbnode_t * -mdns_count_and_purge_record_from_cache( - struct getdns_context *context, - uint8_t * name, int name_len, - int record_type, int record_class, - _getdns_rbnode_t* next_node, - uint64_t current_time_microsec, - int *nb_records, int *total_content) -{ - int current_record = 0; - int record_length_sum = 0; - int valid_ttl = 0; - getdns_mdns_known_record *next_key; - _getdns_rbnode_t *first_valid_node = NULL, *old_node = NULL; - - while (next_node) - { - next_key = (getdns_mdns_known_record *)next_node->key; - if (next_key->name_len != name_len || - next_key->record_class != record_class || - next_key->record_type != record_type || - memcmp(next_key->name, name, name_len) != 0) - { - next_node = NULL; - next_key = NULL; - break; - } - - old_node = next_node; - next_node = _getdns_rbtree_next(next_node); - - if (next_key->insertion_microsec + (((uint64_t)next_key->ttl) << 20) >= - current_time_microsec) - { - current_record++; - record_length_sum += next_key->record_data_len; - - if (first_valid_node == NULL) - { - first_valid_node = old_node; - } - } - else - { - _getdns_rbnode_t * deleted = _getdns_rbtree_delete( - &context->mdns_known_records_by_value, next_key); - - if (deleted != NULL) - { - GETDNS_FREE(context->mf, next_key); - } - } - } - - *nb_records = current_record; - *total_content = record_length_sum; - - return first_valid_node; -} - -/* - * Fill a response buffer with the records present in the set, - * up to a limit - */ -_getdns_rbnode_t * -mdns_fill_response_buffer_from_cache( -struct getdns_context *context, - uint8_t * name, int name_len, - int record_type, int record_class, - uint8_t * response, int* response_len, int response_len_max, - int * nb_records, - _getdns_rbnode_t* next_node, - uint64_t current_time_microsec, - int name_offset - ) -{ - int current_length = 0; - getdns_mdns_known_record * next_key; - int64_t ttl_64; - int32_t current_ttl; - int current_record = 0; - int coding_length; - int name_coding_length = (name_offset < 0) ? name_len : 2; - - while (next_node) - { - next_key = (getdns_mdns_known_record *)next_node->key; - if (next_key->name_len != name_len || - next_key->record_class != record_class || - next_key->record_type != record_type || - memcmp(next_key->name, name, name_len) != 0) - { - next_node = NULL; - next_key = NULL; - break; - } - - /* - * Compute the required size. - */ - coding_length = name_len + name_coding_length + 2 + 4 + 2 + next_key->record_data_len; - - if (current_length + coding_length > response_len_max) - { - break; - } - - /* - * encode the record with the updated TTL - */ - - ttl_64 = current_time_microsec - next_key->insertion_microsec; - ttl_64 += (((uint64_t)next_key->ttl) << 20); - - if (ttl_64 > 0) - { - current_ttl = max(ttl_64 >> 20, 1); - } - else - { - current_ttl = 1; - } - - if (name_offset >= 0) - { - /* Perform name compression, per DNS spec */ - response[current_length++] = (uint8_t)((0xC0 | (name_offset >> 8)) & 0xFF); - response[current_length++] = (uint8_t)(name_offset & 0xFF); - } - else - { - memcpy(response + current_length, name, name_len); - current_length += name_len; - } - response[current_length++] = (uint8_t)((record_type >> 8) & 0xFF); - response[current_length++] = (uint8_t)((record_type)& 0xFF); - response[current_length++] = (uint8_t)((record_class >> 8) & 0xFF); - response[current_length++] = (uint8_t)((record_class)& 0xFF); - response[current_length++] = (uint8_t)((current_ttl >> 24) & 0xFF); - response[current_length++] = (uint8_t)((current_ttl >> 16) & 0xFF); - response[current_length++] = (uint8_t)((current_ttl >> 8) & 0xFF); - response[current_length++] = (uint8_t)((current_ttl)& 0xFF); - response[current_length++] = (uint8_t)((next_key->record_data_len >> 8) & 0xFF); - response[current_length++] = (uint8_t)((next_key->record_data_len)& 0xFF); - memcpy(response + current_length, next_key->record_data, next_key->record_data_len); - current_length += next_key->record_data_len; - - /* - * continue with the next node - */ - current_record++; - next_node = _getdns_rbtree_next(next_node); - } - - *response_len = current_length; - *nb_records = current_record; - - return next_node; -} - -/* - * Compose a response from the MDNS record cache. - */ -static int -mdns_compose_response_from_cache( - struct getdns_context *context, - uint8_t * name, int name_len, - int record_type, int record_class, - uint8_t ** response, int* response_len, int response_len_max, - int query_id, - getdns_mdns_known_record** next_key) -{ - int ret = 0; - /* First, get a search key */ - int nb_records = 0; - int total_content = 0; - int total_length = 0; - uint64_t current_time = _getdns_get_time_as_uintt64(); - - _getdns_rbnode_t * first_node = mdns_get_first_record_from_cache( - context, name, name_len, record_type, record_class, *next_key); - /* Purge the expired records and compute the desired length */ - first_node = mdns_count_and_purge_record_from_cache( - context, name, name_len, record_type, record_class, first_node, current_time, - &nb_records, &total_content); - - /* todo: check whether encoding an empty message is OK */ - /* todo: check whether something special is needed for continuation records */ - - /* Allocate the required memory */ - total_length = 12 /* DNS header */ - + name_len + 4 /* Query */ - + total_content + (2 + 2 + 2 + 4 + 2)*nb_records /* answers */; - /* TODO: do we need EDNS encoding? */ - - if (response_len_max == 0) - { - /* setting this parameter to zero indicates that a full buffer allocation is desired */ - { - if (*response == NULL) - { - *response = GETDNS_XMALLOC(context->mf, uint8_t, total_length); - } - else - { - *response = GETDNS_XREALLOC(context->mf, *response, uint8_t, total_length); - } - - if (*response == NULL) - { - ret = GETDNS_RETURN_MEMORY_ERROR; - } - else - { - response_len_max = total_length; - } - } - } - if (ret == 0) - { - - /* - * Now, proceed with the encoding - */ - } -} -#endif /* if 0, remove the RB based cache code */ - - - /* * Compare function for the mdns_continuous_query_by_name_rrtype, * used in the red-black tree of all ongoing queries. @@ -1344,7 +905,7 @@ static getdns_return_t mdns_delayed_network_init(struct getdns_context *context) if (context->mdns_extended_support == 2) { - context->mdns_cache = lruhash_create(100, 1000, + context->mdns_cache = lruhash_create(128, 10000000, mdns_cache_entry_size, mdns_cache_key_comp, msdn_cache_delkey, msdn_cache_deldata, context); @@ -1435,64 +996,12 @@ static getdns_return_t mdns_delayed_network_init(struct getdns_context *context) static getdns_return_t mdns_initialize_continuous_request(getdns_network_req *netreq) { int ret = 0; - getdns_mdns_continuous_query temp_query, *continuous_query, *inserted_query; getdns_dns_req *dnsreq = netreq->owner; struct getdns_context *context = dnsreq->context; - _getdns_rbnode_t * node_found; - /* - * Fill the target request, but only initialize name and request_type - */ - temp_query.request_class = dnsreq->request_class; - temp_query.request_type = netreq->request_type; - temp_query.name_len = dnsreq->name_len; - /* TODO: check that dnsreq is in canonical form */ - memcpy(temp_query.name, dnsreq->name, dnsreq->name_len); - /* - * Check whether the continuous query is already in the RB tree. - * if there is not, create one. - * TODO: should lock the context object when doing that. - */ - node_found = _getdns_rbtree_search(&context->mdns_continuous_queries_by_name_rrtype, &temp_query); - - if (node_found != NULL) - { - continuous_query = (getdns_mdns_continuous_query *)node_found->key; - } - else - { - continuous_query = (getdns_mdns_continuous_query *) - GETDNS_MALLOC(context->mf, getdns_mdns_continuous_query); - if (continuous_query != NULL) - { - continuous_query->node.parent = NULL; - continuous_query->node.left = NULL; - continuous_query->node.right = NULL; - continuous_query->node.key = (void*)continuous_query; - continuous_query->request_class = temp_query.request_class; - continuous_query->request_type = temp_query.request_type; - continuous_query->name_len = temp_query.name_len; - memcpy(continuous_query->name, temp_query.name, temp_query.name_len); - continuous_query->netreq_first = NULL; - /* Add the new continuous query to the context */ - inserted_query = (getdns_mdns_continuous_query *) - _getdns_rbtree_insert(&context->mdns_continuous_queries_by_name_rrtype, - &continuous_query->node); - if (inserted_query == NULL) - { - /* Weird. This can only happen in a race condition */ - GETDNS_FREE(context->mf, &continuous_query); - ret = GETDNS_RETURN_GENERIC_ERROR; - } - } - else - { - ret = GETDNS_RETURN_MEMORY_ERROR; - } - } - /* insert netreq into query list */ - netreq->mdns_netreq_next = continuous_query->netreq_first; - continuous_query->netreq_first = netreq; + ret = mdns_propose_entry_to_cache(context, dnsreq->name, dnsreq->name_len, + netreq->request_type, dnsreq->request_class, 1, NULL, 0, + netreq, _getdns_get_time_as_uintt64()); /* to do: queue message request to socket */ @@ -1508,8 +1017,6 @@ void _getdns_mdns_context_init(struct getdns_context *context) context->mdns_connection = NULL; context->mdns_connection_nb = 0; context->mdns_cache = NULL; - _getdns_rbtree_init(&context->mdns_continuous_queries_by_name_rrtype - , mdns_cmp_continuous_queries_by_name_rrtype); } /* @@ -1517,11 +1024,31 @@ void _getdns_mdns_context_init(struct getdns_context *context) */ void _getdns_mdns_context_destroy(struct getdns_context *context) { - /* Close the sockets */ + /* Clear all the cached records. This will terminate all pending network requests */ + if (context->mdns_cache != NULL) + { + lruhash_delete(context->mdns_cache); + context->mdns_cache = NULL; + } + /* Close the connections */ + if (context->mdns_connection != NULL) + { + for (int i = 0; i < context->mdns_connection_nb; i++) + { + /* suppress the receive event */ + GETDNS_CLEAR_EVENT(context->extension, &context->mdns_connection[i].event); + /* close the socket */ +#ifdef USE_WINSOCK + closesocket(context->mdns_connection[i].fd); +#else + close(context->mdns_connection[i].fd); +#endif + } - /* Clear all the continuous queries */ - - /* Clear all the cached records */ + GETDNS_FREE(context->mf, context->mdns_connection); + context->mdns_connection = NULL; + context->mdns_connection_nb = 0; + } } /* TODO: actualy delete what is required.. */ diff --git a/src/mdns.h b/src/mdns.h index d5cc9f79..9642bde1 100644 --- a/src/mdns.h +++ b/src/mdns.h @@ -72,6 +72,8 @@ typedef struct getdns_mdns_cached_record_header uint64_t insertion_microsec; uint32_t content_len; uint32_t allocated_length; + /* list of user queries */ + getdns_network_req *netreq_first; } getdns_mdns_cached_record_header; typedef struct getdns_mdns_continuous_query diff --git a/src/types-internal.h b/src/types-internal.h index 162762e4..8f3e7ce2 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -192,12 +192,9 @@ typedef struct getdns_network_req _getdns_rbnode_t node; #ifdef HAVE_MDNS_SUPPORT /* - * for storage in continuous query context. We never - * expect much more than one query per msdn context, - * so no need for RB Tree. + * for storage of continuous query context in hash table of cached results. */ struct getdns_network_req * mdns_netreq_next; - struct getdns_mdns_continuous_query * mdns_continuous_query; #endif /* HAVE_MDNS_SUPPORT */ /* the async_id from unbound */ int unbound_id;