Skip to content

Commit 27379e9

Browse files
committed
Apply --resolve to every upstream connection, log pins at startup
The DNS pin only covered the matched-rule MITM upstream, so a CONNECT to a host that isn't a configured --from (a directly-hit host, a sub-resource) still went through the blind tunnel via real DNS and ignored the pin. Route all three connection paths (MITM, blind tunnel, plain-HTTP forward) through a shared `connect_upstream` helper that honors the pin case-insensitively. Also log each pin at startup (`--resolve pin: HOST -> IP`) so it's visible at the default info level — the per-connection summary shows the hostname (SNI/cert use it) even though the socket dials the pinned IP.
1 parent 73dea69 commit 27379e9

1 file changed

Lines changed: 31 additions & 12 deletions

File tree

  • crates/trusted-server-cli/src/commands/dev/proxy

crates/trusted-server-cli/src/commands/dev/proxy/server.rs

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ pub async fn serve_on(
5757
) -> Result<(), Report<ProxyError>> {
5858
let is_loopback = is_loopback(cfg.listen.ip());
5959
log::info!("listening on {}", cfg.listen);
60+
for (host, ip) in &cfg.resolve {
61+
log::info!("--resolve pin: {host} -> {ip}");
62+
}
6063
loop {
6164
let (client, peer) = match listener.accept().await {
6265
Ok(pair) => pair,
@@ -161,7 +164,7 @@ async fn handle_connection(
161164
}
162165
// Stray absolute-form plain HTTP.
163166
if is_loopback {
164-
blind_forward_http(client, &head).await
167+
blind_forward_http(client, &head, &cfg.resolve).await
165168
} else {
166169
respond_status_line(&mut client, StatusCode::FORBIDDEN).await
167170
}
@@ -196,7 +199,26 @@ async fn handle_connect(
196199
}
197200

198201
// No match on loopback: connect upstream FIRST, then reply 200 (else 502).
199-
blind_tunnel(client, &host, port).await
202+
blind_tunnel(client, &host, port, &cfg.resolve).await
203+
}
204+
205+
/// Opens an upstream TCP connection, honoring a `--resolve` pin for `host`.
206+
///
207+
/// When `host` (matched case-insensitively) has a pin, the socket dials that IP
208+
/// instead of resolving the name via DNS. The TLS SNI / `Host` set by the caller
209+
/// are unaffected, so the certificate still validates against the hostname.
210+
async fn connect_upstream(
211+
host: &str,
212+
port: u16,
213+
resolve: &HashMap<String, IpAddr>,
214+
) -> std::io::Result<TcpStream> {
215+
if !resolve.is_empty()
216+
&& let Some(ip) = resolve.get(&host.to_ascii_lowercase())
217+
{
218+
log::debug!("--resolve {host}:{port} -> {ip}:{port}");
219+
return TcpStream::connect((*ip, port)).await;
220+
}
221+
TcpStream::connect((host, port)).await
200222
}
201223

202224
/// Connects to the upstream first; on success replies `200` then pipes bytes
@@ -205,8 +227,9 @@ async fn blind_tunnel(
205227
mut client: TcpStream,
206228
host: &str,
207229
port: u16,
230+
resolve: &HashMap<String, IpAddr>,
208231
) -> Result<(), Report<ProxyError>> {
209-
let mut upstream = match TcpStream::connect((host, port)).await {
232+
let mut upstream = match connect_upstream(host, port, resolve).await {
210233
Ok(stream) => stream,
211234
Err(err) => {
212235
log::warn!("blind tunnel to {host}:{port} failed: {err}");
@@ -267,6 +290,7 @@ async fn serve_pac(client: &mut TcpStream, pac: &str) -> Result<(), Report<Proxy
267290
async fn blind_forward_http(
268291
mut client: TcpStream,
269292
head: &RequestHead,
293+
resolve: &HashMap<String, IpAddr>,
270294
) -> Result<(), Report<ProxyError>> {
271295
let Ok(uri) = head.target.parse::<Uri>() else {
272296
return respond_status_line(&mut client, StatusCode::BAD_REQUEST).await;
@@ -275,7 +299,7 @@ async fn blind_forward_http(
275299
return respond_status_line(&mut client, StatusCode::BAD_REQUEST).await;
276300
};
277301
let port = uri.port_u16().unwrap_or(80);
278-
let mut upstream = match TcpStream::connect((host, port)).await {
302+
let mut upstream = match connect_upstream(host, port, resolve).await {
279303
Ok(stream) => stream,
280304
Err(err) => {
281305
log::warn!("plain-HTTP forward to {host}:{port} failed: {err}");
@@ -432,14 +456,9 @@ async fn proxy_to_upstream(
432456

433457
// Dial the `--resolve` pin when the upstream host has one; the SNI/`Host`
434458
// (set above) stay the hostname, so the certificate still validates.
435-
let tcp = match resolve.get(upstream_host) {
436-
Some(ip) => {
437-
log::debug!("--resolve {upstream_host} -> {ip}");
438-
TcpStream::connect((*ip, upstream_port)).await
439-
}
440-
None => TcpStream::connect((upstream_host, upstream_port)).await,
441-
}
442-
.change_context(ProxyError::Server)?;
459+
let tcp = connect_upstream(upstream_host, upstream_port, resolve)
460+
.await
461+
.change_context(ProxyError::Server)?;
443462

444463
let response = if outcome.scheme_is_tls {
445464
let connector = TlsConnector::from(client_config(insecure));

0 commit comments

Comments
 (0)