/*
* Copyright (c) 2017-2024 Free Software Foundation, Inc.
*
* This file is part of libwget.
*
* Libwget is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Libwget 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libwget. If not, see .
*/
#include
#include
#include
#include
#include
#include "private.h"
/**
* \file
* \brief Functions for DNS caching
* \defgroup libwget-dns-caching DNS caching
*
* @{
*
* DNS cache management functions.
*
*/
/* Resolver / DNS cache entry */
struct cache_entry {
const char *
host;
struct addrinfo *
addrinfo;
uint16_t
port;
};
struct wget_dns_cache_st {
wget_hashmap
*cache;
wget_thread_mutex
mutex;
};
#ifdef __clang__
__attribute__((no_sanitize("integer")))
#endif
static unsigned int WGET_GCC_PURE hash_dns(const struct cache_entry *entry)
{
unsigned int hash = entry->port;
const unsigned char *p = (unsigned char *) entry->host;
while (*p)
hash = hash * 101 + *p++;
return hash;
}
static int WGET_GCC_PURE compare_dns(const struct cache_entry *a1, const struct cache_entry *a2)
{
if (a1->port < a2->port)
return -1;
if (a1->port > a2->port)
return 1;
return wget_strcasecmp(a1->host, a2->host);
}
static void free_dns(struct cache_entry *entry)
{
freeaddrinfo(entry->addrinfo);
xfree(entry);
}
/**
* \param[out] cache Pointer to return newly allocated and initialized wget_dns_cache instance
* \return WGET_E_SUCCESS if OK, WGET_E_MEMORY if out-of-memory or WGET_E_INVALID
* if the mutex initialization failed.
*
* Allocates and initializes a wget_dns_cache instance.
*/
int wget_dns_cache_init(wget_dns_cache **cache)
{
wget_dns_cache *_cache = wget_calloc(1, sizeof(wget_dns_cache));
if (!_cache)
return WGET_E_MEMORY;
if (wget_thread_mutex_init(&_cache->mutex)) {
xfree(_cache);
return WGET_E_INVALID;
}
if (!(_cache->cache = wget_hashmap_create(16, (wget_hashmap_hash_fn *) hash_dns, (wget_hashmap_compare_fn *) compare_dns))) {
wget_dns_cache_free(&_cache);
return WGET_E_MEMORY;
}
wget_hashmap_set_key_destructor(_cache->cache, (wget_hashmap_key_destructor *) free_dns);
wget_hashmap_set_value_destructor(_cache->cache, (wget_hashmap_value_destructor *) free_dns);
*cache = _cache;
return WGET_E_SUCCESS;
}
/**
* \param[in/out] cache Pointer to wget_dns_cache instance that will be freed and NULLified.
*
* Free the resources allocated by wget_dns_cache_init().
*/
void wget_dns_cache_free(wget_dns_cache **cache)
{
if (cache && *cache) {
wget_thread_mutex_lock((*cache)->mutex);
wget_hashmap_free(&(*cache)->cache);
wget_thread_mutex_unlock((*cache)->mutex);
wget_thread_mutex_destroy(&(*cache)->mutex);
xfree(*cache);
}
}
/**
* \param[in] cache A `wget_dns_cache` instance, created by wget_dns_cache_init().
* \param[in] host Hostname to look up
* \param[in] port Port to look up
* \return The cached addrinfo structure or NULL if not found
*/
struct addrinfo *wget_dns_cache_get(wget_dns_cache *cache, const char *host, uint16_t port)
{
if (cache) {
struct cache_entry *entryp, entry = { .host = host, .port = port };
wget_thread_mutex_lock(cache->mutex);
if (!wget_hashmap_get(cache->cache, &entry, &entryp))
entryp = NULL;
wget_thread_mutex_unlock(cache->mutex);
if (entryp) {
// DNS cache entry found
if (wget_ip_is_family(entryp->host, WGET_NET_FAMILY_IPV6))
debug_printf("Found dns cache entry [%s]:%d\n", entryp->host, entryp->port);
else
debug_printf("Found dns cache entry %s:%d\n", entryp->host, entryp->port);
return entryp->addrinfo;
}
}
return NULL;
}
/**
* \param[in] cache A `wget_dns_cache` instance, created by wget_dns_cache_init().
* \param[in] host Hostname part of the key
* \param[in] port Port part of the key
* \param[in/out] addrinfo Addrinfo structure to cache, returns cached addrinfo
* \return WGET_E_SUCCESS on success, else a WGET_E_* error value
*
* This functions adds \p addrinfo to the given DNS cache \p cache.
*
* If an entry for [host,port] already exists, \p addrinfo is free'd and replaced by the cached entry.
* Do not free \p addrinfo yourself - this will be done when the whole cache is freed.
*/
int wget_dns_cache_add(wget_dns_cache *cache, const char *host, uint16_t port, struct addrinfo **addrinfo)
{
if (!cache || !host || !addrinfo)
return WGET_E_INVALID;
struct cache_entry entry = { .host = host, .port = port };
struct cache_entry *entryp;
wget_thread_mutex_lock(cache->mutex);
if (wget_hashmap_get(cache->cache, &entry, &entryp)) {
// host+port is already in cache
wget_thread_mutex_unlock(cache->mutex);
if (*addrinfo != entryp->addrinfo)
freeaddrinfo(*addrinfo);
*addrinfo = entryp->addrinfo;
return WGET_E_SUCCESS;
}
// insert addrinfo into dns cache
size_t hostlen = strlen(host) + 1;
entryp = wget_malloc(sizeof(struct cache_entry) + hostlen);
if (!entryp) {
wget_thread_mutex_unlock(cache->mutex);
return WGET_E_MEMORY;
}
entryp->port = port;
entryp->host = (char *)(entryp + 1);
memcpy((char *)entryp->host, host, hostlen); // ugly cast, but semantically ok
entryp->addrinfo = *addrinfo;
// key and value are the same to make wget_hashmap_get() return old entry
wget_hashmap_put(cache->cache, entryp, entryp);
wget_thread_mutex_unlock(cache->mutex);
return WGET_E_SUCCESS;
}
/** @} */