Skip to content

Commit f4254c5

Browse files
committed
Add support for Proxy Protocol v1 & v2 for HTTP/1.x
1 parent e518170 commit f4254c5

File tree

14 files changed

+422
-26
lines changed

14 files changed

+422
-26
lines changed

.github/workflows/ci-post-merge.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
set -e
6363
cargo test --lib --tests -p=actix-router --all-features
6464
cargo test --lib --tests -p=actix-http --all-features
65-
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
65+
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
6666
cargo test --lib --tests -p=actix-web-codegen --all-features
6767
cargo test --lib --tests -p=awc --all-features
6868
cargo test --lib --tests -p=actix-http-test --all-features

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
set -e
7777
cargo test --lib --tests -p=actix-router --all-features
7878
cargo test --lib --tests -p=actix-http --all-features
79-
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
79+
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
8080
cargo test --lib --tests -p=actix-web-codegen --all-features
8181
cargo test --lib --tests -p=awc --all-features
8282
cargo test --lib --tests -p=actix-http-test --all-features

actix-http/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
6666
# TLS via Rustls v0.22
6767
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
6868

69+
# Proxy protocol support
70+
proxy-protocol = ["ppp"]
71+
6972
# Compression codecs
7073
compress-brotli = ["__compress", "brotli"]
7174
compress-gzip = ["__compress", "flate2"]
@@ -111,13 +114,16 @@ rand = { version = "0.8", optional = true }
111114
sha1 = { version = "0.10", optional = true }
112115

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

116119
# compress-*
117120
brotli = { version = "3.3.3", optional = true }
118121
flate2 = { version = "1.0.13", optional = true }
119122
zstd = { version = "0.13", optional = true }
120123

124+
# Proxy protocol support
125+
ppp = { version = "0.2", optional = true }
126+
121127
[dev-dependencies]
122128
actix-http-test = { version = "3", features = ["openssl"] }
123129
actix-server = "2"

actix-http/src/builder.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
2323
upgrade: Option<U>,
2424
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
2525
_phantom: PhantomData<S>,
26+
proxy_protocol: bool,
2627
}
2728

2829
impl<T, S> Default for HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
@@ -46,6 +47,8 @@ where
4647
upgrade: None,
4748
on_connect_ext: None,
4849
_phantom: PhantomData,
50+
51+
proxy_protocol: false,
4952
}
5053
}
5154
}
@@ -124,6 +127,12 @@ where
124127
self.client_disconnect_timeout(dur)
125128
}
126129

130+
/// Enable `PROXY` protocol support.
131+
pub fn proxy_protocol(mut self, enabled: bool) -> Self {
132+
self.proxy_protocol = enabled;
133+
self
134+
}
135+
127136
/// Provide service for `EXPECT: 100-Continue` support.
128137
///
129138
/// Service get called with request that contains `EXPECT` header.
@@ -146,6 +155,7 @@ where
146155
upgrade: self.upgrade,
147156
on_connect_ext: self.on_connect_ext,
148157
_phantom: PhantomData,
158+
proxy_protocol: self.proxy_protocol,
149159
}
150160
}
151161

@@ -170,6 +180,7 @@ where
170180
upgrade: Some(upgrade.into_factory()),
171181
on_connect_ext: self.on_connect_ext,
172182
_phantom: PhantomData,
183+
proxy_protocol: self.proxy_protocol,
173184
}
174185
}
175186

@@ -201,6 +212,7 @@ where
201212
self.client_disconnect_timeout,
202213
self.secure,
203214
self.local_addr,
215+
self.proxy_protocol,
204216
);
205217

206218
H1Service::with_config(cfg, service.into_factory())
@@ -226,6 +238,7 @@ where
226238
self.client_disconnect_timeout,
227239
self.secure,
228240
self.local_addr,
241+
self.proxy_protocol,
229242
);
230243

231244
crate::h2::H2Service::with_config(cfg, service.into_factory())
@@ -248,6 +261,7 @@ where
248261
self.client_disconnect_timeout,
249262
self.secure,
250263
self.local_addr,
264+
self.proxy_protocol,
251265
);
252266

253267
HttpService::with_config(cfg, service.into_factory())

actix-http/src/config.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct Inner {
2020
secure: bool,
2121
local_addr: Option<std::net::SocketAddr>,
2222
date_service: DateService,
23+
proxy_protocol: bool,
2324
}
2425

