use crate::extractor::{Extracted, FlowExtractor};
use crate::view::PacketView;
use super::parse;
pub const DEFAULT_GTPU_PORT: u16 = 2152;
#[derive(Debug, Clone)]
pub struct InnerGtpU<E> {
pub extractor: E,
pub udp_port: u16,
}
impl<E> InnerGtpU<E> {
pub fn new(extractor: E) -> Self {
Self {
extractor,
udp_port: DEFAULT_GTPU_PORT,
}
}
pub fn with_port(extractor: E, udp_port: u16) -> Self {
Self {
extractor,
udp_port,
}
}
}
impl<E: FlowExtractor> FlowExtractor for InnerGtpU<E> {
type Key = E::Key;
fn extract(&self, view: PacketView<'_>) -> Option<Extracted<E::Key>> {
let inner_ip = peel_gtp_u(view.frame, self.udp_port)?;
let synthetic = synthesize_eth_for_ip(inner_ip)?;
self.extractor.extract(PacketView {
frame: &synthetic,
timestamp: view.timestamp,
})
}
}
fn peel_gtp_u(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,
};
let sp = etherparse::SlicedPacket::from_ethernet(frame).ok()?;
let udp_slice = match sp.transport {
Some(etherparse::TransportSlice::Udp(u)) => u,
_ => return None,
};
if udp_slice.destination_port() != expected_port && udp_slice.source_port() != expected_port {
return None;
}
let payload_start = udp.payload_offset;
let payload = &frame[payload_start..payload_start + udp.payload_len];
if payload.len() < 8 {
return None;
}
let flags = payload[0];
let has_optional = flags & 0b0000_0111 != 0;
let mut header_len = 8usize;
if has_optional {
header_len += 4;
if flags & 0b0000_0100 != 0 {
let mut ext_type = payload[11];
let mut offset = header_len;
while ext_type != 0 {
if payload.len() < offset + 1 {
return None;
}
let ext_len_u32s = payload[offset] as usize;
if ext_len_u32s == 0 {
return None;
}
let ext_len_bytes = ext_len_u32s * 4;
if payload.len() < offset + ext_len_bytes {
return None;
}
ext_type = payload[offset + ext_len_bytes - 1];
offset += ext_len_bytes;
if offset > payload.len() {
return None;
}
}
header_len = offset;
}
}
if payload.len() <= header_len {
return None;
}
Some(&payload[header_len..])
}
fn synthesize_eth_for_ip(ip: &[u8]) -> Option<Vec<u8>> {
if ip.is_empty() {
return None;
}
let ethertype: u16 = match ip[0] >> 4 {
4 => 0x0800,
6 => 0x86dd,
_ => return None,
};
let mut out = Vec::with_capacity(14 + ip.len());
out.extend_from_slice(&[0u8; 12]); out.extend_from_slice(ðertype.to_be_bytes());
out.extend_from_slice(ip);
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Timestamp;
use crate::extract::FiveTuple;
use etherparse::{Ethernet2Header, IpNumber, Ipv4Header, TcpHeader, UdpHeader};
fn gtpu_ipv4_tcp(teid: u32, port: u16) -> Vec<u8> {
let mut tcp = TcpHeader::new(10, 20, 0, 8192);
tcp.syn = true;
let inner_ip = Ipv4Header::new(
tcp.header_len() as u16,
64,
IpNumber::TCP,
[10, 1, 1, 1],
[10, 1, 1, 2],
)
.unwrap();
let mut inner = Vec::new();
inner_ip.write(&mut inner).unwrap();
tcp.write(&mut inner).unwrap();
let mut gtpu = Vec::with_capacity(8);
gtpu.push(0x30); gtpu.push(0xff); gtpu.extend_from_slice(&(inner.len() as u16).to_be_bytes());
gtpu.extend_from_slice(&teid.to_be_bytes());
let mut outer_payload = Vec::with_capacity(gtpu.len() + inner.len());
outer_payload.extend_from_slice(>pu);
outer_payload.extend_from_slice(&inner);
let udp = UdpHeader::without_ipv4_checksum(40000, port, outer_payload.len()).unwrap();
let outer_ip = Ipv4Header::new(
(udp.header_len_u16() as usize + outer_payload.len()) as u16,
64,
IpNumber::UDP,
[203, 0, 113, 1],
[203, 0, 113, 2],
)
.unwrap();
let eth = Ethernet2Header {
destination: [0; 6],
source: [0; 6],
ether_type: etherparse::EtherType::IPV4,
};
let mut out = Vec::new();
eth.write(&mut out).unwrap();
outer_ip.write(&mut out).unwrap();
udp.write(&mut out).unwrap();
out.extend_from_slice(&outer_payload);
out
}
#[test]
fn extracts_inner_5tuple() {
let f = gtpu_ipv4_tcp(0x1234_5678, DEFAULT_GTPU_PORT);
let e = InnerGtpU::new(FiveTuple::bidirectional())
.extract(PacketView::new(&f, Timestamp::default()))
.unwrap();
assert!(e.tcp.is_some());
match (e.key.a, e.key.b) {
(std::net::SocketAddr::V4(a), std::net::SocketAddr::V4(b)) => {
let oct_a = a.ip().octets();
let oct_b = b.ip().octets();
assert!(oct_a == [10, 1, 1, 1] || oct_a == [10, 1, 1, 2]);
assert!(oct_b == [10, 1, 1, 1] || oct_b == [10, 1, 1, 2]);
}
_ => panic!("expected ipv4 inner"),
}
}
#[test]
fn wrong_port_returns_none() {
let f = gtpu_ipv4_tcp(1, 9999);
assert!(
InnerGtpU::new(FiveTuple::bidirectional())
.extract(PacketView::new(&f, Timestamp::default()))
.is_none()
);
}
#[test]
fn non_gtp_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!(
InnerGtpU::new(FiveTuple::bidirectional())
.extract(PacketView::new(&f, Timestamp::default()))
.is_none()
);
}
}