1use std::{
6 collections::HashMap,
7 fmt,
8 future::Future,
9 io,
10 net::{SocketAddr, ToSocketAddrs},
11 pin::Pin,
12 sync::{Arc, Mutex},
13 task::{self, Poll},
14 time::Instant,
15 vec,
16};
17
18use eyre::{Context, Result, eyre};
19use hyper_util::client::legacy::connect::{HttpConnector, dns::Name};
20use once_cell::sync::OnceCell;
21use tokio::task::JoinHandle;
22use tokio_rustls::rustls::ClientConfig;
23use tonic::transport::{Channel, Endpoint, Uri};
24use tower::Service;
25use tracing::{info, trace};
26
27use crate::{
28 config::Config,
29 multiaddr::{Multiaddr, Protocol, parse_dns, parse_ip4, parse_ip6},
30};
31
32pub async fn connect(address: &Multiaddr, tls_config: ClientConfig) -> Result<Channel> {
33 let channel = endpoint_from_multiaddr(address, tls_config)?
34 .connect()
35 .await?;
36 Ok(channel)
37}
38
39pub fn connect_lazy(address: &Multiaddr, tls_config: ClientConfig) -> Result<Channel> {
40 let channel = endpoint_from_multiaddr(address, tls_config)?.connect_lazy();
41 Ok(channel)
42}
43
44pub(crate) async fn connect_with_config(
45 address: &Multiaddr,
46 tls_config: ClientConfig,
47 config: &Config,
48) -> Result<Channel> {
49 let channel = endpoint_from_multiaddr(address, tls_config)?
50 .apply_config(config)
51 .connect()
52 .await?;
53 Ok(channel)
54}
55
56pub(crate) fn connect_lazy_with_config(
57 address: &Multiaddr,
58 tls_config: ClientConfig,
59 config: &Config,
60) -> Result<Channel> {
61 let channel = endpoint_from_multiaddr(address, tls_config)?
62 .apply_config(config)
63 .connect_lazy();
64 Ok(channel)
65}
66
67fn endpoint_from_multiaddr(addr: &Multiaddr, tls_config: ClientConfig) -> Result<MyEndpoint> {
68 let mut iter = addr.iter();
69
70 let channel = match iter.next().ok_or_else(|| eyre!("address is empty"))? {
71 Protocol::Dns(_) => {
72 let (dns_name, tcp_port, http_or_https) = parse_dns(addr)?;
73 let uri = format!("{http_or_https}://{dns_name}:{tcp_port}");
74 MyEndpoint::try_from_uri(uri, tls_config)?
75 }
76 Protocol::Ip4(_) => {
77 let (socket_addr, http_or_https) = parse_ip4(addr)?;
78 let uri = format!("{http_or_https}://{socket_addr}");
79 MyEndpoint::try_from_uri(uri, tls_config)?
80 }
81 Protocol::Ip6(_) => {
82 let (socket_addr, http_or_https) = parse_ip6(addr)?;
83 let uri = format!("{http_or_https}://{socket_addr}");
84 MyEndpoint::try_from_uri(uri, tls_config)?
85 }
86 unsupported => return Err(eyre!("unsupported protocol {unsupported}")),
87 };
88
89 Ok(channel)
90}
91
92struct MyEndpoint {
93 endpoint: Endpoint,
94 tls_config: ClientConfig,
95}
96
97static DISABLE_CACHING_RESOLVER: OnceCell<bool> = OnceCell::new();
98
99impl MyEndpoint {
100 fn new(endpoint: Endpoint, tls_config: ClientConfig) -> Self {
101 Self {
102 endpoint,
103 tls_config,
104 }
105 }
106
107 fn try_from_uri(uri: String, tls_config: ClientConfig) -> Result<Self> {
108 let uri: Uri = uri
109 .parse()
110 .with_context(|| format!("unable to create Uri from '{uri}'"))?;
111 let endpoint = Endpoint::from(uri);
112 Ok(Self::new(endpoint, tls_config))
113 }
114
115 fn apply_config(mut self, config: &Config) -> Self {
116 self.endpoint = apply_config_to_endpoint(config, self.endpoint);
117 self
118 }
119
120 fn connect_lazy(self) -> Channel {
121 let disable_caching_resolver = *DISABLE_CACHING_RESOLVER.get_or_init(|| {
122 let disable_caching_resolver = std::env::var("DISABLE_CACHING_RESOLVER").is_ok();
123 info!("DISABLE_CACHING_RESOLVER: {disable_caching_resolver}");
124 disable_caching_resolver
125 });
126
127 let connect_timeout = self.endpoint.get_connect_timeout();
134
135 if disable_caching_resolver {
136 let mut http = HttpConnector::new();
137 http.enforce_http(false);
138 http.set_nodelay(true);
139 http.set_keepalive(None);
140 http.set_connect_timeout(connect_timeout);
141
142 Channel::new(
143 hyper_rustls::HttpsConnectorBuilder::new()
144 .with_tls_config(self.tls_config)
145 .https_only()
146 .enable_http2()
147 .wrap_connector(http),
148 self.endpoint,
149 )
150 } else {
151 let mut http = HttpConnector::new_with_resolver(CachingResolver::new());
152 http.enforce_http(false);
153 http.set_nodelay(true);
154 http.set_keepalive(None);
155 http.set_connect_timeout(connect_timeout);
156
157 let https = hyper_rustls::HttpsConnectorBuilder::new()
158 .with_tls_config(self.tls_config)
159 .https_only()
160 .enable_http2()
161 .wrap_connector(http);
162 Channel::new(https, self.endpoint)
163 }
164 }
165
166 async fn connect(self) -> Result<Channel> {
167 let https_connector = hyper_rustls::HttpsConnectorBuilder::new()
168 .with_tls_config(self.tls_config)
169 .https_only()
170 .enable_http2()
171 .build();
172 Channel::connect(https_connector, self.endpoint)
173 .await
174 .map_err(Into::into)
175 }
176}
177
178fn apply_config_to_endpoint(config: &Config, mut endpoint: Endpoint) -> Endpoint {
179 if let Some(limit) = config.concurrency_limit_per_connection {
180 endpoint = endpoint.concurrency_limit(limit);
181 }
182
183 if let Some(timeout) = config.request_timeout {
184 endpoint = endpoint.timeout(timeout);
185 }
186
187 if let Some(timeout) = config.connect_timeout {
188 endpoint = endpoint.connect_timeout(timeout);
189 }
190
191 if let Some(tcp_nodelay) = config.tcp_nodelay {
192 endpoint = endpoint.tcp_nodelay(tcp_nodelay);
193 }
194
195 if let Some(http2_keepalive_interval) = config.http2_keepalive_interval {
196 endpoint = endpoint.http2_keep_alive_interval(http2_keepalive_interval);
197 }
198
199 if let Some(http2_keepalive_timeout) = config.http2_keepalive_timeout {
200 endpoint = endpoint.keep_alive_timeout(http2_keepalive_timeout);
201 }
202
203 if let Some((limit, duration)) = config.rate_limit {
204 endpoint = endpoint.rate_limit(limit, duration);
205 }
206
207 endpoint
208 .initial_stream_window_size(config.http2_initial_stream_window_size)
209 .initial_connection_window_size(config.http2_initial_connection_window_size)
210 .tcp_keepalive(config.tcp_keepalive)
211}
212
213type CacheEntry = (Instant, Vec<SocketAddr>);
214
215#[derive(Clone)]
217pub struct CachingResolver {
218 cache: Arc<Mutex<HashMap<Name, CacheEntry>>>,
219}
220
221type SocketAddrs = vec::IntoIter<SocketAddr>;
222
223pub struct CachingFuture {
224 inner: JoinHandle<Result<SocketAddrs, io::Error>>,
225}
226
227impl CachingResolver {
228 pub fn new() -> Self {
229 CachingResolver {
230 cache: Arc::new(Mutex::new(HashMap::new())),
231 }
232 }
233}
234
235impl Default for CachingResolver {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl Service<Name> for CachingResolver {
242 type Response = SocketAddrs;
243 type Error = io::Error;
244 type Future = CachingFuture;
245
246 fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
247 Poll::Ready(Ok(()))
248 }
249
250 fn call(&mut self, name: Name) -> Self::Future {
251 let blocking = {
252 let cache = self.cache.clone();
253 tokio::task::spawn_blocking(move || {
254 let entry = cache.lock().unwrap().get(&name).cloned();
255
256 if let Some((when, addrs)) = entry {
257 trace!("cached host={:?}", name.as_str());
258
259 if when.elapsed().as_secs() > 60 {
260 trace!("refreshing cache for host={:?}", name.as_str());
261 tokio::task::spawn_blocking(move || {
263 if let Ok(addrs) = (name.as_str(), 0).to_socket_addrs() {
264 let addrs: Vec<_> = addrs.collect();
265 trace!("updating cached host={:?}", name.as_str());
266 cache.lock().unwrap().insert(name, (Instant::now(), addrs));
267 }
268 });
269 }
270
271 Ok(addrs.into_iter())
272 } else {
273 trace!("resolving host={:?}", name.as_str());
274 match (name.as_str(), 0).to_socket_addrs() {
275 Ok(addrs) => {
276 let addrs: Vec<_> = addrs.collect();
277 cache
278 .lock()
279 .unwrap()
280 .insert(name, (Instant::now(), addrs.clone()));
281 Ok(addrs.into_iter())
282 }
283 res => res,
284 }
285 }
286 })
287 };
288
289 CachingFuture { inner: blocking }
290 }
291}
292
293impl fmt::Debug for CachingResolver {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 f.pad("CachingResolver")
296 }
297}
298
299impl Future for CachingFuture {
300 type Output = Result<SocketAddrs, io::Error>;
301
302 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
303 Pin::new(&mut self.inner).poll(cx).map(|res| match res {
304 Ok(Ok(addrs)) => Ok(addrs),
305 Ok(Err(err)) => Err(err),
306 Err(join_err) => {
307 if join_err.is_cancelled() {
308 Err(io::Error::new(io::ErrorKind::Interrupted, join_err))
309 } else {
310 panic!("background task failed: {join_err:?}")
311 }
312 }
313 })
314 }
315}
316
317impl fmt::Debug for CachingFuture {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 f.pad("CachingFuture")
320 }
321}
322
323impl Drop for CachingFuture {
324 fn drop(&mut self) {
325 self.inner.abort();
326 }
327}