use crate::extractor::{Extracted, FlowExtractor};
use crate::view::PacketView;
use super::parse;
pub const DEFAULT_VXLAN_PORT: u16 = 4789;
#[derive(Debug, Clone, Copy)]
pub struct InnerVxlan<E> {
pub extractor: E,
pub udp_port: u16,
}
impl<E> InnerVxlan<E> {
pub fn new(extractor: E) -> Self {
Self {
extractor,
udp_port: DEFAULT_VXLAN_PORT,
}
}
pub fn with_port(extractor: E, udp_port: u16) -> Self {
Self {
extractor,
udp_port,
}
}
}
impl<E: FlowExtractor> FlowExtractor for InnerVxlan<E> {
type Key = E::Key;
fn extract(&self, view: PacketView<'_>) -> Option<Extracted<E::Key>> {
let inner = peel_vxlan(view.frame, self.udp_port)?;
self.extractor.extract(view.with_frame(inner))
}
}
fn peel_vxlan(frame: &[u8], expected_port: u16) -> Option<&[u8]> {
let parsed = parse::parse_eth(frame)?;
let l4 = parsed.l4?;
let udp = match l4 {
parse::ParsedL4::Udp(u) => u,
_ => return None,
};
if udp.payload_len < 8 {
return None;
}
let payload_start = udp.payload_offset;
let frame_after_udp = &frame[payload_start..payload_start + udp.payload_len];
let sp = etherparse::SlicedPacket::from_ethernet(frame).ok()?;
let udp_slice = match sp.transport {
Some(etherparse::TransportSlice::Udp(u)) => u,
_ => return None,
};
let dst_port = udp_slice.destination_port();
let src_port = udp_slice.source_port();
if dst_port != expected_port && src_port != expected_port {
return None;
}
if frame_after_udp.len() < 8 {
return None;
}
Some(&frame_after_udp[8..])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Timestamp;
use crate::extract::FiveTuple;
use etherparse::{Ethernet2Header, IpNumber, Ipv4Header, TcpHeader, UdpHeader};
fn vxlan_ipv4_tcp(vni: u32, vxlan_port: u16) -> Vec<u8> {
let mut tcp = TcpHeader::new(10, 20, 1000, 8192);
tcp.syn = true;
let inner_ip = Ipv4Header::new(
tcp.header_len() as u16,
64,
IpNumber::TCP,
[192, 168, 1, 1],
[192, 168, 1, 2],
)
.unwrap();
let inner_eth = Ethernet2Header {
destination: [0; 6],
source: [0; 6],
ether_type: etherparse::EtherType::IPV4,
};
let mut inner = Vec::new();
inner_eth.write(&mut inner).unwrap();
inner_ip.write(&mut inner).unwrap();
tcp.write(&mut inner).unwrap();
let mut vxlan = [0u8; 8];
vxlan[0] = 0x08;
vxlan[4..7].copy_from_slice(&vni.to_be_bytes()[1..]);
let mut outer_payload = Vec::with_capacity(8 + inner.len());
outer_payload.extend_from_slice(&vxlan);
outer_payload.extend_from_slice(&inner);
let outer_udp =
UdpHeader::without_ipv4_checksum(12345, vxlan_port, outer_payload.len()).unwrap();
let outer_ip = Ipv4Header::new(
(outer_udp.header_len_u16() as usize + outer_payload.len()) as u16,
64,
IpNumber::UDP,
[10, 0, 0, 1],
[10, 0, 0, 2],
)
.unwrap();
let outer_eth = Ethernet2Header {
destination: [0; 6],
source: [0; 6],
ether_type: etherparse::EtherType::IPV4,
};
let mut out = Vec::new();
outer_eth.write(&mut out).unwrap();
outer_ip.write(&mut out).unwrap();
outer_udp.write(&mut out).unwrap();
out.extend_from_slice(&outer_payload);
out
}
#[test]
fn extracts_inner_5tuple() {
let f = vxlan_ipv4_tcp(42, DEFAULT_VXLAN_PORT);
let e = InnerVxlan::new(FiveTuple::bidirectional())
.extract(PacketView::new(&f, Timestamp::default()))
.unwrap();
match (e.key.a, e.key.b) {
(std::net::SocketAddr::V4(a), std::net::SocketAddr::V4(b)) => {
assert!(matches!(
(a.ip().octets(), b.ip().octets()),
([192, 168, 1, 1], [192, 168, 1, 2]) | ([192, 168, 1, 2], [192, 168, 1, 1])
));
}
_ => panic!("expected ipv4 inner"),
}
assert!(e.tcp.is_some());
}
#[test]
fn wrong_port_returns_none() {
let f = vxlan_ipv4_tcp(1, 9999);
assert!(
InnerVxlan::new(FiveTuple::bidirectional())
.extract(PacketView::new(&f, Timestamp::default()))
.is_none()
);
}
#[test]
fn custom_port_works() {
let f = vxlan_ipv4_tcp(1, 8472);
let e = InnerVxlan::with_port(FiveTuple::bidirectional(), 8472)
.extract(PacketView::new(&f, Timestamp::default()))
.unwrap();
assert!(e.tcp.is_some());
}
#[test]
fn non_udp_returns_none() {
use crate::extract::parse::test_frames::ipv4_tcp;
let f = ipv4_tcp(
[0; 6],
[0; 6],
[10, 0, 0, 1],
[10, 0, 0, 2],
1,
2,
0,
0,
0x02,
b"",
);
assert!(
InnerVxlan::new(FiveTuple::bidirectional())
.extract(PacketView::new(&f, Timestamp::default()))
.is_none()
);
}
}