Co-authored-by: Guillaume FOURRIER <guillaume.fourrier@devialet.com>
This commit is contained in:
EkilDeew
2025-05-28 21:45:33 +02:00
committed by GitHub
parent c5ef45128d
commit eba8f86094
13 changed files with 172 additions and 62 deletions

View File

@@ -62,7 +62,7 @@ tokio-util = { version = "0.7.15", features = ["io"] }
tokio-fd = "0.3.0"
[target.'cfg(all(any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies]
tokio-rustls = { version = "0.26.2", features = [] }
tokio-rustls = { version = "0.26.2", features = ["ring"] }
rcgen = { version = "0.13.2", default-features = false, features = ["aws_lc_rs"] }
hickory-resolver = { version = "0.25.2", features = ["tls-aws-lc-rs", "https-aws-lc-rs", "tokio", "rustls-platform-verifier"] }

View File

@@ -97,6 +97,10 @@ pub struct Client {
#[cfg_attr(feature = "clap", arg(long, verbatim_doc_comment))]
pub tls_sni_disable: bool,
/// Enable ECH during TLS handshake
#[cfg_attr(feature = "clap", arg(long, verbatim_doc_comment))]
pub tls_ech_enable: bool,
/// Enable TLS certificate verification.
/// Disabled by default. The client will happily connect to any server with self-signed certificate.
#[cfg_attr(feature = "clap", arg(long, verbatim_doc_comment))]

View File

@@ -82,23 +82,26 @@ async fn create_client_tunnels(
let transport_scheme = TransportScheme::from_str(args.remote_addr.scheme()).expect("invalid scheme in server url");
let tls = match transport_scheme {
TransportScheme::Ws | TransportScheme::Http => None,
TransportScheme::Wss | TransportScheme::Https => Some(TlsClientConfig {
tls_connector: Arc::new(RwLock::new(
tls::tls_connector(
args.tls_verify_certificate,
transport_scheme.alpn_protocols(),
!args.tls_sni_disable,
tls_certificate,
tls_key,
)
.expect("Cannot create tls connector"),
)),
tls_sni_override: args.tls_sni_override,
tls_verify_certificate: args.tls_verify_certificate,
tls_sni_disabled: args.tls_sni_disable,
tls_certificate_path: args.tls_certificate.clone(),
tls_key_path: args.tls_private_key.clone(),
}),
TransportScheme::Wss | TransportScheme::Https => {
let opts = tls::tls_connector(
args.tls_verify_certificate,
transport_scheme.alpn_protocols(),
!args.tls_sni_disable,
tls_certificate,
tls_key,
).expect("Cannot create tls connector");
Some(TlsClientConfig {
tls_connector: Arc::new(RwLock::new(opts.0)),
root_store: opts.1,
tls_sni_override: args.tls_sni_override,
tls_verify_certificate: args.tls_verify_certificate,
tls_sni_disabled: args.tls_sni_disable,
tls_certificate_path: args.tls_certificate.clone(),
tls_key_path: args.tls_private_key.clone(),
tls_ech_enabled: args.tls_ech_enable
})
}
};
// Extract host header from http_headers
@@ -143,6 +146,7 @@ async fn create_client_tunnels(
http_proxy.clone(),
SoMark::new(args.socket_so_mark),
!args.dns_resolver_prefer_ipv4,
args.tls_ech_enable
)
.expect("cannot create dns resolver"),
http_proxy,
@@ -488,6 +492,7 @@ async fn run_server_impl(args: Server, executor: impl TokioExecutor) -> anyhow::
None,
SoMark::new(args.socket_so_mark),
!args.dns_resolver_prefer_ipv4,
false
)
.expect("Cannot create DNS resolver"),
restriction_config: args.restrict_config,

View File

@@ -2,13 +2,18 @@ use crate::protocols;
use crate::somark::SoMark;
use anyhow::{Context, anyhow};
use futures_util::{FutureExt, TryFutureExt};
use hickory_resolver::Resolver;
use hickory_resolver::config::{LookupIpStrategy, NameServerConfig, ResolverConfig, ResolverOpts};
use hickory_resolver::name_server::GenericConnector;
use hickory_resolver::name_server::{ConnectionProvider, GenericConnector};
use hickory_resolver::proto::rr::rdata::svcb::{SvcParamKey, SvcParamValue};
use hickory_resolver::proto::rr::{RData, RecordType};
use hickory_resolver::proto::runtime::iocompat::AsyncIoTokioAsStd;
use hickory_resolver::proto::runtime::{RuntimeProvider, TokioHandle, TokioRuntimeProvider, TokioTime};
use hickory_resolver::proto::xfer::Protocol;
use hickory_resolver::{Resolver, ResolveError};
use log::warn;
use tokio_rustls::rustls::client::EchConfig;
use tokio_rustls::rustls::crypto::aws_lc_rs::hpke::ALL_SUPPORTED_SUITES;
use tokio_rustls::rustls::pki_types::EchConfigListBytes;
use std::future::Future;
use std::net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::pin::Pin;
@@ -40,14 +45,23 @@ pub enum DnsResolver {
TrustDns {
resolver: Resolver<GenericConnector<TokioRuntimeProviderWithSoMark>>,
prefer_ipv6: bool,
ech_enabled: bool,
},
}
impl DnsResolver {
pub async fn lookup_host(&self, domain: &str, port: u16) -> anyhow::Result<Vec<SocketAddr>> {
let addrs: Vec<SocketAddr> = match self {
Self::System => tokio::net::lookup_host(format!("{}:{}", domain, port)).await?.collect(),
Self::TrustDns { resolver, prefer_ipv6 } => {
pub async fn lookup_host(&self, domain: &str, port: u16) -> anyhow::Result<(Vec<SocketAddr>, Option<EchConfig>)> {
let addrs: (Vec<SocketAddr>, Option<EchConfig>) = match self {
Self::System => {
(tokio::net::lookup_host(format!("{}:{}", domain, port)).await?.collect(), None)
},
Self::TrustDns { resolver, prefer_ipv6, ech_enabled } => {
let mut ech_config: Option<EchConfig> = None;
if *ech_enabled {
ech_config = Self::lookup_ech_config(domain, resolver).await?;
}
let addrs: Vec<_> = resolver
.lookup_ip(domain)
.await?
@@ -57,18 +71,50 @@ impl DnsResolver {
IpAddr::V6(ip) => SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)),
})
.collect();
sort_socket_addrs(&addrs, *prefer_ipv6).copied().collect()
let sorted_addr = sort_socket_addrs(&addrs, *prefer_ipv6).copied().collect();
(sorted_addr, ech_config)
}
};
Ok(addrs)
}
async fn lookup_ech_config<P: ConnectionProvider>(domain: &str, resolver: &Resolver<P>) -> Result<Option<EchConfig>, ResolveError> {
let lookup = resolver
.lookup(domain, RecordType::HTTPS)
.await?;
let mut ech_config_lists = Vec::new();
for r in lookup.record_iter() {
let RData::HTTPS(svcb) = r.data() else {
continue;
};
ech_config_lists.extend(
svcb.svc_params()
.iter()
.find_map(|sp| match sp {
(SvcParamKey::EchConfigList, SvcParamValue::EchConfigList(e)) => {
Some(EchConfigListBytes::from(e.clone().0))
}
_ => None,
}),
)
}
let ech_config = ech_config_lists
.into_iter()
.find_map(|list| EchConfig::new(list, ALL_SUPPORTED_SUITES).ok());
Ok(ech_config)
}
pub fn new_from_urls(
resolvers: &[Url],
proxy: Option<Url>,
so_mark: SoMark,
prefer_ipv6: bool,
ech_enabled: bool,
) -> anyhow::Result<Self> {
fn mk_resolver(
cfg: ResolverConfig,
@@ -144,6 +190,7 @@ impl DnsResolver {
return Ok(Self::TrustDns {
resolver: mk_resolver(cfg, opts, proxy, so_mark),
prefer_ipv6,
ech_enabled
});
};
@@ -161,6 +208,7 @@ impl DnsResolver {
Ok(Self::TrustDns {
resolver: mk_resolver(cfg, ResolverOpts::default(), proxy, so_mark),
prefer_ipv6,
ech_enabled
})
}
}
@@ -218,8 +266,10 @@ impl RuntimeProvider for TokioRuntimeProviderWithSoMark {
&DnsResolver::System, // not going to be used as host is directly an ip address
)
.map_err(std::io::Error::other)
.map_ok(|s| s.0)
.map(|s| s.map(AsyncIoTokioAsStd))
.await
} else {
protocols::tcp::connect(
&host,
@@ -229,6 +279,7 @@ impl RuntimeProvider for TokioRuntimeProviderWithSoMark {
&DnsResolver::System, // not going to be used as host is directly an ip address
)
.map_err(std::io::Error::other)
.map_ok(|s| s.0)
.map(|s| s.map(AsyncIoTokioAsStd))
.await
}

View File

@@ -1,4 +1,5 @@
use anyhow::{Context, anyhow};
use tokio_rustls::rustls::client::EchConfig;
use std::{io, vec};
use tokio::task::JoinSet;
@@ -53,23 +54,23 @@ pub async fn connect(
so_mark: SoMark,
connect_timeout: Duration,
dns_resolver: &DnsResolver,
) -> Result<TcpStream, anyhow::Error> {
) -> Result<(TcpStream, Option<EchConfig>), anyhow::Error> {
info!("Opening TCP connection to {}:{}", host, port);
let socket_addrs: Vec<SocketAddr> = match host {
let socket_addrs: (Vec<SocketAddr>, Option<EchConfig>) = match host {
Host::Domain(domain) => dns_resolver
.lookup_host(domain.as_str(), port)
.await
.with_context(|| format!("cannot resolve domain: {}", domain))?,
Host::Ipv4(ip) => vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))],
Host::Ipv6(ip) => vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))],
Host::Ipv4(ip) => (vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))], None),
Host::Ipv6(ip) => (vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))], None),
};
let mut cnx = None;
let mut last_err = None;
let mut join_set = JoinSet::new();
for (ix, addr) in socket_addrs.into_iter().enumerate() {
for (ix, addr) in socket_addrs.0.into_iter().enumerate() {
let socket = match &addr {
SocketAddr::V4(_) => TcpSocket::new_v4(),
SocketAddr::V6(_) => TcpSocket::new_v6(),
@@ -115,7 +116,7 @@ pub async fn connect(
"Connected to tcp endpoint {}, aborted all other connection attempts",
stream.peer_addr()?
);
cnx = Some(stream);
cnx = Some((stream, socket_addrs.1.clone()));
}
Ok(Err((addr, err))) => {
debug!("Cannot connect to tcp endpoint {addr} reason {err}");
@@ -141,13 +142,13 @@ pub async fn connect_with_http_proxy(
so_mark: SoMark,
connect_timeout: Duration,
dns_resolver: &DnsResolver,
) -> Result<TcpStream, anyhow::Error> {
) -> Result<(TcpStream, Option<EchConfig>), anyhow::Error> {
let proxy_host = proxy.host().context("Cannot parse proxy host")?.to_owned();
let proxy_port = proxy.port_or_known_default().unwrap_or(80);
info!("Connecting to http proxy {}:{}", proxy_host, proxy_port);
let mut socket = connect(&proxy_host, proxy_port, so_mark, connect_timeout, dns_resolver).await?;
debug!("Connected to http proxy {}", socket.peer_addr().unwrap());
debug!("Connected to http proxy {}", socket.0.peer_addr().unwrap());
let authorization = if let Some((user, password)) = proxy.password().map(|p| (proxy.username(), p)) {
let user = urlencoding::decode(user).with_context(|| format!("Cannot urldecode proxy user: {}", user))?;
@@ -161,11 +162,11 @@ pub async fn connect_with_http_proxy(
let connect_request = format!("CONNECT {host}:{port} HTTP/1.0\r\nHost: {host}:{port}\r\n{authorization}\r\n");
debug!("Sending request:\n{}", connect_request);
socket.write_all(connect_request.as_bytes()).await?;
socket.0.write_all(connect_request.as_bytes()).await?;
let mut buf = BytesMut::with_capacity(1024);
loop {
let nb_bytes = tokio::time::timeout(connect_timeout, socket.read_buf(&mut buf)).await;
let nb_bytes = tokio::time::timeout(connect_timeout, socket.0.read_buf(&mut buf)).await;
match nb_bytes {
Ok(Ok(0)) => {
return Err(anyhow!(
@@ -294,7 +295,7 @@ mod tests {
.await
.unwrap();
client.write_all(b"GET / HTTP/1.1\r\n\r\n".as_slice()).await.unwrap();
client.0.write_all(b"GET / HTTP/1.1\r\n\r\n".as_slice()).await.unwrap();
let client_srv = server.accept().await.unwrap().0;
pin_mut!(client_srv);
@@ -304,7 +305,7 @@ mod tests {
client_srv.write_all(b"HTTP/1.1 200 OK\r\n\r\n").await.unwrap();
client_srv.get_mut().shutdown().await.unwrap();
let _ = client.read(&mut buf).await.unwrap();
let _ = client.0.read(&mut buf).await.unwrap();
assert!(buf.starts_with(b"HTTP/1.1 200 OK\r\n\r\n"));
}
}

View File

@@ -1,4 +1,6 @@
use anyhow::{Context, anyhow};
use tokio_rustls::rustls::client::{EchConfig, EchMode};
use tokio_rustls::rustls::crypto::ring;
use std::fs::File;
use log::warn;
@@ -8,13 +10,13 @@ use std::sync::Arc;
use tokio::net::TcpStream;
use tokio_rustls::client::TlsStream;
use crate::tunnel::client::WsClientConfig;
use crate::tunnel::client::{TlsClientConfig, WsClientConfig};
use crate::tunnel::server::TlsServerConfig;
use crate::tunnel::transport::TransportAddr;
use tokio_rustls::rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
use tokio_rustls::rustls::server::WebPkiClientVerifier;
use tokio_rustls::rustls::{ClientConfig, DigitallySignedStruct, Error, KeyLogFile, SignatureScheme};
use tokio_rustls::rustls::{ClientConfig, DigitallySignedStruct, Error, KeyLogFile, RootCertStore, SignatureScheme};
use tokio_rustls::{TlsAcceptor, TlsConnector, rustls};
use tracing::info;
@@ -108,7 +110,7 @@ pub fn tls_connector(
enable_sni: bool,
tls_client_certificate: Option<Vec<CertificateDer<'static>>>,
tls_client_key: Option<PrivateKeyDer<'static>>,
) -> anyhow::Result<TlsConnector> {
) -> anyhow::Result<(TlsConnector, RootCertStore)> {
let mut root_store = rustls::RootCertStore::empty();
// Load system certificates and add them to the root store
@@ -122,8 +124,10 @@ pub fn tls_connector(
continue;
}
}
rustls::crypto::ring::default_provider().install_default().expect("Failed to install rustls crypto provider");
let config_builder = ClientConfig::builder().with_root_certificates(root_store);
let config_builder = ClientConfig::builder().with_root_certificates(root_store.clone());
let mut config = match (tls_client_certificate, tls_client_key) {
(Some(tls_client_certificate), Some(tls_client_key)) => config_builder
@@ -142,7 +146,7 @@ pub fn tls_connector(
config.alpn_protocols = alpn_protocols;
let tls_connector = TlsConnector::from(Arc::new(config));
Ok(tls_connector)
Ok((tls_connector, root_store))
}
pub fn tls_acceptor(tls_cfg: &TlsServerConfig, alpn_protocols: Option<Vec<Vec<u8>>>) -> anyhow::Result<TlsAcceptor> {
@@ -173,7 +177,7 @@ pub fn tls_acceptor(tls_cfg: &TlsServerConfig, alpn_protocols: Option<Vec<Vec<u8
Ok(TlsAcceptor::from(Arc::new(config)))
}
pub async fn connect(client_cfg: &WsClientConfig, tcp_stream: TcpStream) -> anyhow::Result<TlsStream<TcpStream>> {
pub async fn connect(client_cfg: &WsClientConfig, tcp_stream: (TcpStream, Option<EchConfig>)) -> anyhow::Result<TlsStream<TcpStream>> {
let sni = client_cfg.tls_server_name();
let (tls_connector, sni_disabled) = match &client_cfg.remote_addr {
TransportAddr::Wss { tls, .. } => (tls.tls_connector(), tls.tls_sni_disabled),
@@ -197,13 +201,54 @@ pub async fn connect(client_cfg: &WsClientConfig, tcp_stream: TcpStream) -> anyh
);
}
let tls_stream = tls_connector.connect(sni, tcp_stream).await.with_context(|| {
let connector = tls_connector.clone();
let tls_config = match &client_cfg.remote_addr {
TransportAddr::Wss { tls, .. } => Some(tls),
TransportAddr::Https { tls, .. } => Some(tls),
_ => None,
};
let tls_stream = if let (Some(tls_config) , Some(ech_config), Some(true)) = (tls_config, tcp_stream.1, is_ech_enabled(tls_config)) {
tls_connect_with_ech(ech_config, tls_config, tls_connector.clone(), client_cfg, tcp_stream.0, sni).await?
} else {
connector.connect(sni, tcp_stream.0).await?
};
Ok(tls_stream)
}
fn is_ech_enabled(tls_config: Option<&TlsClientConfig>) -> Option<bool> {
match tls_config {
Some(config) => return Some(config.tls_ech_enabled),
_ => None
}
}
async fn tls_connect_with_ech(
ech_config: EchConfig,
tls_config: &TlsClientConfig,
original_connector: TlsConnector,
client_cfg: &WsClientConfig,
tcp_stream: TcpStream,
sni: ServerName<'static>
) -> anyhow::Result<TlsStream<TcpStream>> {
let mut ech_client_config = ClientConfig::builder_with_provider(ring::default_provider().into())
.with_ech(EchMode::from(ech_config))?
.with_root_certificates(tls_config.root_store.clone())
.with_no_client_auth();
let original_config = original_connector.config();
ech_client_config.key_log = original_config.key_log.clone();
ech_client_config.alpn_protocols = original_config.alpn_protocols.clone();
return TlsConnector::from(Arc::new(ech_client_config))
.connect(sni, tcp_stream)
.await.with_context(|| {
format!(
"failed to do TLS handshake with the server {}:{}",
client_cfg.remote_addr.host(),
client_cfg.remote_addr.port()
)
})?;
Ok(tls_stream)
}
});
}

View File

@@ -3,6 +3,7 @@ use futures_util::{Stream, stream};
use parking_lot::RwLock;
use pin_project::{pin_project, pinned_drop};
use tokio_rustls::rustls::client::EchConfig;
use std::collections::HashMap;
use std::future::Future;
use std::io::{Error, ErrorKind};
@@ -343,20 +344,20 @@ pub async fn connect(
) -> anyhow::Result<WsUdpSocket> {
info!("Opening UDP connection to {}:{}", host, port);
let socket_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(ip) => vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))],
Host::Ipv6(ip) => vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))],
let socket_addrs: (Vec<SocketAddr>, Option<EchConfig>) = match host {
Host::Domain(domain) => dns_resolver
.lookup_host(domain.as_str(), port)
.await
.with_context(|| format!("cannot resolve domain: {}", domain))?,
Host::Ipv4(ip) => (vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))], None),
Host::Ipv6(ip) => (vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))], None),
};
let mut cnx = None;
let mut last_err = None;
let mut join_set = JoinSet::new();
for (ix, addr) in socket_addrs.into_iter().enumerate() {
for (ix, addr) in socket_addrs.0.into_iter().enumerate() {
let socket = match &addr {
SocketAddr::V4(_) => UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await,
SocketAddr::V6(_) => UdpSocket::bind(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)).await,

View File

@@ -25,7 +25,7 @@ use url::Host;
#[fixture]
fn dns_resolver() -> DnsResolver {
DnsResolver::new_from_urls(&[], None, SoMark::new(None), true).expect("Cannot create DNS resolver")
DnsResolver::new_from_urls(&[], None, SoMark::new(None), true, false).expect("Cannot create DNS resolver")
}
#[fixture]
@@ -148,7 +148,7 @@ async fn test_tcp_tunnel(
.await
.unwrap();
client.write_all(b"Hello").await.unwrap();
client.0.write_all(b"Hello").await.unwrap();
let mut dd = tcp_listener.next().await.unwrap().unwrap();
let mut buf = BytesMut::new();
dd.read_buf(&mut buf).await.unwrap();
@@ -156,7 +156,7 @@ async fn test_tcp_tunnel(
buf.clear();
dd.write_all(b"world!").await.unwrap();
client.read_buf(&mut buf).await.unwrap();
client.0.read_buf(&mut buf).await.unwrap();
assert_eq!(&buf[..6], b"world!");
}

View File

@@ -58,7 +58,7 @@ impl ManageConnection for WsConnection {
let tls_stream = tls::connect(self, tcp_stream).await?;
Ok(Some(TransportStream::from_client_tls(tls_stream, Bytes::default())))
} else {
Ok(Some(TransportStream::from_tcp(tcp_stream, Bytes::default())))
Ok(Some(TransportStream::from_tcp(tcp_stream.0, Bytes::default())))
}
}

View File

@@ -3,6 +3,7 @@ use crate::somark::SoMark;
use crate::tunnel::transport::TransportAddr;
use hyper::header::{HeaderName, HeaderValue};
use parking_lot::RwLock;
use tokio_rustls::rustls::RootCertStore;
use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;
@@ -57,6 +58,8 @@ pub struct TlsClientConfig {
pub tls_connector: Arc<RwLock<TlsConnector>>,
pub tls_certificate_path: Option<PathBuf>,
pub tls_key_path: Option<PathBuf>,
pub root_store: RootCertStore,
pub tls_ech_enabled: bool,
}
impl TlsClientConfig {

View File

@@ -51,7 +51,7 @@ impl TunnelConnector for Socks5TunnelConnector<'_> {
self.dns_resolver,
)
.await?;
let (reader, writer) = stream.into_split();
let (reader, writer) = stream.0.into_split();
Ok((Socks5Reader::Tcp(reader), Socks5Writer::Tcp(writer)))
}
LocalProtocol::Udp { .. } => {
@@ -84,7 +84,7 @@ impl TunnelConnector for Socks5TunnelConnector<'_> {
self.dns_resolver,
)
.await?;
let (reader, writer) = stream.into_split();
let (reader, writer) = stream.0.into_split();
Ok((Socks5Reader::Tcp(reader), Socks5Writer::Tcp(writer)))
}
_ => Err(anyhow!("Socks5 UDP cannot use http proxy to connect to destination")),

View File

@@ -46,7 +46,7 @@ impl TunnelConnector for TcpTunnelConnector<'_> {
};
let stream = protocols::tcp::connect(host, port, self.so_mark, self.connect_timeout, self.dns_resolver).await?;
Ok(stream.into_split())
Ok(stream.0.into_split())
}
async fn connect_with_http_proxy(
@@ -68,6 +68,6 @@ impl TunnelConnector for TcpTunnelConnector<'_> {
self.dns_resolver,
)
.await?;
Ok(stream.into_split())
Ok(stream.0.into_split())
}
}

View File

@@ -300,7 +300,7 @@ impl TlsReloader {
return;
}
};
*tls.tls_connector.write() = tls_connector;
*tls.tls_connector.write() = tls_connector.0;
this.tls_reload_certificate.store(true, Ordering::Relaxed);
}
(Err(err), _) | (_, Err(err)) => {
@@ -343,7 +343,7 @@ impl TlsReloader {
return;
}
};
*tls.tls_connector.write() = tls_connector;
*tls.tls_connector.write() = tls_connector.0;
this.tls_reload_certificate.store(true, Ordering::Relaxed);
}
(Err(err), _) | (_, Err(err)) => {