2526
impl Default for ServiceConfig {
@@ -30,6 +31,7 @@ impl Default for ServiceConfig {
3031
Duration::ZERO,
3132
false,
3233
None,
34+
false,
3335
)
3436
}
3537
}
@@ -42,6 +44,7 @@ impl ServiceConfig {
4244
client_disconnect_timeout: Duration,
4345
secure: bool,
4446
local_addr: Option<net::SocketAddr>,
47+
proxy_protocol: bool,
4548
) -> ServiceConfig {
4649
ServiceConfig(Rc::new(Inner {
4750
keep_alive: keep_alive.normalize(),
@@ -50,6 +53,7 @@ impl ServiceConfig {
5053
secure,
5154
local_addr,
5255
date_service: DateService::new(),
56+
proxy_protocol,
5357
}))
5458
}
5559

@@ -73,6 +77,12 @@ impl ServiceConfig {
7377
self.0.keep_alive
7478
}
7579

80+
/// Proxy protocol setting.
81+
#[inline]
82+
pub fn proxy_protocol(&self) -> bool {
83+
self.0.proxy_protocol
84+
}
85+
7686
/// Creates a time object representing the deadline for this connection's keep-alive period, if
7787
/// enabled.
7888
///
@@ -143,8 +153,14 @@ mod tests {
143153

144154
#[actix_rt::test]
145155
async fn test_date_service_update() {
146-
let settings =
147-
ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None);
156+
let settings = ServiceConfig::new(
157+
KeepAlive::Os,
158+
Duration::ZERO,
159+
Duration::ZERO,
160+
false,
161+
None,
162+
false,
163+
);
148164

149165
yield_now().await;
150166

actix-http/src/h1/codec.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use super::{
1010
encoder, Message, MessageType,
1111
};
1212
use crate::{body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig};
13+
#[cfg(feature = "proxy-protocol")]
14+
use crate::{http_message::HttpMessage, proxy_protocol::ProxyProtocol};
1315

1416
bitflags! {
1517
#[derive(Debug, Clone, Copy)]
@@ -110,6 +112,7 @@ impl Decoder for Codec {
110112
type Error = ParseError;
111113

112114
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
115+
#[allow(clippy::collapsible_else_if)]
113116
if let Some(ref mut payload) = self.payload {
114117
Ok(match payload.decode(src)? {
115118
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
@@ -119,29 +122,49 @@ impl Decoder for Codec {
119122
}
120123
None => None,
121124
})
122-
} else if let Some((req, payload)) = self.decoder.decode(src)? {
123-
let head = req.head();
124-
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
125-
self.version = head.version;
126-
self.conn_type = head.connection_type();
127-
128-
if self.conn_type == ConnectionType::KeepAlive
129-
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
130-
{
131-
self.conn_type = ConnectionType::Close
132-
}
125+
} else {
126+
#[cfg(feature = "proxy-protocol")]
127+
let proxy_protocol = if self.config.proxy_protocol() {
128+
let p = ProxyProtocol::decode(src)?;
129+
if p.is_none() {
130+
return Ok(None);
131+
}
132+
p
133+
} else {
134+
None
135+
};
136+
137+
if let Some((req, payload)) = self.decoder.decode(src)? {
138+
let head = req.head();
139+
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
140+
self.version = head.version;
141+
self.conn_type = head.connection_type();
142+
143+
if self.conn_type == ConnectionType::KeepAlive
144+
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
145+
{
146+
self.conn_type = ConnectionType::Close
147+
}
133148

134-
match payload {
135-
PayloadType::None => self.payload = None,
136-
PayloadType::Payload(pl) => self.payload = Some(pl),
137-
PayloadType::Stream(pl) => {
138-
self.payload = Some(pl);
139-
self.flags.insert(Flags::STREAM);
149+
#[cfg(feature = "proxy-protocol")]
150+
// set proxy protocol
151+
if let Some(proxy_protocol) = proxy_protocol {
152+
let mut extensions = req.extensions_mut();
153+
extensions.insert(proxy_protocol);
140154
}
155+
156+
match payload {
157+
PayloadType::None => self.payload = None,
158+
PayloadType::Payload(pl) => self.payload = Some(pl),
159+
PayloadType::Stream(pl) => {
160+
self.payload = Some(pl);
161+
self.flags.insert(Flags::STREAM);
162+
}
163+
}
164+
Ok(Some(Message::Item(req)))
165+
} else {
166+
Ok(None)
141167
}
142-
Ok(Some(Message::Item(req)))
143-
} else {
144-
Ok(None)
145168
}
146169
}
147170
}

actix-http/src/h1/dispatcher_tests.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ async fn late_request() {
8484
Duration::ZERO,
8585
false,
8686
None,
87+
false,
8788
);
8889
let services = HttpFlow::new(ok_service(), ExpectHandler, None);
8990

@@ -151,6 +152,7 @@ async fn oneshot_connection() {
151152
Duration::ZERO,
152153
false,
153154
None,
155+
false,
154156
);
155157
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
156158

@@ -212,6 +214,7 @@ async fn keep_alive_timeout() {
212214
Duration::ZERO,
213215
false,
214216
None,
217+
false,
215218
);
216219
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
217220

@@ -291,6 +294,7 @@ async fn keep_alive_follow_up_req() {
291294
Duration::ZERO,
292295
false,
293296
None,
297+
false,
294298
);
295299
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
296300

@@ -455,6 +459,7 @@ async fn pipelining_ok_then_ok() {
455459
Duration::from_millis(1),
456460
false,
457461
None,
462+
false,
458463
);
459464

460465
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
@@ -525,6 +530,7 @@ async fn pipelining_ok_then_bad() {
525530
Duration::from_millis(1),
526531
false,
527532
None,
533+
false,
528534
);
529535

530536
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
@@ -588,6 +594,7 @@ async fn expect_handling() {
588594
Duration::ZERO,
589595
false,
590596
None,
597+
false,
591598
);
592599

593600
let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None);
@@ -665,6 +672,7 @@ async fn expect_eager() {
665672
Duration::ZERO,
666673
false,
667674
None,
675+
false,
668676
);
669677

670678
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
@@ -748,6 +756,7 @@ async fn upgrade_handling() {
748756
Duration::ZERO,
749757
false,
750758
None,
759+
false,
751760
);
752761

753762
let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade));

actix-http/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ mod message;
4949
#[cfg(test)]
5050
mod notify_on_drop;
5151
mod payload;
52+
#[cfg(feature = "proxy-protocol")]
53+
pub mod proxy_protocol;
5254
mod requests;
5355
mod responses;
5456
mod service;

0 commit comments

Comments
 (0)