/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 | | /** @} */ |