mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 11:38:32 -04:00
Add optional IP packet capture for WireGuard tunnel
This commit is contained in:
parent
953bc18279
commit
ff0f5b967e
5 changed files with 141 additions and 1 deletions
|
@ -20,6 +20,7 @@ pub struct Config {
|
|||
pub(crate) max_transmission_unit: usize,
|
||||
pub(crate) log: String,
|
||||
pub(crate) warnings: Vec<String>,
|
||||
pub(crate) pcap_file: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -96,7 +97,13 @@ impl Config {
|
|||
.long("log")
|
||||
.env("ONETUN_LOG")
|
||||
.default_value("info")
|
||||
.help("Configures the log level and format.")
|
||||
.help("Configures the log level and format."),
|
||||
Arg::with_name("pcap")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.long("pcap")
|
||||
.env("ONETUN_PCAP")
|
||||
.help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file.")
|
||||
]).get_matches();
|
||||
|
||||
// Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs
|
||||
|
@ -174,6 +181,7 @@ impl Config {
|
|||
max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit"))
|
||||
.with_context(|| "Invalid max-transmission-unit value")?,
|
||||
log: matches.value_of("log").unwrap_or_default().into(),
|
||||
pcap_file: matches.value_of("pcap").map(String::from),
|
||||
warnings,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use crate::wg::WireGuardTunnel;
|
|||
|
||||
pub mod config;
|
||||
pub mod events;
|
||||
pub mod pcap;
|
||||
pub mod tunnel;
|
||||
pub mod virtual_device;
|
||||
pub mod virtual_iface;
|
||||
|
@ -37,6 +38,12 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
let bus = Bus::default();
|
||||
|
||||
if let Some(pcap_file) = config.pcap_file.clone() {
|
||||
// Start packet capture
|
||||
let bus = bus.clone();
|
||||
tokio::spawn(async move { pcap::capture(pcap_file, bus).await });
|
||||
}
|
||||
|
||||
let wg = WireGuardTunnel::new(&config, bus.clone())
|
||||
.await
|
||||
.with_context(|| "Failed to initialize WireGuard tunnel")?;
|
||||
|
|
113
src/pcap.rs
Normal file
113
src/pcap.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
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")?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue