Skip to content

Commit

Permalink
Add support for Proxy Protocol v1 & v2 for HTTP/1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
veeshi committed Feb 3, 2024
1 parent b1eb57a commit 9b9e988
Show file tree
Hide file tree
Showing 14 changed files with 419 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-post-merge.yml
Expand Up @@ -60,7 +60,7 @@ jobs:
run: |
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl,proxy-protocol -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -74,7 +74,7 @@ jobs:
run: |
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl,proxy-protocol -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
Expand Down
6 changes: 6 additions & 0 deletions actix-http/Cargo.toml
Expand Up @@ -53,6 +53,9 @@ rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]

# Proxy protocol support
proxy-protocol = ["ppp"]

# Compression codecs
compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"]
Expand Down Expand Up @@ -100,6 +103,9 @@ sha1 = { version = "0.10", optional = true }
# openssl/rustls
actix-tls = { version = "3.1", default-features = false, optional = true }

# proxy-protocol v1
ppp = { version = "2.0", optional = true }

# compress-*
brotli = { version = "3.3.3", optional = true }
flate2 = { version = "1.0.13", optional = true }
Expand Down
14 changes: 14 additions & 0 deletions actix-http/src/builder.rs
Expand Up @@ -23,6 +23,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
upgrade: Option<U>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_phantom: PhantomData<S>,
proxy_protocol: bool,
}

impl<T, S> Default for HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
Expand All @@ -46,6 +47,8 @@ where
upgrade: None,
on_connect_ext: None,
_phantom: PhantomData,

proxy_protocol: false,
}
}
}
Expand Down Expand Up @@ -124,6 +127,12 @@ where
self.client_disconnect_timeout(dur)
}

/// Enable `PROXY` protocol support.
pub fn proxy_protocol(mut self, enabled: bool) -> Self {
self.proxy_protocol = enabled;
self
}

/// Provide service for `EXPECT: 100-Continue` support.
///
/// Service get called with request that contains `EXPECT` header.
Expand All @@ -146,6 +155,7 @@ where
upgrade: self.upgrade,
on_connect_ext: self.on_connect_ext,
_phantom: PhantomData,
proxy_protocol: self.proxy_protocol,
}
}

Expand All @@ -170,6 +180,7 @@ where
upgrade: Some(upgrade.into_factory()),
on_connect_ext: self.on_connect_ext,
_phantom: PhantomData,
proxy_protocol: self.proxy_protocol,
}
}

Expand Down Expand Up @@ -201,6 +212,7 @@ where
self.client_disconnect_timeout,
self.secure,
self.local_addr,
self.proxy_protocol,
);

H1Service::with_config(cfg, service.into_factory())
Expand All @@ -226,6 +238,7 @@ where
self.client_disconnect_timeout,
self.secure,
self.local_addr,
self.proxy_protocol,
);

crate::h2::H2Service::with_config(cfg, service.into_factory())
Expand All @@ -248,6 +261,7 @@ where
self.client_disconnect_timeout,
self.secure,
self.local_addr,
self.proxy_protocol,
);

HttpService::with_config(cfg, service.into_factory())
Expand Down
20 changes: 18 additions & 2 deletions actix-http/src/config.rs
Expand Up @@ -20,6 +20,7 @@ struct Inner {
secure: bool,
local_addr: Option<std::net::SocketAddr>,
date_service: DateService,
proxy_protocol: bool,
}

