use std::net::{Ipv4Addr, Ipv6Addr};
use crate::{
Arp, ArpOperation, Dhcpv4, Dhcpv4ClientIdentifier, Dhcpv4MessageType, Dhcpv6,
Dhcpv6MessageType, Dns, Icmpv4, Icmpv6, Igmp, Ipv4, Ipv6, Packet, Tcp, Udp, BOOTP_REPLY,
DHCPV4_CLIENT_PORT, DHCPV4_SERVER_PORT, DHCPV6_CLIENT_PORT, DHCPV6_SERVER_PORT, DNS_PORT,
ICMPV6_ECHO_REPLY, ICMPV6_ECHO_REQUEST, ICMP_ECHO_REPLY, ICMP_ECHO_REQUEST,
};
pub struct ReplyMatcher {
request: Packet,
}
impl ReplyMatcher {
pub fn from_packet(request: &Packet) -> Self {
Self {
request: request.clone(),
}
}
pub fn reply_filter(&self) -> Option<String> {
request_reply_filter(&self.request)
}
pub fn matches(&self, candidate: &Packet) -> bool {
reply_matches(&self.request, candidate)
}
}
pub fn reply_filter(packet: &Packet) -> Option<String> {
ReplyMatcher::from_packet(packet).reply_filter()
}
pub fn reply_matches(request: &Packet, candidate: &Packet) -> bool {
if request.layer::<Dns>().is_some() {
return dns_reply_matches(request, candidate);
}
if request.layer::<Dhcpv4>().is_some() {
return dhcpv4_reply_matches(request, candidate);
}
if request.layer::<Dhcpv6>().is_some() {
return dhcpv6_reply_matches(request, candidate);
}
arp_reply_matches(request, candidate)
|| icmp_reply_matches(request, candidate)
|| icmpv6_reply_matches(request, candidate)
|| tcp_reply_matches(request, candidate)
|| udp_reply_matches(request, candidate)
}
fn request_reply_filter(packet: &Packet) -> Option<String> {
if packet.layer::<Dhcpv4>().is_some() {
return Some(dhcpv4_filter());
}
if packet.layer::<Dhcpv6>().is_some() {
return Some(dhcpv6_filter(packet));
}
if packet.layer::<Dns>().is_some() {
return Some(transport_filter("udp", packet, Some(DNS_PORT), None));
}
if let Some(arp) = packet.layer::<Arp>() {
return Some(arp_filter(arp));
}
if packet.layer::<Igmp>().is_some() {
return Some(igmp_filter(packet));
}
if packet.layer::<Icmpv4>().is_some() {
return Some(protocol_filter("icmp", packet));
}
if packet.layer::<Icmpv6>().is_some() {
return Some(protocol_filter("icmp6", packet));
}
if let Some(tcp) = packet.layer::<Tcp>() {
return Some(transport_filter(
"tcp",
packet,
Some(tcp.destination_port_value()),
Some(tcp.source_port_value()),
));
}
if let Some(udp) = packet.layer::<Udp>() {
return Some(transport_filter(
"udp",
packet,
Some(udp.destination_port_value()),
Some(udp.source_port_value()),
));
}
None
}
pub(crate) fn combine_filters(derived: Option<String>, user: Option<&str>) -> Option<String> {
let user = user.map(str::trim).filter(|filter| !filter.is_empty());
match (derived, user) {
(Some(derived), Some(user)) => Some(format!("({derived}) and ({user})")),
(Some(derived), None) => Some(derived),
(None, Some(user)) => Some(user.to_string()),
(None, None) => None,
}
}
pub(crate) fn batch_reply_filter(packets: &[Packet]) -> Option<String> {
let mut filters = Vec::new();
for packet in packets {
let Some(filter) = ReplyMatcher::from_packet(packet).reply_filter() else {
continue;
};
if !filters.iter().any(|existing| existing == &filter) {
filters.push(filter);
}
}
match filters.len() {
0 => None,
1 => filters.pop(),
_ => Some(
filters
.into_iter()
.map(|filter| format!("({filter})"))
.collect::<Vec<_>>()
.join(" or "),
),
}
}
fn dhcpv4_filter() -> String {
format!("udp and src port {DHCPV4_SERVER_PORT} and dst port {DHCPV4_CLIENT_PORT}")
}
fn dhcpv6_filter(packet: &Packet) -> String {
let mut terms = vec!["udp".to_string()];
if let Some(hosts) = dhcpv6_reversed_ipv6_host_terms(packet) {
terms.extend(hosts);
}
let (source_port, destination_port) = match packet.layer::<Udp>() {
Some(udp) => (udp.destination_port_value(), udp.source_port_value()),
None => (DHCPV6_SERVER_PORT, DHCPV6_CLIENT_PORT),
};
terms.push(format!("src port {source_port}"));
terms.push(format!("dst port {destination_port}"));
terms.join(" and ")
}
fn dhcpv6_reversed_ipv6_host_terms(packet: &Packet) -> Option<Vec<String>> {
let ipv6 = packet.layer::<Ipv6>()?;
let source = ipv6.source();
let destination = ipv6.destination();
let mut terms = Vec::new();
if dhcpv6_ipv6_addr_can_be_matched_as_remote(destination) {
terms.push(format!("src host {destination}"));
}
if dhcpv6_ipv6_addr_can_be_matched_as_local(source) {
terms.push(format!("dst host {source}"));
}
(!terms.is_empty()).then_some(terms)
}
fn dhcpv6_ipv6_addr_can_be_matched_as_remote(address: Ipv6Addr) -> bool {
!address.is_unspecified() && !address.is_multicast()
}
fn dhcpv6_ipv6_addr_can_be_matched_as_local(address: Ipv6Addr) -> bool {
!address.is_unspecified() && !address.is_multicast()
}
fn arp_filter(arp: &Arp) -> String {
match (arp.target_ipv4(), arp.sender_ipv4()) {
(Some(target), Some(sender)) => {
format!("arp and src host {target} and dst host {sender}")
}
_ => "arp".to_string(),
}
}
fn igmp_filter(packet: &Packet) -> String {
let mut terms = vec!["igmp".to_string()];
if let Some(ipv4) = packet.layer::<Ipv4>() {
let source = ipv4.source();
let destination = ipv4.destination();
if !source.is_unspecified() && !destination.is_unspecified() {
terms.push(format!("src host {destination}"));
terms.push(format!("dst host {source}"));
}
}
terms.join(" and ")
}
fn protocol_filter(protocol: &str, packet: &Packet) -> String {
let mut terms = vec![protocol.to_string()];
if let Some(hosts) = reversed_host_terms(packet) {
terms.extend(hosts);
}
terms.join(" and ")
}
fn transport_filter(
protocol: &str,
packet: &Packet,
source_port: Option<u16>,
destination_port: Option<u16>,
) -> String {
let mut terms = vec![protocol.to_string()];
if let Some(hosts) = reversed_host_terms(packet) {
terms.extend(hosts);
}
if let Some(source_port) = source_port {
terms.push(format!("src port {source_port}"));
}
if let Some(destination_port) = destination_port {
terms.push(format!("dst port {destination_port}"));
}
terms.join(" and ")
}
fn reversed_host_terms(packet: &Packet) -> Option<Vec<String>> {
if let Some(ipv4) = packet.layer::<Ipv4>() {
Some(vec![
format!("src host {}", ipv4.destination()),
format!("dst host {}", ipv4.source()),
])
} else {
packet.layer::<Ipv6>().map(|ipv6| {
vec![
format!("src host {}", ipv6.destination()),
format!("dst host {}", ipv6.source()),
]
})
}
}
fn arp_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_arp) = request.layer::<Arp>() else {
return false;
};
let Some(candidate_arp) = candidate.layer::<Arp>() else {
return false;
};
request_arp.opcode_value() == u16::from(ArpOperation::Request)
&& candidate_arp.opcode_value() == u16::from(ArpOperation::Reply)
&& candidate_arp.sender_protocol_bytes_value() == request_arp.target_protocol_bytes_value()
&& candidate_arp.target_protocol_bytes_value() == request_arp.sender_protocol_bytes_value()
&& candidate_arp.target_hardware_bytes_value() == request_arp.sender_hardware_bytes_value()
}
fn icmp_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_icmp) = request.layer::<Icmpv4>() else {
return false;
};
let Some(candidate_icmp) = candidate.layer::<Icmpv4>() else {
return false;
};
request_icmp.icmp_type_value() == ICMP_ECHO_REQUEST
&& candidate_icmp.icmp_type_value() == ICMP_ECHO_REPLY
&& request_icmp.identifier_value() == candidate_icmp.identifier_value()
&& request_icmp.sequence_number_value() == candidate_icmp.sequence_number_value()
&& ip_addresses_are_reversed(request, candidate)
}
fn icmpv6_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_icmp) = request.layer::<Icmpv6>() else {
return false;
};
let Some(candidate_icmp) = candidate.layer::<Icmpv6>() else {
return false;
};
request_icmp.icmp_type_value() == ICMPV6_ECHO_REQUEST
&& candidate_icmp.icmp_type_value() == ICMPV6_ECHO_REPLY
&& request_icmp.identifier_value() == candidate_icmp.identifier_value()
&& request_icmp.sequence_number_value() == candidate_icmp.sequence_number_value()
&& ip_addresses_are_reversed(request, candidate)
}
fn tcp_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_tcp) = request.layer::<Tcp>() else {
return false;
};
let Some(candidate_tcp) = candidate.layer::<Tcp>() else {
return false;
};
candidate_tcp.source_port_value() == request_tcp.destination_port_value()
&& candidate_tcp.destination_port_value() == request_tcp.source_port_value()
&& ip_addresses_are_reversed(request, candidate)
}
fn udp_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_udp) = request.layer::<Udp>() else {
return false;
};
let Some(candidate_udp) = candidate.layer::<Udp>() else {
return false;
};
candidate_udp.source_port_value() == request_udp.destination_port_value()
&& candidate_udp.destination_port_value() == request_udp.source_port_value()
&& ip_addresses_are_reversed(request, candidate)
}
fn dns_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_dns) = request.layer::<Dns>() else {
return false;
};
let Some(candidate_dns) = candidate.layer::<Dns>() else {
return false;
};
!request_dns.is_response()
&& candidate_dns.is_response()
&& request_dns.id_value() == candidate_dns.id_value()
&& udp_optional_reply_matches(request, candidate)
}
fn dhcpv4_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_dhcpv4) = request.layer::<Dhcpv4>() else {
return false;
};
let Some(candidate_dhcpv4) = candidate.layer::<Dhcpv4>() else {
return false;
};
if candidate_dhcpv4.op_value() != BOOTP_REPLY
|| request_dhcpv4.transaction_id_value() != candidate_dhcpv4.transaction_id_value()
{
return false;
}
if !dhcpv4_transport_reply_matches(request, candidate) {
return false;
}
dhcpv4_client_hardware_address_matches(request_dhcpv4, candidate_dhcpv4)
&& dhcpv4_client_identifier_matches(request_dhcpv4, candidate_dhcpv4)
&& dhcpv4_server_identifier_matches(request_dhcpv4, candidate_dhcpv4)
&& dhcpv4_relay_giaddr_matches(request_dhcpv4, candidate_dhcpv4)
&& dhcpv4_message_type_matches(request_dhcpv4, candidate_dhcpv4)
}
fn dhcpv4_client_hardware_address_matches(request: &Dhcpv4, candidate: &Dhcpv4) -> bool {
let request_chaddr = request.client_hardware_address_value();
let candidate_chaddr = candidate.client_hardware_address_value();
if request_chaddr.is_empty() || candidate_chaddr.is_empty() {
return true;
}
request_chaddr == candidate_chaddr
}
fn dhcpv4_client_identifier_matches(request: &Dhcpv4, candidate: &Dhcpv4) -> bool {
match (
dhcpv4_client_identifier(request),
dhcpv4_client_identifier(candidate),
) {
(Some(request_id), Some(candidate_id)) => request_id == candidate_id,
_ => true,
}
}
fn dhcpv4_client_identifier(dhcpv4: &Dhcpv4) -> Option<Dhcpv4ClientIdentifier> {
dhcpv4.client_identifier_value().and_then(Result::ok)
}
fn dhcpv4_server_identifier_matches(request: &Dhcpv4, candidate: &Dhcpv4) -> bool {
match (
request.server_identifier_value(),
candidate.server_identifier_value(),
) {
(Some(request_server), Some(candidate_server)) => request_server == candidate_server,
_ => true,
}
}
fn dhcpv4_relay_giaddr_matches(request: &Dhcpv4, candidate: &Dhcpv4) -> bool {
let request_giaddr = request.gateway_ip_address_value();
let candidate_giaddr = candidate.gateway_ip_address_value();
if request_giaddr == Ipv4Addr::UNSPECIFIED || candidate_giaddr == Ipv4Addr::UNSPECIFIED {
return true;
}
request_giaddr == candidate_giaddr
}
fn dhcpv4_message_type_matches(request: &Dhcpv4, candidate: &Dhcpv4) -> bool {
let Some(request_type) = request.message_type_value() else {
return true;
};
if !is_leasequery_request_type(request_type) {
return true;
}
candidate
.message_type_value()
.is_some_and(is_leasequery_reply_type)
}
fn is_leasequery_request_type(message_type: Dhcpv4MessageType) -> bool {
matches!(
message_type,
Dhcpv4MessageType::LeaseQuery
| Dhcpv4MessageType::BulkLeaseQuery
| Dhcpv4MessageType::ActiveLeaseQuery
)
}
fn is_leasequery_reply_type(message_type: Dhcpv4MessageType) -> bool {
matches!(
message_type,
Dhcpv4MessageType::LeaseUnassigned
| Dhcpv4MessageType::LeaseUnknown
| Dhcpv4MessageType::LeaseActive
| Dhcpv4MessageType::LeaseQueryDone
| Dhcpv4MessageType::LeaseQueryStatus
| Dhcpv4MessageType::LeaseQuery
| Dhcpv4MessageType::BulkLeaseQuery
| Dhcpv4MessageType::ActiveLeaseQuery
)
}
fn dhcpv4_transport_reply_matches(request: &Packet, candidate: &Packet) -> bool {
match (request.layer::<Udp>(), candidate.layer::<Udp>()) {
(Some(request_udp), Some(candidate_udp)) => {
request_udp.source_port_value() == DHCPV4_CLIENT_PORT
&& request_udp.destination_port_value() == DHCPV4_SERVER_PORT
&& candidate_udp.source_port_value() == DHCPV4_SERVER_PORT
&& candidate_udp.destination_port_value() == DHCPV4_CLIENT_PORT
}
(None, None) => true,
_ => false,
}
}
fn dhcpv6_reply_matches(request: &Packet, candidate: &Packet) -> bool {
let Some(request_dhcpv6) = request.layer::<Dhcpv6>() else {
return false;
};
let Some(candidate_dhcpv6) = candidate.layer::<Dhcpv6>() else {
return false;
};
dhcpv6_transport_reply_matches(request, candidate)
&& dhcpv6_ipv6_reply_matches(request, candidate)
&& dhcpv6_message_pair_matches(request_dhcpv6, candidate_dhcpv6)
}
fn dhcpv6_message_pair_matches(request: &Dhcpv6, candidate: &Dhcpv6) -> bool {
match (request.message_type_value(), candidate.message_type_value()) {
(Dhcpv6MessageType::RelayForw, Dhcpv6MessageType::RelayRepl) => {
dhcpv6_relay_reply_matches(request, candidate)
}
(Dhcpv6MessageType::RelayForw, _) | (_, Dhcpv6MessageType::RelayRepl) => false,
_ => {
request.transaction_id_value() == candidate.transaction_id_value()
&& dhcpv6_client_id_matches(request, candidate)
&& dhcpv6_server_id_matches(request, candidate)
&& dhcpv6_message_type_family_matches(request, candidate)
}
}
}
fn dhcpv6_relay_reply_matches(request: &Dhcpv6, candidate: &Dhcpv6) -> bool {
if !dhcpv6_relay_header_matches(request, candidate) {
return false;
}
if !dhcpv6_relay_interface_id_matches(request, candidate) {
return false;
}
match (
request.relayed_message_value(),
candidate.relayed_message_value(),
) {
(Ok(Some(request_inner)), Ok(Some(candidate_inner))) => {
dhcpv6_message_pair_matches(&request_inner, &candidate_inner)
}
_ => false,
}
}
fn dhcpv6_relay_header_matches(request: &Dhcpv6, candidate: &Dhcpv6) -> bool {
match (request.relay(), candidate.relay()) {
(Some(request_relay), Some(candidate_relay)) => {
request_relay.link_address_value() == candidate_relay.link_address_value()
&& request_relay.peer_address_value() == candidate_relay.peer_address_value()
}
_ => false,
}
}
fn dhcpv6_relay_interface_id_matches(request: &Dhcpv6, candidate: &Dhcpv6) -> bool {
match (request.interface_id_value(), candidate.interface_id_value()) {
(Some(request_id), Some(candidate_id)) => request_id == candidate_id,
(Some(_), None) => false,
_ => true,
}
}
fn dhcpv6_client_id_matches(request: &Dhcpv6, candidate: &Dhcpv6) -> bool {
match (request.client_id_value(), candidate.client_id_value()) {
(Some(request_id), Some(candidate_id)) => request_id == candidate_id,
(Some(_), None) => false,
_ => true,
}
}
fn dhcpv6_server_id_matches(request: &Dhcpv6, candidate: &Dhcpv6) -> bool {
match (request.server_id_value(), candidate.server_id_value()) {
(Some(request_id), Some(candidate_id)) => request_id == candidate_id,
(Some(_), None) => false,
_ => true,
}
}
fn dhcpv6_message_type_family_matches(request: &Dhcpv6, candidate: &Dhcpv6) -> bool {
let candidate_type = candidate.message_type_value();
match request.message_type_value() {
Dhcpv6MessageType::Solicit => {
matches!(
candidate_type,
Dhcpv6MessageType::Advertise | Dhcpv6MessageType::Reply
)
}
Dhcpv6MessageType::Request
| Dhcpv6MessageType::Confirm
| Dhcpv6MessageType::Renew
| Dhcpv6MessageType::Rebind
| Dhcpv6MessageType::Release
| Dhcpv6MessageType::Decline
| Dhcpv6MessageType::InformationRequest => {
matches!(candidate_type, Dhcpv6MessageType::Reply)
}
Dhcpv6MessageType::Reconfigure => {
dhcpv6_reconfigure_response_type_matches(request, candidate_type)
}
Dhcpv6MessageType::LeaseQuery => {
matches!(candidate_type, Dhcpv6MessageType::LeaseQueryReply)
}
Dhcpv6MessageType::ActiveLeaseQuery => {
matches!(
candidate_type,
Dhcpv6MessageType::LeaseQueryData | Dhcpv6MessageType::LeaseQueryDone
)
}
Dhcpv6MessageType::ReconfigureRequest => {
matches!(candidate_type, Dhcpv6MessageType::ReconfigureReply)
}
Dhcpv6MessageType::Dhcpv4Query => {
matches!(candidate_type, Dhcpv6MessageType::Dhcpv4Response)
}
Dhcpv6MessageType::BndUpd => matches!(candidate_type, Dhcpv6MessageType::BndReply),
Dhcpv6MessageType::PoolReq => matches!(candidate_type, Dhcpv6MessageType::PoolResp),
Dhcpv6MessageType::UpdReq | Dhcpv6MessageType::UpdReqAll => {
matches!(candidate_type, Dhcpv6MessageType::UpdDone)
}
Dhcpv6MessageType::Connect => {
matches!(candidate_type, Dhcpv6MessageType::ConnectReply)
}
Dhcpv6MessageType::AddrRegInform => {
matches!(candidate_type, Dhcpv6MessageType::AddrRegReply)
}
_ => false,
}
}
fn dhcpv6_reconfigure_response_type_matches(
request: &Dhcpv6,
candidate_type: Dhcpv6MessageType,
) -> bool {
match request.reconfigure_message_value() {
Ok(Some(requested_type)) => candidate_type == requested_type,
_ => matches!(
candidate_type,
Dhcpv6MessageType::Renew
| Dhcpv6MessageType::Rebind
| Dhcpv6MessageType::InformationRequest
),
}
}
fn dhcpv6_transport_reply_matches(request: &Packet, candidate: &Packet) -> bool {
match (request.layer::<Udp>(), candidate.layer::<Udp>()) {
(Some(request_udp), Some(candidate_udp)) => {
request_udp.source_port_value() == candidate_udp.destination_port_value()
&& request_udp.destination_port_value() == candidate_udp.source_port_value()
&& dhcpv6_udp_port_pair_is_valid(
request_udp.source_port_value(),
request_udp.destination_port_value(),
)
&& dhcpv6_udp_port_pair_is_valid(
candidate_udp.source_port_value(),
candidate_udp.destination_port_value(),
)
}
(None, None) => true,
_ => false,
}
}
fn dhcpv6_udp_port_pair_is_valid(source_port: u16, destination_port: u16) -> bool {
matches!(
(source_port, destination_port),
(DHCPV6_CLIENT_PORT, DHCPV6_SERVER_PORT)
| (DHCPV6_SERVER_PORT, DHCPV6_CLIENT_PORT)
| (DHCPV6_SERVER_PORT, DHCPV6_SERVER_PORT)
)
}
fn dhcpv6_ipv6_reply_matches(request: &Packet, candidate: &Packet) -> bool {
match (request.layer::<Ipv6>(), candidate.layer::<Ipv6>()) {
(Some(request_ip), Some(candidate_ip)) => {
let request_source = request_ip.source();
let request_destination = request_ip.destination();
if dhcpv6_ipv6_addr_can_be_matched_as_local(request_source)
&& candidate_ip.destination() != request_source
{
return false;
}
if dhcpv6_ipv6_addr_can_be_matched_as_remote(request_destination)
&& candidate_ip.source() != request_destination
{
return false;
}
true
}
(None, None) => true,
_ => false,
}
}
fn udp_optional_reply_matches(request: &Packet, candidate: &Packet) -> bool {
match (request.layer::<Udp>(), candidate.layer::<Udp>()) {
(Some(_), Some(_)) => udp_reply_matches(request, candidate),
(None, None) => ip_addresses_are_reversed(request, candidate),
_ => false,
}
}
fn ip_addresses_are_reversed(request: &Packet, candidate: &Packet) -> bool {
ipv4_addresses_are_reversed(request, candidate)
&& ipv6_addresses_are_reversed(request, candidate)
}
fn ipv4_addresses_are_reversed(request: &Packet, candidate: &Packet) -> bool {
match (request.layer::<Ipv4>(), candidate.layer::<Ipv4>()) {
(Some(request_ip), Some(candidate_ip)) => {
candidate_ip.source() == request_ip.destination()
&& candidate_ip.destination() == request_ip.source()
}
(None, None) => true,
_ => false,
}
}
fn ipv6_addresses_are_reversed(request: &Packet, candidate: &Packet) -> bool {
match (request.layer::<Ipv6>(), candidate.layer::<Ipv6>()) {
(Some(request_ip), Some(candidate_ip)) => {
candidate_ip.source() == request_ip.destination()
&& candidate_ip.destination() == request_ip.source()
}
(None, None) => true,
_ => false,
}
}