deltachat/net/
dns.rs

1//! DNS resolution and cache.
2//!
3//! DNS cache in Delta Chat has two layers:
4//! in-memory cache and persistent `dns_cache` SQL table.
5//!
6//! In-memory cache is using a "stale-while-revalidate" strategy.
7//! If there is a cached value, it is returned immediately
8//! and revalidation task is started in the background
9//! to replace old cached IP addresses with new ones.
10//! If there is no cached value yet,
11//! lookup only finishes when `lookup_host` returns first results.
12//! In-memory cache is shared between all accounts
13//! and is never stored on the disk.
14//! It can be thought of as an extension
15//! of the system resolver.
16//!
17//! Persistent `dns_cache` SQL table is used to collect
18//! all IP addresses ever seen for the hostname
19//! together with the timestamp
20//! of the last time IP address has been seen.
21//! Note that this timestamp reflects the time
22//! IP address was returned by the in-memory cache
23//! rather than the underlying system resolver.
24//! Unused entries are removed after 30 days
25//! (`CACHE_TTL` constant) to avoid having
26//! old non-working IP addresses in the cache indefinitely.
27//!
28//! When Delta Chat needs an IP address for the host,
29//! it queries in-memory cache for the next result
30//! and merges the list of IP addresses
31//! with the list of IP addresses from persistent cache.
32//! Resulting list is constructed
33//! by taking the first two results from the resolver
34//! followed up by persistent cache results
35//! and terminated by the rest of resolver results.
36//!
37//! Persistent cache results are sorted
38//! by the time of the most recent successful connection
39//! using the result. For results that have never been
40//! used for successful connection timestamp of
41//! retrieving them from in-memory cache is used.
42
43use anyhow::{Context as _, Result};
44use std::collections::HashMap;
45use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
46use std::str::FromStr;
47use std::sync::LazyLock;
48use tokio::net::lookup_host;
49use tokio::time::timeout;
50
51use super::load_connection_timestamp;
52use crate::context::Context;
53use crate::log::warn;
54use crate::tools::time;
55
56/// Inserts entry into DNS cache
57/// or updates existing one with a new timestamp.
58async fn update_cache(context: &Context, host: &str, addr: &str, now: i64) -> Result<()> {
59    context
60        .sql
61        .execute(
62            "INSERT INTO dns_cache
63             (hostname, address, timestamp)
64             VALUES (?, ?, ?)
65             ON CONFLICT (hostname, address)
66             DO UPDATE SET timestamp=excluded.timestamp",
67            (host, addr, now),
68        )
69        .await?;
70    Ok(())
71}
72
73pub(crate) async fn prune_dns_cache(context: &Context) -> Result<()> {
74    let now = time();
75    context
76        .sql
77        .execute(
78            "DELETE FROM dns_cache
79             WHERE ? > timestamp + ?",
80            (now, super::CACHE_TTL),
81        )
82        .await?;
83    Ok(())
84}
85
86/// Map from hostname to IP addresses.
87///
88/// NOTE: sync RwLock is used, so it must not be held across `.await`
89/// to avoid deadlocks.
90/// See
91/// <https://docs.rs/tokio/1.40.0/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use>
92/// and
93/// <https://stackoverflow.com/questions/63712823/why-do-i-get-a-deadlock-when-using-tokio-with-a-stdsyncmutex>.
94static LOOKUP_HOST_CACHE: LazyLock<parking_lot::RwLock<HashMap<String, Vec<IpAddr>>>> =
95    LazyLock::new(Default::default);
96
97/// Wrapper for `lookup_host` that returns IP addresses.
98async fn lookup_ips(host: impl tokio::net::ToSocketAddrs) -> Result<impl Iterator<Item = IpAddr>> {
99    Ok(lookup_host(host)
100        .await
101        .context("DNS lookup failure")?
102        .map(|addr| addr.ip()))
103}
104
105async fn lookup_host_with_memory_cache(
106    context: &Context,
107    hostname: &str,
108    port: u16,
109) -> Result<Vec<IpAddr>> {
110    let stale_result = {
111        let rwlock_read_guard = LOOKUP_HOST_CACHE.read();
112        rwlock_read_guard.get(hostname).cloned()
113    };
114    if let Some(stale_result) = stale_result {
115        // Revalidate the cache in the background.
116        {
117            let context = context.clone();
118            let hostname = hostname.to_string();
119            tokio::spawn(async move {
120                match lookup_ips((hostname.clone(), port)).await {
121                    Ok(res) => {
122                        LOOKUP_HOST_CACHE.write().insert(hostname, res.collect());
123                    }
124                    Err(err) => {
125                        warn!(
126                            context,
127                            "Failed to revalidate results for {hostname:?}: {err:#}."
128                        );
129                    }
130                }
131            });
132        }
133
134        info!(
135            context,
136            "Using memory-cached DNS resolution for {hostname}."
137        );
138        Ok(stale_result)
139    } else {
140        info!(
141            context,
142            "No memory-cached DNS resolution for {hostname} available, waiting for the resolver."
143        );
144        let res: Vec<IpAddr> = lookup_ips((hostname, port)).await?.collect();
145
146        // Insert initial result into the cache.
147        //
148        // There may already be a result from a parallel
149        // task stored, overwriting it is not a problem.
150        LOOKUP_HOST_CACHE
151            .write()
152            .insert(hostname.to_string(), res.clone());
153        Ok(res)
154    }
155}
156
157/// Looks up the hostname and updates
158/// persistent DNS cache on success.
159async fn lookup_host_and_update_cache(
160    context: &Context,
161    hostname: &str,
162    port: u16,
163    now: i64,
164) -> Result<Vec<SocketAddr>> {
165    let res: Vec<IpAddr> = timeout(
166        super::TIMEOUT,
167        lookup_host_with_memory_cache(context, hostname, port),
168    )
169    .await
170    .context("DNS lookup timeout")?
171    .context("DNS lookup with memory cache failure")?;
172
173    for ip in &res {
174        let ip_string = ip.to_string();
175        if ip_string == hostname {
176            // IP address resolved into itself, not interesting to cache.
177            continue;
178        }
179
180        info!(context, "Resolved {hostname} into {ip}.");
181
182        // Update the cache.
183        update_cache(context, hostname, &ip_string, now).await?;
184    }
185
186    let res = res
187        .into_iter()
188        .map(|ip| SocketAddr::new(ip, port))
189        .collect();
190    Ok(res)
191}
192
193// Updates timestamp of the cached entry
194// or inserts a new one if cached entry does not exist.
195//
196// This function should be called when a successful TLS
197// connection is established with strict TLS checks.
198//
199// This increases priority of existing cached entries
200// and copies fallback addresses from built-in cache
201// into database cache on successful use.
202//
203// Unlike built-in cache,
204// database cache is used even if DNS
205// resolver returns a non-empty
206// (but potentially incorrect and unusable) result.
207pub(crate) async fn update_connect_timestamp(
208    context: &Context,
209    host: &str,
210    address: &str,
211) -> Result<()> {
212    if host == address {
213        return Ok(());
214    }
215
216    context
217        .sql
218        .execute(
219            "INSERT INTO dns_cache (hostname, address, timestamp)
220                 VALUES (?, ?, ?)
221                 ON CONFLICT (hostname, address)
222                 DO UPDATE SET timestamp=excluded.timestamp",
223            (host, address, time()),
224        )
225        .await?;
226    Ok(())
227}
228
229/// Preloaded DNS results that can be used in case of DNS server failures.
230static DNS_PRELOAD: LazyLock<HashMap<&'static str, Vec<IpAddr>>> = LazyLock::new(|| {
231    HashMap::from([
232        (
233            "mail.sangham.net",
234            vec![
235                IpAddr::V4(Ipv4Addr::new(159, 69, 186, 85)),
236                IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0xc17, 0x798c, 0, 0, 0, 1)),
237            ],
238        ),
239        (
240            "nine.testrun.org",
241            vec![
242                IpAddr::V4(Ipv4Addr::new(116, 202, 233, 236)),
243                IpAddr::V4(Ipv4Addr::new(128, 140, 126, 197)),
244                IpAddr::V4(Ipv4Addr::new(49, 12, 116, 128)),
245                IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0x241, 0x4ce8, 0, 0, 0, 2)),
246            ],
247        ),
248        (
249            "disroot.org",
250            vec![IpAddr::V4(Ipv4Addr::new(178, 21, 23, 139))],
251        ),
252        (
253            "imap.gmail.com",
254            vec![
255                IpAddr::V4(Ipv4Addr::new(142, 250, 110, 108)),
256                IpAddr::V4(Ipv4Addr::new(142, 250, 110, 109)),
257                IpAddr::V4(Ipv4Addr::new(66, 102, 1, 108)),
258                IpAddr::V4(Ipv4Addr::new(66, 102, 1, 109)),
259                IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x400c, 0xc1f, 0, 0, 0, 0x6c)),
260                IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x400c, 0xc1f, 0, 0, 0, 0x6d)),
261            ],
262        ),
263        (
264            "smtp.gmail.com",
265            vec![
266                IpAddr::V4(Ipv4Addr::new(142, 250, 110, 109)),
267                IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x4013, 0xc04, 0, 0, 0, 0x6c)),
268            ],
269        ),
270        (
271            "mail.autistici.org",
272            vec![
273                IpAddr::V4(Ipv4Addr::new(198, 167, 222, 108)),
274                IpAddr::V4(Ipv4Addr::new(82, 94, 249, 234)),
275                IpAddr::V4(Ipv4Addr::new(93, 190, 126, 19)),
276            ],
277        ),
278        (
279            "smtp.autistici.org",
280            vec![
281                IpAddr::V4(Ipv4Addr::new(198, 167, 222, 108)),
282                IpAddr::V4(Ipv4Addr::new(82, 94, 249, 234)),
283                IpAddr::V4(Ipv4Addr::new(93, 190, 126, 19)),
284            ],
285        ),
286        (
287            "daleth.cafe",
288            vec![IpAddr::V4(Ipv4Addr::new(37, 27, 6, 204))],
289        ),
290        (
291            "imap.163.com",
292            vec![IpAddr::V4(Ipv4Addr::new(111, 124, 203, 45))],
293        ),
294        (
295            "smtp.163.com",
296            vec![IpAddr::V4(Ipv4Addr::new(103, 129, 252, 45))],
297        ),
298        (
299            "imap.aol.com",
300            vec![
301                IpAddr::V4(Ipv4Addr::new(212, 82, 101, 33)),
302                IpAddr::V4(Ipv4Addr::new(87, 248, 98, 69)),
303            ],
304        ),
305        (
306            "smtp.aol.com",
307            vec![IpAddr::V4(Ipv4Addr::new(87, 248, 97, 31))],
308        ),
309        (
310            "mail.arcor.de",
311            vec![IpAddr::V4(Ipv4Addr::new(2, 207, 150, 234))],
312        ),
313        (
314            "imap.arcor.de",
315            vec![IpAddr::V4(Ipv4Addr::new(2, 207, 150, 230))],
316        ),
317        (
318            "imap.fastmail.com",
319            vec![
320                IpAddr::V4(Ipv4Addr::new(103, 168, 172, 43)),
321                IpAddr::V4(Ipv4Addr::new(103, 168, 172, 58)),
322            ],
323        ),
324        (
325            "smtp.fastmail.com",
326            vec![
327                IpAddr::V4(Ipv4Addr::new(103, 168, 172, 45)),
328                IpAddr::V4(Ipv4Addr::new(103, 168, 172, 60)),
329            ],
330        ),
331        (
332            "imap.gmx.net",
333            vec![
334                IpAddr::V4(Ipv4Addr::new(212, 227, 17, 170)),
335                IpAddr::V4(Ipv4Addr::new(212, 227, 17, 186)),
336            ],
337        ),
338        (
339            "imap.mail.de",
340            vec![IpAddr::V4(Ipv4Addr::new(62, 201, 172, 16))],
341        ),
342        (
343            "smtp.mailbox.org",
344            vec![IpAddr::V4(Ipv4Addr::new(185, 97, 174, 196))],
345        ),
346        (
347            "imap.mailbox.org",
348            vec![IpAddr::V4(Ipv4Addr::new(185, 97, 174, 199))],
349        ),
350        (
351            "imap.naver.com",
352            vec![IpAddr::V4(Ipv4Addr::new(125, 209, 238, 153))],
353        ),
354        (
355            "imap.ouvaton.coop",
356            vec![IpAddr::V4(Ipv4Addr::new(194, 36, 166, 20))],
357        ),
358        (
359            "imap.purelymail.com",
360            vec![IpAddr::V4(Ipv4Addr::new(18, 204, 123, 63))],
361        ),
362        (
363            "imap.tiscali.it",
364            vec![IpAddr::V4(Ipv4Addr::new(213, 205, 33, 10))],
365        ),
366        (
367            "smtp.tiscali.it",
368            vec![IpAddr::V4(Ipv4Addr::new(213, 205, 33, 13))],
369        ),
370        (
371            "imap.web.de",
372            vec![
373                IpAddr::V4(Ipv4Addr::new(212, 227, 17, 162)),
374                IpAddr::V4(Ipv4Addr::new(212, 227, 17, 178)),
375            ],
376        ),
377        (
378            "imap.ziggo.nl",
379            vec![IpAddr::V4(Ipv4Addr::new(84, 116, 6, 3))],
380        ),
381        (
382            "imap.zoho.eu",
383            vec![IpAddr::V4(Ipv4Addr::new(185, 230, 214, 25))],
384        ),
385        (
386            "imaps.bluewin.ch",
387            vec![
388                IpAddr::V4(Ipv4Addr::new(16, 62, 253, 42)),
389                IpAddr::V4(Ipv4Addr::new(16, 63, 141, 244)),
390                IpAddr::V4(Ipv4Addr::new(16, 63, 146, 183)),
391            ],
392        ),
393        (
394            "mail.buzon.uy",
395            vec![IpAddr::V4(Ipv4Addr::new(185, 101, 93, 79))],
396        ),
397        (
398            "mail.ecloud.global",
399            vec![IpAddr::V4(Ipv4Addr::new(95, 217, 246, 96))],
400        ),
401        (
402            "mail.ende.in.net",
403            vec![IpAddr::V4(Ipv4Addr::new(95, 217, 5, 72))],
404        ),
405        (
406            "mail.gmx.net",
407            vec![
408                IpAddr::V4(Ipv4Addr::new(212, 227, 17, 168)),
409                IpAddr::V4(Ipv4Addr::new(212, 227, 17, 190)),
410            ],
411        ),
412        (
413            "mail.infomaniak.com",
414            vec![
415                IpAddr::V4(Ipv4Addr::new(83, 166, 143, 44)),
416                IpAddr::V4(Ipv4Addr::new(83, 166, 143, 45)),
417            ],
418        ),
419        (
420            "mail.mymagenta.at",
421            vec![IpAddr::V4(Ipv4Addr::new(80, 109, 253, 241))],
422        ),
423        (
424            "mail.nubo.coop",
425            vec![IpAddr::V4(Ipv4Addr::new(79, 99, 201, 10))],
426        ),
427        (
428            "mail.riseup.net",
429            vec![
430                IpAddr::V4(Ipv4Addr::new(198, 252, 153, 70)),
431                IpAddr::V4(Ipv4Addr::new(198, 252, 153, 71)),
432            ],
433        ),
434        (
435            "mail.systemausfall.org",
436            vec![
437                IpAddr::V4(Ipv4Addr::new(51, 75, 71, 249)),
438                IpAddr::V4(Ipv4Addr::new(80, 153, 252, 42)),
439            ],
440        ),
441        (
442            "mail.systemli.org",
443            vec![IpAddr::V4(Ipv4Addr::new(93, 190, 126, 36))],
444        ),
445        (
446            "mehl.cloud",
447            vec![IpAddr::V4(Ipv4Addr::new(95, 217, 223, 172))],
448        ),
449        (
450            "mx.freenet.de",
451            vec![
452                IpAddr::V4(Ipv4Addr::new(195, 4, 92, 210)),
453                IpAddr::V4(Ipv4Addr::new(195, 4, 92, 211)),
454                IpAddr::V4(Ipv4Addr::new(195, 4, 92, 212)),
455                IpAddr::V4(Ipv4Addr::new(195, 4, 92, 213)),
456            ],
457        ),
458        (
459            "newyear.aktivix.org",
460            vec![IpAddr::V4(Ipv4Addr::new(162, 247, 75, 192))],
461        ),
462        (
463            "pimap.schulon.org",
464            vec![IpAddr::V4(Ipv4Addr::new(194, 77, 246, 20))],
465        ),
466        (
467            "posteo.de",
468            vec![
469                IpAddr::V4(Ipv4Addr::new(185, 67, 36, 168)),
470                IpAddr::V4(Ipv4Addr::new(185, 67, 36, 169)),
471            ],
472        ),
473        (
474            "psmtp.schulon.org",
475            vec![IpAddr::V4(Ipv4Addr::new(194, 77, 246, 20))],
476        ),
477        (
478            "secureimap.t-online.de",
479            vec![
480                IpAddr::V4(Ipv4Addr::new(194, 25, 134, 114)),
481                IpAddr::V4(Ipv4Addr::new(194, 25, 134, 115)),
482                IpAddr::V4(Ipv4Addr::new(194, 25, 134, 50)),
483                IpAddr::V4(Ipv4Addr::new(194, 25, 134, 51)),
484            ],
485        ),
486        (
487            "securesmtp.t-online.de",
488            vec![
489                IpAddr::V4(Ipv4Addr::new(194, 25, 134, 110)),
490                IpAddr::V4(Ipv4Addr::new(194, 25, 134, 46)),
491            ],
492        ),
493        (
494            "smtp.aliyun.com",
495            vec![IpAddr::V4(Ipv4Addr::new(47, 246, 136, 232))],
496        ),
497        (
498            "smtp.mail.de",
499            vec![IpAddr::V4(Ipv4Addr::new(62, 201, 172, 21))],
500        ),
501        (
502            "smtp.mail.ru",
503            vec![
504                IpAddr::V4(Ipv4Addr::new(217, 69, 139, 160)),
505                IpAddr::V4(Ipv4Addr::new(94, 100, 180, 160)),
506            ],
507        ),
508        (
509            "imap.mail.yahoo.com",
510            vec![
511                IpAddr::V4(Ipv4Addr::new(87, 248, 103, 8)),
512                IpAddr::V4(Ipv4Addr::new(212, 82, 101, 24)),
513            ],
514        ),
515        (
516            "smtp.mail.yahoo.com",
517            vec![IpAddr::V4(Ipv4Addr::new(87, 248, 97, 36))],
518        ),
519        (
520            "imap.mailo.com",
521            vec![IpAddr::V4(Ipv4Addr::new(213, 182, 54, 20))],
522        ),
523        (
524            "smtp.mailo.com",
525            vec![IpAddr::V4(Ipv4Addr::new(213, 182, 54, 20))],
526        ),
527        (
528            "smtp.naver.com",
529            vec![IpAddr::V4(Ipv4Addr::new(125, 209, 238, 155))],
530        ),
531        (
532            "smtp.ouvaton.coop",
533            vec![IpAddr::V4(Ipv4Addr::new(194, 36, 166, 20))],
534        ),
535        (
536            "smtp.purelymail.com",
537            vec![IpAddr::V4(Ipv4Addr::new(18, 204, 123, 63))],
538        ),
539        (
540            "imap.qq.com",
541            vec![IpAddr::V4(Ipv4Addr::new(43, 129, 255, 54))],
542        ),
543        (
544            "smtp.qq.com",
545            vec![IpAddr::V4(Ipv4Addr::new(43, 129, 255, 54))],
546        ),
547        (
548            "imap.rambler.ru",
549            vec![
550                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 169)),
551                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 171)),
552                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 168)),
553                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 170)),
554            ],
555        ),
556        (
557            "smtp.rambler.ru",
558            vec![
559                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 165)),
560                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 167)),
561                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 166)),
562                IpAddr::V4(Ipv4Addr::new(81, 19, 77, 164)),
563            ],
564        ),
565        (
566            "imap.vivaldi.net",
567            vec![IpAddr::V4(Ipv4Addr::new(31, 209, 137, 15))],
568        ),
569        (
570            "smtp.vivaldi.net",
571            vec![IpAddr::V4(Ipv4Addr::new(31, 209, 137, 12))],
572        ),
573        (
574            "imap.vodafonemail.de",
575            vec![IpAddr::V4(Ipv4Addr::new(2, 207, 150, 230))],
576        ),
577        (
578            "smtp.vodafonemail.de",
579            vec![IpAddr::V4(Ipv4Addr::new(2, 207, 150, 234))],
580        ),
581        (
582            "smtp.web.de",
583            vec![
584                IpAddr::V4(Ipv4Addr::new(213, 165, 67, 108)),
585                IpAddr::V4(Ipv4Addr::new(213, 165, 67, 124)),
586            ],
587        ),
588        (
589            "imap.yandex.com",
590            vec![IpAddr::V4(Ipv4Addr::new(77, 88, 21, 125))],
591        ),
592        (
593            "smtp.yandex.com",
594            vec![IpAddr::V4(Ipv4Addr::new(77, 88, 21, 158))],
595        ),
596        (
597            "smtp.ziggo.nl",
598            vec![IpAddr::V4(Ipv4Addr::new(84, 116, 6, 3))],
599        ),
600        (
601            "smtp.zoho.eu",
602            vec![IpAddr::V4(Ipv4Addr::new(185, 230, 212, 164))],
603        ),
604        (
605            "smtpauths.bluewin.ch",
606            vec![IpAddr::V4(Ipv4Addr::new(195, 186, 120, 54))],
607        ),
608        (
609            "stinpriza.net",
610            vec![IpAddr::V4(Ipv4Addr::new(5, 9, 122, 184))],
611        ),
612        (
613            "undernet.uy",
614            vec![IpAddr::V4(Ipv4Addr::new(167, 62, 254, 153))],
615        ),
616        (
617            "webbox222.server-home.org",
618            vec![IpAddr::V4(Ipv4Addr::new(91, 203, 111, 88))],
619        ),
620    ])
621});
622
623async fn lookup_cache(
624    context: &Context,
625    host: &str,
626    port: u16,
627    alpn: &str,
628    now: i64,
629) -> Result<Vec<SocketAddr>> {
630    let mut res = Vec::new();
631    for cached_address in context
632        .sql
633        .query_map_vec(
634            "SELECT dns_cache.address
635             FROM dns_cache
636             LEFT JOIN connection_history
637               ON dns_cache.hostname = connection_history.host
638               AND dns_cache.address = connection_history.addr
639               AND connection_history.port = ?
640               AND connection_history.alpn = ?
641             WHERE dns_cache.hostname = ?
642             AND ? < dns_cache.timestamp + ?
643             ORDER BY IFNULL(connection_history.timestamp, dns_cache.timestamp) DESC
644             LIMIT 50",
645            (port, alpn, host, now, super::CACHE_TTL),
646            |row| {
647                let address: String = row.get(0)?;
648                Ok(address)
649            },
650        )
651        .await?
652    {
653        match IpAddr::from_str(&cached_address) {
654            Ok(ip_addr) => {
655                let addr = SocketAddr::new(ip_addr, port);
656                res.push(addr);
657            }
658            Err(err) => {
659                warn!(
660                    context,
661                    "Failed to parse cached address {:?}: {:#}.", cached_address, err
662                );
663            }
664        }
665    }
666    Ok(res)
667}
668
669/// Sorts DNS resolution results by connection timestamp in descending order
670/// so IP addresses that we recently connected to successfully are tried first.
671async fn sort_by_connection_timestamp(
672    context: &Context,
673    input: Vec<SocketAddr>,
674    alpn: &str,
675    host: &str,
676) -> Result<Vec<SocketAddr>> {
677    let mut res: Vec<(Option<i64>, SocketAddr)> = Vec::with_capacity(input.len());
678    for addr in input {
679        let timestamp = load_connection_timestamp(
680            &context.sql,
681            alpn,
682            host,
683            addr.port(),
684            Some(&addr.ip().to_string()),
685        )
686        .await?;
687        res.push((timestamp, addr));
688    }
689    res.sort_by_key(|(ts, _addr)| std::cmp::Reverse(*ts));
690    Ok(res.into_iter().map(|(_ts, addr)| addr).collect())
691}
692
693/// Looks up hostname and port using DNS and updates the address resolution cache.
694///
695/// `alpn` is used to sort DNS results by the time we have successfully
696/// connected to the IP address using given `alpn`.
697/// If result sorting is not needed or `alpn` is unknown,
698/// pass empty string here, e.g. for HTTP requests
699/// or when resolving the IP address of SOCKS proxy.
700///
701/// If `load_cache` is true, appends cached results not older than 30 days to the end
702/// or entries from fallback cache if there are no cached addresses.
703pub(crate) async fn lookup_host_with_cache(
704    context: &Context,
705    hostname: &str,
706    port: u16,
707    alpn: &str,
708    load_cache: bool,
709) -> Result<Vec<SocketAddr>> {
710    let now = time();
711    let resolved_addrs = match lookup_host_and_update_cache(context, hostname, port, now).await {
712        Ok(res) => {
713            if alpn.is_empty() {
714                res
715            } else {
716                sort_by_connection_timestamp(context, res, alpn, hostname).await?
717            }
718        }
719        Err(err) => {
720            warn!(
721                context,
722                "DNS resolution for {hostname}:{port} failed: {err:#}."
723            );
724            Vec::new()
725        }
726    };
727
728    if load_cache {
729        let mut cache = lookup_cache(context, hostname, port, alpn, now).await?;
730        if let Some(ips) = DNS_PRELOAD.get(hostname) {
731            for ip in ips {
732                let addr = SocketAddr::new(*ip, port);
733                if !cache.contains(&addr) {
734                    cache.push(addr);
735                }
736            }
737        }
738
739        Ok(merge_with_cache(resolved_addrs, cache))
740    } else {
741        Ok(resolved_addrs)
742    }
743}
744
745/// Merges results received from DNS with cached results.
746///
747/// At most 10 results are returned.
748fn merge_with_cache(
749    mut resolved_addrs: Vec<SocketAddr>,
750    cache: Vec<SocketAddr>,
751) -> Vec<SocketAddr> {
752    let rest = resolved_addrs.split_off(std::cmp::min(resolved_addrs.len(), 2));
753
754    for addr in cache.into_iter().chain(rest.into_iter()) {
755        if !resolved_addrs.contains(&addr) {
756            resolved_addrs.push(addr);
757            if resolved_addrs.len() >= 10 {
758                break;
759            }
760        }
761    }
762
763    resolved_addrs
764}
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769
770    use crate::net::update_connection_history;
771    use crate::test_utils::TestContext;
772
773    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
774    async fn test_sort_by_connection_timestamp() {
775        let alice = &TestContext::new_alice().await;
776        let now = time();
777
778        let ipv6_addr = IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0x241, 0x4ce8, 0, 0, 0, 2));
779        let ipv4_addr = IpAddr::V4(Ipv4Addr::new(116, 202, 233, 236));
780
781        assert_eq!(
782            sort_by_connection_timestamp(
783                alice,
784                vec![
785                    SocketAddr::new(ipv6_addr, 993),
786                    SocketAddr::new(ipv4_addr, 993)
787                ],
788                "imap",
789                "nine.testrun.org"
790            )
791            .await
792            .unwrap(),
793            vec![
794                SocketAddr::new(ipv6_addr, 993),
795                SocketAddr::new(ipv4_addr, 993)
796            ]
797        );
798        update_connection_history(
799            alice,
800            "imap",
801            "nine.testrun.org",
802            993,
803            "116.202.233.236",
804            now,
805        )
806        .await
807        .unwrap();
808        assert_eq!(
809            sort_by_connection_timestamp(
810                alice,
811                vec![
812                    SocketAddr::new(ipv6_addr, 993),
813                    SocketAddr::new(ipv4_addr, 993)
814                ],
815                "imap",
816                "nine.testrun.org"
817            )
818            .await
819            .unwrap(),
820            vec![
821                SocketAddr::new(ipv4_addr, 993),
822                SocketAddr::new(ipv6_addr, 993),
823            ]
824        );
825
826        assert_eq!(
827            sort_by_connection_timestamp(
828                alice,
829                vec![
830                    SocketAddr::new(ipv6_addr, 465),
831                    SocketAddr::new(ipv4_addr, 465)
832                ],
833                "smtp",
834                "nine.testrun.org"
835            )
836            .await
837            .unwrap(),
838            vec![
839                SocketAddr::new(ipv6_addr, 465),
840                SocketAddr::new(ipv4_addr, 465),
841            ]
842        );
843        update_connection_history(
844            alice,
845            "smtp",
846            "nine.testrun.org",
847            465,
848            "116.202.233.236",
849            now,
850        )
851        .await
852        .unwrap();
853        assert_eq!(
854            sort_by_connection_timestamp(
855                alice,
856                vec![
857                    SocketAddr::new(ipv6_addr, 465),
858                    SocketAddr::new(ipv4_addr, 465)
859                ],
860                "smtp",
861                "nine.testrun.org"
862            )
863            .await
864            .unwrap(),
865            vec![
866                SocketAddr::new(ipv4_addr, 465),
867                SocketAddr::new(ipv6_addr, 465),
868            ]
869        );
870
871        update_connection_history(
872            alice,
873            "imap",
874            "nine.testrun.org",
875            993,
876            "2a01:4f8:241:4ce8::2",
877            now,
878        )
879        .await
880        .unwrap();
881        assert_eq!(
882            sort_by_connection_timestamp(
883                alice,
884                vec![
885                    SocketAddr::new(ipv6_addr, 993),
886                    SocketAddr::new(ipv4_addr, 993)
887                ],
888                "imap",
889                "nine.testrun.org"
890            )
891            .await
892            .unwrap(),
893            vec![
894                SocketAddr::new(ipv6_addr, 993),
895                SocketAddr::new(ipv4_addr, 993)
896            ]
897        );
898    }
899
900    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
901    async fn test_lookup_cache() {
902        let alice = &TestContext::new_alice().await;
903
904        let ipv4_addr = IpAddr::V4(Ipv4Addr::new(116, 202, 233, 236));
905        let ipv6_addr = IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0x241, 0x4ce8, 0, 0, 0, 2));
906
907        let now = time();
908        assert!(
909            lookup_cache(alice, "nine.testrun.org", 587, "smtp", now)
910                .await
911                .unwrap()
912                .is_empty()
913        );
914
915        update_cache(alice, "nine.testrun.org", "116.202.233.236", now)
916            .await
917            .unwrap();
918
919        assert_eq!(
920            lookup_cache(alice, "nine.testrun.org", 587, "smtp", now)
921                .await
922                .unwrap(),
923            vec![SocketAddr::new(ipv4_addr, 587)]
924        );
925
926        // Cache should be returned for other ports and no ALPN as well,
927        // port and ALPN should only affect the order
928        assert_eq!(
929            lookup_cache(alice, "nine.testrun.org", 443, "", now)
930                .await
931                .unwrap(),
932            vec![SocketAddr::new(ipv4_addr, 443)]
933        );
934
935        update_cache(alice, "nine.testrun.org", "2a01:4f8:241:4ce8::2", now + 30)
936            .await
937            .unwrap();
938
939        // New DNS cache entry should go first.
940        assert_eq!(
941            lookup_cache(alice, "nine.testrun.org", 443, "", now + 60)
942                .await
943                .unwrap(),
944            vec![
945                SocketAddr::new(ipv6_addr, 443),
946                SocketAddr::new(ipv4_addr, 443)
947            ],
948        );
949
950        // After successful connection to SMTP over port 465 using IPv4 address,
951        // IPv4 address has higher priority.
952        update_connection_history(
953            alice,
954            "smtp",
955            "nine.testrun.org",
956            465,
957            "116.202.233.236",
958            now + 100,
959        )
960        .await
961        .unwrap();
962        assert_eq!(
963            lookup_cache(alice, "nine.testrun.org", 465, "smtp", now + 120)
964                .await
965                .unwrap(),
966            vec![
967                SocketAddr::new(ipv4_addr, 465),
968                SocketAddr::new(ipv6_addr, 465)
969            ]
970        );
971
972        // For other ports and ALPNs order remains the same.
973        assert_eq!(
974            lookup_cache(alice, "nine.testrun.org", 993, "imap", now + 120)
975                .await
976                .unwrap(),
977            vec![
978                SocketAddr::new(ipv6_addr, 993),
979                SocketAddr::new(ipv4_addr, 993)
980            ],
981        );
982        assert_eq!(
983            lookup_cache(alice, "nine.testrun.org", 465, "imap", now + 120)
984                .await
985                .unwrap(),
986            vec![
987                SocketAddr::new(ipv6_addr, 465),
988                SocketAddr::new(ipv4_addr, 465)
989            ],
990        );
991        assert_eq!(
992            lookup_cache(alice, "nine.testrun.org", 993, "smtp", now + 120)
993                .await
994                .unwrap(),
995            vec![
996                SocketAddr::new(ipv6_addr, 993),
997                SocketAddr::new(ipv4_addr, 993)
998            ],
999        );
1000    }
1001
1002    #[test]
1003    fn test_merge_with_cache() {
1004        let first_addr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
1005        let second_addr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2));
1006
1007        // If there is no cache, just return resolved addresses.
1008        {
1009            let resolved_addrs = vec![
1010                SocketAddr::new(first_addr, 993),
1011                SocketAddr::new(second_addr, 993),
1012            ];
1013            let cache = vec![];
1014            assert_eq!(
1015                merge_with_cache(resolved_addrs.clone(), cache),
1016                resolved_addrs
1017            );
1018        }
1019
1020        // If cache contains address that is not in resolution results,
1021        // it is inserted in the merged result.
1022        {
1023            let resolved_addrs = vec![SocketAddr::new(first_addr, 993)];
1024            let cache = vec![SocketAddr::new(second_addr, 993)];
1025            assert_eq!(
1026                merge_with_cache(resolved_addrs, cache),
1027                vec![
1028                    SocketAddr::new(first_addr, 993),
1029                    SocketAddr::new(second_addr, 993),
1030                ]
1031            );
1032        }
1033
1034        // If cache contains address that is already in resolution results,
1035        // it is not duplicated.
1036        {
1037            let resolved_addrs = vec![
1038                SocketAddr::new(first_addr, 993),
1039                SocketAddr::new(second_addr, 993),
1040            ];
1041            let cache = vec![SocketAddr::new(second_addr, 993)];
1042            assert_eq!(
1043                merge_with_cache(resolved_addrs, cache),
1044                vec![
1045                    SocketAddr::new(first_addr, 993),
1046                    SocketAddr::new(second_addr, 993),
1047                ]
1048            );
1049        }
1050
1051        // If DNS resolvers returns a lot of results,
1052        // we should try cached results before going through all
1053        // the resolver results.
1054        {
1055            let resolved_addrs = vec![
1056                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 993),
1057                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 993),
1058                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3)), 993),
1059                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 4)), 993),
1060                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5)), 993),
1061                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6)), 993),
1062                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)), 993),
1063                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8)), 993),
1064                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 9)), 993),
1065            ];
1066            let cache = vec![SocketAddr::new(second_addr, 993)];
1067            assert_eq!(
1068                merge_with_cache(resolved_addrs, cache),
1069                vec![
1070                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 993),
1071                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 993),
1072                    SocketAddr::new(second_addr, 993),
1073                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3)), 993),
1074                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 4)), 993),
1075                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5)), 993),
1076                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6)), 993),
1077                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)), 993),
1078                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8)), 993),
1079                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 9)), 993),
1080                ]
1081            );
1082        }
1083
1084        // Even if cache already contains all the incorrect results
1085        // that resolver returns, this should not result in them being sorted to the top.
1086        // Cache has known to work result returned first,
1087        // so we should try it after the second result.
1088        {
1089            let resolved_addrs = vec![
1090                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 993),
1091                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 993),
1092                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3)), 993),
1093                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 4)), 993),
1094                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5)), 993),
1095                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6)), 993),
1096                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)), 993),
1097                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8)), 993),
1098                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 9)), 993),
1099            ];
1100            let cache = vec![
1101                SocketAddr::new(second_addr, 993),
1102                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 9)), 993),
1103                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8)), 993),
1104                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)), 993),
1105                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6)), 993),
1106                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5)), 993),
1107                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 4)), 993),
1108                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3)), 993),
1109                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 993),
1110            ];
1111            assert_eq!(
1112                merge_with_cache(resolved_addrs, cache),
1113                vec![
1114                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 993),
1115                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 993),
1116                    SocketAddr::new(second_addr, 993),
1117                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 9)), 993),
1118                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8)), 993),
1119                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)), 993),
1120                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6)), 993),
1121                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5)), 993),
1122                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 4)), 993),
1123                    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3)), 993),
1124                ]
1125            );
1126        }
1127    }
1128}