Skip to content

Commit

Permalink
Add basic sniffing and L2-3 parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
LVala committed Aug 6, 2023
1 parent 3ae54ee commit 790f38a
Show file tree
Hide file tree
Showing 9 changed files with 567 additions and 12 deletions.
271 changes: 264 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ edition = "2021"
[dependencies]
tokio = { version = "1", features = [ "full" ] }
warp = "0.3"
pcap = "1.0.0"
etherparse = "0.13.0"
rtp = "0.6.8"
rtcp = "0.7.2"
webrtc-util = "0.7.0"

# not using workspaces, as the crates use different targets
Binary file added pcap_examples/rtp.pcap
Binary file not shown.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod sniffer;
24 changes: 19 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
#[tokio::main]
async fn main() {
warp::serve(warp::fs::dir("client/dist"))
.run(([127, 0, 0, 1], 3550))
.await;
// #[tokio::main]
// async fn main() {
// warp::serve(warp::fs::dir("client/dist"))
// .run(([127, 0, 0, 1], 3550))
// .await;
// }

use rtpeeker::sniffer;

fn main() {
let Ok(mut sniffer) = sniffer::Sniffer::from_file("./pcap_examples/rtp.pcap") else {
println!("Cannot open file");
return;
};

while let Ok(mut packet) = sniffer.next_packet() {
packet.parse_as(sniffer::packet::PacketType::RtpOverUdp);
println!("{:?}", packet);
}
}
55 changes: 55 additions & 0 deletions src/sniffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use packet::Packet;
use std::result;

pub mod packet;
pub mod rtcp;
pub mod rtp;

pub enum Error {
FileNotFound,
DeviceNotFound,
DeviceUnavailable,
CouldntReceivePacket,
UnsupportedPacketType,
}

type Result<T> = result::Result<T, Error>;

pub struct Sniffer<T: pcap::State> {
capture: pcap::Capture<T>,
}

impl Sniffer<pcap::Offline> {
pub fn from_file(file: &str) -> Result<Self> {
match pcap::Capture::from_file(file) {
Ok(capture) => Ok(Self { capture }),
Err(_) => Err(Error::FileNotFound),
}
}
}

impl Sniffer<pcap::Active> {
pub fn from_device(device: &str) -> Result<Self> {
let Ok(capture) = pcap::Capture::from_device(device) else {
return Err(Error::DeviceNotFound);
};

match capture.open() {
Ok(capture) => Ok(Self { capture }),
Err(_) => Err(Error::DeviceUnavailable),
}
}
}

impl<T: pcap::Activated> Sniffer<T> {
pub fn next_packet(&mut self) -> Result<Packet> {
let Ok(packet) = self.capture.next_packet() else {
return Err(Error::CouldntReceivePacket);
};

match Packet::build(&packet) {
Some(packet) => Ok(packet),
None => Err(Error::UnsupportedPacketType),
}
}
}
169 changes: 169 additions & 0 deletions src/sniffer/packet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use super::{rtcp::RtcpPacket, rtp::RtpPacket};
use etherparse::{
IpHeader::{self, Version4, Version6},
Ipv4Header, Ipv6Header, PacketHeaders, TcpHeader,
TransportHeader::{self, Tcp, Udp},
UdpHeader,
};
use std::net::{
IpAddr::{V4, V6},
Ipv4Addr, Ipv6Addr, SocketAddr,
};
use std::ops::Add;
use std::time::Duration;

#[derive(Debug)]
pub enum PacketType {
RtpOverUdp,
RtcpOverUdp,
}

#[derive(Debug)]
pub enum TransportProtocol {
Tcp,
Udp,
}

#[derive(Debug)]
pub enum SessionPacket {
Unknown,
Rtp(RtpPacket),
Rtcp(Vec<RtcpPacket>),
}

#[derive(Debug)]
pub struct Packet {
pub payload: Vec<u8>,
pub timestamp: Duration,
pub length: u32,
pub source_addr: SocketAddr,
pub destination_addr: SocketAddr,
pub transport_protocol: TransportProtocol,
pub contents: SessionPacket,
}

impl Packet {
pub fn build(raw_packet: &pcap::Packet) -> Option<Self> {
let Ok(packet) = PacketHeaders::from_ethernet_slice(raw_packet) else {
return None;
};
let PacketHeaders {
ip: Some(ip),
transport: Some(transport),
..
} = packet else {
return None;
};

let transport_protocol = get_transport_protocol(&transport)?;
let (source_addr, destination_addr) = convert_addr(&ip, &transport)?;
let duration = get_duration(raw_packet);

Some(Self {
payload: packet.payload.to_vec(),
length: raw_packet.header.len,
timestamp: duration,
source_addr,
destination_addr,
transport_protocol,
contents: SessionPacket::Unknown,
})
}

pub fn parse_as(&mut self, packet_type: PacketType) {
if let PacketType::RtpOverUdp = packet_type {
let Some(rtp) = RtpPacket::build(self) else {
return;
};
self.contents = SessionPacket::Rtp(rtp);
}
}
}

fn get_transport_protocol(transport: &TransportHeader) -> Option<TransportProtocol> {
let protocol = match transport {
Udp(_) => TransportProtocol::Udp,
Tcp(_) => TransportProtocol::Tcp,
_ => return None,
};

Some(protocol)
}

fn get_duration(raw_packet: &pcap::Packet) -> Duration {
// i64 -> u64, but seconds should never be negative
let secs = raw_packet.header.ts.tv_sec.try_into().unwrap();
let micrs = raw_packet.header.ts.tv_usec.try_into().unwrap();

let sec_duration = Duration::from_secs(secs);
let micros_duration = Duration::from_micros(micrs);

sec_duration.add(micros_duration)
}

fn convert_addr(
ip_header: &IpHeader,
transport: &TransportHeader,
) -> Option<(SocketAddr, SocketAddr)> {
let (source_port, dest_port) = match *transport {
Udp(UdpHeader {
source_port,
destination_port,
..
})
| Tcp(TcpHeader {
source_port,
destination_port,
..
}) => (source_port, destination_port),
_ => return None,
};

let (source_ip_addr, dest_ip_addr) = match *ip_header {
Version4(
Ipv4Header {
source: [s0, s1, s2, s3],
destination: [d0, d1, d2, d3],
..
},
_,
) => {
let source = V4(Ipv4Addr::new(s0, s1, s2, s3));
let destination = V4(Ipv4Addr::new(d0, d1, d2, d3));
(source, destination)
}
Version6(
Ipv6Header {
source,
destination,
..
},
_,
) => {
let s = to_u16(&source);
let d = to_u16(&destination);

let source = V6(Ipv6Addr::new(
s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
));
let destination = V6(Ipv6Addr::new(
d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7],
));
(source, destination)
}
};
let source = SocketAddr::new(source_ip_addr, source_port);
let destination = SocketAddr::new(dest_ip_addr, dest_port);
Some((source, destination))
}

