mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 01:16:31 +03:00
refactor: add net/dns submodule
This commit is contained in:
184
src/net.rs
184
src/net.rs
@@ -1,22 +1,22 @@
|
|||||||
//! # Common network utilities.
|
//! # Common network utilities.
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::SocketAddr;
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{format_err, Context as _, Result};
|
use anyhow::{format_err, Context as _, Result};
|
||||||
use tokio::net::{lookup_host, TcpStream};
|
use tokio::net::TcpStream;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tokio_io_timeout::TimeoutStream;
|
use tokio_io_timeout::TimeoutStream;
|
||||||
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::tools::time;
|
use crate::tools::time;
|
||||||
|
|
||||||
|
pub(crate) mod dns;
|
||||||
pub(crate) mod http;
|
pub(crate) mod http;
|
||||||
pub(crate) mod session;
|
pub(crate) mod session;
|
||||||
pub(crate) mod tls;
|
pub(crate) mod tls;
|
||||||
|
|
||||||
|
use dns::lookup_host_with_cache;
|
||||||
pub use http::{read_url, read_url_blob, Response as HttpResponse};
|
pub use http::{read_url, read_url_blob, Response as HttpResponse};
|
||||||
|
|
||||||
async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result<TcpStream> {
|
async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result<TcpStream> {
|
||||||
@@ -27,182 +27,6 @@ async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result<Tc
|
|||||||
Ok(tcp_stream)
|
Ok(tcp_stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn lookup_host_with_timeout(
|
|
||||||
hostname: &str,
|
|
||||||
port: u16,
|
|
||||||
timeout_val: Duration,
|
|
||||||
) -> Result<Vec<SocketAddr>> {
|
|
||||||
let res = timeout(timeout_val, lookup_host((hostname, port)))
|
|
||||||
.await
|
|
||||||
.context("DNS lookup timeout")?
|
|
||||||
.context("DNS lookup failure")?;
|
|
||||||
Ok(res.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Looks up hostname and port using DNS and updates the address resolution cache.
|
|
||||||
///
|
|
||||||
/// If `load_cache` is true, appends cached results not older than 30 days to the end
|
|
||||||
/// or entries from fallback cache if there are no cached addresses.
|
|
||||||
pub(crate) async fn lookup_host_with_cache(
|
|
||||||
context: &Context,
|
|
||||||
hostname: &str,
|
|
||||||
port: u16,
|
|
||||||
timeout_val: Duration,
|
|
||||||
load_cache: bool,
|
|
||||||
) -> Result<Vec<SocketAddr>> {
|
|
||||||
let now = time();
|
|
||||||
let mut resolved_addrs = match lookup_host_with_timeout(hostname, port, timeout_val).await {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"DNS resolution for {}:{} failed: {:#}.", hostname, port, err
|
|
||||||
);
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for addr in &resolved_addrs {
|
|
||||||
let ip_string = addr.ip().to_string();
|
|
||||||
if ip_string == hostname {
|
|
||||||
// IP address resolved into itself, not interesting to cache.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(context, "Resolved {}:{} into {}.", hostname, port, &addr);
|
|
||||||
|
|
||||||
// Update the cache.
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.execute(
|
|
||||||
"INSERT INTO dns_cache
|
|
||||||
(hostname, address, timestamp)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
ON CONFLICT (hostname, address)
|
|
||||||
DO UPDATE SET timestamp=excluded.timestamp",
|
|
||||||
(hostname, ip_string, now),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if load_cache {
|
|
||||||
for cached_address in context
|
|
||||||
.sql
|
|
||||||
.query_map(
|
|
||||||
"SELECT address
|
|
||||||
FROM dns_cache
|
|
||||||
WHERE hostname = ?
|
|
||||||
AND ? < timestamp + 30 * 24 * 3600
|
|
||||||
ORDER BY timestamp DESC",
|
|
||||||
(hostname, now),
|
|
||||||
|row| {
|
|
||||||
let address: String = row.get(0)?;
|
|
||||||
Ok(address)
|
|
||||||
},
|
|
||||||
|rows| {
|
|
||||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
|
||||||
.map_err(Into::into)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
match IpAddr::from_str(&cached_address) {
|
|
||||||
Ok(ip_addr) => {
|
|
||||||
let addr = SocketAddr::new(ip_addr, port);
|
|
||||||
if !resolved_addrs.contains(&addr) {
|
|
||||||
resolved_addrs.push(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"Failed to parse cached address {:?}: {:#}.", cached_address, err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resolved_addrs.is_empty() {
|
|
||||||
// Load hardcoded cache if everything else fails.
|
|
||||||
//
|
|
||||||
// See <https://support.delta.chat/t/no-dns-resolution-result/2778> and
|
|
||||||
// <https://github.com/deltachat/deltachat-core-rust/issues/4920> for reasons.
|
|
||||||
//
|
|
||||||
// In the future we may pre-resolve all provider database addresses
|
|
||||||
// and build them in.
|
|
||||||
match hostname {
|
|
||||||
"mail.sangham.net" => {
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0xc17, 0x798c, 0, 0, 0, 1)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(159, 69, 186, 85)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"nine.testrun.org" => {
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0x241, 0x4ce8, 0, 0, 0, 2)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(116, 202, 233, 236)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"disroot.org" => {
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(178, 21, 23, 139)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"mail.riseup.net" => {
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(198, 252, 153, 70)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(198, 252, 153, 71)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"imap.gmail.com" => {
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x400c, 0xc1f, 0, 0, 0, 0x6c)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x400c, 0xc1f, 0, 0, 0, 0x6d)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(142, 250, 110, 109)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(142, 250, 110, 108)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"smtp.gmail.com" => {
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x4013, 0xc04, 0, 0, 0, 0x6c)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
resolved_addrs.push(SocketAddr::new(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(142, 250, 110, 109)),
|
|
||||||
port,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(resolved_addrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a TCP connection stream with read/write timeouts set
|
/// Returns a TCP connection stream with read/write timeouts set
|
||||||
/// and Nagle's algorithm disabled with `TCP_NODELAY`.
|
/// and Nagle's algorithm disabled with `TCP_NODELAY`.
|
||||||
///
|
///
|
||||||
|
|||||||
187
src/net/dns.rs
Normal file
187
src/net/dns.rs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
//! DNS resolution and cache.
|
||||||
|
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::net::lookup_host;
|
||||||
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::tools::time;
|
||||||
|
|
||||||
|
async fn lookup_host_with_timeout(
|
||||||
|
hostname: &str,
|
||||||
|
port: u16,
|
||||||
|
timeout_val: Duration,
|
||||||
|
) -> Result<Vec<SocketAddr>> {
|
||||||
|
let res = timeout(timeout_val, lookup_host((hostname, port)))
|
||||||
|
.await
|
||||||
|
.context("DNS lookup timeout")?
|
||||||
|
.context("DNS lookup failure")?;
|
||||||
|
Ok(res.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks up hostname and port using DNS and updates the address resolution cache.
|
||||||
|
///
|
||||||
|
/// If `load_cache` is true, appends cached results not older than 30 days to the end
|
||||||
|
/// or entries from fallback cache if there are no cached addresses.
|
||||||
|
pub(crate) async fn lookup_host_with_cache(
|
||||||
|
context: &Context,
|
||||||
|
hostname: &str,
|
||||||
|
port: u16,
|
||||||
|
timeout_val: Duration,
|
||||||
|
load_cache: bool,
|
||||||
|
) -> Result<Vec<SocketAddr>> {
|
||||||
|
let now = time();
|
||||||
|
let mut resolved_addrs = match lookup_host_with_timeout(hostname, port, timeout_val).await {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"DNS resolution for {}:{} failed: {:#}.", hostname, port, err
|
||||||
|
);
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for addr in &resolved_addrs {
|
||||||
|
let ip_string = addr.ip().to_string();
|
||||||
|
if ip_string == hostname {
|
||||||
|
// IP address resolved into itself, not interesting to cache.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(context, "Resolved {}:{} into {}.", hostname, port, &addr);
|
||||||
|
|
||||||
|
// Update the cache.
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"INSERT INTO dns_cache
|
||||||
|
(hostname, address, timestamp)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT (hostname, address)
|
||||||
|
DO UPDATE SET timestamp=excluded.timestamp",
|
||||||
|
(hostname, ip_string, now),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if load_cache {
|
||||||
|
for cached_address in context
|
||||||
|
.sql
|
||||||
|
.query_map(
|
||||||
|
"SELECT address
|
||||||
|
FROM dns_cache
|
||||||
|
WHERE hostname = ?
|
||||||
|
AND ? < timestamp + 30 * 24 * 3600
|
||||||
|
ORDER BY timestamp DESC",
|
||||||
|
(hostname, now),
|
||||||
|
|row| {
|
||||||
|
let address: String = row.get(0)?;
|
||||||
|
Ok(address)
|
||||||
|
},
|
||||||
|
|rows| {
|
||||||
|
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
match IpAddr::from_str(&cached_address) {
|
||||||
|
Ok(ip_addr) => {
|
||||||
|
let addr = SocketAddr::new(ip_addr, port);
|
||||||
|
if !resolved_addrs.contains(&addr) {
|
||||||
|
resolved_addrs.push(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"Failed to parse cached address {:?}: {:#}.", cached_address, err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolved_addrs.is_empty() {
|
||||||
|
// Load hardcoded cache if everything else fails.
|
||||||
|
//
|
||||||
|
// See <https://support.delta.chat/t/no-dns-resolution-result/2778> and
|
||||||
|
// <https://github.com/deltachat/deltachat-core-rust/issues/4920> for reasons.
|
||||||
|
//
|
||||||
|
// In the future we may pre-resolve all provider database addresses
|
||||||
|
// and build them in.
|
||||||
|
match hostname {
|
||||||
|
"mail.sangham.net" => {
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0xc17, 0x798c, 0, 0, 0, 1)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(159, 69, 186, 85)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"nine.testrun.org" => {
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V6(Ipv6Addr::new(0x2a01, 0x4f8, 0x241, 0x4ce8, 0, 0, 0, 2)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(116, 202, 233, 236)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"disroot.org" => {
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(178, 21, 23, 139)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"mail.riseup.net" => {
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(198, 252, 153, 70)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(198, 252, 153, 71)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"imap.gmail.com" => {
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x400c, 0xc1f, 0, 0, 0, 0x6c)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x400c, 0xc1f, 0, 0, 0, 0x6d)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(142, 250, 110, 109)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(142, 250, 110, 108)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"smtp.gmail.com" => {
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x4013, 0xc04, 0, 0, 0, 0x6c)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
resolved_addrs.push(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(142, 250, 110, 109)),
|
||||||
|
port,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(resolved_addrs)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user