pub mod flow;
pub mod parallel;
pub use flow::{FlowKey, HttpProcessors, ObservableHttpPackage, TcpFlow};
pub use parallel::{
DispatchResult, PoolStats, SharedHttpMatcher, SharedObservedOsSource, WorkerPool, WorkerStats,
};
use self::flow as http_process;
use crate::error::HuginnNetHttpError;
use crate::http::ObservedOsSource;
#[cfg(any(feature = "p0f-request", feature = "p0f-response"))]
use crate::http::{build_params, MatchedSignatureNotes};
#[cfg(feature = "p0f-request")]
use crate::http::{check_ua_os_agreement, ObservedOsInput};
use crate::matcher_api::HttpMatcher;
#[cfg(feature = "p0f-request")]
use crate::output::{BrowserQualityMatched, HttpRequestOutput};
use crate::output::{HttpAnalysisResult, IpPort};
#[cfg(feature = "p0f-response")]
use crate::output::{HttpResponseOutput, WebServerQualityMatched};
#[cfg(any(feature = "p0f-request", feature = "p0f-response"))]
use crate::output::{MatchQuality, OsKind};
use pnet::packet::ipv4::Ipv4Packet;
use pnet::packet::ipv6::Ipv6Packet;
use pnet::packet::tcp::TcpPacket;
use pnet::packet::Packet;
use std::net::IpAddr;
use ttl_cache::TtlCache;
pub struct ObservablePackage {
pub source: IpPort,
pub destination: IpPort,
pub http_result: HttpAnalysisResult,
}
#[inline]
pub fn process_ipv4_packet(
ipv4: &Ipv4Packet,
http_flows: &mut TtlCache<http_process::FlowKey, http_process::TcpFlow>,
http_processors: &http_process::HttpProcessors,
matcher: Option<&dyn HttpMatcher>,
os_source: Option<&dyn ObservedOsSource>,
) -> Result<HttpAnalysisResult, HuginnNetHttpError> {
let observable_package =
create_observable_package_ipv4(ipv4, http_flows, http_processors, matcher, os_source)?;
Ok(observable_package.http_result)
}
fn create_observable_package_ipv4(
ipv4: &Ipv4Packet,
http_flows: &mut TtlCache<http_process::FlowKey, http_process::TcpFlow>,
http_processors: &http_process::HttpProcessors,
matcher: Option<&dyn HttpMatcher>,
os_source: Option<&dyn ObservedOsSource>,
) -> Result<ObservablePackage, HuginnNetHttpError> {
let tcp = TcpPacket::new(ipv4.payload())
.ok_or_else(|| HuginnNetHttpError::Parse("Invalid TCP packet".to_string()))?;
let source = IpPort { ip: IpAddr::V4(ipv4.get_source()), port: tcp.get_source() };
let destination =
IpPort { ip: IpAddr::V4(ipv4.get_destination()), port: tcp.get_destination() };
let http_package = http_process::process_http_ipv4(ipv4, http_flows, http_processors)?;
let http_result =
build_http_result(http_package, source.clone(), destination.clone(), matcher, os_source);
Ok(ObservablePackage { source, destination, http_result })
}
#[inline]
pub fn process_ipv6_packet(
ipv6: &Ipv6Packet,
http_flows: &mut TtlCache<http_process::FlowKey, http_process::TcpFlow>,
http_processors: &http_process::HttpProcessors,
matcher: Option<&dyn HttpMatcher>,
os_source: Option<&dyn ObservedOsSource>,
) -> Result<HttpAnalysisResult, HuginnNetHttpError> {
let observable_package =
create_observable_package_ipv6(ipv6, http_flows, http_processors, matcher, os_source)?;
Ok(observable_package.http_result)
}
fn create_observable_package_ipv6(
ipv6: &Ipv6Packet,
http_flows: &mut TtlCache<http_process::FlowKey, http_process::TcpFlow>,
http_processors: &http_process::HttpProcessors,
matcher: Option<&dyn HttpMatcher>,
os_source: Option<&dyn ObservedOsSource>,
) -> Result<ObservablePackage, HuginnNetHttpError> {
let tcp = TcpPacket::new(ipv6.payload())
.ok_or_else(|| HuginnNetHttpError::Parse("Invalid TCP packet".to_string()))?;
let source = IpPort { ip: IpAddr::V6(ipv6.get_source()), port: tcp.get_source() };
let destination =
IpPort { ip: IpAddr::V6(ipv6.get_destination()), port: tcp.get_destination() };
let http_package = http_process::process_http_ipv6(ipv6, http_flows, http_processors)?;
let http_result =
build_http_result(http_package, source.clone(), destination.clone(), matcher, os_source);
Ok(ObservablePackage { source, destination, http_result })
}
#[cfg_attr(
not(any(feature = "p0f-request", feature = "p0f-response")),
allow(unused_mut)
)]
fn build_http_result(
http_package: http_process::ObservableHttpPackage,
source: IpPort,
destination: IpPort,
matcher: Option<&dyn HttpMatcher>,
os_source: Option<&dyn ObservedOsSource>,
) -> HttpAnalysisResult {
#[cfg(not(any(feature = "p0f-request", feature = "p0f-response")))]
let _ = (&http_package, &source, &destination, &matcher, &os_source);
#[cfg(all(feature = "p0f-response", not(feature = "p0f-request")))]
let _ = os_source;
let mut http_result = HttpAnalysisResult::empty();
#[cfg(feature = "p0f-request")]
if let Some(http_request) = http_package.http_request {
let (browser_quality, notes, req_match) = match matcher {
Some(m) => match m.match_http_request(&http_request.matching) {
Some(req_match) => {
let notes = MatchedSignatureNotes {
dishonest: req_match.dishonest,
generic: req_match.browser.kind == OsKind::Generic,
};
(
BrowserQualityMatched {
browser: Some(req_match.browser.clone()),
quality: MatchQuality::Matched(req_match.rank.clone()),
},
Some(notes),
Some(req_match),
)
}
None => (
BrowserQualityMatched { browser: None, quality: MatchQuality::NotMatched },
None,
None,
),
},
None => (
BrowserQualityMatched { browser: None, quality: MatchQuality::Disabled },
None,
None,
),
};
let owned_os = os_source.and_then(|s| s.observed_os(source.ip, source.port));
let observed = match (os_source, owned_os.as_ref()) {
(None, _) => ObservedOsInput::NoSource,
(Some(_), None) => ObservedOsInput::Missing,
(Some(_), Some(os)) => ObservedOsInput::Present(os),
};
let request_output = HttpRequestOutput {
source: source.clone(),
destination: destination.clone(),
lang: http_request.lang.clone(),
params: build_params(&http_request.matching.expsw, notes),
browser_matched: browser_quality,
ua_os: check_ua_os_agreement(
http_request.user_agent.as_deref(),
req_match.as_ref(),
matcher,
observed,
),
sig: http_request,
};
http_result.http_request = Some(request_output);
}
#[cfg(feature = "p0f-response")]
if let Some(http_response) = http_package.http_response {
let (web_server_quality, notes) = match matcher {
Some(m) => match m.match_http_response(&http_response.matching) {
Some(resp_match) => {
let notes = MatchedSignatureNotes {
dishonest: resp_match.dishonest,
generic: resp_match.web_server.kind == OsKind::Generic,
};
(
WebServerQualityMatched {
web_server: Some(resp_match.web_server),
quality: MatchQuality::Matched(resp_match.rank),
},
Some(notes),
)
}
None => (
WebServerQualityMatched { web_server: None, quality: MatchQuality::NotMatched },
None,
),
},
None => (
WebServerQualityMatched { web_server: None, quality: MatchQuality::Disabled },
None,
),
};
let response_output = HttpResponseOutput {
source,
destination,
params: build_params(&http_response.matching.expsw, notes),
web_server_matched: web_server_quality,
sig: http_response,
};
http_result.http_response = Some(response_output);
}
http_result
}