Coverage Report

Created: 2025-01-28 06:58

/src/wget2/libwget/dns_cache.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2017-2024 Free Software Foundation, Inc.
3
 *
4
 * This file is part of libwget.
5
 *
6
 * Libwget is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * Libwget is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with libwget.  If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
#include <config.h>
21
22
#include <stdlib.h>
23
#include <string.h>
24
#include <netdb.h>
25
26
#include <wget.h>
27
#include "private.h"
28
29
/**
30
 * \file
31
 * \brief Functions for DNS caching
32
 * \defgroup libwget-dns-caching DNS caching
33
 *
34
 * @{
35
 *
36
 * DNS cache management functions.
37
 *
38
 */
39
40
/* Resolver / DNS cache entry */
41
struct cache_entry {
42
  const char *
43
    host;
44
  struct addrinfo *
45
    addrinfo;
46
  uint16_t
47
    port;
48
};
49
50
struct wget_dns_cache_st {
51
  wget_hashmap
52
    *cache;
53
  wget_thread_mutex
54
    mutex;
55
};
56
57
#ifdef __clang__
58
__attribute__((no_sanitize("integer")))
59
#endif
60
static unsigned int WGET_GCC_PURE hash_dns(const struct cache_entry *entry)
61
0
{
62
0
  unsigned int hash = entry->port;
63
0
  const unsigned char *p = (unsigned char *) entry->host;
64
65
0
  while (*p)
66
0
    hash = hash * 101 + *p++;
67
68
0
  return hash;
69
0
}
70
71
static int WGET_GCC_PURE compare_dns(const struct cache_entry *a1, const struct cache_entry *a2)
72
0
{
73
0
  if (a1->port < a2->port)
74
0
    return -1;
75
0
  if (a1->port > a2->port)
76
0
    return 1;
77
78
0
  return wget_strcasecmp(a1->host, a2->host);
79
0
}
80
81
static void free_dns(struct cache_entry *entry)
82
0
{
83
0
  freeaddrinfo(entry->addrinfo);
84
0
  xfree(entry);
85
0
}
86
87
/**
88
 * \param[out] cache Pointer to return newly allocated and initialized wget_dns_cache instance
89
 * \return WGET_E_SUCCESS if OK, WGET_E_MEMORY if out-of-memory or WGET_E_INVALID
90
 *   if the mutex initialization failed.
91
 *
92
 * Allocates and initializes a wget_dns_cache instance.
93
 */
94
int wget_dns_cache_init(wget_dns_cache **cache)
95
1.66k
{
96
1.66k
  wget_dns_cache *_cache = wget_calloc(1, sizeof(wget_dns_cache));
97
98
1.66k
  if (!_cache)
99
0
    return WGET_E_MEMORY;
100
101
1.66k
  if (wget_thread_mutex_init(&_cache->mutex)) {
102
0
    xfree(_cache);
103
0
    return WGET_E_INVALID;
104
0
  }
105
106
1.66k
  if (!(_cache->cache = wget_hashmap_create(16, (wget_hashmap_hash_fn *) hash_dns, (wget_hashmap_compare_fn *) compare_dns))) {
107
0
    wget_dns_cache_free(&_cache);
108
0
    return WGET_E_MEMORY;
109
0
  }
110
111
1.66k
  wget_hashmap_set_key_destructor(_cache->cache, (wget_hashmap_key_destructor *) free_dns);
112
1.66k
  wget_hashmap_set_value_destructor(_cache->cache, (wget_hashmap_value_destructor *) free_dns);
113
114
1.66k
  *cache = _cache;
115
116
1.66k
  return WGET_E_SUCCESS;
117
1.66k
}
118
119
/**
120
 * \param[in/out] cache Pointer to wget_dns_cache instance that will be freed and NULLified.
121
 *
122
 * Free the resources allocated by wget_dns_cache_init().
123
 */
124
void wget_dns_cache_free(wget_dns_cache **cache)
125
10.2k
{
126
10.2k
  if (cache && *cache) {
127
1.66k
    wget_thread_mutex_lock((*cache)->mutex);
128
1.66k
    wget_hashmap_free(&(*cache)->cache);
129
1.66k
    wget_thread_mutex_unlock((*cache)->mutex);
130
131
1.66k
    wget_thread_mutex_destroy(&(*cache)->mutex);
132
1.66k
    xfree(*cache);
133
1.66k
  }
134
10.2k
}
135
136
/**
137
 * \param[in] cache A `wget_dns_cache` instance, created by wget_dns_cache_init().
138
 * \param[in] host Hostname to look up
139
 * \param[in] port Port to look up
140
 * \return The cached addrinfo structure or NULL if not found
141
 */
142
struct addrinfo *wget_dns_cache_get(wget_dns_cache *cache, const char *host, uint16_t port)
143
0
{
144
0
  if (cache) {
145
0
    struct cache_entry *entryp, entry = { .host = host, .port = port };
146
147
0
    wget_thread_mutex_lock(cache->mutex);
148
0
    if (!wget_hashmap_get(cache->cache, &entry, &entryp))
149
0
      entryp = NULL;
150
0
    wget_thread_mutex_unlock(cache->mutex);
151
152
0
    if (entryp) {
153
      // DNS cache entry found
154
0
      if (wget_ip_is_family(entryp->host, WGET_NET_FAMILY_IPV6))
155
0
        debug_printf("Found dns cache entry [%s]:%d\n", entryp->host, entryp->port);
156
0
      else
157
0
        debug_printf("Found dns cache entry %s:%d\n", entryp->host, entryp->port);
158
0
      return entryp->addrinfo;
159
0
    }
160
0
  }
161
162
0
  return NULL;
163
0
}
164
165
/**
166
 * \param[in] cache A `wget_dns_cache` instance, created by wget_dns_cache_init().
167
 * \param[in] host Hostname part of the key
168
 * \param[in] port Port part of the key
169
 * \param[in/out] addrinfo Addrinfo structure to cache, returns cached addrinfo
170
 * \return WGET_E_SUCCESS on success, else a WGET_E_* error value
171
 *
172
 * This functions adds \p addrinfo to the given DNS cache \p cache.
173
 *
174
 * If an entry for [host,port] already exists, \p addrinfo is free'd and replaced by the cached entry.
175
 * Do not free \p addrinfo yourself - this will be done when the whole cache is freed.
176
 */
177
int wget_dns_cache_add(wget_dns_cache *cache, const char *host, uint16_t port, struct addrinfo **addrinfo)
178
0
{
179
0
  if (!cache || !host || !addrinfo)
180
0
    return WGET_E_INVALID;
181
182
0
  struct cache_entry entry = { .host = host, .port = port };
183
0
  struct cache_entry *entryp;
184
185
0
  wget_thread_mutex_lock(cache->mutex);
186
187
0
  if (wget_hashmap_get(cache->cache, &entry, &entryp)) {
188
    // host+port is already in cache
189
0
    wget_thread_mutex_unlock(cache->mutex);
190
0
    if (*addrinfo != entryp->addrinfo)
191
0
      freeaddrinfo(*addrinfo);
192
0
    *addrinfo = entryp->addrinfo;
193
0
    return WGET_E_SUCCESS;
194
0
  }
195
196
  // insert addrinfo into dns cache
197
0
  size_t hostlen = strlen(host) + 1;
198
0
  entryp = wget_malloc(sizeof(struct cache_entry) + hostlen);
199
200
0
  if (!entryp) {
201
0
    wget_thread_mutex_unlock(cache->mutex);
202
0
    return WGET_E_MEMORY;
203
0
  }
204
205
0
  entryp->port = port;
206
0
  entryp->host = (char *)(entryp + 1);
207
0
  memcpy((char *)entryp->host, host, hostlen); // ugly cast, but semantically ok
208
0
  entryp->addrinfo = *addrinfo;
209
210
  // key and value are the same to make wget_hashmap_get() return old entry
211
0
  wget_hashmap_put(cache->cache, entryp, entryp);
212
213
0
  wget_thread_mutex_unlock(cache->mutex);
214
215
0
  return WGET_E_SUCCESS;
216
0
}
217
218
/** @} */