guard/tests/http_protocol.rs
loki5512344 6863249ad4
feat: HTTP protocol plugin + XDP globals patching from config
protocol-http (compile-time feature):
- HttpHandler: incremental HTTP/1.1 request parse (fragment-safe),
  header size/timeout limits, method whitelist, Host required,
  blocked_paths, optional User-Agent requirement
- [protocol.http] config section; registry wiring behind feature

XDP globals:
- src/xdp/globals.rs: XdpGlobals + build_rodata_image() mirroring
  xdp/core/config.h layout; set via OpenMapMut::set_initial_value()
  before load ([xdp] section: ports, udp policy, throttle, challenge)
- wire set_globals into rampart binary startup

fix: gate preflight() behind xdp feature (dead code without it)
2026-08-24 12:18:22 +02:00

131 lines
4.2 KiB
Rust

#![cfg(feature = "protocol-http")]
use rampart::config::HttpProtocolConfig;
use rampart::protocol::http::HttpProtocolHandler;
use rampart::protocol::{ProtocolHandler, ProtocolRegistry};
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
const REQUEST: &[u8] = b"GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n";
fn handler() -> HttpProtocolHandler {
HttpProtocolHandler::new(&HttpProtocolConfig::default(), &["127.0.0.1:9000".to_string()])
}
async fn socket_pair() -> (TcpStream, TcpStream) {
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let addr = listener.local_addr().expect("local addr");
let client = TcpStream::connect(addr).await.expect("connect");
let (server, _) = listener.accept().await.expect("accept");
(client, server)
}
#[tokio::test]
async fn valid_request_is_allowed() {
let (mut client, mut server) = socket_pair().await;
client.write_all(REQUEST).await.expect("write");
assert!(handler().handshake(&mut server).await.is_ok());
}
#[tokio::test]
async fn fragmented_head_is_accumulated_and_parsed() {
let (mut client, mut server) = socket_pair().await;
for chunk in REQUEST.chunks(7) {
client.write_all(chunk).await.expect("write chunk");
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
assert!(handler().handshake(&mut server).await.is_ok());
}
#[tokio::test]
async fn oversized_head_is_rejected() {
let config = HttpProtocolConfig {
max_header_bytes: 32,
..HttpProtocolConfig::default()
};
let (mut client, mut server) = socket_pair().await;
client
.write_all(b"GET / HTTP/1.1\r\nHost: a-very-long-host-name-value\r\n")
.await
.expect("write");
let err = handler_with(&config)
.handshake(&mut server)
.await
.expect_err("oversized head must be rejected");
assert!(err.to_string().contains("exceeds"));
}
#[tokio::test]
async fn unknown_method_is_rejected() {
let (mut client, mut server) = socket_pair().await;
client
.write_all(b"TRACE / HTTP/1.1\r\nHost: h\r\n\r\n")
.await
.expect("write");
let err = handler()
.handshake(&mut server)
.await
.expect_err("unknown method must be rejected");
assert!(err.to_string().contains("method"));
}
#[tokio::test]
async fn missing_host_is_rejected() {
let (mut client, mut server) = socket_pair().await;
client.write_all(b"GET / HTTP/1.1\r\n\r\n").await.expect("write");
let err = handler()
.handshake(&mut server)
.await
.expect_err("missing host must be rejected");
assert!(err.to_string().contains("host"));
}
#[tokio::test]
async fn blocked_path_is_rejected() {
let config = HttpProtocolConfig {
blocked_paths: vec!["/admin".to_string()],
..HttpProtocolConfig::default()
};
let (mut client, mut server) = socket_pair().await;
client
.write_all(b"GET /admin/secrets HTTP/1.1\r\nHost: h\r\n\r\n")
.await
.expect("write");
let err = handler_with(&config)
.handshake(&mut server)
.await
.expect_err("blocked path must be rejected");
assert!(err.to_string().contains("blocklisted"));
}
#[tokio::test]
async fn required_user_agent_missing_is_rejected() {
let config = HttpProtocolConfig {
require_user_agent: true,
..HttpProtocolConfig::default()
};
let (mut client, mut server) = socket_pair().await;
client
.write_all(b"GET / HTTP/1.1\r\nHost: h\r\n\r\n")
.await
.expect("write");
let err = handler_with(&config)
.handshake(&mut server)
.await
.expect_err("missing user-agent must be rejected");
assert!(err.to_string().contains("user-agent"));
}
#[tokio::test]
async fn registry_resolves_http_handler_when_feature_enabled() {
let mut registry = ProtocolRegistry::new();
registry.register(Box::new(handler()));
assert_eq!(registry.names(), vec!["http"]);
let resolved = registry.get("http").expect("http handler must resolve");
assert_eq!(resolved.name(), "http");
assert!(registry.primary().is_ok());
}
fn handler_with(config: &HttpProtocolConfig) -> HttpProtocolHandler {
HttpProtocolHandler::new(config, &["127.0.0.1:9000".to_string()])
}