Skip to main content

o_sfu/runtime/
request_origin.rs

1use std::{convert::Infallible, net::SocketAddr, str};
2
3use axum::{
4    extract::{ConnectInfo, FromRequestParts},
5    http::{HeaderMap, header, request::Parts},
6};
7
8use crate::runtime::RuntimeState;
9
10const UNKNOWN_REMOTE_ADDRESS: &str = "unknown";
11
12/// Proxy-aware request origin derived by the HTTP edge.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RequestOrigin {
15    pub base_url: String,
16    pub remote_address: String,
17}
18
19impl FromRequestParts<RuntimeState> for RequestOrigin {
20    type Rejection = Infallible;
21
22    async fn from_request_parts(
23        parts: &mut Parts,
24        state: &RuntimeState,
25    ) -> Result<Self, Self::Rejection> {
26        let connect_info = parts
27            .extensions
28            .get::<ConnectInfo<SocketAddr>>()
29            .map(|ConnectInfo(addr)| *addr);
30        Ok(resolve_request_origin(
31            &parts.headers,
32            state.config.http.trust_proxy_headers,
33            state.config.http.bind_address,
34            connect_info,
35        ))
36    }
37}
38
39/// Resolves proxy headers only when `trust_proxy_headers` is set.
40///
41/// Set `trust_proxy_headers` only when every request reaches this listener
42/// through a proxy that strips or overwrites client-supplied `x-forwarded-*`
43/// values.
44#[must_use]
45pub fn resolve_request_origin(
46    headers: &HeaderMap,
47    trust_proxy_headers: bool,
48    fallback_bind_address: SocketAddr,
49    connect_info: Option<SocketAddr>,
50) -> RequestOrigin {
51    RequestOrigin {
52        base_url: request_base_url(headers, trust_proxy_headers, fallback_bind_address),
53        remote_address: resolve_remote_address(headers, trust_proxy_headers, connect_info),
54    }
55}
56
57pub(crate) fn resolve_remote_address(
58    headers: &HeaderMap,
59    trust_proxy_headers: bool,
60    connect_info: Option<SocketAddr>,
61) -> String {
62    trusted_forwarded_header(headers, trust_proxy_headers, "x-forwarded-for")
63        .map(str::to_owned)
64        .or_else(|| connect_info.map(|addr| addr.ip().to_string()))
65        .unwrap_or_else(|| UNKNOWN_REMOTE_ADDRESS.to_owned())
66}
67
68pub(crate) fn trusted_forwarded_header<'headers>(
69    headers: &'headers HeaderMap,
70    trust_proxy_headers: bool,
71    name: &str,
72) -> Option<&'headers str> {
73    if !trust_proxy_headers {
74        return None;
75    }
76    forwarded_header(headers, name)
77}
78
79fn forwarded_header<'headers>(headers: &'headers HeaderMap, name: &str) -> Option<&'headers str> {
80    let value = headers.get(name)?.to_str().ok()?;
81    value.split(',').next().map(str::trim)
82}
83
84pub(crate) fn request_base_url(
85    headers: &HeaderMap,
86    trust_proxy_headers: bool,
87    fallback_bind_address: SocketAddr,
88) -> String {
89    let scheme = trusted_forwarded_header(headers, trust_proxy_headers, "x-forwarded-proto")
90        .unwrap_or("http");
91    let host = trusted_forwarded_header(headers, trust_proxy_headers, "x-forwarded-host")
92        .map(str::to_owned)
93        .or_else(|| {
94            headers
95                .get(header::HOST)
96                .and_then(|value| value.to_str().ok())
97                .map(str::to_owned)
98        })
99        .unwrap_or_else(|| fallback_bind_address.to_string());
100    format!("{scheme}://{host}")
101}