Skip to content

Commit 9b9e988

Browse files
committed
Add support for Proxy Protocol v1 & v2 for HTTP/1.x
1 parent b1eb57a commit 9b9e988

File tree

14 files changed

+419
-25
lines changed

14 files changed

+419
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
run: |
6161
cargo test --lib --tests -p=actix-router --all-features
6262
cargo test --lib --tests -p=actix-http --all-features
63-
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
63+
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
6464
cargo test --lib --tests -p=actix-web-codegen --all-features
6565
cargo test --lib --tests -p=awc --all-features
6666
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
@@ -74,7 +74,7 @@ jobs:
7474
run: |
7575
cargo test --lib --tests -p=actix-router --all-features
7676
cargo test --lib --tests -p=actix-http --all-features
77-
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
77+
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
7878
cargo test --lib --tests -p=actix-web-codegen --all-features
7979
cargo test --lib --tests -p=awc --all-features
8080
cargo test --lib --tests -p=actix-http-test --all-features

actix-http/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
5353
# TLS via Rustls v0.21
5454
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
5555

56+
# Proxy protocol support
57+
proxy-protocol = ["ppp"]
58+
5659
# Compression codecs
5760
compress-brotli = ["__compress", "brotli"]
5861
compress-gzip = ["__compress", "flate2"]
@@ -100,6 +103,9 @@ sha1 = { version = "0.10", optional = true }
100103
# openssl/rustls
101104
actix-tls = { version = "3.1", default-features = false, optional = true }
102105

106+
# proxy-protocol v1
107+
ppp = { version = "2.0", optional = true }
108+
103109
# compress-*
104110
brotli = { version = "3.3.3", optional = true }
105111
flate2 = { version = "1.0.13", optional = true }

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: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use super::{
1111
};
1212
use crate::{body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig};
1313

14+
#[cfg(feature = "proxy-protocol")]
15+
use crate::{http_message::HttpMessage, proxy_protocol::ProxyProtocol};
16+
1417
bitflags! {
1518
#[derive(Debug, Clone, Copy)]
1619
struct Flags: u8 {
@@ -110,6 +113,7 @@ impl Decoder for Codec {
110113
type Error = ParseError;
111114

112115
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
116+
#[allow(clippy::collapsible_else_if)]
113117
if let Some(ref mut payload) = self.payload {
114118
Ok(match payload.decode(src)? {
115119
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
@@ -119,29 +123,49 @@ impl Decoder for Codec {
119123
}
120124
None => None,
121125
})
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-
}
126+
} else {
127+
#[cfg(feature = "proxy-protocol")]
128+
let proxy_protocol = if self.config.proxy_protocol() {
129+
let p = ProxyProtocol::decode(src)?;
130+
if p.is_none() {
131+
return Ok(None);
132+
}
133+
p
134+
} else {
135+
None
136+
};
137+
138+
if let Some((req, payload)) = self.decoder.decode(src)? {
139+
let head = req.head();
140+
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
141+
self.version = head.version;
142+
self.conn_type = head.connection_type();
143+
144+
if self.conn_type == ConnectionType::KeepAlive
145+
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
146+
{
147+
self.conn_type = ConnectionType::Close
148+
}
133149

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);
150+
#[cfg(feature = "proxy-protocol")]
151+
// set proxy protocol
152+
if let Some(proxy_protocol) = proxy_protocol {
153+
let mut extensions = req.extensions_mut();
154+
extensions.insert(proxy_protocol);
140155
}
156+
157+
match payload {
158+
PayloadType::None => self.payload = None,
159+
PayloadType::Payload(pl) => self.payload = Some(pl),
160+
PayloadType::Stream(pl) => {
161+
self.payload = Some(pl);
162+
self.flags.insert(Flags::STREAM);
163+
}
164+
}
165+
Ok(Some(Message::Item(req)))
166+
} else {
167+
Ok(None)
141168
}
142-
Ok(Some(Message::Item(req)))
143-
} else {
144-
Ok(None)
145169
}
146170
}
147171
}

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)