diff --git a/Cargo.lock b/Cargo.lock index 4d65cca..5a4476c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + [[package]] name = "humantime" version = "2.1.0" @@ -546,6 +552,7 @@ version = "0.1.0-alpha.2" dependencies = [ "clap", "failure", + "httparse", "lazy_static", "log", "log4rs", diff --git a/Cargo.toml b/Cargo.toml index cd00a14..b1b3ea4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ failure = "0.1.8" log = "0.4.22" ttl_cache = "0.5.1" lazy_static = "1.5.0" +httparse = "1.9.5" [dev-dependencies] clap = { version = "4.5.23", features = ["derive"] } diff --git a/src/http_process.rs b/src/http_process.rs index 5074b9c..50f6fe7 100644 --- a/src/http_process.rs +++ b/src/http_process.rs @@ -8,6 +8,8 @@ use pnet::packet::Packet; use std::net::IpAddr; use std::time::{Duration, Instant}; use ttl_cache::TtlCache; +use httparse::{Request, Response, EMPTY_HEADER}; +use log::{debug, error, info}; pub type FlowKey = (IpAddr, IpAddr, u16, u16); // (Client IP, Server IP, Client Port, Server Port) @@ -68,7 +70,6 @@ fn process_tcp_packet( let flow_key = (src_ip, dst_ip, src_port, dst_port); if tcp.get_flags() & pnet::packet::tcp::TcpFlags::SYN != 0 { - //println!("New TCP flow detected: {}:{} -> {}:{}", src_ip, src_port, dst_ip, dst_port); let flow = TcpFlow { client_ip: src_ip, server_ip: dst_ip, @@ -81,72 +82,75 @@ fn process_tcp_packet( last_seen: Instant::now(), }; cache.insert(flow_key, flow, Duration::new(60, 0)); - return Ok(ObservableHttpPackage { http_request: None }); //TODO: WIP + return Ok(ObservableHttpPackage { http_request: None }); } if let Some(flow) = cache.get_mut(&flow_key) { flow.last_seen = Instant::now(); - // Handle data payload if !tcp.payload().is_empty() { + // Append the new data to the flow data if src_ip == flow.client_ip && src_port == flow.client_port { flow.client_data.extend_from_slice(tcp.payload()); - if let Ok(request) = std::str::from_utf8(&flow.client_data) { - if request.contains("HTTP") { - println!("HTTP Request: {}", request); - } + if let Ok(request) = parse_http_request(&flow.client_data) { + info!("HTTP Request:\n{:?}", request); } } else { flow.server_data.extend_from_slice(tcp.payload()); - if let Ok(response) = std::str::from_utf8(&flow.server_data) { - if response.contains("HTTP") { - println!("HTTP Response: {}", response); - } - } - } - } - - if let Some(flow) = cache.get_mut(&flow_key) { - flow.last_seen = Instant::now(); - if !tcp.payload().is_empty() { - //TODO: Process payload here... - } - - // Check for termination flags - let should_remove = tcp.get_flags() - & (pnet::packet::tcp::TcpFlags::FIN | pnet::packet::tcp::TcpFlags::RST) - != 0; - - if should_remove { - /*println!( - "TCP flow closing or reset: {}:{} -> {}:{}", - flow.client_ip, flow.client_port, flow.server_ip, flow.server_port - );*/ + // Try to parse the HTTP response when enough data is accumulated + /*if let Ok(response) = parse_http_response(&flow.server_data) { + info!("HTTP Response:\n{:?}", response); + }*/ } } if tcp.get_flags() & (pnet::packet::tcp::TcpFlags::FIN | pnet::packet::tcp::TcpFlags::RST) != 0 { + debug!("Connection closed or reset"); cache.remove(&flow_key); } - } else { - // TODO: Handle case where packet belongs to an untracked flow - /*println!( - "Untracked TCP flow: {}:{} -> {}:{}", - src_ip, src_port, dst_ip, dst_port - );*/ } - Ok(ObservableHttpPackage { http_request: None }) //TODO: WIP + + Ok(ObservableHttpPackage { http_request: None }) +} + +fn parse_http_request(data: &[u8]) -> Result, Error> { + let mut headers = [EMPTY_HEADER; 16]; + let mut req = Request::new(&mut headers); + + match req.parse(data) { + Ok(httparse::Status::Complete(_)) => { + let headers: Vec<_> = req.headers.iter() + .map(|h| (h.name.to_string(), String::from_utf8_lossy(h.value).to_string())) + .collect(); + + info!("Successfully parsed HTTP Request. Headers: {:?}", headers); + + Ok(Some(ObservableHttpRequest { + lang: headers.iter().find(|(k, _)| k.eq_ignore_ascii_case("Accept-Language")).map(|(_, v)| v.clone()), + user_agent: headers.iter().find(|(k, _)| k.eq_ignore_ascii_case("User-Agent")).map(|(_, v)| v.clone()), + })) + }, + Ok(httparse::Status::Partial) => { + debug!("Incomplete HTTP request data. Data: {:?}", data); + Ok(None) + }, + Err(e) => { + error!("Failed to parse HTTP request. Error: {}", e); + Err(failure::err_msg(format!("Failed to parse HTTP request: {}", e))) + }, + } } pub struct ObservableHttpPackage { http_request: Option, } +#[derive(Debug)] pub struct ObservableHttpRequest { pub lang: Option, pub user_agent: Option, - pub signature: http::Signature, + //pub signature: http::Signature, } diff --git a/src/lib.rs b/src/lib.rs index cdf5533..4b64af0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,7 @@ impl<'a> P0f<'a> { freq: update.freq, }); - let http_request = +/* let http_request = observable_package .http_request .map(|http_request| HttpRequestOutput { @@ -182,9 +182,9 @@ impl<'a> P0f<'a> { .matching_by_http_request(&http_request.signature) .map(|(label, _)| label.clone()), sig: http_request.signature, - }); + });*/ - (syn, syn_ack, mtu, uptime, http_request) + (syn, syn_ack, mtu, uptime, None) }; P0fOutput {