Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Proxy Protocol v1 & v2 for HTTP/1.x #3277

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-post-merge.yml
Expand Up @@ -62,7 +62,7 @@ jobs:
set -e
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,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,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 @@ -76,7 +76,7 @@ jobs:
set -e
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,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,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
8 changes: 7 additions & 1 deletion actix-http/Cargo.toml
Expand Up @@ -66,6 +66,9 @@ rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]

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

# Compression codecs
compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"]
Expand Down Expand Up @@ -111,13 +114,16 @@ rand = { version = "0.8", optional = true }
sha1 = { version = "0.10", optional = true }

# openssl/rustls
actix-tls = { version = "3.3", default-features = false, optional = true }
actix-tls = { version = "3.1", default-features = false, optional = true }

# compress-*
brotli = { version = "3.3.3", optional = true }
flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.13", optional = true }

# Proxy protocol support
ppp = { version = "2.2.0", optional = true }

[dev-dependencies]
actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2"
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
63 changes: 43 additions & 20 deletions actix-http/src/h1/codec.rs
Expand Up @@ -10,6 +10,8 @@
encoder, Message, MessageType,
};
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)]
Expand Down Expand Up @@ -110,6 +112,7 @@
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 +122,49 @@
}
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 Expand Up @@ -202,7 +225,7 @@
use http::Method;

use super::*;
use crate::HttpMessage as _;

Check failure on line 228 in actix-http/src/h1/codec.rs

View workflow job for this annotation

GitHub Actions / Linux / msrv

unused import: `crate::HttpMessage`

Check failure on line 228 in actix-http/src/h1/codec.rs

View workflow job for this annotation

GitHub Actions / Linux / stable

unused import: `crate::HttpMessage`

Check failure on line 228 in actix-http/src/h1/codec.rs

View workflow job for this annotation

GitHub Actions / macOS / msrv

unused import: `crate::HttpMessage`

Check failure on line 228 in actix-http/src/h1/codec.rs

View workflow job for this annotation

GitHub Actions / macOS / stable

unused import: `crate::HttpMessage`

Check warning on line 228 in actix-http/src/h1/codec.rs

View workflow job for this annotation

GitHub Actions / Windows / msrv

unused import: `crate::HttpMessage`

Check warning on line 228 in actix-http/src/h1/codec.rs

View workflow job for this annotation

GitHub Actions / Windows / stable

unused import: `crate::HttpMessage`

#[actix_rt::test]
async fn test_http_request_chunked_payload_and_next_message() {
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