use crate::packet::{header::TransportLayer, icmp::IcmpType, icmp6::Icmp6Type, Packet};
use std::{cmp::Ordering, fmt};
const PORT_DNS: u16 = 53;
const PORT_DHCP_SERVER: u16 = 67;
const PORT_DHCP_CLIENT: u16 = 68;
const PORT_NTP: u16 = 123;
const PORT_NETBIOS_NS: u16 = 137;
const PORT_NETBIOS_DGM: u16 = 138;
const PORT_SNMP: u16 = 161;
const PORT_SNMP_TRAP: u16 = 162;
const PORT_CLDAP: u16 = 389;
const PORT_HTTPS: u16 = 443;
const PORT_IKE: u16 = 500;
const PORT_SYSLOG: u16 = 514;
const PORT_RIP: u16 = 520;
const PORT_DHCPV6_CLIENT: u16 = 546;
const PORT_DHCPV6_SERVER: u16 = 547;
const PORT_OPENVPN: u16 = 1194;
const PORT_SSDP: u16 = 1900;
const PORT_IPSEC_NATT: u16 = 4500;
const PORT_MDNS: u16 = 5353;
const PORT_LLMNR: u16 = 5355;
const PORT_HTTPS_ALT: u16 = 8443;
const PORT_HTTP: u16 = 80;
const PORT_HTTP_ALT: u16 = 8080;
const PORT_STUN: u16 = 3478;
const PORT_STUN_TLS: u16 = 5349;
const DNS_QR_BIT_MASK: u8 = 0x80;
const NTP_MODE_MASK: u8 = 0x07;
const TLS_HANDSHAKE_CONTENT_TYPE: u8 = 0x16;
const TLS_CLIENT_HELLO: u8 = 0x01;
const TLS_SERVER_HELLO: u8 = 0x02;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PacketDirection {
#[default]
Upwards,
Downwards,
}
impl fmt::Display for PacketDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PacketDirection::Upwards => write!(f, "Upwards"),
PacketDirection::Downwards => write!(f, "Downwards"),
}
}
}
impl PacketDirection {
pub fn infer(pkt: &Packet<'_>) -> PacketDirection {
let transport = pkt.transport();
match transport {
Some(TransportLayer::Tcp(tcp)) => {
if tcp.header.has_syn() {
if tcp.header.has_ack() {
return PacketDirection::Downwards; } else {
return PacketDirection::Upwards; }
}
return Self::infer_direction_tcp(tcp.src_port(), tcp.dst_port(), pkt.data());
}
Some(TransportLayer::Udp(udp)) => {
Self::infer_direction_udp(udp.src_port(), udp.dst_port(), pkt.data())
}
Some(TransportLayer::Icmp(icmp)) => match icmp.icmp_type() {
IcmpType::ECHO => PacketDirection::Upwards,
IcmpType::ECHO_REPLY => PacketDirection::Downwards,
IcmpType::TIMESTAMP => PacketDirection::Upwards,
IcmpType::TIMESTAMP_REPLY => PacketDirection::Downwards,
IcmpType::INFO_REQUEST => PacketDirection::Upwards,
IcmpType::INFO_REPLY => PacketDirection::Downwards,
IcmpType::ADDRESS => PacketDirection::Upwards,
IcmpType::ADDRESS_REPLY => PacketDirection::Downwards,
IcmpType::EX_ECHO => PacketDirection::Upwards,
IcmpType::EX_ECHO_REPLY => PacketDirection::Downwards,
IcmpType::DEST_UNREACH => PacketDirection::Upwards,
IcmpType::SOURCE_QUENCH => PacketDirection::Upwards,
IcmpType::REDIRECT => PacketDirection::Upwards,
IcmpType::ROUTER_ADV => PacketDirection::Downwards,
IcmpType::ROUTER_SOLICIT => PacketDirection::Upwards,
IcmpType::TIME_EXCEEDED => PacketDirection::Upwards,
IcmpType::PARAMETER_PROBLEM => PacketDirection::Upwards,
_ => PacketDirection::Upwards,
},
Some(TransportLayer::Icmp6(icmp6)) => match icmp6.icmp6_type() {
Icmp6Type::DST_UNREACH => PacketDirection::Upwards,
Icmp6Type::PACKET_TOO_BIG => PacketDirection::Upwards,
Icmp6Type::TIME_EXCEEDED => PacketDirection::Upwards,
Icmp6Type::PARAM_PROB => PacketDirection::Upwards,
Icmp6Type::ECHO_REQUEST => PacketDirection::Upwards,
Icmp6Type::ECHO_REPLY => PacketDirection::Downwards,
Icmp6Type::MLD_LISTENER_QUERY => PacketDirection::Downwards,
Icmp6Type::MLD_LISTENER_REPORT => PacketDirection::Upwards,
Icmp6Type::MLD_LISTENER_REDUCTION => PacketDirection::Upwards,
Icmp6Type::ROUTER_SOLICITATION => PacketDirection::Upwards,
Icmp6Type::ROUTER_ADVERTISEMENT => PacketDirection::Downwards,
Icmp6Type::NEIGHBOR_SOLICITATION => PacketDirection::Upwards,
Icmp6Type::NEIGHBOR_ADVERTISEMENT => PacketDirection::Downwards,
Icmp6Type::REDIRECT_MESSAGE => PacketDirection::Upwards,
Icmp6Type::ROUTER_RENUMBERING => PacketDirection::Downwards,
Icmp6Type::NODE_INFORMATION_QUERY => PacketDirection::Upwards,
Icmp6Type::NODE_INFORMATION_RESPONSE => PacketDirection::Downwards,
Icmp6Type::INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION => PacketDirection::Upwards,
Icmp6Type::INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT => PacketDirection::Downwards,
Icmp6Type::MULTICAST_LISTENER_DISCOVERY_REPORTS => PacketDirection::Upwards,
Icmp6Type::HOME_AGENT_ADDRESS_DISCOVERY_REQUEST => PacketDirection::Upwards,
Icmp6Type::HOME_AGENT_ADDRESS_DISCOVERY_REPLY => PacketDirection::Downwards,
Icmp6Type::MOBILE_PREFIX_SOLICITATION => PacketDirection::Upwards,
Icmp6Type::MOBILE_PREFIX_ADVERTISEMENT => PacketDirection::Downwards,
Icmp6Type::MULTICAST_ROUTER_SOLICITATION => PacketDirection::Upwards,
Icmp6Type::MULTICAST_ROUTER_TERMINATION => PacketDirection::Upwards,
Icmp6Type::FMIPV6 => PacketDirection::Upwards,
Icmp6Type::RPL_CONTROL_MESSAGE => PacketDirection::Upwards,
Icmp6Type::ILNPV6_LOCATOR_UPDATE => PacketDirection::Upwards,
Icmp6Type::DUPLICATE_ADDRESS_REQUEST => PacketDirection::Upwards,
Icmp6Type::DUPLICATE_ADDRESS_CONFIRM => PacketDirection::Downwards,
Icmp6Type::MPL_CONTROL_MESSAGE => PacketDirection::Upwards,
Icmp6Type::EXTENDED_ECHO_REQUEST => PacketDirection::Upwards,
Icmp6Type::EXTENDED_ECHO_REPLY => PacketDirection::Downwards,
_ => PacketDirection::Upwards,
},
Some(TransportLayer::Sctp(_)) | None => PacketDirection::Upwards,
}
}
fn infer_direction_udp(source: u16, dest: u16, data: &[u8]) -> PacketDirection {
match (source, dest) {
(PORT_DHCP_CLIENT, PORT_DHCP_SERVER) | (PORT_DHCPV6_CLIENT, PORT_DHCPV6_SERVER) => {
return PacketDirection::Upwards;
}
(PORT_DHCP_SERVER, PORT_DHCP_CLIENT) | (PORT_DHCPV6_SERVER, PORT_DHCPV6_CLIENT) => {
return PacketDirection::Downwards;
}
_ => {}
}
if (matches!(source, PORT_DNS | PORT_MDNS | PORT_LLMNR | PORT_NETBIOS_NS)
|| matches!(dest, PORT_DNS | PORT_MDNS | PORT_LLMNR | PORT_NETBIOS_NS))
&& source != dest
&& data.len() >= 3
{
let is_response = (data[2] & DNS_QR_BIT_MASK) != 0;
return if is_response { PacketDirection::Downwards } else { PacketDirection::Upwards };
}
if (source == PORT_SSDP || dest == PORT_SSDP) && data.len() >= 8 {
if data.starts_with(b"HTTP/1.") {
return PacketDirection::Downwards;
}
if data.starts_with(b"M-SE") {
return PacketDirection::Upwards;
}
}
if (source == PORT_HTTPS || dest == PORT_HTTPS) && data.len() >= 1200 {
if (data[0] & 0xC0) == 0xC0 {
return PacketDirection::Upwards;
}
}
if (source == PORT_STUN || dest == PORT_STUN || source == PORT_STUN_TLS || dest == PORT_STUN_TLS) && data.len() >= 20 {
if (data[0] & 0xC0) == 0x00 {
let msg_type = ((data[0] as u16) << 8) | (data[1] as u16);
let is_response = (msg_type & 0x0110) != 0;
return if is_response { PacketDirection::Downwards } else { PacketDirection::Upwards };
}
}
if (source == PORT_NTP || dest == PORT_NTP) && !data.is_empty() {
match data[0] & NTP_MODE_MASK {
1 | 3 => return PacketDirection::Upwards, 2 | 4 | 5 => return PacketDirection::Downwards, _ => {
if source != dest {
return if dest == PORT_NTP { PacketDirection::Upwards } else { PacketDirection::Downwards };
}
}
}
}
if source == dest {
let threshold = match source {
PORT_DNS => Some(64), PORT_NTP => Some(48), PORT_NETBIOS_NS => Some(60), PORT_NETBIOS_DGM => Some(100), PORT_SNMP => Some(80), PORT_SNMP_TRAP => Some(80), PORT_CLDAP => Some(150), PORT_IKE => Some(200), PORT_SYSLOG => Some(200), PORT_RIP => Some(60), PORT_OPENVPN => Some(100), PORT_SSDP => Some(200), PORT_IPSEC_NATT => Some(200), PORT_MDNS => Some(80), PORT_LLMNR => Some(64), _ => None,
};
if let Some(t) = threshold {
return if data.len() <= t {
PacketDirection::Upwards
} else {
PacketDirection::Downwards
};
}
}
Self::infer_direction_from_ports(source, dest).unwrap_or(PacketDirection::Upwards)
}
fn infer_direction_tcp(source: u16, dest: u16, data: &[u8]) -> PacketDirection {
if (source == PORT_HTTP || dest == PORT_HTTP || source == PORT_HTTP_ALT || dest == PORT_HTTP_ALT) && data.len() >= 8 {
if data.starts_with(b"HTTP/1.") {
return PacketDirection::Downwards;
}
if data.starts_with(b"GET ") || data.starts_with(b"POST") || data.starts_with(b"PUT ") || data.starts_with(b"HEAD") {
return PacketDirection::Upwards;
}
}
if (source == PORT_HTTPS
|| dest == PORT_HTTPS
|| source == PORT_HTTPS_ALT
|| dest == PORT_HTTPS_ALT)
&& data.len() >= 6
{
if data[0] == TLS_HANDSHAKE_CONTENT_TYPE {
let handshake_type = data[5];
return match handshake_type {
TLS_CLIENT_HELLO => PacketDirection::Upwards, TLS_SERVER_HELLO => PacketDirection::Downwards, _ => {
if dest == PORT_HTTPS || dest == PORT_HTTPS_ALT {
PacketDirection::Upwards
} else {
PacketDirection::Downwards
}
}
};
}
}
if (source == PORT_DNS || dest == PORT_DNS) && data.len() >= 5 {
let is_response = (data[4] & DNS_QR_BIT_MASK) != 0;
return if is_response {
PacketDirection::Downwards
} else {
PacketDirection::Upwards
};
}
Self::infer_direction_from_ports(source, dest).unwrap_or(PacketDirection::Upwards)
}
fn infer_direction_from_ports(source: u16, dest: u16) -> Option<PacketDirection> {
if source == dest {
return None; }
let port_rank = |p: u16| -> u8 {
match p {
0..=1024 => 0,
1025..=49151 => 1,
49152..=u16::MAX => 2,
}
};
match port_rank(source).cmp(&port_rank(dest)) {
Ordering::Greater => Some(PacketDirection::Upwards), Ordering::Less => Some(PacketDirection::Downwards), Ordering::Equal => {
if source > dest {
Some(PacketDirection::Upwards)
} else {
Some(PacketDirection::Downwards)
}
}
}
}
}