mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 17:18:31 -04:00
113 lines
3.8 KiB
Rust
113 lines
3.8 KiB
Rust
use crate::events::Event;
|
|
use crate::Bus;
|
|
use anyhow::Context;
|
|
use smoltcp::time::Instant;
|
|
use tokio::fs::File;
|
|
use tokio::io::{AsyncWriteExt, BufWriter};
|
|
|
|
struct Pcap {
|
|
writer: BufWriter<File>,
|
|
}
|
|
|
|
/// libpcap file writer
|
|
/// This is mostly taken from `smoltcp`, but rewritten to be async.
|
|
impl Pcap {
|
|
async fn flush(&mut self) -> anyhow::Result<()> {
|
|
self.writer
|
|
.flush()
|
|
.await
|
|
.with_context(|| "Failed to flush pcap writer")
|
|
}
|
|
|
|
async fn write(&mut self, data: &[u8]) -> anyhow::Result<usize> {
|
|
self.writer
|
|
.write(data)
|
|
.await
|
|
.with_context(|| format!("Failed to write {} bytes to pcap writer", data.len()))
|
|
}
|
|
|
|
async fn write_u16(&mut self, value: u16) -> anyhow::Result<()> {
|
|
self.writer
|
|
.write_u16(value)
|
|
.await
|
|
.with_context(|| "Failed to write u16 to pcap writer")
|
|
}
|
|
|
|
async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> {
|
|
self.writer
|
|
.write_u32(value)
|
|
.await
|
|
.with_context(|| "Failed to write u32 to pcap writer")
|
|
}
|
|
|
|
async fn global_header(&mut self) -> anyhow::Result<()> {
|
|
self.write_u32(0xa1b2c3d4).await?; // magic number
|
|
self.write_u16(2).await?; // major version
|
|
self.write_u16(4).await?; // minor version
|
|
self.write_u32(0).await?; // timezone (= UTC)
|
|
self.write_u32(0).await?; // accuracy (not used)
|
|
self.write_u32(65535).await?; // maximum packet length
|
|
self.write_u32(101).await?; // link-layer header type (101 = IP)
|
|
self.flush().await
|
|
}
|
|
|
|
async fn packet_header(&mut self, timestamp: Instant, length: usize) -> anyhow::Result<()> {
|
|
assert!(length <= 65535);
|
|
|
|
self.write_u32(timestamp.secs() as u32).await?; // timestamp seconds
|
|
self.write_u32(timestamp.micros() as u32).await?; // timestamp microseconds
|
|
self.write_u32(length as u32).await?; // captured length
|
|
self.write_u32(length as u32).await?; // original length
|
|
Ok(())
|
|
}
|
|
|
|
async fn packet(&mut self, timestamp: Instant, packet: &[u8]) -> anyhow::Result<()> {
|
|
self.packet_header(timestamp, packet.len())
|
|
.await
|
|
.with_context(|| "Failed to write packet header to pcap writer")?;
|
|
self.write(packet)
|
|
.await
|
|
.with_context(|| "Failed to write packet to pcap writer")?;
|
|
self.writer
|
|
.flush()
|
|
.await
|
|
.with_context(|| "Failed to flush pcap writer")?;
|
|
self.flush().await
|
|
}
|
|
}
|
|
|
|
/// Listens on the event bus for IP packets sent from and to the WireGuard tunnel.
|
|
pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> {
|
|
let mut endpoint = bus.new_endpoint();
|
|
let file = File::create(&pcap_file)
|
|
.await
|
|
.with_context(|| "Failed to create pcap file")?;
|
|
let writer = BufWriter::new(file);
|
|
|
|
let mut writer = Pcap { writer };
|
|
writer
|
|
.global_header()
|
|
.await
|
|
.with_context(|| "Failed to write global header to pcap writer")?;
|
|
|
|
info!("Capturing WireGuard IP packets to {}", &pcap_file);
|
|
loop {
|
|
match endpoint.recv().await {
|
|
Event::InboundInternetPacket(_proto, ip) => {
|
|
let instant = Instant::now();
|
|
writer
|
|
.packet(instant, &ip)
|
|
.await
|
|
.with_context(|| "Failed to write inbound IP packet to pcap writer")?;
|
|
}
|
|
Event::OutboundInternetPacket(ip) => {
|
|
let instant = Instant::now();
|
|
writer
|
|
.packet(instant, &ip)
|
|
.await
|
|
.with_context(|| "Failed to write output IP packet to pcap writer")?;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|