mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-08 23:58:31 -04:00
Merge pull request #30 from aramperes/pcap
This commit is contained in:
commit
1aadea86d5
5 changed files with 141 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
|||
/.idea
|
||||
.envrc
|
||||
*.log
|
||||
*.pcap
|
||||
|
|
11
README.md
11
README.md
|
@ -133,6 +133,17 @@ INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.
|
|||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
||||
### Packet Capture
|
||||
|
||||
For debugging purposes, you can enable the capture of IP packets sent between onetun and the WireGuard peer.
|
||||
The output is a libpcap capture file that can be viewed with Wireshark.
|
||||
|
||||
```
|
||||
$ ./onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080
|
||||
INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap
|
||||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
||||
## Download
|
||||
|
||||
Normally I would publish `onetun` to crates.io. However, it depends on some features
|
||||
|
|
|
@ -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