use super::ipv6::Ipv6HeaderExt;
use super::iter::LinkType;
use super::protocol::{EtherProto, IpProto};
use super::tunnel::{
geneve::GENEVE_PORT,
gtpv1::{GTPV1_C_PORT, GTPV1_U_PORT},
l2tp::L2TP_PORT,
stt::STT_PORT,
teredo::TEREDO_PORT,
vxlan::VXLAN_PORT,
};
#[derive(Debug, Clone, Copy)]
pub(crate) enum NextLayer {
Link(LinkType),
Network(EtherProto),
Transport(IpProto),
Tunnel(TunnelType),
Done,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum TunnelType {
Vxlan,
Geneve,
Gre,
Mpls,
Teredo,
Gtpv1,
Gtpv2,
L2tpv2,
L2tpv3,
Nvgre,
Pbb,
Stt,
Pptp,
}
#[inline]
pub(crate) fn detect_udp_tunnel(
src_port: u16,
dst_port: u16,
payload: &[u8],
) -> Option<TunnelType> {
match dst_port {
VXLAN_PORT => return Some(TunnelType::Vxlan),
GENEVE_PORT => return Some(TunnelType::Geneve),
TEREDO_PORT => return Some(TunnelType::Teredo),
6635 => return Some(TunnelType::Mpls), L2TP_PORT => return Some(TunnelType::L2tpv2), GTPV1_U_PORT => {
if let Some(gtp_type) = detect_gtp_version(payload) {
return Some(gtp_type);
}
}
GTPV1_C_PORT => {
if let Some(gtp_type) = detect_gtp_version(payload) {
return Some(gtp_type);
}
}
3386 => {
if let Some(gtp_type) = detect_gtp_version(payload) {
return Some(gtp_type);
}
}
_ => {}
}
if src_port == TEREDO_PORT {
return Some(TunnelType::Teredo);
}
if src_port == L2TP_PORT {
return Some(TunnelType::L2tpv2);
}
None
}
#[inline]
pub(crate) fn detect_gtp_version(payload: &[u8]) -> Option<TunnelType> {
if payload.is_empty() {
return None;
}
let version_byte = payload[0];
match version_byte & 0xF0 {
0x10 | 0x20 => Some(TunnelType::Gtpv1), 0x30 => Some(TunnelType::Gtpv1), 0x40 => Some(TunnelType::Gtpv2), _ => None,
}
}
#[inline]
pub(crate) fn detect_gre_variant(payload: &[u8]) -> TunnelType {
if payload.len() < 4 {
return TunnelType::Gre;
}
let flags_version = u16::from_be_bytes([payload[0], payload[1]]);
let version = flags_version & 0x0007;
let has_key = (flags_version & 0x2000) != 0;
let protocol_type = u16::from_be_bytes([payload[2], payload[3]]);
if version == 1 {
return TunnelType::Pptp;
}
if version == 0 && has_key && protocol_type == 0x6558 {
return TunnelType::Nvgre;
}
TunnelType::Gre
}
#[inline]
pub(crate) fn detect_mpls_inner_protocol(payload: &[u8]) -> Option<NextLayer> {
if payload.is_empty() {
return None;
}
let version_nibble = (payload[0] & 0xF0) >> 4;
match version_nibble {
0 => Some(NextLayer::Link(LinkType::Ethernet)), 4 => Some(NextLayer::Network(EtherProto::IPV4)),
6 => Some(NextLayer::Network(EtherProto::IPV6)),
_ => None,
}
}
#[inline]
pub(crate) fn find_ipv6_upper_protocol(ipv6: &Ipv6HeaderExt<'_>) -> IpProto {
if ipv6.raw_extensions.is_empty() {
return ipv6.next_header();
}
let mut remaining = ipv6.raw_extensions;
let mut next_header = ipv6.next_header();
while !remaining.is_empty() {
if remaining.len() < 2 {
break;
}
next_header = IpProto(remaining[0]);
let ext_len = match next_header {
IpProto::IPV6_FRAG => 8,
_ => ((remaining[1] as usize) + 1) * 8,
};
if remaining.len() < ext_len {
break;
}
remaining = &remaining[ext_len..];
}
next_header
}
#[inline]
pub(crate) fn is_stt_port(port: u16) -> bool {
port == STT_PORT
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_udp_tunnel_vxlan() {
assert!(matches!(
detect_udp_tunnel(12345, VXLAN_PORT, &[]),
Some(TunnelType::Vxlan)
));
}
#[test]
fn test_detect_udp_tunnel_geneve() {
assert!(matches!(
detect_udp_tunnel(12345, GENEVE_PORT, &[]),
Some(TunnelType::Geneve)
));
}
#[test]
fn test_detect_udp_tunnel_teredo_dst() {
assert!(matches!(
detect_udp_tunnel(12345, TEREDO_PORT, &[]),
Some(TunnelType::Teredo)
));
}
#[test]
fn test_detect_udp_tunnel_teredo_src() {
assert!(matches!(
detect_udp_tunnel(TEREDO_PORT, 12345, &[]),
Some(TunnelType::Teredo)
));
}
#[test]
fn test_detect_udp_tunnel_l2tp() {
assert!(matches!(
detect_udp_tunnel(12345, L2TP_PORT, &[]),
Some(TunnelType::L2tpv2)
));
}
#[test]
fn test_detect_udp_tunnel_none() {
assert!(detect_udp_tunnel(12345, 80, &[]).is_none());
}
#[test]
fn test_detect_gtp_version_v1() {
assert!(matches!(
detect_gtp_version(&[0x30]),
Some(TunnelType::Gtpv1)
));
}
#[test]
fn test_detect_gtp_version_v2() {
assert!(matches!(
detect_gtp_version(&[0x40]),
Some(TunnelType::Gtpv2)
));
}
#[test]
fn test_detect_gtp_version_empty() {
assert!(detect_gtp_version(&[]).is_none());
}
#[test]
fn test_detect_gre_standard() {
let gre = [0x00, 0x00, 0x08, 0x00]; assert!(matches!(detect_gre_variant(&gre), TunnelType::Gre));
}
#[test]
fn test_detect_gre_pptp() {
let gre = [0x00, 0x01, 0x88, 0x0B]; assert!(matches!(detect_gre_variant(&gre), TunnelType::Pptp));
}
#[test]
fn test_detect_gre_nvgre() {
let gre = [0x20, 0x00, 0x65, 0x58]; assert!(matches!(detect_gre_variant(&gre), TunnelType::Nvgre));
}
#[test]
fn test_detect_gre_too_short() {
assert!(matches!(detect_gre_variant(&[0x00]), TunnelType::Gre));
}
#[test]
fn test_detect_mpls_inner_ipv4() {
let payload = [0x45, 0x00, 0x00, 0x28];
assert!(matches!(
detect_mpls_inner_protocol(&payload),
Some(NextLayer::Network(EtherProto::IPV4))
));
}
#[test]
fn test_detect_mpls_inner_ipv6() {
let payload = [0x60, 0x00, 0x00, 0x00];
assert!(matches!(
detect_mpls_inner_protocol(&payload),
Some(NextLayer::Network(EtherProto::IPV6))
));
}
#[test]
fn test_detect_mpls_inner_ethernet() {
let payload = [0x00, 0x11, 0x22, 0x33];
assert!(matches!(
detect_mpls_inner_protocol(&payload),
Some(NextLayer::Link(LinkType::Ethernet))
));
}
#[test]
fn test_detect_mpls_inner_empty() {
assert!(detect_mpls_inner_protocol(&[]).is_none());
}
#[test]
fn test_is_stt_port() {
assert!(is_stt_port(STT_PORT));
assert!(!is_stt_port(80));
}
}