diff options
| author | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2025-07-28 17:36:44 +0200 |
|---|---|---|
| committer | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2025-10-03 12:39:27 +0200 |
| commit | 84adeff45f4b2b6092e3b5a22123234248320bb6 (patch) | |
| tree | 06c1ec2323e5e75e0ababd6fd947caa9780414fb | |
| parent | 41142a60af334dba61f57ec74e5e5bd2577a4266 (diff) | |
| download | mullvadvpn-84adeff45f4b2b6092e3b5a22123234248320bb6.tar.xz mullvadvpn-84adeff45f4b2b6092e3b5a22123234248320bb6.zip | |
Add a UDS for wiresharking gotatun multihop traffic
| -rw-r--r-- | Cargo.lock | 32 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-wireguard/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-wireguard/src/boringtun/mod.rs | 76 |
5 files changed, 110 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock index 6bc38cb52e..ba67067ff2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,6 +460,7 @@ dependencies = [ "log", "nix 0.30.1", "parking_lot", + "pcap-file", "pnet_packet 0.35.0", "rand 0.9.2", "rand_core 0.6.4", @@ -494,6 +495,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] +name = "byteorder_slice" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b294e30387378958e8bf8f4242131b930ea615ff81e8cac2440cea0a6013190" +dependencies = [ + "byteorder", +] + +[[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1107,6 +1117,17 @@ dependencies = [ ] [[package]] +name = "derive-into-owned" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d94d81e3819a7b06a8638f448bc6339371ca9b6076a99d4a43eece3c4c923" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "derive-try-from-primitive" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3983,6 +4004,17 @@ dependencies = [ ] [[package]] +name = "pcap-file" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc1f139757b058f9f37b76c48501799d12c9aa0aa4c0d4c980b062ee925d1b2" +dependencies = [ + "byteorder_slice", + "derive-into-owned", + "thiserror 1.0.59", +] + +[[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index e98b126942..dfc1607467 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -15,6 +15,7 @@ workspace = true api-override = ["mullvad-api/api-override"] boringtun = ["talpid-core/boringtun"] staggered-obfuscation = ["mullvad-relay-selector/staggered-obfuscation"] +multihop-pcap = ["talpid-core/multihop-pcap"] [dependencies] anyhow = { workspace = true } diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index f2919169da..0c14671c6e 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [features] boringtun = ["talpid-wireguard/boringtun"] +multihop-pcap = ["talpid-wireguard/multihop-pcap"] [dependencies] anyhow = { workspace = true } diff --git a/talpid-wireguard/Cargo.toml b/talpid-wireguard/Cargo.toml index fb198c9502..306c58e845 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [features] boringtun = ["dep:boringtun", "dep:tun07", "talpid-tunnel/boringtun"] +multihop-pcap = ["boringtun/pcap"] [dependencies] async-trait = "0.1" diff --git a/talpid-wireguard/src/boringtun/mod.rs b/talpid-wireguard/src/boringtun/mod.rs index c854213d71..d1ad0b1587 100644 --- a/talpid-wireguard/src/boringtun/mod.rs +++ b/talpid-wireguard/src/boringtun/mod.rs @@ -31,6 +31,12 @@ use talpid_tunnel::tun_provider::{self, Tun, TunProvider}; use talpid_tunnel_config_client::DaitaSettings; use tun07::{AbstractDevice, AsyncDevice}; +#[cfg(all(feature = "multihop-pcap", target_os = "linux"))] +use boringtun::tun::{ + IpRecv, IpSend, + pcap::{PcapSniffer, PcapStream}, +}; + #[cfg(target_os = "android")] type UdpFactory = AndroidUdpSocketFactory; @@ -38,9 +44,17 @@ type UdpFactory = AndroidUdpSocketFactory; type UdpFactory = UdpSocketFactory; type SinglehopDevice = DeviceHandle<(UdpFactory, Arc<tun07::AsyncDevice>, Arc<tun07::AsyncDevice>)>; -type EntryDevice = DeviceHandle<(UdpFactory, TunChannelTx, TunChannelRx)>; type ExitDevice = DeviceHandle<(PacketChannelUdp, Arc<AsyncDevice>, Arc<AsyncDevice>)>; +#[cfg(not(all(feature = "multihop-pcap", target_os = "linux")))] +type EntryDevice = DeviceHandle<(UdpFactory, TunChannelTx, TunChannelRx)>; +#[cfg(all(feature = "multihop-pcap", target_os = "linux"))] +type EntryDevice = DeviceHandle<( + UdpFactory, + PcapSniffer<TunChannelTx>, + PcapSniffer<TunChannelRx>, +)>; + const PACKET_CHANNEL_CAPACITY: usize = 100; pub struct BoringTun { @@ -261,6 +275,11 @@ async fn create_devices( #[cfg(not(target_os = "android"))] let factory = UdpSocketFactory; + // Hacky way of dumping entry<->exit traffic to a unix socket which wireshark can read. + // See docs on wrap_in_pcap_sniffer for an explanation. + #[cfg(all(feature = "multihop-pcap", target_os = "linux"))] + let (tun_tx, tun_rx) = wrap_in_pcap_sniffer(tun_tx, tun_rx); + let entry_device = EntryDevice::new(factory, tun_tx, tun_rx, boringtun_entry_config).await; let private_key = &config.tunnel.private_key; @@ -570,3 +589,58 @@ pub fn get_tunnel_for_userspace( last_error.expect("Should be collected in loop"), )) } + +/// Wrap `ip_send` and `ip_recv` in [PcapSniffer]s for use with Wireshark. +/// +/// With userspace multihop, the [ExitDevice] communicates with the network through the +/// [EntryDevice], without going through the kernel. That means there is no network interface +/// for wireshark to sniff. By interposing [PcapSniffer]s, any packets that are sent to `ip_send`, +/// or received from `ip_recv`, will _also_ be written to a unix socket, encoded using the pcap +/// file format. +/// +/// The unix socket can be opened in wireshark to inspect communication with the [ExitDevice]s peer. +/// ```sh +/// wireshark -k -i /tmp/mullvad-multihop.pcap +/// ``` +#[cfg(all(feature = "multihop-pcap", target_os = "linux"))] +fn wrap_in_pcap_sniffer<S, R>(ip_send: S, ip_recv: R) -> (PcapSniffer<S>, PcapSniffer<R>) +where + S: IpSend, + R: IpRecv, +{ + use std::{ + fs, + os::unix::{fs::PermissionsExt, net::UnixListener}, + sync::LazyLock, + time::Instant, + }; + + const SOCKET_PATH: &str = "/tmp/mullvad-multihop.pcap"; + + /// The global pcap writer. We initialize it once so that we can re-use the same unix socket + /// for the entire lifetime of the application. + static WRITER: LazyLock<PcapStream> = LazyLock::new(|| { + log::warn!("Binding pcap socket to {SOCKET_PATH:?}"); + let _ = fs::remove_file(SOCKET_PATH); + let listener = UnixListener::bind(SOCKET_PATH).unwrap(); + let _ = fs::set_permissions(SOCKET_PATH, fs::Permissions::from_mode(0o777)); + + log::warn!("Waiting for connection to pcap socket"); + log::warn!(" wireshark -k -i {SOCKET_PATH:?}"); + let (stream, _) = listener + .accept() + .expect("Error while waiting for pcap listener"); + + PcapStream::new(Box::new(stream)) + }); + + let start_time = Instant::now(); + + let w = WRITER.clone(); + let ip_send = PcapSniffer::new(ip_send, w, start_time); + + let w = WRITER.clone(); + let ip_recv = PcapSniffer::new(ip_recv, w, start_time); + + (ip_send, ip_recv) +} |
