GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/network/dns.h
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 34 37 91.9%
Branches: 0 0 -%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #ifndef CVMFS_NETWORK_DNS_H_
6 #define CVMFS_NETWORK_DNS_H_
7
8 #include <stdint.h>
9
10 #include <cstdio>
11 #include <ctime>
12 #include <map>
13 #include <set>
14 #include <string>
15 #include <vector>
16
17 #include "duplex_cares.h"
18 #include "gtest/gtest_prod.h"
19 #include "util/atomic.h"
20 #include "util/prng.h"
21 #include "util/single_copy.h"
22
23 namespace dns {
24
25 /**
26 * Possible errors when trying to resolve a host name.
27 */
28 enum Failures {
29 kFailOk = 0,
30 kFailInvalidResolvers,
31 kFailTimeout,
32 kFailInvalidHost,
33 kFailUnknownHost, ///< Resolver returned a negative reply
34 kFailMalformed,
35 kFailNoAddress, ///< Resolver returned a positive reply but without IPs
36 kFailNotYetResolved,
37 kFailOther,
38
39 kFailNumEntries
40 };
41
42
43 /**
44 * Steers IP protocol selection.
45 */
46 enum IpPreference {
47 // use system default, currently unused and mapped to IPv4
48 kIpPreferSystem = 0,
49 kIpPreferV4,
50 kIpPreferV6,
51 };
52
53 80 inline const char *Code2Ascii(const Failures error) {
54 const char *texts[kFailNumEntries + 1];
55 80 texts[0] = "OK";
56 80 texts[1] = "invalid resolver addresses";
57 80 texts[2] = "DNS query timeout";
58 80 texts[3] = "invalid host name to resolve";
59 80 texts[4] = "unknown host name";
60 80 texts[5] = "malformed DNS request";
61 80 texts[6] = "no IP address for host";
62 80 texts[7] = "internal error, not yet resolved";
63 80 texts[8] = "unknown name resolving error";
64 80 texts[9] = "no text";
65 80 return texts[error];
66 }
67
68
69 std::string ExtractHost(const std::string &url);
70 std::string ExtractPort(const std::string &url);
71 std::string RewriteUrl(const std::string &url, const std::string &ip);
72 std::string StripIp(const std::string &decorated_ip);
73 std::string AddDefaultScheme(const std::string &proxy);
74
75
76 /**
77 * Stores the resolved IPv4 and IPv6 addresses of a host name including their
78 * time to live. Data in these objects are immutable. They are created by a
79 * a Resolver. Once the TTL has expired, they become invalid and a new Host
80 * object should be fetched from a resolver.
81 *
82 * A host object can be copied into a new object with an extended deadline.
83 * This is useful if an attempt to resolve a name fails where it previously
84 * succeeded. In this case, the extended deadline can be used to delay another
85 * name resolution attempt for some grace period.
86 *
87 * For successful name resolution, the name is the fully qualified domain name,
88 * even if a short name was given to the resolver.
89 */
90 class Host {
91 FRIEND_TEST(T_Dns, HostEquivalent);
92 FRIEND_TEST(T_Dns, HostExpired);
93 FRIEND_TEST(T_Dns, HostValid);
94 FRIEND_TEST(T_Dns, HostExtendDeadline);
95 FRIEND_TEST(T_Dns, HostBestAddresses);
96 friend class Resolver;
97
98 public:
99 static Host ExtendDeadline(const Host &original, unsigned seconds_from_now);
100 Host();
101 Host(const Host &other);
102 Host &operator=(const Host &other);
103 bool IsEquivalent(const Host &other) const;
104 bool IsExpired() const;
105 bool IsValid() const;
106
107 30 time_t deadline() const { return deadline_; }
108 20 int64_t id() const { return id_; }
109 456 bool HasIpv4() const { return !ipv4_addresses_.empty(); }
110 255 bool HasIpv6() const { return !ipv6_addresses_.empty(); }
111 410 const std::set<std::string> &ipv4_addresses() const {
112 410 return ipv4_addresses_;
113 }
114 265 const std::set<std::string> &ipv6_addresses() const {
115 265 return ipv6_addresses_;
116 }
117 const std::set<std::string> &ViewBestAddresses(IpPreference preference) const;
118 5162 const std::string &name() const { return name_; }
119 652 Failures status() const { return status_; }
120
121 private:
122 void CopyFrom(const Host &other);
123
124 /**
125 * Counter that is increased with every creation of a host object. Allows to
126 * distinguish two Host objects with the same host name. E.g. when the proxy
127 * list in Download.cc reads "http://A:3128|http://A:3128".
128 */
129 static atomic_int64 global_id_;
130
131 /**
132 * When the name resolution becomes outdated, in UTC seconds since UNIX epoch.
133 * Once the deadline is passed, IsValid returns false.
134 */
135 time_t deadline_;
136
137 /**
138 * The unique id of this instance of Host.
139 */
140 int64_t id_;
141
142 /**
143 * ASCII representation of IPv4 addresses (a.b.c.d), so that they can be
144 * readily used by curl.
145 */
146 std::set<std::string> ipv4_addresses_;
147
148 /**
149 * ASCII representation of IPv6 addresses in the form "[a:b:c:d:e:f:g:h]",
150 * so that they can be readily used by curl.
151 */
152 std::set<std::string> ipv6_addresses_;
153
154 /**
155 * The host name either fully qualified or within the search domain.
156 */
157 std::string name_;
158
159 /**
160 * Error code of the name resolution that led to this object.
161 */
162 Failures status_;
163 };
164
165
166 /**
167 * Abstract interface of a name resolver. Returns a Host object upon successful
168 * name resolution. Also provides a vector interface to resolve multiple names
169 * in parallel. Can be configured with DNS servers, with a timeout, and whether
170 * to use IPv4 only or not.
171 */
172 class Resolver : SingleCopy {
173 public:
174 /**
175 * Enlarge very small TTLs by default to 1 minute.
176 */
177 static const unsigned kDefaultMinTtl = 60;
178
179 /**
180 * Cut off very large TTLs by default to 1 day.
181 */
182 static const unsigned kDefaultMaxTtl = 86400;
183
184 Resolver(const bool ipv4_only,
185 const unsigned retries,
186 const unsigned timeout_ms);
187 39054 virtual ~Resolver() { }
188
189 /**
190 * A list of new resolvers in the form <IP address>[:port].
191 */
192 virtual bool SetResolvers(const std::vector<std::string> &resolvers) = 0;
193 virtual bool SetSearchDomains(const std::vector<std::string> &domains) = 0;
194 virtual void SetSystemResolvers() = 0;
195 virtual void SetSystemSearchDomains() = 0;
196 Host Resolve(const std::string &name);
197 void ResolveMany(const std::vector<std::string> &names,
198 std::vector<Host> *hosts);
199
200 12770 const std::vector<std::string> &domains() const { return domains_; }
201 12983 bool ipv4_only() const { return ipv4_only_; }
202 6280 const std::vector<std::string> &resolvers() const { return resolvers_; }
203 11109 unsigned retries() const { return retries_; }
204 8719679 unsigned timeout_ms() const { return timeout_ms_; }
205 1613 void set_throttle(const unsigned throttle) { throttle_ = throttle; }
206 1608 unsigned throttle() const { return throttle_; }
207 3226 void set_min_ttl(unsigned seconds) { min_ttl_ = seconds; }
208 1608 unsigned min_ttl() const { return min_ttl_; }
209 3226 void set_max_ttl(unsigned seconds) { max_ttl_ = seconds; }
210 1608 unsigned max_ttl() const { return max_ttl_; }
211
212 protected:
213 /**
214 * Takes host names and returns the resolved lists of A and AAAA records in
215 * the same order. To keep it simple, returns only a single TTL per host,
216 * the lower value of both record types A/AAAA. The output vectors have
217 * the same size as the input vector names. Names that are handled by the
218 * base class are marked with skip[i] set to true. The input names are
219 * completed to fully qualified domain names.
220 */
221 virtual void DoResolve(const std::vector<std::string> &names,
222 const std::vector<bool> &skip,
223 std::vector<std::vector<std::string> > *ipv4_addresses,
224 std::vector<std::vector<std::string> > *ipv6_addresses,
225 std::vector<Failures> *failures,
226 std::vector<unsigned> *ttls,
227 std::vector<std::string> *fqdns) = 0;
228 bool IsIpv4Address(const std::string &address);
229 bool IsIpv6Address(const std::string &address);
230
231 /**
232 * Currently active search domain list
233 */
234 std::vector<std::string> domains_;
235
236 /**
237 * Do not try to get AAAA records if true.
238 */
239 bool ipv4_only_;
240
241 /**
242 * Currently used resolver list in the form <ip address>:<port>
243 */
244 std::vector<std::string> resolvers_;
245
246 /**
247 * 1 + retries_ attempts to unresponsive servers, each attempt bounded by
248 * timeout_ms_
249 */
250 unsigned retries_;
251
252 /**
253 * Timeout in milliseconds for DNS queries. Zero means no timeout.
254 */
255 unsigned timeout_ms_;
256
257 /**
258 * Limit number of resolved IP addresses. If throttle_ is 0 it has no effect.
259 * Otherwise, if more than thottle_ IPs are registered for a host, only
260 * throttle_ randomly picked IPs are returned.
261 */
262 unsigned throttle_;
263
264 /**
265 * Effective minimum TTL, which by default is kDefaultMinTtl
266 */
267 unsigned min_ttl_;
268
269 /**
270 * Effective maximum TTL, which by default is kDefaultMaxTtl
271 */
272 unsigned max_ttl_;
273
274 /**
275 * Required for picking IP addresses in throttle_
276 */
277 Prng prng_;
278 };
279
280
281 /**
282 * Implementation of the Resolver interface using the c-ares library.
283 */
284 class CaresResolver : public Resolver {
285 friend class NormalResolver;
286
287 public:
288 /**
289 * More IP addresses for a single name will be ignored. Due to c-ares
290 * exponential backoff, the number of retries should be limited to 2.
291 * That results in 2 apptempts with the given timeout and a third one with
292 * the timeout doubled.
293 */
294 static const unsigned kMaxAddresses = 16;
295
296 static CaresResolver *Create(const bool ipv4_only,
297 const unsigned retries,
298 const unsigned timeout_ms);
299 virtual ~CaresResolver();
300
301 virtual bool SetResolvers(const std::vector<std::string> &resolvers);
302 virtual bool SetSearchDomains(const std::vector<std::string> &domains);
303 virtual void SetSystemResolvers();
304 virtual void SetSystemSearchDomains();
305
306 protected:
307 CaresResolver(const bool ipv4_only,
308 const unsigned retries,
309 const unsigned timeout_ms);
310 virtual void DoResolve(const std::vector<std::string> &names,
311 const std::vector<bool> &skip,
312 std::vector<std::vector<std::string> > *ipv4_addresses,
313 std::vector<std::vector<std::string> > *ipv6_addresses,
314 std::vector<Failures> *failures,
315 std::vector<unsigned> *ttls,
316 std::vector<std::string> *fqdns);
317
318 private:
319 void WaitOnCares();
320 ares_channel *channel_;
321 char *lookup_options_;
322 std::vector<std::string> system_resolvers_;
323 std::vector<std::string> system_domains_;
324 };
325
326
327 /**
328 * Resolves against static name information like in /etc/hosts. Setting
329 * resolver addresses is a no-op for this resolver. Search domains are not
330 * automatically found but need to be set. Not the most efficient
331 * implementation but in the context of cvmfs should be called at most every 5
332 * minutes.
333 */
334 class HostfileResolver : public Resolver {
335 friend class NormalResolver;
336
337 public:
338 static HostfileResolver *Create(const std::string &path, bool ipv4_only);
339 virtual ~HostfileResolver();
340
341 virtual bool SetResolvers(const std::vector<std::string> & /* resolvers */) {
342 return true;
343 }
344 virtual bool SetSearchDomains(const std::vector<std::string> &domains);
345 virtual void SetSystemResolvers() { }
346 virtual void SetSystemSearchDomains();
347
348 protected:
349 explicit HostfileResolver(const bool ipv4_only);
350 virtual void DoResolve(const std::vector<std::string> &names,
351 const std::vector<bool> &skip,
352 std::vector<std::vector<std::string> > *ipv4_addresses,
353 std::vector<std::vector<std::string> > *ipv6_addresses,
354 std::vector<Failures> *failures,
355 std::vector<unsigned> *ttls,
356 std::vector<std::string> *fqdns);
357
358 private:
359 struct HostEntry {
360 std::vector<std::string> ipv4_addresses;
361 std::vector<std::string> ipv6_addresses;
362 };
363 static const int kIpMaxLength = 45;
364 static const int kHostnameMaxLength = 253;
365 void ParseHostFile();
366 /**
367 * Host names to lists of IPv4 and IPv6 addresses. Reverse lookup in the
368 * hosts file.
369 */
370 std::map<std::string, HostEntry> host_map_;
371
372 /**
373 * Open the file descriptor when the resolver is constructed and only release
374 * on destruction. Thereby we can be relatively sure to not see I/O errors
375 * once constructed.
376 */
377 FILE *fhosts_;
378 };
379
380
381 /**
382 * The normal resolver combines Hostfile and C-ares resolver. First looks up
383 * host names in the host file. All non-matches are looked up by c-ares.
384 */
385 class NormalResolver : public Resolver {
386 FRIEND_TEST(T_Dns, NormalResolverConstruct);
387
388 public:
389 static NormalResolver *Create(const bool ipv4_only,
390 const unsigned retries,
391 const unsigned timeout_ms);
392 virtual bool SetResolvers(const std::vector<std::string> &resolvers);
393 virtual bool SetSearchDomains(const std::vector<std::string> &domains);
394 virtual void SetSystemResolvers();
395 virtual void SetSystemSearchDomains();
396 virtual ~NormalResolver();
397
398 protected:
399 virtual void DoResolve(const std::vector<std::string> &names,
400 const std::vector<bool> &skip,
401 std::vector<std::vector<std::string> > *ipv4_addresses,
402 std::vector<std::vector<std::string> > *ipv6_addresses,
403 std::vector<Failures> *failures,
404 std::vector<unsigned> *ttls,
405 std::vector<std::string> *fqdns);
406 NormalResolver();
407
408 private:
409 CaresResolver *cares_resolver_;
410 HostfileResolver *hostfile_resolver_;
411 };
412
413 } // namespace dns
414
415 #endif // CVMFS_NETWORK_DNS_H_
416