cf_mach/nq_core/connection/
http.rs1use std::fmt::Debug;
5use std::future::Future;
6use std::net::SocketAddr;
7use std::pin::Pin;
8use std::sync::atomic::{AtomicBool, Ordering};
9
10use anyhow::bail;
11use boring::ssl::{SslConnector, SslMethod, SslVerifyMode, SslVersion};
12use boring::x509::X509;
13use boring::x509::store::X509StoreBuilder;
14use http::header::HOST;
15use http::{HeaderValue, Request, Response};
16use hyper::body::Incoming;
17use hyper::client::conn::{http1, http2};
18use hyper_util::rt::TokioIo;
19use tokio::select;
20use tokio_util::sync::CancellationToken;
21use tracing::{Instrument, debug, error, info, warn};
22
23use crate::nq_core::body::NqBody;
24use crate::nq_core::util::ByteStream;
25use crate::nq_core::{ConnectionTiming, ConnectionType, ResponseFuture, Time};
26
27pub type TlsStream = tokio_boring::SslStream<Box<dyn ByteStream>>;
28
29static INSECURE_TLS: AtomicBool = AtomicBool::new(false);
35
36pub fn set_insecure_tls(insecure: bool) {
38 INSECURE_TLS.store(insecure, Ordering::Relaxed);
39}
40
41pub fn insecure_tls() -> bool {
43 INSECURE_TLS.load(Ordering::Relaxed)
44}
45
46#[derive(Debug)]
49pub struct EstablishedConnection {
50 timing: ConnectionTiming,
51 send_request: Option<SendRequest>,
52}
53
54impl EstablishedConnection {
56 pub fn new(timing: ConnectionTiming, send_request: SendRequest) -> Self {
58 Self {
59 timing,
60 send_request: Some(send_request),
61 }
62 }
63
64 pub fn send_request(&mut self, req: Request<NqBody>) -> Option<ResponseFuture> {
66 self.send_request.as_mut().map(|s| s.send_request(req))
67 }
68
69 pub fn timing(&self) -> ConnectionTiming {
71 self.timing
72 }
73
74 pub fn drop_send_request(&mut self) {
76 self.send_request = None;
77 }
78}
79
80#[tracing::instrument(skip(io, time))]
81pub async fn tls_connection(
82 conn_type: ConnectionType,
83 domain: &str,
84 timing: &mut ConnectionTiming,
85 io: impl ByteStream,
86 time: &dyn Time,
87) -> anyhow::Result<TlsStream> {
88 let mut builder = SslConnector::builder(SslMethod::tls())?;
89
90 let mut store_builder = X509StoreBuilder::new()?;
92 if let Ok(ca_certs) = rustls_native_certs::load_native_certs() {
93 for root in ca_certs {
94 let _ = store_builder.add_cert(X509::from_der(&root)?);
95 }
96 }
97 builder.set_verify_cert_store(store_builder.build())?;
98 if insecure_tls() {
99 debug!("TLS certificate verification disabled (insecure mode)");
100 builder.set_verify(SslVerifyMode::NONE);
101 } else {
102 builder.set_verify(SslVerifyMode::PEER);
103 }
104
105 let alpn: &[u8] = match conn_type {
106 ConnectionType::H1 { use_tls: false } => {
107 bail!("cannot create tls connection if `use_tls: false`")
108 }
109 ConnectionType::H1 { use_tls: true } => b"\x08http/1.1",
110 ConnectionType::H2 => b"\x02h2",
111 ConnectionType::H3 => b"\x02h3",
112 };
113
114 builder.set_alpn_protos(alpn)?;
115 let config = builder.build().configure()?;
116
117 let ssl_stream = tokio_boring::connect(config, domain, Box::new(io) as Box<dyn ByteStream>)
118 .await
119 .map_err(|e| anyhow::anyhow!("unable to create tls stream: {e}"))?;
120
121 timing.set_secure(time.now());
122
123 let tls_round_trips = match ssl_stream.ssl().version2() {
127 Some(SslVersion::TLS1_3) => 1,
128 Some(SslVersion::TLS1_2) => 2,
129 _ => 1,
130 };
131 timing.set_tls_round_trips(tls_round_trips);
132
133 debug!(tls_round_trips, "created tls connection");
134
135 Ok(ssl_stream)
136}
137
138#[tracing::instrument(skip(io, time, shutdown))]
139pub async fn start_h1_conn(
140 domain: String,
141 mut timing: ConnectionTiming,
142 io: impl ByteStream,
143 time: &dyn Time,
144 shutdown: CancellationToken,
145) -> anyhow::Result<EstablishedConnection> {
146 let (send_request, connection) = http1::handshake(TokioIo::new(io)).await?;
147 timing.set_application(time.now());
148
149 tokio::spawn(
150 async move {
151 select! {
152 Err(e) = connection => {
153 debug!(error=%e, "error running h1 connection");
154 }
155 _ = shutdown.cancelled() => {
156 debug!("shutting down h1 connection");
157 }
158 }
159
160 info!("connection finished");
161 }
162 .in_current_span(),
163 );
164
165 let established_connection = EstablishedConnection::new(
166 timing,
167 SendRequest::H1 {
168 dispatch: send_request,
169 },
170 );
171
172 Ok(established_connection)
173}
174
175#[tracing::instrument(skip(timing, io, time, shutdown))]
176pub async fn start_h2_conn(
177 addr: SocketAddr,
178 domain: String,
179 mut timing: ConnectionTiming,
180 io: impl ByteStream,
181 time: &dyn Time,
182 shutdown: CancellationToken,
183) -> anyhow::Result<EstablishedConnection> {
184 let (dispatch, connection) = http2::handshake(TokioExecutor, TokioIo::new(io)).await?;
185 timing.set_application(time.now());
186
187 debug!("finished h2 handshake");
188
189 tokio::spawn(
190 async move {
191 select! {
192 Err(e) = connection => {
193 error!(error=%e, "error running h2 connection");
194 }
195 _ = shutdown.cancelled() => {
196 debug!("shutting down h2 connection");
197 }
198 }
199
200 info!("connection finished");
201 }
202 .in_current_span(),
203 );
204
205 info!(?timing, "established connection");
206 let established_connection = EstablishedConnection::new(timing, SendRequest::H2 { dispatch });
207
208 Ok(established_connection)
209}
210
211#[derive(Debug)]
212pub enum SendRequest {
213 #[allow(unused)]
214 H1 {
215 dispatch: http1::SendRequest<NqBody>,
216 },
217 H2 {
218 dispatch: http2::SendRequest<NqBody>,
219 },
220}
221
222impl SendRequest {
223 fn send_request(
224 &mut self,
225 mut req: Request<NqBody>,
226 ) -> Pin<Box<dyn Future<Output = hyper::Result<Response<Incoming>>> + Send>> {
227 match self {
228 SendRequest::H1 {
229 dispatch: send_request,
230 } => {
231 Self::normalize_h1_request(&mut req);
237 Box::pin(send_request.send_request(req))
238 }
239 SendRequest::H2 {
240 dispatch: send_request,
241 } => {
242 Box::pin(send_request.send_request(req))
245 }
246 }
247 }
248
249 fn normalize_h1_request(req: &mut Request<NqBody>) {
251 if !req.headers().contains_key(HOST)
253 && let Some(authority) = req.uri().authority().cloned()
254 {
255 if let Ok(host) = HeaderValue::from_str(authority.as_str()) {
256 req.headers_mut().insert(HOST, host);
257 } else {
258 warn!(
264 %authority,
265 "could not build a Host header from the URI authority; \
266 sending the request without one"
267 );
268 }
269 }
270
271 let path_and_query = req
273 .uri()
274 .path_and_query()
275 .map(|pq| pq.as_str().to_owned())
276 .unwrap_or_else(|| "/".to_owned());
277
278 match path_and_query.parse::<http::Uri>() {
279 Ok(uri) => *req.uri_mut() = uri,
280 Err(error) => warn!(
285 path_and_query,
286 %error,
287 "failed to parse origin-form URI; sending absolute-form"
288 ),
289 }
290 }
291}
292
293#[derive(Clone)]
294struct TokioExecutor;
295
296impl<F> hyper::rt::Executor<F> for TokioExecutor
297where
298 F: Future + Send + 'static,
299 F::Output: Send + 'static,
300{
301 fn execute(&self, future: F) {
302 tokio::spawn(future);
303 }
304}