use std::sync::OnceLock;
use crate::endian::read_u32_be;
use crate::error::Result;
use crate::packet::{LinkType, NetworkLayer, Packet, Raw};
use crate::protocols::bgp::{decode::append_bgp_packet_with_registry, BGP_PORT};
use crate::protocols::dhcp::{
append_dhcpv4_packet, append_dhcpv6_packet, is_dhcpv4_port_pair, looks_like_dhcpv4_payload,
looks_like_dhcpv6_payload,
};
use crate::protocols::dns::{append_dns_packet, DNS_PORT, MDNS_PORT};
use crate::protocols::eapol::append_eapol_packet;
use crate::protocols::icmp::{
append_icmp_packet, append_icmp_packet_with_checksum_validation, append_icmpv6_packet,
};
use crate::protocols::igmp::append_igmp_packet;
use crate::protocols::ip::shared::IPPROTO_IGMP;
use crate::protocols::ipsec::ah::decode::append_ah_packet_with_registry_sa;
use crate::protocols::ipsec::esp::decode::append_esp_packet_with_registry_sa;
use crate::protocols::ipsec::esp::header::ESP_HEADER_LEN;
use crate::protocols::ipsec::ikev2::decode::append_ikev2_packet_with_registry;
use crate::protocols::ipsec::natt::{is_non_esp_marker, NatTraversal, NON_ESP_MARKER_LEN};
use crate::protocols::ipsec::sa::SecurityAssociation;
use crate::protocols::ipv4::{
append_ipv4_packet_with_registry, IPPROTO_AH, IPPROTO_ESP, IPPROTO_ICMP, IPPROTO_ICMPV6,
IPPROTO_OSPF, IPPROTO_TCP, IPPROTO_UDP,
};
use crate::protocols::ipv6::{append_ipv6_packet_with_registry, IPPROTO_IPV6_AH, IPPROTO_IPV6_ESP};
use crate::protocols::mqtt::{decode::append_mqtt_packet_with_registry, MQTT_PORT};
use crate::protocols::ntp::{append_ntp_packet, looks_like_ntp_payload, NTP_PORT};
use crate::protocols::ospf::decode::append_ospf_packet_with_checksum_validation;
use crate::protocols::ospf::v3::append_ospfv3_packet_with_checksum_validation;
use crate::protocols::link::{
append_arp_packet, append_vlan_packet_with_registry, decode_ble_ll_with_registry,
decode_dot11_with_registry, decode_dot15d4_with_registry, decode_ethernet_with_registry,
decode_linux_sll_with_registry, decode_null_loopback_with_registry,
decode_radiotap_with_registry, ETHERTYPE_ARP, ETHERTYPE_EAPOL, ETHERTYPE_IPV4, ETHERTYPE_IPV6,
ETHERTYPE_VLAN,
};
#[allow(unused_imports)]
pub(crate) use crate::protocols::ospf::decode::append_ospf_packet;
use crate::protocols::quic::decode::{append_quic_packet, looks_like_quic_udp_payload};
use crate::protocols::rip::ripng::{append_ripng_packet, looks_like_ripng_payload, RIPNG_UDP_PORT};
use crate::protocols::rip::{append_rip_packet, looks_like_rip_payload, RIP_UDP_PORT};
use crate::protocols::snmp::{
decode::{append_snmp_packet, looks_like_snmp_payload},
SNMP_PORT, SNMP_TRAP_PORT,
};
use crate::protocols::ssdp::{
decode::{append_ssdp_packet, looks_like_ssdp_payload},
SSDP_UDP_PORT,
};
use crate::protocols::tls::{
constants::TLS_COMMON_TCP_PORTS,
decode::{append_tls_packet_with_registry, looks_like_tls_payload},
};
use crate::protocols::transport::{
append_tcp_packet_with_registry, append_udp_packet_with_registry,
};
const IKEV2_UDP_PORT: u16 = 500;
const NATT_UDP_PORT: u16 = 4500;
const fn is_snmp_udp_port(port: u16) -> bool {
matches!(port, SNMP_PORT | SNMP_TRAP_PORT)
}
const QUIC_HTTPS_UDP_PORT: u16 = 443;
const QUIC_EXAMPLE_UDP_PORT: u16 = 4433;
type ProtocolDecoder = dyn for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static;
#[derive(Debug, Clone, Copy)]
pub struct EthertypeBindingContext<'a> {
pub ethertype: u16,
pub payload: &'a [u8],
}
#[derive(Debug, Clone, Copy)]
pub struct Ipv4ProtocolBindingContext<'a> {
pub protocol: u8,
pub payload: &'a [u8],
}
#[derive(Debug, Clone, Copy)]
pub struct Ipv6NextHeaderBindingContext<'a> {
pub next_header: u8,
pub payload: &'a [u8],
}
#[derive(Debug, Clone, Copy)]
pub struct UdpBindingContext<'a> {
pub source_port: u16,
pub destination_port: u16,
pub payload: &'a [u8],
}
#[derive(Debug, Clone, Copy)]
pub struct TcpBindingContext<'a> {
pub source_port: u16,
pub destination_port: u16,
pub payload: &'a [u8],
}
struct EthertypeBinding {
predicate: Box<dyn for<'a> Fn(EthertypeBindingContext<'a>) -> bool + Send + Sync + 'static>,
decoder: Box<ProtocolDecoder>,
}
struct Ipv4ProtocolBinding {
predicate: Box<dyn for<'a> Fn(Ipv4ProtocolBindingContext<'a>) -> bool + Send + Sync + 'static>,
decoder: Box<ProtocolDecoder>,
}
struct Ipv6NextHeaderBinding {
predicate:
Box<dyn for<'a> Fn(Ipv6NextHeaderBindingContext<'a>) -> bool + Send + Sync + 'static>,
decoder: Box<ProtocolDecoder>,
}
struct UdpBinding {
predicate: Box<dyn for<'a> Fn(UdpBindingContext<'a>) -> bool + Send + Sync + 'static>,
decoder: Box<ProtocolDecoder>,
}
struct TcpBinding {
predicate: Box<dyn for<'a> Fn(TcpBindingContext<'a>) -> bool + Send + Sync + 'static>,
decoder: Box<ProtocolDecoder>,
}
pub struct ProtocolRegistry {
ethertype_bindings: Vec<EthertypeBinding>,
builtin_ethertype_dispatch: bool,
ipv4_bindings: Vec<Ipv4ProtocolBinding>,
builtin_ipv4_protocol_dispatch: bool,
ipv6_bindings: Vec<Ipv6NextHeaderBinding>,
builtin_ipv6_next_header_dispatch: bool,
udp_bindings: Vec<UdpBinding>,
builtin_udp_application_dispatch: bool,
tcp_bindings: Vec<TcpBinding>,
security_associations: Vec<SecurityAssociation>,
validate_checksums: bool,
decode_applications: bool,
}
impl ProtocolRegistry {
pub fn new() -> Self {
Self::with_builtin_bindings()
}
pub(crate) fn builtin() -> &'static Self {
static BUILTIN_REGISTRY: OnceLock<ProtocolRegistry> = OnceLock::new();
BUILTIN_REGISTRY.get_or_init(Self::with_builtin_bindings)
}
pub(crate) fn transport_only_builtin() -> &'static Self {
static TRANSPORT_ONLY_REGISTRY: OnceLock<ProtocolRegistry> = OnceLock::new();
TRANSPORT_ONLY_REGISTRY.get_or_init(Self::transport_only)
}
pub fn empty() -> Self {
Self {
ethertype_bindings: Vec::new(),
builtin_ethertype_dispatch: false,
ipv4_bindings: Vec::new(),
builtin_ipv4_protocol_dispatch: false,
ipv6_bindings: Vec::new(),
builtin_ipv6_next_header_dispatch: false,
udp_bindings: Vec::new(),
builtin_udp_application_dispatch: false,
tcp_bindings: Vec::new(),
security_associations: Vec::new(),
validate_checksums: true,
decode_applications: true,
}
}
pub fn with_builtin_bindings() -> Self {
let mut registry = Self::empty();
registry.bind_ethertype_with_registry(ETHERTYPE_ARP, |_registry, packet, payload| {
append_arp_packet(packet, payload)
});
registry.bind_ethertype_with_registry(ETHERTYPE_VLAN, |registry, packet, payload| {
append_vlan_packet_with_registry(registry, packet, payload)
});
registry.bind_ethertype_with_registry(ETHERTYPE_IPV4, |registry, packet, payload| {
append_ipv4_packet_with_registry(registry, packet, payload)
});
registry.bind_ethertype_with_registry(ETHERTYPE_IPV6, |registry, packet, payload| {
append_ipv6_packet_with_registry(registry, packet, payload)
});
registry.bind_ethertype_with_registry(ETHERTYPE_EAPOL, |_registry, packet, payload| {
append_eapol_packet(packet, payload)
});
registry.builtin_ethertype_dispatch = true;
registry.bind_ipv4_protocol_with_registry(IPPROTO_ICMP, |registry, packet, payload| {
append_icmp_packet_with_checksum_validation(
packet,
payload,
registry.validates_checksums(),
)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_TCP, |registry, packet, payload| {
append_tcp_packet_with_registry(registry, packet, payload)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_UDP, |registry, packet, payload| {
append_udp_packet_with_registry(registry, packet, payload)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_IGMP, |_registry, packet, payload| {
append_igmp_packet(packet, payload)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_ESP, |registry, packet, payload| {
decode_esp_with_registry_sa(registry, packet, payload)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_AH, |registry, packet, payload| {
decode_ah_with_registry_sa(registry, packet, payload)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_OSPF, |registry, packet, payload| {
append_ospf_packet_with_checksum_validation(
packet,
payload,
registry.validates_checksums(),
)
});
registry
.bind_ipv6_next_header_with_registry(IPPROTO_ICMPV6, |_registry, packet, payload| {
append_icmpv6_packet(packet, payload)
});
registry.bind_ipv6_next_header_with_registry(IPPROTO_TCP, |registry, packet, payload| {
append_tcp_packet_with_registry(registry, packet, payload)
});
registry.bind_ipv6_next_header_with_registry(IPPROTO_UDP, |registry, packet, payload| {
append_udp_packet_with_registry(registry, packet, payload)
});
registry
.bind_ipv6_next_header_with_registry(IPPROTO_IPV6_ESP, |registry, packet, payload| {
decode_esp_with_registry_sa(registry, packet, payload)
});
registry
.bind_ipv6_next_header_with_registry(IPPROTO_IPV6_AH, |registry, packet, payload| {
decode_ah_with_registry_sa(registry, packet, payload)
});
registry.bind_ipv6_next_header_with_registry(IPPROTO_OSPF, |registry, packet, payload| {
append_ospfv3_packet_with_checksum_validation(
packet,
payload,
registry.validates_checksums(),
)
});
registry.bind_udp_port_with_registry(DNS_PORT, |_registry, packet, payload| {
append_dns_packet(packet, payload)
});
registry.bind_udp_port_with_registry(MDNS_PORT, |_registry, packet, payload| {
append_dns_packet(packet, payload)
});
registry.bind_udp_port_with_registry(IKEV2_UDP_PORT, |registry, packet, payload| {
append_ikev2_packet_with_registry(registry, packet, payload)
});
registry.bind_udp_with_registry(
|ctx| {
(ctx.source_port == NATT_UDP_PORT || ctx.destination_port == NATT_UDP_PORT)
&& ctx.payload.len() >= ESP_HEADER_LEN
},
|registry, packet, payload| {
if is_non_esp_marker(payload) {
let marker =
NatTraversal::marker().bytes(payload[..NON_ESP_MARKER_LEN].to_vec());
let packet = packet.push(marker);
append_ikev2_packet_with_registry(
registry,
packet,
&payload[NON_ESP_MARKER_LEN..],
)
} else {
decode_esp_with_registry_sa(registry, packet, payload)
}
},
);
registry.bind_udp_with_registry(
|ctx| {
is_dhcpv4_port_pair(ctx.source_port, ctx.destination_port)
&& looks_like_dhcpv4_payload(ctx.payload)
},
|_registry, packet, payload| append_dhcpv4_packet(packet, payload),
);
registry.bind_udp_with_registry(
|ctx| looks_like_dhcpv6_payload(ctx.source_port, ctx.destination_port, ctx.payload),
|_registry, packet, payload| append_dhcpv6_packet(packet, payload),
);
registry.bind_udp_with_registry(
|ctx| {
(ctx.source_port == RIP_UDP_PORT || ctx.destination_port == RIP_UDP_PORT)
&& looks_like_rip_payload(ctx.payload)
},
|_registry, packet, payload| append_rip_packet(packet, payload),
);
registry.bind_udp_with_registry(
|ctx| {
(ctx.source_port == RIPNG_UDP_PORT || ctx.destination_port == RIPNG_UDP_PORT)
&& looks_like_ripng_payload(ctx.payload)
},
|_registry, packet, payload| append_ripng_packet(packet, payload),
);
registry.bind_udp_with_registry(
|ctx| {
(ctx.source_port == NTP_PORT || ctx.destination_port == NTP_PORT)
&& looks_like_ntp_payload(ctx.payload)
},
|_registry, packet, payload| append_ntp_packet(packet, payload),
);
registry.bind_udp_with_registry(
|ctx| {
(is_snmp_udp_port(ctx.source_port) || is_snmp_udp_port(ctx.destination_port))
&& looks_like_snmp_payload(ctx.payload)
},
|_registry, packet, payload| append_snmp_packet(packet, payload),
);
registry.bind_udp_with_registry(
|ctx| {
(ctx.source_port == SSDP_UDP_PORT || ctx.destination_port == SSDP_UDP_PORT)
&& looks_like_ssdp_payload(ctx.payload)
},
|_registry, packet, payload| append_ssdp_packet(packet, payload),
);
registry.bind_tcp_port_with_registry(BGP_PORT, |registry, packet, payload| {
append_bgp_packet_with_registry(registry, packet, payload)
});
registry.bind_tcp_port_with_registry(MQTT_PORT, |registry, packet, payload| {
append_mqtt_packet_with_registry(registry, packet, payload)
});
registry.bind_tcp_with_registry(
|ctx| {
(TLS_COMMON_TCP_PORTS.contains(&ctx.source_port)
|| TLS_COMMON_TCP_PORTS.contains(&ctx.destination_port))
&& looks_like_tls_payload(ctx.payload)
},
append_tls_packet_with_registry,
);
registry.builtin_ipv4_protocol_dispatch = true;
registry.builtin_ipv6_next_header_dispatch = true;
registry.builtin_udp_application_dispatch = true;
registry
}
pub(crate) fn transport_only() -> Self {
let mut registry = Self::empty();
registry.bind_ipv4_protocol_with_registry(IPPROTO_ICMP, |_registry, packet, payload| {
append_icmp_packet(packet, payload)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_TCP, |registry, packet, payload| {
append_tcp_packet_with_registry(registry, packet, payload)
});
registry.bind_ipv4_protocol_with_registry(IPPROTO_UDP, |registry, packet, payload| {
append_udp_packet_with_registry(registry, packet, payload)
});
registry
}
#[must_use]
pub fn checksum_validation(mut self, enabled: bool) -> Self {
self.validate_checksums = enabled;
self
}
pub fn set_checksum_validation(&mut self, enabled: bool) -> &mut Self {
self.validate_checksums = enabled;
self
}
pub(crate) const fn validates_checksums(&self) -> bool {
self.validate_checksums
}
#[must_use]
pub fn application_decoding(mut self, enabled: bool) -> Self {
self.decode_applications = enabled;
self
}
pub fn set_application_decoding(&mut self, enabled: bool) -> &mut Self {
self.decode_applications = enabled;
self
}
pub(crate) const fn decodes_applications(&self) -> bool {
self.decode_applications
}
pub(crate) const fn uses_builtin_ethertype_dispatch(&self) -> bool {
self.builtin_ethertype_dispatch
}
pub fn decode_from_link(&self, link_type: LinkType, bytes: impl AsRef<[u8]>) -> Result<Packet> {
let bytes = bytes.as_ref();
match link_type {
LinkType::Raw => Packet::decode_raw(bytes),
LinkType::Ethernet => self.decode_ethernet(bytes),
LinkType::Ieee80211 => decode_dot11_with_registry(self, bytes),
LinkType::Radiotap => decode_radiotap_with_registry(self, bytes),
LinkType::BluetoothLeLl => decode_ble_ll_with_registry(self, bytes),
LinkType::Ieee802154 => decode_dot15d4_with_registry(self, bytes, false),
LinkType::Ieee802154Tap => decode_dot15d4_with_registry(self, bytes, true),
LinkType::LinuxCooked | LinkType::LinuxSll => self.decode_linux_sll(bytes),
LinkType::NullLoopback => decode_null_loopback_with_registry(self, bytes),
}
}
pub fn decode_ethernet(&self, bytes: impl AsRef<[u8]>) -> Result<Packet> {
decode_ethernet_with_registry(self, bytes.as_ref())
}
pub fn decode_linux_sll(&self, bytes: impl AsRef<[u8]>) -> Result<Packet> {
decode_linux_sll_with_registry(self, bytes.as_ref())
}
pub fn decode_from_l3(
&self,
network_layer: NetworkLayer,
bytes: impl AsRef<[u8]>,
) -> Result<Packet> {
let bytes = bytes.as_ref();
match network_layer {
NetworkLayer::Raw => Packet::decode_raw(bytes),
NetworkLayer::Ipv4 => self.decode_ipv4(bytes),
NetworkLayer::Ipv6 => self.decode_ipv6(bytes),
}
}
pub fn decode_ipv4(&self, bytes: impl AsRef<[u8]>) -> Result<Packet> {
append_ipv4_packet_with_registry(self, Packet::new(), bytes.as_ref())
}
pub fn decode_ipv6(&self, bytes: impl AsRef<[u8]>) -> Result<Packet> {
append_ipv6_packet_with_registry(self, Packet::new(), bytes.as_ref())
}
pub fn register_security_association(&mut self, sa: SecurityAssociation) -> &mut Self {
self.security_associations.push(sa);
self
}
#[must_use]
pub fn with_security_association(mut self, sa: SecurityAssociation) -> Self {
self.security_associations.push(sa);
self
}
pub(crate) fn security_association_for_spi(&self, spi: u32) -> Option<&SecurityAssociation> {
self.security_associations
.iter()
.rev()
.find(|sa| sa.spi == spi)
}
pub fn bind_ethertype<D>(&mut self, ethertype: u16, decoder: D) -> &mut Self
where
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_ethertype_if(move |ctx| ctx.ethertype == ethertype, decoder)
}
pub fn bind_ethertype_if<P, D>(&mut self, predicate: P, decoder: D) -> &mut Self
where
P: for<'a> Fn(EthertypeBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_ethertype_if_with_registry(predicate, move |_registry, packet, payload| {
decoder(packet, payload)
})
}
pub fn bind_ipv4_protocol<D>(&mut self, protocol: u8, decoder: D) -> &mut Self
where
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_ipv4_protocol_if(move |ctx| ctx.protocol == protocol, decoder)
}
pub fn bind_ipv4_protocol_if<P, D>(&mut self, predicate: P, decoder: D) -> &mut Self
where
P: for<'a> Fn(Ipv4ProtocolBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_ipv4_protocol_if_with_registry(predicate, move |_registry, packet, payload| {
decoder(packet, payload)
})
}
pub fn bind_ipv6_next_header<D>(&mut self, next_header: u8, decoder: D) -> &mut Self
where
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_ipv6_next_header_if(move |ctx| ctx.next_header == next_header, decoder)
}
pub fn bind_ipv6_next_header_if<P, D>(&mut self, predicate: P, decoder: D) -> &mut Self
where
P: for<'a> Fn(Ipv6NextHeaderBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_ipv6_next_header_if_with_registry(predicate, move |_registry, packet, payload| {
decoder(packet, payload)
})
}
pub fn bind_udp_port<D>(&mut self, port: u16, decoder: D) -> &mut Self
where
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_udp(
move |ctx| ctx.source_port == port || ctx.destination_port == port,
decoder,
)
}
pub fn bind_udp<P, D>(&mut self, predicate: P, decoder: D) -> &mut Self
where
P: for<'a> Fn(UdpBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_udp_with_registry(predicate, move |_registry, packet, payload| {
decoder(packet, payload)
})
}
pub fn bind_tcp_port<D>(&mut self, port: u16, decoder: D) -> &mut Self
where
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_tcp(
move |ctx| ctx.source_port == port || ctx.destination_port == port,
decoder,
)
}
pub fn bind_tcp<P, D>(&mut self, predicate: P, decoder: D) -> &mut Self
where
P: for<'a> Fn(TcpBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(Packet, &'a [u8]) -> Result<Packet> + Send + Sync + 'static,
{
self.bind_tcp_with_registry(predicate, move |_registry, packet, payload| {
decoder(packet, payload)
})
}
pub(crate) fn decode_ethertype(
&self,
packet: Packet,
ethertype: u16,
payload: &[u8],
) -> Result<Packet> {
if self.builtin_ethertype_dispatch {
return match ethertype {
ETHERTYPE_ARP => append_arp_packet(packet, payload),
ETHERTYPE_VLAN => append_vlan_packet_with_registry(self, packet, payload),
ETHERTYPE_IPV4 => append_ipv4_packet_with_registry(self, packet, payload),
ETHERTYPE_IPV6 => append_ipv6_packet_with_registry(self, packet, payload),
ETHERTYPE_EAPOL => append_eapol_packet(packet, payload),
_ => Ok(packet.push_raw(Raw::from_bytes(payload))),
};
}
let ctx = EthertypeBindingContext { ethertype, payload };
if let Some(binding) = self
.ethertype_bindings
.iter()
.rev()
.find(|binding| (binding.predicate)(ctx))
{
return (binding.decoder)(self, packet, payload);
}
Ok(packet.push_raw(Raw::from_bytes(payload)))
}
pub(crate) fn decode_ipv4_protocol(
&self,
packet: Packet,
protocol: u8,
payload: &[u8],
) -> Result<Packet> {
if self.builtin_ipv4_protocol_dispatch {
return match protocol {
IPPROTO_ICMP => append_icmp_packet_with_checksum_validation(
packet,
payload,
self.validates_checksums(),
),
IPPROTO_TCP => append_tcp_packet_with_registry(self, packet, payload),
IPPROTO_UDP => append_udp_packet_with_registry(self, packet, payload),
IPPROTO_IGMP => append_igmp_packet(packet, payload),
IPPROTO_ESP => decode_esp_with_registry_sa(self, packet, payload),
IPPROTO_AH => decode_ah_with_registry_sa(self, packet, payload),
IPPROTO_OSPF => append_ospf_packet_with_checksum_validation(
packet,
payload,
self.validates_checksums(),
),
_ => append_raw_if_needed(packet, payload),
};
}
let ctx = Ipv4ProtocolBindingContext { protocol, payload };
if let Some(binding) = self
.ipv4_bindings
.iter()
.rev()
.find(|binding| (binding.predicate)(ctx))
{
return (binding.decoder)(self, packet, payload);
}
append_raw_if_needed(packet, payload)
}
pub(crate) fn decode_ipv6_next_header(
&self,
packet: Packet,
next_header: u8,
payload: &[u8],
) -> Result<Packet> {
if self.builtin_ipv6_next_header_dispatch {
return match next_header {
IPPROTO_ICMPV6 => append_icmpv6_packet(packet, payload),
IPPROTO_TCP => append_tcp_packet_with_registry(self, packet, payload),
IPPROTO_UDP => append_udp_packet_with_registry(self, packet, payload),
IPPROTO_IPV6_ESP => decode_esp_with_registry_sa(self, packet, payload),
IPPROTO_IPV6_AH => decode_ah_with_registry_sa(self, packet, payload),
IPPROTO_OSPF => append_ospfv3_packet_with_checksum_validation(
packet,
payload,
self.validates_checksums(),
),
_ => append_raw_if_needed(packet, payload),
};
}
let ctx = Ipv6NextHeaderBindingContext {
next_header,
payload,
};
if let Some(binding) = self
.ipv6_bindings
.iter()
.rev()
.find(|binding| (binding.predicate)(ctx))
{
return (binding.decoder)(self, packet, payload);
}
append_raw_if_needed(packet, payload)
}
pub(crate) fn decode_udp_application(
&self,
packet: Packet,
source_port: u16,
destination_port: u16,
payload: &[u8],
) -> Result<Packet> {
if !self.decode_applications {
return append_raw_if_needed(packet, payload);
}
if self.builtin_udp_application_dispatch {
if is_dhcpv4_port_pair(source_port, destination_port)
&& looks_like_dhcpv4_payload(payload)
{
return append_dhcpv4_packet(packet, payload);
}
if looks_like_dhcpv6_payload(source_port, destination_port, payload) {
return append_dhcpv6_packet(packet, payload);
}
if (source_port == RIP_UDP_PORT || destination_port == RIP_UDP_PORT)
&& looks_like_rip_payload(payload)
{
return append_rip_packet(packet, payload);
}
if (source_port == RIPNG_UDP_PORT || destination_port == RIPNG_UDP_PORT)
&& looks_like_ripng_payload(payload)
{
return append_ripng_packet(packet, payload);
}
if (source_port == NTP_PORT || destination_port == NTP_PORT)
&& looks_like_ntp_payload(payload)
{
return append_ntp_packet(packet, payload);
}
if (is_snmp_udp_port(source_port) || is_snmp_udp_port(destination_port))
&& looks_like_snmp_payload(payload)
{
return append_snmp_packet(packet, payload);
}
if (source_port == SSDP_UDP_PORT || destination_port == SSDP_UDP_PORT)
&& looks_like_ssdp_payload(payload)
{
return append_ssdp_packet(packet, payload);
}
if (source_port == NATT_UDP_PORT || destination_port == NATT_UDP_PORT)
&& payload.len() >= ESP_HEADER_LEN
{
if is_non_esp_marker(payload) {
let marker =
NatTraversal::marker().bytes(payload[..NON_ESP_MARKER_LEN].to_vec());
let packet = packet.push(marker);
return append_ikev2_packet_with_registry(
self,
packet,
&payload[NON_ESP_MARKER_LEN..],
);
}
return decode_esp_with_registry_sa(self, packet, payload);
}
if source_port == IKEV2_UDP_PORT || destination_port == IKEV2_UDP_PORT {
return append_ikev2_packet_with_registry(self, packet, payload);
}
if source_port == DNS_PORT
|| destination_port == DNS_PORT
|| source_port == MDNS_PORT
|| destination_port == MDNS_PORT
{
return append_dns_packet(packet, payload);
}
if is_quic_default_port_pair(source_port, destination_port)
&& looks_like_quic_udp_payload(payload)
{
return append_quic_packet(packet, payload);
}
return append_raw_if_needed(packet, payload);
}
let ctx = UdpBindingContext {
source_port,
destination_port,
payload,
};
if let Some(binding) = self
.udp_bindings
.iter()
.rev()
.find(|binding| (binding.predicate)(ctx))
{
return (binding.decoder)(self, packet, payload);
}
append_raw_if_needed(packet, payload)
}
pub(crate) fn decode_tcp_application(
&self,
packet: Packet,
source_port: u16,
destination_port: u16,
payload: &[u8],
) -> Result<Packet> {
if !self.decode_applications {
return append_raw_if_needed(packet, payload);
}
let ctx = TcpBindingContext {
source_port,
destination_port,
payload,
};
if let Some(binding) = self
.tcp_bindings
.iter()
.rev()
.find(|binding| (binding.predicate)(ctx))
{
return (binding.decoder)(self, packet, payload);
}
append_raw_if_needed(packet, payload)
}
pub(crate) fn bind_ethertype_with_registry<D>(
&mut self,
ethertype: u16,
decoder: D,
) -> &mut Self
where
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.bind_ethertype_if_with_registry(move |ctx| ctx.ethertype == ethertype, decoder)
}
pub(crate) fn bind_ethertype_if_with_registry<P, D>(
&mut self,
predicate: P,
decoder: D,
) -> &mut Self
where
P: for<'a> Fn(EthertypeBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.builtin_ethertype_dispatch = false;
self.ethertype_bindings.push(EthertypeBinding {
predicate: Box::new(predicate),
decoder: Box::new(decoder),
});
self
}
pub(crate) fn bind_ipv4_protocol_with_registry<D>(
&mut self,
protocol: u8,
decoder: D,
) -> &mut Self
where
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.bind_ipv4_protocol_if_with_registry(move |ctx| ctx.protocol == protocol, decoder)
}
pub(crate) fn bind_ipv4_protocol_if_with_registry<P, D>(
&mut self,
predicate: P,
decoder: D,
) -> &mut Self
where
P: for<'a> Fn(Ipv4ProtocolBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.ipv4_bindings.push(Ipv4ProtocolBinding {
predicate: Box::new(predicate),
decoder: Box::new(decoder),
});
self.builtin_ipv4_protocol_dispatch = false;
self
}
pub(crate) fn bind_ipv6_next_header_with_registry<D>(
&mut self,
next_header: u8,
decoder: D,
) -> &mut Self
where
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.bind_ipv6_next_header_if_with_registry(
move |ctx| ctx.next_header == next_header,
decoder,
)
}
pub(crate) fn bind_ipv6_next_header_if_with_registry<P, D>(
&mut self,
predicate: P,
decoder: D,
) -> &mut Self
where
P: for<'a> Fn(Ipv6NextHeaderBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.ipv6_bindings.push(Ipv6NextHeaderBinding {
predicate: Box::new(predicate),
decoder: Box::new(decoder),
});
self.builtin_ipv6_next_header_dispatch = false;
self
}
pub(crate) fn bind_udp_port_with_registry<D>(&mut self, port: u16, decoder: D) -> &mut Self
where
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.bind_udp_with_registry(
move |ctx| ctx.source_port == port || ctx.destination_port == port,
decoder,
)
}
pub(crate) fn bind_udp_with_registry<P, D>(&mut self, predicate: P, decoder: D) -> &mut Self
where
P: for<'a> Fn(UdpBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.udp_bindings.push(UdpBinding {
predicate: Box::new(predicate),
decoder: Box::new(decoder),
});
self.builtin_udp_application_dispatch = false;
self
}
pub(crate) fn bind_tcp_port_with_registry<D>(&mut self, port: u16, decoder: D) -> &mut Self
where
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.bind_tcp_with_registry(
move |ctx| ctx.source_port == port || ctx.destination_port == port,
decoder,
)
}
pub(crate) fn bind_tcp_with_registry<P, D>(&mut self, predicate: P, decoder: D) -> &mut Self
where
P: for<'a> Fn(TcpBindingContext<'a>) -> bool + Send + Sync + 'static,
D: for<'a> Fn(&'a ProtocolRegistry, Packet, &'a [u8]) -> Result<Packet>
+ Send
+ Sync
+ 'static,
{
self.tcp_bindings.push(TcpBinding {
predicate: Box::new(predicate),
decoder: Box::new(decoder),
});
self
}
}
fn is_quic_default_port_pair(source_port: u16, destination_port: u16) -> bool {
matches!(source_port, QUIC_HTTPS_UDP_PORT | QUIC_EXAMPLE_UDP_PORT)
|| matches!(
destination_port,
QUIC_HTTPS_UDP_PORT | QUIC_EXAMPLE_UDP_PORT
)
}
impl Default for ProtocolRegistry {
fn default() -> Self {
Self::with_builtin_bindings()
}
}
fn append_raw_if_needed(packet: Packet, payload: &[u8]) -> Result<Packet> {
if payload.is_empty() {
Ok(packet)
} else {
Ok(packet.push_raw(Raw::from_bytes(payload)))
}
}
fn decode_esp_with_registry_sa(
registry: &ProtocolRegistry,
packet: Packet,
payload: &[u8],
) -> Result<Packet> {
let sa = read_u32_be(payload.get(0..4).unwrap_or(payload))
.ok()
.and_then(|spi| registry.security_association_for_spi(spi));
append_esp_packet_with_registry_sa(registry, packet, payload, sa)
}
fn decode_ah_with_registry_sa(
registry: &ProtocolRegistry,
packet: Packet,
payload: &[u8],
) -> Result<Packet> {
let sa = payload
.get(4..8)
.and_then(|spi_bytes| read_u32_be(spi_bytes).ok())
.and_then(|spi| registry.security_association_for_spi(spi));
append_ah_packet_with_registry_sa(registry, packet, payload, sa)
}
#[cfg(test)]
mod protocol_registry {
use super::ProtocolRegistry;
use crate::protocols::igmp::Igmp;
use crate::protocols::ip::shared::IPPROTO_IGMP;
use crate::{Ipv4, Ipv4ChecksumStatus, NetworkLayer, Packet, Raw, Udp, UdpChecksumStatus};
#[test]
fn custom_ipv4_protocol_binding_decodes_without_global_state() {
let mut registry = ProtocolRegistry::empty();
registry.bind_ipv4_protocol(253, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let bytes = (Ipv4::new().protocol(253) / Raw::from("agent-proto"))
.compile()
.unwrap();
let decoded = registry
.decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), b"agent-proto");
assert!(Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap()
.layer::<Raw>()
.is_some());
}
#[test]
fn later_bindings_override_builtins_for_that_registry_only() {
let mut registry = ProtocolRegistry::new();
registry.bind_udp_port(crate::DNS_PORT, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let bytes = (Ipv4::new()
/ crate::Udp::new().sport(53001).dport(crate::DNS_PORT)
/ crate::Dns::a_query("example.com."))
.compile()
.unwrap();
let custom = registry
.decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
let builtin = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(custom.layer::<crate::Dns>().is_none());
assert!(custom.layer::<Raw>().is_some());
assert!(builtin.layer::<crate::Dns>().is_some());
}
#[test]
fn registry_can_skip_application_decoding() {
let bytes = (Ipv4::new()
/ crate::Udp::new().sport(53001).dport(crate::DNS_PORT)
/ crate::Dns::a_query("example.com."))
.compile()
.unwrap();
let registry = ProtocolRegistry::new().application_decoding(false);
let decoded = registry
.decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert!(decoded.layer::<crate::Udp>().is_some());
assert!(decoded.layer::<crate::Dns>().is_none());
assert!(decoded.layer::<Raw>().is_some());
assert!(Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap()
.layer::<crate::Dns>()
.is_some());
}
#[test]
fn default_registry_decodes_ospf_over_ipv4() {
use core::net::Ipv4Addr;
use crate::protocols::ospf::decode::{
append_ospf_packet, append_ospf_packet_with_checksum_validation,
};
use crate::protocols::ospf::Ospfv2;
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ Ospfv2::hello())
.compile()
.expect("Ipv4 / Ospfv2 Hello compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.expect("the default registry decodes OSPF over IPv4");
assert!(
decoded.layer::<Ospfv2>().is_some(),
"the decoded packet exposes a typed Ospfv2 layer"
);
let recompiled = decoded.compile().expect("decoded OSPF re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
let ipv4_header_len = (bytes.as_bytes()[0] & 0x0f) as usize * 4;
let ospf_payload = &bytes.as_bytes()[ipv4_header_len..];
assert!(append_ospf_packet(Packet::new(), ospf_payload)
.expect("append_ospf_packet decodes the payload")
.layer::<Ospfv2>()
.is_some());
assert!(
append_ospf_packet_with_checksum_validation(Packet::new(), ospf_payload, false)
.expect("append_ospf_packet_with_checksum_validation decodes the payload")
.layer::<Ospfv2>()
.is_some()
);
}
#[test]
fn registry_can_skip_decode_checksum_validation() {
let bytes = (Ipv4::new() / Udp::new().sport(53001).dport(9000) / Raw::from("payload"))
.compile()
.unwrap();
let registry = ProtocolRegistry::new().checksum_validation(false);
let decoded = registry
.decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert_eq!(
decoded.layer::<Ipv4>().unwrap().checksum_status(),
Ipv4ChecksumStatus::NotChecked
);
assert_eq!(
decoded.layer::<Udp>().unwrap().checksum_status(),
UdpChecksumStatus::NotChecked
);
assert_eq!(
Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap()
.layer::<Udp>()
.unwrap()
.checksum_status(),
UdpChecksumStatus::Valid
);
}
#[test]
fn igmp_registry_binding_default_l3_decode_produces_ipv4_igmp() {
let bytes = (Ipv4::new().protocol(IPPROTO_IGMP) / Igmp::membership_query())
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert_eq!(decoded.len(), 2);
assert!(decoded
.get(0)
.is_some_and(|layer| layer.as_any().is::<Ipv4>()));
assert!(decoded
.get(1)
.is_some_and(|layer| layer.as_any().is::<Igmp>()));
assert!(decoded.layer::<Raw>().is_none());
}
#[test]
fn igmp_registry_binding_empty_registry_preserves_payload_as_raw() {
let bytes = (Ipv4::new().protocol(IPPROTO_IGMP) / Igmp::membership_query())
.compile()
.unwrap();
let registry = ProtocolRegistry::empty();
let decoded = registry
.decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
let raw = decoded
.layer::<Raw>()
.expect("empty registry raw IGMP payload");
assert_eq!(decoded.len(), 2);
assert!(decoded.layer::<Igmp>().is_none());
assert_eq!(raw.as_bytes(), &bytes.as_bytes()[20..]);
}
}
#[cfg(test)]
mod mdns_udp_5353_decode {
use core::net::{Ipv4Addr, Ipv6Addr};
use super::ProtocolRegistry;
use crate::{
CrafterError, Dns, Ipv4, Ipv6, NetworkLayer, Packet, Raw, Udp, DNS_PORT, DNS_TYPE_A,
MDNS_IPV4_MULTICAST, MDNS_IPV6_LINK_LOCAL_MULTICAST, MDNS_PORT,
};
fn query() -> Dns {
Dns::mdns_query_for("printer.local.", DNS_TYPE_A).id(0x1234)
}
fn dns_payload(dns: Dns) -> Vec<u8> {
Packet::from_layer(dns).compile().unwrap().into_bytes()
}
#[test]
fn ipv4_destination_port_decodes_as_dns() {
let dns = query();
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(MDNS_IPV4_MULTICAST)
/ Udp::new().sport(49_152).dport(MDNS_PORT)
/ dns.clone())
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
let decoded_dns = decoded.layer::<Dns>().expect("mDNS DNS layer");
assert_eq!(decoded_dns.id_value(), 0x1234);
assert_eq!(decoded_dns.questions(), dns.questions());
assert!(decoded.layer::<Raw>().is_none());
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_bytes());
}
#[test]
fn ipv6_source_port_decodes_as_dns() {
let dns = query();
let bytes = (Ipv6::new()
.src(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 10))
.dst(MDNS_IPV6_LINK_LOCAL_MULTICAST)
/ Udp::new().sport(MDNS_PORT).dport(49_153)
/ dns.clone())
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes()).unwrap();
let decoded_dns = decoded.layer::<Dns>().expect("mDNS DNS layer");
assert_eq!(decoded_dns.id_value(), 0x1234);
assert_eq!(decoded_dns.questions(), dns.questions());
assert!(decoded.layer::<Raw>().is_none());
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_bytes());
}
#[test]
fn udp_53_dns_regression_still_decodes() {
let dns = Dns::a_query("example.com.").id(0xbeef);
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 10))
/ Udp::new().sport(49_152).dport(DNS_PORT)
/ dns.clone())
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
let decoded_dns = decoded.layer::<Dns>().expect("DNS layer");
assert_eq!(decoded_dns.id_value(), 0xbeef);
assert_eq!(decoded_dns.questions(), dns.questions());
assert!(decoded.layer::<Raw>().is_none());
}
#[test]
fn disabled_application_decoding_preserves_mdns_payload_as_raw() {
let dns = query();
let payload = dns_payload(dns.clone());
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(MDNS_IPV4_MULTICAST)
/ Udp::new().sport(49_152).dport(MDNS_PORT)
/ dns)
.compile()
.unwrap();
let registry = ProtocolRegistry::new().application_decoding(false);
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert!(decoded.layer::<Udp>().is_some());
assert!(decoded.layer::<Dns>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
#[test]
fn malformed_port_5353_payload_reports_structured_dns_error() {
let payload = [0xde, 0xad, 0xbe, 0xef];
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(MDNS_IPV4_MULTICAST)
/ Udp::new().sport(MDNS_PORT).dport(49_152)
/ Raw::from_bytes(payload))
.compile()
.unwrap();
match Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()) {
Err(CrafterError::BufferTooShort {
context,
required,
available,
}) => {
assert_eq!(context, "dns header");
assert_eq!(required, 12);
assert_eq!(available, payload.len());
}
other => panic!("expected structured DNS header error, got {other:?}"),
}
}
#[test]
fn unrelated_udp_ports_preserve_non_dns_payload_as_raw() {
let payload = [0xde, 0xad, 0xbe, 0xef];
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 10))
/ Udp::new().sport(49_152).dport(49_153)
/ Raw::from_bytes(payload))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Dns>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
}
#[cfg(test)]
mod decode_dispatch {
use super::ProtocolRegistry;
use crate::{Ethernet, Ipv4, Ipv4Protocol, LinkType, NetworkLayer, Packet, Raw, Udp};
#[test]
fn default_registry_dispatches_from_ethernet_to_ipv4_udp() {
let bytes = (Ethernet::new()
/ Ipv4::new().ipv4_protocol(Ipv4Protocol::Udp)
/ Udp::new().sport(53002).dport(9999)
/ Raw::from("payload"))
.compile()
.unwrap();
let decoded = Packet::decode_from_link(LinkType::Ethernet, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Ethernet>().is_some());
assert!(decoded.layer::<Ipv4>().is_some());
assert!(decoded.layer::<Udp>().is_some());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), b"payload");
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_bytes());
}
#[test]
fn registry_ble_decode_from_link_builds_radio_and_advertising_layers() {
let frame = [
37, 0xc4, 0x00, 0x00, 0xd6, 0xbe, 0x89, 0x8e, 0x13, 0x0c, 0xd6, 0xbe, 0x89, 0x8e, 0x40,
0x0f, 0x01, 0x53, 0x00, 0x5e, 0x00, 0x02, 0x02, 0x01, 0x06, 0x05, 0x09, b't', b'e',
b's', b't',
];
let decoded = ProtocolRegistry::new()
.decode_from_link(LinkType::BluetoothLeLl, frame)
.unwrap();
assert_eq!(decoded.iter().count(), 2);
let radio = decoded.get(0).unwrap();
let adv = decoded.get(1).unwrap();
assert_eq!(radio.name(), "BleRadio");
assert!(radio.summary().contains("ch=37"));
assert!(radio.summary().contains("aa=0x8e89bed6"));
assert_eq!(adv.name(), "BleLlAdv");
assert_eq!(
adv.summary(),
"BleLlAdv(ADV_IND, AdvA=02:00:5E:00:53:01, len=15)"
);
assert!(decoded.layer::<Raw>().is_none());
}
#[test]
fn explicit_registry_decodes_ipv4_and_ipv6_custom_protocols() {
let mut registry = ProtocolRegistry::new();
registry.bind_ipv4_protocol(254, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
registry.bind_ipv6_next_header(253, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let ipv4_bytes = (Ipv4::new().protocol(254) / Raw::from("v4-private"))
.compile()
.unwrap();
let ipv6_bytes = (crate::Ipv6::new().nh(253) / Raw::from("v6-private"))
.compile()
.unwrap();
let ipv4 = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, ipv4_bytes)
.unwrap();
let ipv6 = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv6, ipv6_bytes)
.unwrap();
assert_eq!(ipv4.layer::<Raw>().unwrap().as_bytes(), b"v4-private");
assert_eq!(ipv6.layer::<Raw>().unwrap().as_bytes(), b"v6-private");
}
}
#[cfg(test)]
mod registry_dot15d4 {
use crate::protocols::link::{Dot15d4, Dot15d4Radio, ZigbeeAps, ZigbeeNwk};
use crate::{LinkType, Packet, Raw};
fn full_stack_packet() -> Packet {
Dot15d4Radio::on_channel(20).rssi(-55)
/ Dot15d4::data()
.seq(9)
.dest_short(0x1234, 0x0000)
.src_short(0x1234, 0xABCD)
/ ZigbeeNwk::data().dest(0x0000).src(0xABCD).radius(30).seq(5)
/ ZigbeeAps::data()
.cluster(0x0006)
.profile(0x0104)
.dest_endpoint(1)
.src_endpoint(1)
.counter(7)
.payload(&[0x01, 0x02])
}
#[test]
fn decode_from_link_tap_routes_to_dot15d4_entrypoint() {
let compiled = full_stack_packet()
.compile()
.expect("compile TAP + MAC + NWK + APS stack");
let decoded = Packet::decode_from_link(LinkType::Ieee802154Tap, compiled.as_bytes())
.expect("decode TAP 802.15.4 frame via decode_from_link");
assert!(
decoded.layer::<Dot15d4Radio>().is_some(),
"Dot15d4Radio layer present"
);
assert!(
decoded.layer::<Dot15d4>().is_some(),
"Dot15d4 MAC layer present"
);
assert!(
decoded.layer::<ZigbeeNwk>().is_some(),
"ZigbeeNwk layer present"
);
assert!(
decoded.layer::<ZigbeeAps>().is_some(),
"ZigbeeAps layer present"
);
assert_eq!(
decoded
.layer::<Raw>()
.expect("APS payload preserved as Raw")
.as_bytes(),
&[0x01, 0x02]
);
}
#[test]
fn decode_from_link_bare_mac_skips_radio_descriptor() {
let mac_only = (Dot15d4::data()
.seq(9)
.dest_short(0x1234, 0x0000)
.src_short(0x1234, 0xABCD)
/ ZigbeeNwk::data().dest(0x0000).src(0xABCD).radius(30).seq(5)
/ ZigbeeAps::data()
.cluster(0x0006)
.profile(0x0104)
.dest_endpoint(1)
.src_endpoint(1)
.counter(7)
.payload(&[0x01, 0x02]))
.compile()
.expect("compile bare MAC + NWK + APS frame");
let decoded = Packet::decode_from_link(LinkType::Ieee802154, mac_only.as_bytes())
.expect("decode bare 802.15.4 frame via decode_from_link");
assert!(
decoded.layer::<Dot15d4Radio>().is_none(),
"no radio descriptor for bare frame"
);
assert!(
decoded.layer::<Dot15d4>().is_some(),
"Dot15d4 MAC layer present"
);
assert!(
decoded.layer::<ZigbeeNwk>().is_some(),
"ZigbeeNwk layer present"
);
assert!(
decoded.layer::<ZigbeeAps>().is_some(),
"ZigbeeAps layer present"
);
}
#[test]
fn decode_from_link_truncated_buffer_is_structured_error() {
let err = Packet::decode_from_link(LinkType::Ieee802154, [0x01u8])
.expect_err("truncated 802.15.4 buffer must error");
let rendered = err.to_string();
assert!(
!rendered.is_empty(),
"structured error renders a non-empty message: {rendered}"
);
assert!(
Packet::decode_from_link(LinkType::Ieee802154Tap, [0x00u8, 0x00]).is_err(),
"truncated TAP pseudo-header must error",
);
}
}
#[cfg(test)]
mod esp_protocol_binding {
use crate::protocols::ipsec::esp::Esp;
use crate::protocols::ipv4::IPPROTO_ESP;
use crate::protocols::ipv6::IPPROTO_IPV6_ESP;
use crate::{Ipv4, Ipv6, NetworkLayer, Packet, Raw};
#[test]
fn default_registry_decodes_ipv4_protocol_50_as_opaque_esp() {
let bytes = (Ipv4::new().protocol(IPPROTO_ESP)
/ Esp::new().spi(0x0000_2000).sequence(7)
/ Raw::from_bytes(vec![0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04]))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Ipv4>().is_some());
let esp = decoded
.get(1)
.unwrap()
.as_any()
.downcast_ref::<Esp>()
.expect("second layer is Esp");
assert_eq!(esp.spi_value(), Some(0x0000_2000));
assert_eq!(esp.sequence_value(), Some(7));
assert!(esp.opaque_body().is_some());
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_bytes());
}
#[test]
fn default_registry_decodes_ipv6_next_header_50_as_opaque_esp() {
let bytes = (Ipv6::new().nh(IPPROTO_IPV6_ESP)
/ Esp::new().spi(0x0000_3000).sequence(9)
/ Raw::from_bytes(vec![0xAA, 0xBB, 0xCC, 0xDD, 0x10, 0x20, 0x30, 0x40]))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Ipv6>().is_some());
let esp = decoded
.get(1)
.unwrap()
.as_any()
.downcast_ref::<Esp>()
.expect("second layer is Esp");
assert_eq!(esp.spi_value(), Some(0x0000_3000));
assert_eq!(esp.sequence_value(), Some(9));
assert!(esp.opaque_body().is_some());
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_bytes());
}
}
#[cfg(test)]
mod ah_protocol_binding {
use crate::protocols::ipsec::ah::Ah;
use crate::protocols::ipsec::sa::{IntegrityAlgorithm, SecurityAssociation};
use crate::protocols::ipv4::{IPPROTO_AH, IPPROTO_TCP};
use crate::protocols::ipv6::IPPROTO_IPV6_AH;
use crate::{Ipv4, Ipv6, NetworkLayer, Packet, Raw, Tcp};
fn ah_sa() -> SecurityAssociation {
SecurityAssociation::new(0x0000_2000)
.integrity(IntegrityAlgorithm::HmacSha2_256_128, vec![0x77u8; 32])
}
#[test]
fn default_registry_decodes_ipv4_protocol_51_as_ah_with_inner_tcp() {
let bytes = (Ipv4::new()
.protocol(IPPROTO_AH)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap())
.ttl(64)
/ Ah::secured(ah_sa()).spi(0x0000_2000).sequence(1)
/ Tcp::new().sport(1234).dport(443)
/ Raw::from_bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Ipv4>().is_some());
let ah = decoded
.get(1)
.unwrap()
.as_any()
.downcast_ref::<Ah>()
.expect("second layer is Ah");
assert_eq!(ah.spi_value(), Some(0x0000_2000));
assert_eq!(ah.sequence_value(), Some(1));
assert_eq!(ah.next_header_value(), Some(IPPROTO_TCP));
assert_eq!(ah.verification_status(), None);
let tcp = decoded
.layer::<Tcp>()
.expect("inner Tcp decoded in the clear");
assert_eq!(tcp.source_port_value(), 1234);
assert_eq!(tcp.destination_port_value(), 443);
assert_eq!(
decoded
.layer::<Raw>()
.expect("inner Raw decoded")
.as_bytes(),
&[0xDE, 0xAD, 0xBE, 0xEF]
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_bytes());
}
#[test]
fn default_registry_decodes_ipv6_next_header_51_as_ah_with_inner_tcp() {
let bytes = (Ipv6::new()
.nh(IPPROTO_IPV6_AH)
.src("2001:db8::1".parse().unwrap())
.dst("2001:db8::2".parse().unwrap())
.hop_limit(64)
/ Ah::secured(ah_sa()).spi(0x0000_3000).sequence(9)
/ Tcp::new().sport(2345).dport(80)
/ Raw::from_bytes(vec![0xAA, 0xBB, 0xCC, 0xDD]))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Ipv6>().is_some());
let ah = decoded
.get(1)
.unwrap()
.as_any()
.downcast_ref::<Ah>()
.expect("second layer is Ah");
assert_eq!(ah.spi_value(), Some(0x0000_3000));
assert_eq!(ah.sequence_value(), Some(9));
assert_eq!(ah.next_header_value(), Some(IPPROTO_TCP));
assert_eq!(ah.verification_status(), None);
let tcp = decoded
.layer::<Tcp>()
.expect("inner Tcp decoded in the clear");
assert_eq!(tcp.source_port_value(), 2345);
assert_eq!(tcp.destination_port_value(), 80);
assert_eq!(
decoded
.layer::<Raw>()
.expect("inner Raw decoded")
.as_bytes(),
&[0xAA, 0xBB, 0xCC, 0xDD]
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_bytes());
}
#[test]
fn ah_in_ipv6_decodes_after_a_preceding_extension_header() {
use crate::protocols::ipv6::Ipv6DestinationOptionsHeader;
use crate::Ipv6Option;
let direct = (Ipv6::new()
.nh(IPPROTO_IPV6_AH)
.src("2001:db8::1".parse().unwrap())
.dst("2001:db8::2".parse().unwrap())
.hop_limit(64)
/ Ah::secured(ah_sa()).spi(0x0000_4000).sequence(3)
/ Tcp::new().sport(3456).dport(8080)
/ Raw::from_bytes(vec![0x11, 0x22, 0x33, 0x44]))
.compile()
.unwrap();
let ah_datagram = direct.as_bytes()[40..].to_vec();
let bytes = (Ipv6::new()
.src("2001:db8::1".parse().unwrap())
.dst("2001:db8::2".parse().unwrap())
.hop_limit(64)
/ Ipv6DestinationOptionsHeader::new()
.nh(IPPROTO_IPV6_AH)
.option(Ipv6Option::pad1())
/ Raw::from_bytes(ah_datagram))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes()).unwrap();
let ah = decoded
.layer::<Ah>()
.expect("Ah decoded after the extension header");
assert_eq!(ah.spi_value(), Some(0x0000_4000));
assert_eq!(ah.next_header_value(), Some(IPPROTO_TCP));
let tcp = decoded
.layer::<Tcp>()
.expect("inner Tcp decoded in the clear");
assert_eq!(tcp.source_port_value(), 3456);
assert_eq!(tcp.destination_port_value(), 8080);
}
}
#[cfg(test)]
mod dns_udp_binding {
use super::ProtocolRegistry;
use crate::{
Dns, DnsQuestion, Ipv4, NetworkLayer, Packet, Raw, Udp, DNS_PORT, DNS_TYPE_AAAA, MDNS_PORT,
};
#[test]
fn default_registry_decodes_dns_on_udp_53() {
let bytes = (Ipv4::new()
/ Udp::new().sport(53001).dport(DNS_PORT)
/ Dns::new().question(DnsQuestion::new("example.org.", DNS_TYPE_AAAA)))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
let dns = decoded.layer::<Dns>().unwrap();
assert_eq!(dns.questions()[0].name(), "example.org.");
assert_eq!(dns.questions()[0].question_type(), DNS_TYPE_AAAA);
}
#[test]
fn custom_udp_port_binding_decodes_application_payload() {
let mut registry = ProtocolRegistry::new();
registry.bind_udp_port(MDNS_PORT, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let bytes =
(Ipv4::new() / Udp::new().sport(MDNS_PORT).dport(50000) / Raw::from("custom-dns-like"))
.compile()
.unwrap();
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert_eq!(
decoded.layer::<Raw>().unwrap().as_bytes(),
b"custom-dns-like"
);
}
}
#[cfg(test)]
mod ntp_udp_registry {
use super::ProtocolRegistry;
use crate::{Ipv4, NetworkLayer, Ntp, NtpMode, Packet, Raw, Udp, NTP_PORT};
fn udp_ipv4_packet(source_port: u16, destination_port: u16, payload: impl Into<Raw>) -> Packet {
Ipv4::new() / Udp::new().sport(source_port).dport(destination_port) / payload.into()
}
#[test]
fn ntp_udp_registry_decodes_destination_port_123_payloads() {
let bytes = (Ipv4::new() / Udp::new().sport(49_152).dport(NTP_PORT) / Ntp::client())
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
let ntp = decoded.layer::<Ntp>().expect("NTP layer");
assert_eq!(ntp.first_octet_value(), 0x23);
assert_eq!(ntp.mode_value(), NtpMode::Client);
assert!(decoded.layer::<Raw>().is_none());
}
#[test]
fn ntp_udp_registry_preserves_non_ntp_payloads_on_port_123_as_raw() {
let payload = [0x23, 0x01, 0x02, 0x03, 0xde, 0xad];
let bytes = udp_ipv4_packet(49_152, NTP_PORT, Raw::from_bytes(payload))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Ntp>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
#[test]
fn ntp_udp_registry_respects_application_decoding_toggle() {
let registry = ProtocolRegistry::new().application_decoding(false);
let ntp_payload = Packet::from_layer(Ntp::client()).compile().unwrap();
let bytes = udp_ipv4_packet(NTP_PORT, 49_152, Raw::from_bytes(ntp_payload.as_bytes()))
.compile()
.unwrap();
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert!(decoded.layer::<Ntp>().is_none());
assert_eq!(
decoded.layer::<Raw>().unwrap().as_bytes(),
ntp_payload.as_bytes()
);
}
}
#[cfg(test)]
mod snmp_udp_decode {
use std::net::{Ipv4Addr, Ipv6Addr};
use crate::{
Ipv4, Ipv6, NetworkLayer, Packet, Raw, Snmp, SnmpOid, SnmpPdu, SnmpVarBindList,
SnmpVersion, Udp, SNMP_PORT, SNMP_TRAP_PORT,
};
#[test]
fn snmp_udp_decode_decodes_ipv4_and_ipv6_stacks() {
let ipv4_snmp = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 66))
.dst(Ipv4Addr::new(198, 51, 100, 66))
/ Udp::new().sport(49_152).dport(SNMP_PORT)
/ Snmp::v1_get_request(b"public".to_vec(), 1, SnmpVarBindList::empty()).unwrap();
let ipv4_decoded =
Packet::decode_from_l3(NetworkLayer::Ipv4, ipv4_snmp.compile().unwrap().as_bytes())
.unwrap();
let snmp = ipv4_decoded.layer::<Snmp>().expect("IPv4 SNMP layer");
assert_eq!(snmp.version(), SnmpVersion::V1);
assert!(ipv4_decoded.layer::<Raw>().is_none());
let ipv6_snmp = Ipv6::new()
.src("2001:db8::66".parse::<Ipv6Addr>().unwrap())
.dst("2001:db8::67".parse::<Ipv6Addr>().unwrap())
/ Udp::new().sport(SNMP_TRAP_PORT).dport(49_153)
/ Snmp::v2c_snmpv2_trap(b"public".to_vec(), 2, SnmpVarBindList::empty()).unwrap();
let ipv6_decoded =
Packet::decode_from_l3(NetworkLayer::Ipv6, ipv6_snmp.compile().unwrap().as_bytes())
.unwrap();
let snmp = ipv6_decoded.layer::<Snmp>().expect("IPv6 SNMP layer");
assert_eq!(snmp.version(), SnmpVersion::V2c);
assert_eq!(snmp.pdu().tag_number(), SnmpPdu::TAG_TRAP_V2);
assert!(ipv6_decoded.layer::<Raw>().is_none());
}
#[test]
fn snmp_udp_decode_preserves_non_snmp_bytes_on_snmp_ports_as_raw() {
let raw_payload = b"not-snmp".to_vec();
let ipv4_raw = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 66))
.dst(Ipv4Addr::new(198, 51, 100, 66))
/ Udp::new().sport(49_152).dport(SNMP_PORT)
/ Raw::from_bytes(raw_payload.clone());
let ipv4_decoded =
Packet::decode_from_l3(NetworkLayer::Ipv4, ipv4_raw.compile().unwrap().as_bytes())
.unwrap();
assert!(ipv4_decoded.layer::<Snmp>().is_none());
assert_eq!(
ipv4_decoded
.layer::<Raw>()
.expect("raw IPv4 payload")
.as_bytes(),
raw_payload
);
let invalid_sequence = [0x30, 0x03, 0x02, 0x01, 0x00];
let ipv6_raw = Ipv6::new()
.src("2001:db8::66".parse::<Ipv6Addr>().unwrap())
.dst("2001:db8::67".parse::<Ipv6Addr>().unwrap())
/ Udp::new().sport(SNMP_TRAP_PORT).dport(49_153)
/ Raw::from_bytes(invalid_sequence);
let ipv6_decoded =
Packet::decode_from_l3(NetworkLayer::Ipv6, ipv6_raw.compile().unwrap().as_bytes())
.unwrap();
assert!(ipv6_decoded.layer::<Snmp>().is_none());
assert_eq!(
ipv6_decoded
.layer::<Raw>()
.expect("raw IPv6 payload")
.as_bytes(),
invalid_sequence
);
}
#[test]
fn snmp_trap_port_decode_decodes_v1_v2_trap_and_inform_pdus() -> crate::Result<()> {
let enterprise = SnmpOid::from_dotted("1.3.6.1.4.1")?;
let cases = [
(
Snmp::v1_trap(
b"public".to_vec(),
enterprise,
[192, 0, 2, 1],
6,
42,
1234,
SnmpVarBindList::empty(),
)?,
SnmpVersion::V1,
SnmpPdu::TAG_TRAP,
),
(
Snmp::v2c_snmpv2_trap(b"public".to_vec(), 2, SnmpVarBindList::empty())?,
SnmpVersion::V2c,
SnmpPdu::TAG_TRAP_V2,
),
(
Snmp::v2c_inform_request(b"public".to_vec(), 3, SnmpVarBindList::empty())?,
SnmpVersion::V2c,
SnmpPdu::TAG_INFORM_REQUEST,
),
];
for (snmp, expected_version, expected_tag) in cases {
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 67))
.dst(Ipv4Addr::new(198, 51, 100, 67))
/ Udp::new().sport(SNMP_TRAP_PORT).dport(49_154)
/ snmp;
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
let decoded_snmp = decoded.layer::<Snmp>().expect("trap-port SNMP");
assert_eq!(decoded_snmp.version(), expected_version);
assert_eq!(decoded_snmp.pdu().tag_number(), expected_tag);
assert!(decoded.layer::<Raw>().is_none());
}
Ok(())
}
#[test]
fn snmp_trap_port_decode_decodes_unknown_valid_pdu_payload() -> crate::Result<()> {
let unknown_payload = [
0x30, 0x0b, 0x02, 0x01, 0x01, 0x04, 0x01, b'x', 0xa9, 0x03, 0x02, 0x01, 0x05,
];
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 67))
.dst(Ipv4Addr::new(198, 51, 100, 67))
/ Udp::new().sport(49_154).dport(SNMP_TRAP_PORT)
/ Raw::from_bytes(unknown_payload);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
let snmp = decoded.layer::<Snmp>().expect("unknown PDU SNMP");
assert_eq!(snmp.version(), SnmpVersion::V2c);
assert_eq!(snmp.pdu().tag_number(), 9);
assert!(snmp.pdu().as_unknown().is_some());
assert!(decoded.layer::<Raw>().is_none());
Ok(())
}
}
#[cfg(test)]
mod ssdp_udp_binding {
use std::net::{Ipv4Addr, Ipv6Addr};
use super::ProtocolRegistry;
use crate::{
Ipv4, Ipv6, NetworkLayer, Packet, Raw, Ssdp, Udp, SSDP_HEADER_EXT, SSDP_HEADER_HOST,
SSDP_HEADER_ST, SSDP_HEADER_USN, SSDP_IPV4_MULTICAST_HOST, SSDP_ST_ALL, SSDP_UDP_PORT,
};
fn search_payload() -> Ssdp {
Ssdp::m_search()
.with_raw_header(SSDP_HEADER_HOST, SSDP_IPV4_MULTICAST_HOST)
.expect("HOST header is valid")
.with_raw_header(SSDP_HEADER_ST, SSDP_ST_ALL)
.expect("ST header is valid")
}
fn response_payload() -> Ssdp {
Ssdp::response_ok()
.with_raw_header(SSDP_HEADER_EXT, "")
.expect("EXT header is valid")
.with_raw_header(SSDP_HEADER_ST, SSDP_ST_ALL)
.expect("ST header is valid")
.with_raw_header(SSDP_HEADER_USN, "uuid:device-1::ssdp:all")
.expect("USN header is valid")
}
#[test]
fn ssdp_udp_binding_decodes_ipv4_request_and_ipv6_response_on_port_1900() {
let ipv4_packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(239, 255, 255, 250))
/ Udp::new().sport(49_152).dport(SSDP_UDP_PORT)
/ search_payload();
let ipv4_decoded = Packet::decode_from_l3(
NetworkLayer::Ipv4,
ipv4_packet.compile().unwrap().as_bytes(),
)
.unwrap();
assert!(ipv4_decoded.layer::<Ssdp>().is_some());
assert!(ipv4_decoded.layer::<Raw>().is_none());
let ipv6_packet = Ipv6::new()
.src("2001:db8::10".parse::<Ipv6Addr>().unwrap())
.dst("ff02::c".parse::<Ipv6Addr>().unwrap())
/ Udp::new().sport(SSDP_UDP_PORT).dport(49_153)
/ response_payload();
let ipv6_decoded = Packet::decode_from_l3(
NetworkLayer::Ipv6,
ipv6_packet.compile().unwrap().as_bytes(),
)
.unwrap();
assert!(ipv6_decoded.layer::<Ssdp>().is_some());
assert!(ipv6_decoded.layer::<Raw>().is_none());
}
#[test]
fn ssdp_udp_binding_preserves_non_ssdp_payloads_on_port_1900_as_raw() {
let cases: &[(&str, &[u8])] = &[
(
"generic_http",
b"GET / HTTP/1.1\r\nHOST: example.test\r\n\r\n",
),
(
"truncated_candidate",
b"M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\n",
),
("binary", b"\x00\x01\x02\x03"),
];
for (name, payload) in cases {
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(239, 255, 255, 250))
/ Udp::new().sport(49_152).dport(SSDP_UDP_PORT)
/ Raw::from_bytes(payload);
let decoded =
Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile().unwrap().as_bytes())
.unwrap_or_else(|err| panic!("{name} should decode as raw: {err}"));
assert!(
decoded.layer::<Ssdp>().is_none(),
"{name} should not produce SSDP"
);
assert_eq!(
decoded.layer::<Raw>().unwrap().as_bytes(),
*payload,
"{name} raw payload should be preserved"
);
}
}
#[test]
fn ssdp_udp_binding_requires_source_backed_port() {
let payload = search_payload().to_bytes();
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 10))
/ Udp::new().sport(49_152).dport(49_153)
/ Raw::from_bytes(&payload);
let decoded =
Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile().unwrap().as_bytes())
.unwrap();
assert!(decoded.layer::<Ssdp>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
#[test]
fn ssdp_udp_binding_custom_binding_overrides_builtin() {
let mut registry = ProtocolRegistry::new();
registry.bind_udp_port(SSDP_UDP_PORT, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(239, 255, 255, 250))
/ Udp::new().sport(49_152).dport(SSDP_UDP_PORT)
/ search_payload();
let decoded = Packet::decode_from_l3_with_registry(
®istry,
NetworkLayer::Ipv4,
packet.compile().unwrap().as_bytes(),
)
.unwrap();
assert!(decoded.layer::<Ssdp>().is_none());
assert!(decoded.layer::<Raw>().is_some());
}
#[test]
fn ssdp_custom_udp_binding_overrides_builtin_dispatch() {
let mut registry = ProtocolRegistry::new();
registry.bind_udp_port(SSDP_UDP_PORT, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let payload = search_payload().to_bytes();
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(239, 255, 255, 250))
/ Udp::new().sport(49_152).dport(SSDP_UDP_PORT)
/ Raw::from_bytes(&payload);
let decoded = Packet::decode_from_l3_with_registry(
®istry,
NetworkLayer::Ipv4,
packet.compile().unwrap().as_bytes(),
)
.unwrap();
assert!(decoded.layer::<Ssdp>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
#[test]
fn ssdp_custom_udp_binding_unrelated_ssdp_port_payload_remains_raw() {
let payload = b"GET / HTTP/1.1\r\nHOST: example.test\r\n\r\n";
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(239, 255, 255, 250))
/ Udp::new().sport(49_152).dport(SSDP_UDP_PORT)
/ Raw::from_bytes(payload);
let decoded =
Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile().unwrap().as_bytes())
.unwrap();
assert!(decoded.layer::<Ssdp>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
#[test]
fn ssdp_application_decoding_toggle_preserves_valid_ssdp_as_raw() {
let payload = search_payload().to_bytes();
let registry = ProtocolRegistry::new().application_decoding(false);
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(239, 255, 255, 250))
/ Udp::new().sport(49_152).dport(SSDP_UDP_PORT)
/ Raw::from_bytes(&payload);
let decoded = Packet::decode_from_l3_with_registry(
®istry,
NetworkLayer::Ipv4,
packet.compile().unwrap().as_bytes(),
)
.unwrap();
assert!(decoded.layer::<Udp>().is_some());
assert!(decoded.layer::<Ssdp>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
}
#[cfg(test)]
mod quic_udp_dispatch {
use super::ProtocolRegistry;
use crate::{
Ipv4, NetworkLayer, Packet, Quic, Raw, Udp, QUIC_VERSION_1, QUIC_VERSION_NEGOTIATION,
};
const QUIC_PAYLOAD: [u8; 14] = [
0xc0, 0x00, 0x00, 0x00, 0x01, 0x04, 0x83, 0x94, 0xc8, 0xf0, 0x00, 0x00, 0x01, 0x00,
];
fn udp_ipv4_packet(source_port: u16, destination_port: u16, payload: impl Into<Raw>) -> Packet {
Ipv4::new() / Udp::new().sport(source_port).dport(destination_port) / payload.into()
}
fn udp_length(bytes: &[u8]) -> u16 {
u16::from_be_bytes([bytes[24], bytes[25]])
}
#[test]
fn quic_udp_dispatch_decodes_long_header_on_example_port() {
let bytes = udp_ipv4_packet(49_152, 4433, Raw::from_bytes(QUIC_PAYLOAD))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
let quic = decoded.layer::<Quic>().expect("QUIC layer");
assert_eq!(quic.packets().len(), 1);
assert!(quic.packets()[0].is_long_header());
assert_eq!(quic.len(), QUIC_PAYLOAD.len());
assert!(decoded.layer::<Raw>().is_none());
assert_eq!(QUIC_VERSION_1, 0x0000_0001);
}
#[test]
fn quic_udp_dispatch_decodes_version_negotiation_on_https_port() {
let payload = [
0x80, 0x00, 0x00, 0x00, 0x00, 0x04, 0x83, 0x94, 0xc8, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x01,
];
let bytes = udp_ipv4_packet(443, 49_152, Raw::from_bytes(payload))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Quic>().is_some());
assert_eq!(QUIC_VERSION_NEGOTIATION, 0x0000_0000);
}
#[test]
fn quic_udp_dispatch_preserves_non_quic_payloads_as_raw() {
let payload = [0x16, 0xfe, 0xfd, 0x00, 0x00, 0xde, 0xad];
let bytes = udp_ipv4_packet(49_152, 443, Raw::from_bytes(payload))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Quic>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
#[test]
fn quic_udp_dispatch_preserves_ambiguous_short_header_as_raw() {
let payload = [0x43, 0x83, 0x94, 0xc8, 0xf0, 0x01, 0x02];
let bytes = udp_ipv4_packet(49_152, 4433, Raw::from_bytes(payload))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
assert!(decoded.layer::<Quic>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), payload);
}
#[test]
fn quic_multiplexing_classifier_preserves_neighbor_udp_payloads_as_raw_on_quic_ports() {
let cases: &[(&str, &[u8])] = &[
(
"dtls_handshake",
&[0x16, 0xfe, 0xfd, 0x00, 0x00, 0xde, 0xad],
),
(
"stun_binding",
&[0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xa4, 0x42],
),
("rtp", &[0x80, 0x60, 0x00, 0x01, 0x00, 0x00, 0xde, 0xad]),
("zrtp", &[0x10, 0x00, b'Z', b'R', b'T', b'P']),
("turn_channel_data", &[0x40, 0x00, 0x00, 0x04, 0xde, 0xad]),
];
for (name, payload) in cases {
let bytes = udp_ipv4_packet(49_152, 4433, Raw::from_bytes(payload))
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap_or_else(|err| panic!("{name} should decode as raw: {err}"));
assert!(
decoded.layer::<Quic>().is_none(),
"{name} should not produce QUIC"
);
assert_eq!(
decoded.layer::<Raw>().unwrap().as_bytes(),
*payload,
"{name} raw payload should be preserved"
);
}
}
#[test]
fn quic_multiplexing_classifier_preserves_custom_udp_binding_precedence() {
let mut registry = ProtocolRegistry::new();
registry.bind_udp_port(4433, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let bytes = udp_ipv4_packet(49_152, 4433, Raw::from_bytes(QUIC_PAYLOAD))
.compile()
.unwrap();
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert!(decoded.layer::<Quic>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), QUIC_PAYLOAD);
}
#[test]
fn quic_udp_dispatch_custom_binding_overrides_builtin() {
let mut registry = ProtocolRegistry::new();
registry.bind_udp_port(4433, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let bytes = udp_ipv4_packet(49_152, 4433, Raw::from_bytes(QUIC_PAYLOAD))
.compile()
.unwrap();
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert!(decoded.layer::<Quic>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), QUIC_PAYLOAD);
}
#[test]
fn quic_udp_dispatch_disabled_application_decoding_preserves_raw() {
let registry = ProtocolRegistry::new().application_decoding(false);
let bytes = udp_ipv4_packet(49_152, 4433, Raw::from_bytes(QUIC_PAYLOAD))
.compile()
.unwrap();
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_bytes())
.unwrap();
assert!(decoded.layer::<Quic>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), QUIC_PAYLOAD);
}
#[test]
fn quic_udp_dispatch_layer_length_excludes_udp_surplus_raw() {
let packet = Ipv4::new()
/ Udp::new().sport(49_152).dport(4433)
/ Quic::from_bytes(QUIC_PAYLOAD)
/ Raw::from_bytes([0xde, 0xad, 0xbe, 0xef]);
let compiled = packet.compile().unwrap();
assert_eq!(
udp_length(compiled.as_bytes()),
(8 + QUIC_PAYLOAD.len()) as u16
);
}
}
#[cfg(test)]
mod bgp_tcp_binding {
use crate::protocols::bgp::BGP_PORT;
use crate::{Bgp, Ipv4, NetworkLayer, Packet, Raw, Tcp};
#[test]
fn default_registry_decodes_bgp_on_tcp_179_and_preserves_other_tcp_payloads() {
let bgp_bytes = (Ipv4::new() / Tcp::new().sport(49_152).dport(BGP_PORT) / Bgp::keepalive())
.compile()
.unwrap();
let bgp_decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bgp_bytes.as_bytes()).unwrap();
assert!(bgp_decoded.layer::<Bgp>().is_some());
assert!(bgp_decoded.layer::<Raw>().is_none());
let keepalive = Packet::from_layer(Bgp::keepalive())
.compile()
.unwrap()
.into_bytes();
let raw_bytes =
(Ipv4::new() / Tcp::new().sport(49_152).dport(80) / Raw::from_bytes(keepalive.clone()))
.compile()
.unwrap();
let raw_decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, raw_bytes.as_bytes()).unwrap();
assert!(raw_decoded.layer::<Bgp>().is_none());
assert_eq!(raw_decoded.layer::<Raw>().unwrap().as_bytes(), keepalive);
}
}
#[cfg(test)]
mod tls_tcp_binding {
use crate::protocols::mqtt::{Mqtt, MQTT_PORT};
use crate::protocols::tls::constants::{
TLS_COMMON_TCP_PORTS, TLS_PORT_DNS_OVER_TLS, TLS_PORT_EXAMPLE_TESTING, TLS_PORT_HTTPS,
TLS_PORT_MQTT_OVER_TLS,
};
use crate::protocols::tls::{Tls, TlsClientHello, TlsContentType, TlsHandshake, TlsRecord};
use crate::{Ipv4, NetworkLayer, Packet, ProtocolRegistry, Raw, Tcp};
fn tls_client_hello_payload() -> Vec<u8> {
let client_hello = TlsClientHello::new()
.with_raw_cipher_suites([0x1301])
.without_extensions();
let message = TlsHandshake::from_client_hello(client_hello)
.unwrap()
.encode_to_vec()
.unwrap();
TlsRecord::handshake(message).encode_to_vec().unwrap()
}
fn tcp_ipv4_payload(source_port: u16, destination_port: u16, payload: &[u8]) -> Vec<u8> {
(Ipv4::new()
.src("192.0.2.10".parse().unwrap())
.dst("198.51.100.20".parse().unwrap())
/ Tcp::new()
.sport(source_port)
.dport(destination_port)
.seq(0x1111_2222)
.ack(0x3333_4444)
.ack_segment()
/ Raw::from_bytes(payload))
.compile()
.unwrap()
.into_bytes()
}
#[test]
fn tls_registry_decodes_common_tls_tcp_ports() {
for port in [
TLS_PORT_HTTPS,
TLS_PORT_DNS_OVER_TLS,
TLS_PORT_MQTT_OVER_TLS,
TLS_PORT_EXAMPLE_TESTING,
] {
let tls_payload = tls_client_hello_payload();
let bytes = tcp_ipv4_payload(49_152, port, &tls_payload);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_slice()).unwrap();
let tls = decoded
.layer::<Tls>()
.expect("TLS layer on destination port");
assert_eq!(tls.record_count(), 1);
assert_eq!(tls.records()[0].content_type(), TlsContentType::handshake());
assert!(decoded.layer::<Raw>().is_none());
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes.as_slice());
}
let tls_payload = tls_client_hello_payload();
let bytes = tcp_ipv4_payload(TLS_PORT_HTTPS, 49_152, &tls_payload);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_slice()).unwrap();
assert!(
decoded.layer::<Tls>().is_some(),
"TLS source port should also dispatch"
);
}
#[test]
fn tls_registry_rejects_non_tls_payloads_on_tls_ports() {
let non_tls_payloads: &[&[u8]] = &[
b"GET / HTTP/1.1\r\nHost: example.test\r\n\r\n",
b"SSH-2.0-OpenSSH_9.6\r\n",
&[0x10, 0x10, 0x00, 0x04, b'M', b'Q', b'T', b'T'],
];
for port in TLS_COMMON_TCP_PORTS {
for payload in non_tls_payloads {
let bytes = tcp_ipv4_payload(49_152, port, payload);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_slice())
.unwrap_or_else(|err| {
panic!("non-TLS payload on TCP/{port} should decode as Raw: {err}")
});
assert!(decoded.layer::<Tls>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), *payload);
}
}
}
#[test]
fn tls_registry_disabled_application_decoding_preserves_raw() {
let registry = ProtocolRegistry::new().application_decoding(false);
let tls_payload = tls_client_hello_payload();
let bytes = tcp_ipv4_payload(49_152, TLS_PORT_HTTPS, &tls_payload);
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_slice())
.unwrap();
assert!(decoded.layer::<Tls>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), tls_payload);
}
#[test]
fn tls_registry_custom_tcp_binding_overrides_builtin() {
let mut registry = ProtocolRegistry::new();
registry.bind_tcp_port(TLS_PORT_EXAMPLE_TESTING, |packet, payload| {
Ok(packet.push(Raw::from_bytes(payload)))
});
let tls_payload = tls_client_hello_payload();
let bytes = tcp_ipv4_payload(49_152, TLS_PORT_EXAMPLE_TESTING, &tls_payload);
let decoded =
Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, bytes.as_slice())
.unwrap();
assert!(decoded.layer::<Tls>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), tls_payload);
}
#[test]
fn tls_registry_keeps_cleartext_mqtt_on_tcp_1883() {
let bytes = (Ipv4::new()
.src("192.0.2.10".parse().unwrap())
.dst("198.51.100.20".parse().unwrap())
/ Tcp::new()
.sport(49_152)
.dport(MQTT_PORT)
.seq(0x1111_2222)
.ack(0x3333_4444)
.ack_segment()
/ Mqtt::connect().client_id("crafter-client"))
.compile()
.unwrap()
.into_bytes();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_slice()).unwrap();
assert!(decoded.layer::<Tls>().is_none());
assert!(decoded.layer::<Mqtt>().is_some());
assert!(decoded.layer::<Raw>().is_none());
}
}