Files
proxy/src/http/proxy.rs
T
2026-06-08 16:57:10 +02:00

948 lines
26 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::timeout;
use crate::config::{ForwardedMode, ListenerConfig, Protocol, TlsMode};
use crate::error::{ProxyError, Result};
use crate::net::BoxedStream;
use crate::proto::common::Ctx;
use super::head::{BodyFraming, Head, ParseError, ParseOutcome, error_response};
use super::router::{HttpRouter, RouteOutcome};
const READ_CHUNK: usize = 16 * 1024;
#[derive(Clone, Copy)]
struct Idle {
body: Duration,
stream: Duration,
}
struct Reader {
buf: Vec<u8>,
pos: usize,
eof: bool,
}
impl Reader {
fn new() -> Self {
Reader {
buf: Vec::with_capacity(READ_CHUNK),
pos: 0,
eof: false,
}
}
fn data(&self) -> &[u8] {
&self.buf[self.pos..]
}
fn is_empty(&self) -> bool {
self.pos >= self.buf.len()
}
fn consume(&mut self, n: usize) {
self.pos += n;
if self.pos >= self.buf.len() {
self.buf.clear();
self.pos = 0;
} else if self.pos >= READ_CHUNK {
self.buf.drain(..self.pos);
self.pos = 0;
}
}
fn take_data(&mut self) -> Vec<u8> {
let out = self.buf[self.pos..].to_vec();
self.buf.clear();
self.pos = 0;
out
}
async fn fill(&mut self, stream: &mut BoxedStream, idle: Duration) -> Result<usize> {
let start = self.buf.len();
self.buf.resize(start + READ_CHUNK, 0);
let n = match timeout(idle, stream.read(&mut self.buf[start..])).await {
Ok(r) => r.map_err(|e| ProxyError::protocol(format!("reading stream: {e}")))?,
Err(_) => {
self.buf.truncate(start);
return Err(ProxyError::protocol("read idle timeout"));
}
};
self.buf.truncate(start + n);
if n == 0 {
self.eof = true;
}
Ok(n)
}
}
async fn read_client_head(
stream: &mut BoxedStream,
reader: &mut Reader,
max_head: usize,
idle: Duration,
) -> std::result::Result<Option<Head>, ParseError> {
loop {
match Head::parse(reader.data(), max_head)? {
ParseOutcome::Done { head, consumed } => {
reader.consume(consumed);
return Ok(Some(head));
}
ParseOutcome::NeedMore => {}
}
if reader.eof {
if reader.is_empty() {
return Ok(None);
}
return Err(ParseError::BadRequest);
}
let n = reader
.fill(stream, idle)
.await
.map_err(|_| ParseError::BadRequest)?;
if n == 0 && reader.is_empty() {
return Ok(None);
}
}
}
async fn read_backend_head(
stream: &mut BoxedStream,
reader: &mut Reader,
max_head: usize,
idle: Duration,
) -> Result<Head> {
loop {
match Head::parse_response(reader.data(), max_head)
.map_err(|_| ProxyError::backend("malformed backend response head"))?
{
ParseOutcome::Done { head, consumed } => {
reader.consume(consumed);
return Ok(head);
}
ParseOutcome::NeedMore => {}
}
let n = reader
.fill(stream, idle)
.await
.map_err(|_| ProxyError::backend("reading backend"))?;
if n == 0 {
return Err(ProxyError::backend("backend closed before response head"));
}
}
}
async fn push_client(stream: &mut BoxedStream, bytes: &[u8]) -> Result<()> {
stream
.write_all(bytes)
.await
.map_err(|e| ProxyError::protocol(format!("writing client: {e}")))
}
async fn flush_client(stream: &mut BoxedStream) -> Result<()> {
stream
.flush()
.await
.map_err(|e| ProxyError::protocol(format!("flushing client: {e}")))
}
async fn push_backend(stream: &mut BoxedStream, bytes: &[u8]) -> Result<()> {
stream
.write_all(bytes)
.await
.map_err(|e| ProxyError::backend(format!("writing backend: {e}")))
}
async fn flush_backend(stream: &mut BoxedStream) -> Result<()> {
stream
.flush()
.await
.map_err(|e| ProxyError::backend(format!("flushing backend: {e}")))
}
async fn write_client(stream: &mut BoxedStream, bytes: &[u8]) -> Result<()> {
push_client(stream, bytes).await?;
flush_client(stream).await
}
async fn transfer_length(
src: &mut BoxedStream,
reader: &mut Reader,
dst: &mut BoxedStream,
to_backend: bool,
mut remaining: u64,
idle: Duration,
) -> Result<()> {
while remaining > 0 {
if reader.is_empty() {
let n = reader.fill(src, idle).await?;
if n == 0 {
return Err(if to_backend {
ProxyError::protocol("client closed mid body")
} else {
ProxyError::backend("backend closed mid body")
});
}
}
let take = {
let avail = reader.data();
let take = (remaining as usize).min(avail.len());
forward(dst, &avail[..take], to_backend).await?;
take
};
reader.consume(take);
remaining -= take as u64;
}
Ok(())
}
async fn transfer_chunked(
src: &mut BoxedStream,
reader: &mut Reader,
dst: &mut BoxedStream,
to_backend: bool,
idle: Duration,
) -> Result<()> {
loop {
let line = read_chunk_line(src, reader, idle).await?;
let size_str = line.split(';').next().unwrap_or("").trim();
let size = u64::from_str_radix(size_str, 16)
.map_err(|_| ProxyError::protocol("invalid chunk size"))?;
if size == 0 {
let mut frame = format!("{size:x}\r\n").into_bytes();
loop {
let trailer = read_chunk_line(src, reader, idle).await?;
frame.extend_from_slice(trailer.as_bytes());
frame.extend_from_slice(b"\r\n");
if trailer.is_empty() {
break;
}
}
forward(dst, &frame, to_backend).await?;
return Ok(());
}
let header = format!("{size:x}\r\n");
let mut remaining = size;
let mut header_pending = true;
while remaining > 0 {
if reader.is_empty() {
let n = reader.fill(src, idle).await?;
if n == 0 {
return Err(ProxyError::protocol("closed mid chunk"));
}
}
let take = (remaining as usize).min(reader.data().len());
let last = remaining - take as u64 == 0;
{
let avail = reader.data();
if header_pending || last {
let mut frame =
Vec::with_capacity(header.len() * header_pending as usize + take + 2);
if header_pending {
frame.extend_from_slice(header.as_bytes());
}
frame.extend_from_slice(&avail[..take]);
if last {
frame.extend_from_slice(b"\r\n");
}
forward(dst, &frame, to_backend).await?;
} else {
forward(dst, &avail[..take], to_backend).await?;
}
}
header_pending = false;
reader.consume(take);
remaining -= take as u64;
}
let crlf = read_chunk_line(src, reader, idle).await?;
if !crlf.is_empty() {
return Err(ProxyError::protocol("malformed chunk terminator"));
}
}
}
async fn forward(dst: &mut BoxedStream, bytes: &[u8], to_backend: bool) -> Result<()> {
if to_backend {
push_backend(dst, bytes).await
} else {
push_client(dst, bytes).await
}
}
async fn flush_side(dst: &mut BoxedStream, to_backend: bool) -> Result<()> {
if to_backend {
flush_backend(dst).await
} else {
flush_client(dst).await
}
}
async fn read_chunk_line(
src: &mut BoxedStream,
reader: &mut Reader,
idle: Duration,
) -> Result<String> {
loop {
let found = {
let data = reader.data();
if let Some(pos) = data.iter().position(|&b| b == b'\n') {
let mut end = pos;
if end > 0 && data[end - 1] == b'\r' {
end -= 1;
}
let line = String::from_utf8(data[..end].to_vec())
.map_err(|_| ProxyError::protocol("invalid chunk line"))?;
Some((line, pos + 1))
} else {
None
}
};
if let Some((line, consumed)) = found {
reader.consume(consumed);
return Ok(line);
}
if reader.data().len() > 16 * 1024 {
return Err(ProxyError::protocol("chunk line too long"));
}
let n = reader.fill(src, idle).await?;
if n == 0 {
return Err(ProxyError::protocol("closed reading chunk line"));
}
}
}
async fn stream_until_close(
src: &mut BoxedStream,
reader: &mut Reader,
dst: &mut BoxedStream,
to_backend: bool,
idle: Duration,
) -> Result<()> {
if !reader.is_empty() {
let chunk = reader.take_data();
forward(dst, &chunk, to_backend).await?;
flush_side(dst, to_backend).await?;
}
loop {
let n = reader.fill(src, idle).await?;
if n == 0 {
return Ok(());
}
let chunk = reader.take_data();
forward(dst, &chunk, to_backend).await?;
flush_side(dst, to_backend).await?;
}
}
fn sanitize_inbound_forwarded(head: &mut Head, listener: &ListenerConfig) {
if listener.forwarded != ForwardedMode::Trust {
head.remove_header("forwarded");
head.remove_header("x-forwarded-for");
}
}
fn apply_outbound_forwarded(head: &mut Head, dest_forwarded: bool, peer: IpAddr) {
if !dest_forwarded {
return;
}
let for_node = match peer {
IpAddr::V4(v4) => format!("for={v4}"),
IpAddr::V6(v6) => format!("for=\"[{v6}]\""),
};
match head.header("forwarded").map(|v| v.to_string()) {
Some(existing) => head.set_header("forwarded", &format!("{existing}, {for_node}")),
None => head.set_header("forwarded", &for_node),
}
let xff = match head.header("x-forwarded-for").map(|v| v.to_string()) {
Some(existing) => format!("{existing}, {}", peer),
None => peer.to_string(),
};
head.set_header("x-forwarded-for", &xff);
}
fn response_no_body(req_method: &str, head: &Head) -> bool {
if req_method.eq_ignore_ascii_case("HEAD") {
return true;
}
matches!(head.framing, BodyFraming::None)
}
enum Disposition {
Close,
Hijack {
backend: BoxedStream,
to_backend: Vec<u8>,
to_client: Vec<u8>,
},
}
async fn shutdown_client(stream: &mut BoxedStream) {
let _ = timeout(Duration::from_secs(3), stream.shutdown()).await;
}
pub async fn handle(
ctx: &Ctx,
listener: &ListenerConfig,
mut stream: BoxedStream,
peer: SocketAddr,
local: SocketAddr,
) -> Result<()> {
let bridge_idle = ctx.config.server.bridge_idle;
match serve(ctx, listener, &mut stream, peer, local).await {
Ok(Disposition::Close) => {
shutdown_client(&mut stream).await;
Ok(())
}
Ok(Disposition::Hijack {
backend,
to_backend,
to_client,
}) => {
crate::outbound::bridge(stream, backend, &to_backend, &to_client, bridge_idle, None)
.await?;
Ok(())
}
Err(e) => Err(e),
}
}
async fn serve(
ctx: &Ctx,
listener: &ListenerConfig,
stream: &mut BoxedStream,
peer: SocketAddr,
local: SocketAddr,
) -> Result<Disposition> {
let router = ctx.http_router.as_ref();
let max_head = router.max_head_size;
let body_cap = router.body_extract_cap;
let bridge_idle = ctx.config.server.bridge_idle;
let idle = Idle {
body: router.relay_idle,
stream: bridge_idle,
};
let keepalive_timeout = router.keepalive_timeout;
let max_requests = router.max_keepalive_requests;
let mut reader = Reader::new();
let first_head = match timeout(
listener.preauth_timeout,
read_client_head(stream, &mut reader, max_head, listener.preauth_timeout),
)
.await
{
Ok(Ok(Some(h))) => h,
Ok(Ok(None)) => return Ok(Disposition::Close),
Ok(Err(e)) => {
let _ = write_client(stream, &error_response(e.status(), reason(e.status()))).await;
return Ok(Disposition::Close);
}
Err(_) => {
let _ = write_client(stream, &error_response(408, "Request Timeout")).await;
return Ok(Disposition::Close);
}
};
let mut first_head = first_head;
sanitize_inbound_forwarded(&mut first_head, listener);
let (buffered_body, dest_id) = match prepare(
ctx,
router,
stream,
&mut reader,
&mut first_head,
body_cap,
idle.body,
)
.await?
{
Some(t) => t,
None => {
let _ = write_client(stream, &error_response(404, "Not Found")).await;
return Ok(Disposition::Close);
}
};
let (dest, ep, tls_cfg) = match ctx.endpoint(&dest_id, Protocol::Http) {
Ok(t) => t,
Err(_) => {
let _ = write_client(stream, &error_response(502, "Bad Gateway")).await;
return Ok(Disposition::Close);
}
};
if ep.tls == TlsMode::Starttls {
let _ = write_client(stream, &error_response(502, "Bad Gateway")).await;
return Ok(Disposition::Close);
}
let fwd = dest.forwarding_for(Protocol::Http);
if let Err(e) = crate::proto::forward::guard_loop(
&ctx.self_binds,
dest,
ep,
fwd,
ctx.config.server.proxy_ttl,
) {
tracing::warn!(error = %e, destination = %dest_id, "http loop guard");
let _ = write_client(stream, &error_response(502, "Bad Gateway")).await;
return Ok(Disposition::Close);
}
let mut backend = match crate::proto::common::establish(ctx, &dest_id, || {
crate::outbound::dial(dest, ep, fwd, &tls_cfg, peer, local)
})
.await
{
Ok(b) => b,
Err(_) => {
let _ = write_client(stream, &error_response(502, "Bad Gateway")).await;
return Ok(Disposition::Close);
}
};
let mut opts = ExchangeOpts {
dest_forwarded: dest.forwarded,
peer,
max_head,
idle,
force_close: false,
};
let mut backend_reader = Reader::new();
let mut current_head = first_head;
let mut current_body = buffered_body;
let mut served: u32 = 0;
loop {
served += 1;
opts.force_close = max_requests != 0 && served >= max_requests;
let outcome = exchange(
stream,
&mut reader,
&mut backend,
&mut backend_reader,
&mut current_head,
current_body.take(),
&opts,
)
.await;
match outcome {
Ok(ExchangeResult::KeepAlive) => {}
Ok(ExchangeResult::Hijack {
to_backend,
to_client,
}) => {
return Ok(Disposition::Hijack {
backend,
to_backend,
to_client,
});
}
Ok(ExchangeResult::Close) => return Ok(Disposition::Close),
Err(e) => return Err(e),
}
let next = match timeout(
keepalive_timeout,
read_client_head(stream, &mut reader, max_head, idle.body),
)
.await
{
Ok(Ok(Some(h))) => h,
Ok(Ok(None)) => return Ok(Disposition::Close),
Ok(Err(e)) => {
let _ = write_client(stream, &error_response(e.status(), reason(e.status()))).await;
return Ok(Disposition::Close);
}
Err(_) => return Ok(Disposition::Close),
};
let mut next = next;
sanitize_inbound_forwarded(&mut next, listener);
let (next_body, next_dest) = match prepare(
ctx,
router,
stream,
&mut reader,
&mut next,
body_cap,
idle.body,
)
.await?
{
Some(t) => t,
None => {
let _ = write_client(stream, &error_response(404, "Not Found")).await;
return Ok(Disposition::Close);
}
};
if next_dest != dest_id {
if let Some(body) = &next_body {
drain_buffered(stream, &mut reader, &next, body.len(), idle.body)
.await
.ok();
}
let _ = write_client(stream, &error_response(421, "Misdirected Request")).await;
return Ok(Disposition::Close);
}
current_head = next;
current_body = next_body;
}
}
enum ExchangeResult {
KeepAlive,
Hijack {
to_backend: Vec<u8>,
to_client: Vec<u8>,
},
Close,
}
#[derive(Clone, Copy)]
struct ExchangeOpts {
dest_forwarded: bool,
peer: SocketAddr,
max_head: usize,
idle: Idle,
force_close: bool,
}
async fn exchange(
client: &mut BoxedStream,
client_reader: &mut Reader,
backend: &mut BoxedStream,
backend_reader: &mut Reader,
head: &mut Head,
buffered_body: Option<Vec<u8>>,
opts: &ExchangeOpts,
) -> Result<ExchangeResult> {
let max_head = opts.max_head;
let idle = opts.idle;
let flags = head.conn_flags();
let is_ws = flags.is_websocket_upgrade;
let client_close = opts.force_close || flags.close;
let expects_continue = head.expects_continue();
let req_method = head.method.clone();
apply_outbound_forwarded(head, opts.dest_forwarded, opts.peer.ip());
let serialized = head.serialize(false, is_ws);
push_backend(backend, &serialized).await?;
let buffered_len = buffered_body.as_ref().map(|b| b.len());
if let Some(body) = &buffered_body {
push_backend(backend, body).await?;
}
if is_ws {
flush_backend(backend).await?;
let resp = read_backend_head(backend, backend_reader, max_head, idle.body).await?;
if resp.status == 101 {
let resp_bytes = resp.serialize(false, true);
write_client(client, &resp_bytes).await?;
let to_backend = client_reader.take_data();
let to_client = backend_reader.take_data();
return Ok(ExchangeResult::Hijack {
to_backend,
to_client,
});
}
return finish_response(
client,
backend,
backend_reader,
&req_method,
resp,
client_close,
idle,
)
.await;
}
if expects_continue {
flush_backend(backend).await?;
loop {
let interim = read_backend_head(backend, backend_reader, max_head, idle.body).await?;
if interim.status == 100 {
write_client(client, &interim.serialize(false, false)).await?;
stream_request_remainder(client, client_reader, backend, head, buffered_len, idle)
.await?;
flush_backend(backend).await?;
return relay_response(
client,
backend,
backend_reader,
&req_method,
max_head,
client_close,
idle,
)
.await;
}
if (100..200).contains(&interim.status) {
write_client(client, &interim.serialize(false, false)).await?;
continue;
}
return finish_response(
client,
backend,
backend_reader,
&req_method,
interim,
true,
idle,
)
.await;
}
}
stream_request_remainder(client, client_reader, backend, head, buffered_len, idle).await?;
flush_backend(backend).await?;
relay_response(
client,
backend,
backend_reader,
&req_method,
max_head,
client_close,
idle,
)
.await
}
async fn stream_request_remainder(
client: &mut BoxedStream,
client_reader: &mut Reader,
backend: &mut BoxedStream,
head: &Head,
already_forwarded: Option<usize>,
idle: Idle,
) -> Result<()> {
match head.framing {
BodyFraming::None | BodyFraming::UntilClose => Ok(()),
BodyFraming::Length(n) => {
let remaining = n.saturating_sub(already_forwarded.unwrap_or(0) as u64);
transfer_length(client, client_reader, backend, true, remaining, idle.body).await
}
BodyFraming::Chunked => {
transfer_chunked(client, client_reader, backend, true, idle.body).await
}
}
}
async fn relay_response(
client: &mut BoxedStream,
backend: &mut BoxedStream,
backend_reader: &mut Reader,
req_method: &str,
max_head: usize,
client_close: bool,
idle: Idle,
) -> Result<ExchangeResult> {
loop {
let resp = read_backend_head(backend, backend_reader, max_head, idle.body).await?;
if (100..200).contains(&resp.status) {
write_client(client, &resp.serialize(false, false)).await?;
continue;
}
return finish_response(
client,
backend,
backend_reader,
req_method,
resp,
client_close,
idle,
)
.await;
}
}
async fn finish_response(
client: &mut BoxedStream,
backend: &mut BoxedStream,
backend_reader: &mut Reader,
req_method: &str,
resp: Head,
force_close: bool,
idle: Idle,
) -> Result<ExchangeResult> {
let force_close = force_close || resp.conn_flags().close;
let is_sse = resp.is_sse();
let no_body = response_no_body(req_method, &resp);
let bytes = resp.serialize(force_close, false);
push_client(client, &bytes).await?;
let done = || {
if force_close {
ExchangeResult::Close
} else {
ExchangeResult::KeepAlive
}
};
if no_body {
flush_client(client).await?;
return Ok(done());
}
if is_sse {
flush_client(client).await?;
stream_until_close(backend, backend_reader, client, false, idle.stream).await?;
return Ok(ExchangeResult::Close);
}
match resp.framing {
BodyFraming::None => {
flush_client(client).await?;
Ok(done())
}
BodyFraming::Length(n) => {
transfer_length(backend, backend_reader, client, false, n, idle.body).await?;
flush_client(client).await?;
Ok(done())
}
BodyFraming::Chunked => {
transfer_chunked(backend, backend_reader, client, false, idle.body).await?;
flush_client(client).await?;
Ok(done())
}
BodyFraming::UntilClose => {
flush_client(client).await?;
stream_until_close(backend, backend_reader, client, false, idle.stream).await?;
Ok(ExchangeResult::Close)
}
}
}
async fn buffer_body(
client: &mut BoxedStream,
reader: &mut Reader,
head: &Head,
cap: usize,
idle: Duration,
) -> Result<Vec<u8>> {
let mut body = Vec::new();
if let BodyFraming::Length(n) = head.framing {
let want = (n as usize).min(cap);
while body.len() < want {
if reader.is_empty() {
let got = reader.fill(client, idle).await?;
if got == 0 {
break;
}
}
let take = {
let avail = reader.data();
let take = (want - body.len()).min(avail.len());
body.extend_from_slice(&avail[..take]);
take
};
reader.consume(take);
}
}
Ok(body)
}
async fn drain_buffered(
client: &mut BoxedStream,
reader: &mut Reader,
head: &Head,
already: usize,
idle: Duration,
) -> Result<()> {
if let BodyFraming::Length(n) = head.framing {
let mut remaining = (n as usize).saturating_sub(already) as u64;
while remaining > 0 {
if reader.is_empty() {
let got = reader.fill(client, idle).await?;
if got == 0 {
break;
}
}
let take = (remaining as usize).min(reader.data().len());
reader.consume(take);
remaining -= take as u64;
}
}
Ok(())
}
async fn prepare(
ctx: &Ctx,
router: &HttpRouter,
client: &mut BoxedStream,
reader: &mut Reader,
head: &mut Head,
body_cap: usize,
body_idle: Duration,
) -> Result<Option<(Option<Vec<u8>>, String)>> {
let idx = match router.match_index(head) {
Some(i) => i,
None => return Ok(None),
};
let body = if router.needs_body_at(idx) {
if head.expects_continue() {
write_client(client, b"HTTP/1.1 100 Continue\r\n\r\n").await?;
head.remove_header("expect");
}
Some(buffer_body(client, reader, head, body_cap, body_idle).await?)
} else {
None
};
let dest_id = match router.route_at(idx, head, body.as_deref()) {
RouteOutcome::Pin { dest_id } => dest_id,
RouteOutcome::Identifier {
identifier,
fallback,
} => {
if identifier.is_none() && fallback.is_none() {
ctx.resolve(None).await.to_string()
} else {
ctx.resolve(identifier.as_deref()).await.to_string()
}
}
};
Ok(Some((body, dest_id)))
}
fn reason(status: u16) -> &'static str {
match status {
400 => "Bad Request",
404 => "Not Found",
405 => "Method Not Allowed",
408 => "Request Timeout",
421 => "Misdirected Request",
431 => "Request Header Fields Too Large",
502 => "Bad Gateway",
505 => "HTTP Version Not Supported",
_ => "Error",
}
}