impl Default for ServiceConfig {
Expand All @@ -30,6 +31,7 @@ impl Default for ServiceConfig {
Duration::ZERO,
false,
None,
false,
)
}
}
Expand All @@ -42,6 +44,7 @@ impl ServiceConfig {
client_disconnect_timeout: Duration,
secure: bool,
local_addr: Option<net::SocketAddr>,
proxy_protocol: bool,
) -> ServiceConfig {
ServiceConfig(Rc::new(Inner {
keep_alive: keep_alive.normalize(),
Expand All @@ -50,6 +53,7 @@ impl ServiceConfig {
secure,
local_addr,
date_service: DateService::new(),
proxy_protocol,
}))
}

Expand All @@ -73,6 +77,12 @@ impl ServiceConfig {
self.0.keep_alive
}

/// Proxy protocol setting.
#[inline]
pub fn proxy_protocol(&self) -> bool {
self.0.proxy_protocol
}

/// Creates a time object representing the deadline for this connection's keep-alive period, if
/// enabled.
///
Expand Down Expand Up @@ -143,8 +153,14 @@ mod tests {

#[actix_rt::test]
async fn test_date_service_update() {
let settings =
ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None);
let settings = ServiceConfig::new(
KeepAlive::Os,
Duration::ZERO,
Duration::ZERO,
false,
None,
false,
);

yield_now().await;

Expand Down
64 changes: 44 additions & 20 deletions actix-http/src/h1/codec.rs
Expand Up @@ -11,6 +11,9 @@ use super::{
};
use crate::{body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig};

#[cfg(feature = "proxy-protocol")]
use crate::{http_message::HttpMessage, proxy_protocol::ProxyProtocol};

bitflags! {
#[derive(Debug, Clone, Copy)]
struct Flags: u8 {
Expand Down Expand Up @@ -110,6 +113,7 @@ impl Decoder for Codec {
type Error = ParseError;

fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
#[allow(clippy::collapsible_else_if)]
if let Some(ref mut payload) = self.payload {
Ok(match payload.decode(src)? {
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
Expand All @@ -119,29 +123,49 @@ impl Decoder for Codec {
}
None => None,
})
} else if let Some((req, payload)) = self.decoder.decode(src)? {
let head = req.head();
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
self.version = head.version;
self.conn_type = head.connection_type();

if self.conn_type == ConnectionType::KeepAlive
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
{
self.conn_type = ConnectionType::Close
}
} else {
#[cfg(feature = "proxy-protocol")]
let proxy_protocol = if self.config.proxy_protocol() {
let p = ProxyProtocol::decode(src)?;
if p.is_none() {
return Ok(None);
}
p
} else {
None
};

if let Some((req, payload)) = self.decoder.decode(src)? {
let head = req.head();
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
self.version = head.version;
self.conn_type = head.connection_type();

if self.conn_type == ConnectionType::KeepAlive
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
{
self.conn_type = ConnectionType::Close
}

match payload {
PayloadType::None => self.payload = None,
PayloadType::Payload(pl) => self.payload = Some(pl),
PayloadType::Stream(pl) => {
self.payload = Some(pl);
self.flags.insert(Flags::STREAM);
#[cfg(feature = "proxy-protocol")]
// set proxy protocol
if let Some(proxy_protocol) = proxy_protocol {
let mut extensions = req.extensions_mut();
extensions.insert(proxy_protocol);
}

match payload {
PayloadType::None => self.payload = None,
PayloadType::Payload(pl) => self.payload = Some(pl),
PayloadType::Stream(pl) => {
self.payload = Some(pl);
self.flags.insert(Flags::STREAM);
}
}
Ok(Some(Message::Item(req)))
} else {
Ok(None)
}
Ok(Some(Message::Item(req)))
} else {
Ok(None)
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions actix-http/src/h1/dispatcher_tests.rs
Expand Up @@ -84,6 +84,7 @@ async fn late_request() {
Duration::ZERO,
false,
None,
false,
);
let services = HttpFlow::new(ok_service(), ExpectHandler, None);

Expand Down Expand Up @@ -151,6 +152,7 @@ async fn oneshot_connection() {
Duration::ZERO,
false,
None,
false,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);

Expand Down Expand Up @@ -212,6 +214,7 @@ async fn keep_alive_timeout() {
Duration::ZERO,
false,
None,
false,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);

Expand Down Expand Up @@ -291,6 +294,7 @@ async fn keep_alive_follow_up_req() {
Duration::ZERO,
false,
None,
false,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);

Expand Down Expand Up @@ -455,6 +459,7 @@ async fn pipelining_ok_then_ok() {
Duration::from_millis(1),
false,
None,
false,
);

let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
Expand Down Expand Up @@ -525,6 +530,7 @@ async fn pipelining_ok_then_bad() {
Duration::from_millis(1),
false,
None,
false,
);

let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
Expand Down Expand Up @@ -588,6 +594,7 @@ async fn expect_handling() {
Duration::ZERO,
false,
None,
false,
);

let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None);
Expand Down Expand Up @@ -665,6 +672,7 @@ async fn expect_eager() {
Duration::ZERO,
false,
None,
false,
);

let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
Expand Down Expand Up @@ -748,6 +756,7 @@ async fn upgrade_handling() {
Duration::ZERO,
false,
None,
false,
);

let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade));
Expand Down
2 changes: 2 additions & 0 deletions actix-http/src/lib.rs
Expand Up @@ -49,6 +49,8 @@ mod message;
#[cfg(test)]
mod notify_on_drop;
mod payload;
#[cfg(feature = "proxy-protocol")]
pub mod proxy_protocol;
mod requests;
mod responses;
mod service;
Expand Down

0 comments on commit 9b9e988

Please sign in to comment.