pub fn to_u16(buf: &[u8; 16]) -> [u16; 8] {
// TODO: tests
buf.iter()
.zip(buf.iter().skip(1))
.map(|(a, b)| ((*a as u16) << 8) | *b as u16)
.collect::<Vec<_>>()
.as_slice()
.try_into()
.unwrap()
}
16 changes: 16 additions & 0 deletions src/sniffer/rtcp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use rtcp::packet;

#[derive(Debug)]
pub struct RtcpPacket {}

impl RtcpPacket {
pub fn build(packet: &super::Packet) -> Option<Self> {
let mut buffer: &[u8] = &packet.payload;
let Ok(_rtcp_packets) = packet::unmarshal(&mut buffer) else {
return None;
};

// TODO proper mapping of different RTCP packet types
Some(Self {})
}
}
38 changes: 38 additions & 0 deletions src/sniffer/rtp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use rtp::packet::Packet;
use webrtc_util::marshal::Unmarshal;

#[derive(Debug)]
pub struct RtpPacket {
pub version: u8,
pub padding: bool,
pub extension: bool,
pub marker: bool,
pub payload_type: u8,
pub sequence_number: u16,
pub timestamp: u32,
pub ssrc: u32,
pub csrc: Vec<u32>,
pub payload_length: usize, // extension information skipped
}

impl RtpPacket {
pub fn build(packet: &super::Packet) -> Option<Self> {
let mut buffer: &[u8] = &packet.payload;
let Ok(Packet { header, payload }) = Packet::unmarshal(&mut buffer) else {
return None;
};

Some(Self {
version: header.version,
padding: header.padding,
extension: header.extension,
marker: header.marker,
payload_type: header.payload_type,
sequence_number: header.sequence_number,
timestamp: header.timestamp,
ssrc: header.ssrc,
csrc: header.csrc,
payload_length: payload.len(),
})
}
}

0 comments on commit 790f38a

Please sign in to comment.