Skip to content

Commit

Permalink
Take obfuscation overhead into account when setting MTU
Browse files Browse the repository at this point in the history
  • Loading branch information
hulthe committed Oct 18, 2024
1 parent f72638a commit f72944e
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 22 deletions.
9 changes: 8 additions & 1 deletion talpid-wireguard/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,16 @@ impl WireguardMonitor {
let endpoint_addrs: Vec<IpAddr> = config.peers().map(|peer| peer.endpoint.ip()).collect();

let (close_obfs_sender, close_obfs_listener) = sync_mpsc::channel();
// Start obfuscation server and patch the WireGuard config to point the endpoint to it.
let obfuscator = args
.runtime
.block_on(obfuscation::apply_obfuscation_config(
&mut config,
close_obfs_sender.clone(),
))?;
if let Some(obfuscator) = obfuscator.as_ref() {
config.mtu = config.mtu.saturating_sub(obfuscator.packet_overhead());
}

#[cfg(target_os = "windows")]
let (setup_done_tx, setup_done_rx) = mpsc::channel(0);
Expand Down Expand Up @@ -374,14 +378,17 @@ impl WireguardMonitor {
args: TunnelArgs<'_, F>,
) -> Result<WireguardMonitor> {
let (close_obfs_sender, close_obfs_listener) = sync_mpsc::channel();
// TODO: Document the side effect of starting this before opening the tunnel!
// Start obfuscation server and patch the WireGuard config to point the endpoint to it.
let obfuscator = args
.runtime
.block_on(obfuscation::apply_obfuscation_config(
&mut config,
close_obfs_sender.clone(),
args.tun_provider.clone(),
))?;
if let Some(obfuscator) = obfuscator.as_ref() {
config.mtu = config.mtu.saturating_sub(obfuscator.packet_overhead());
}

let should_negotiate_ephemeral_peer = config.quantum_resistant || config.daita;
let tunnel = Self::open_tunnel(
Expand Down
32 changes: 11 additions & 21 deletions talpid-wireguard/src/obfuscation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,15 @@ pub async fn apply_obfuscation_config(
#[cfg(target_os = "linux")]
config.fwmark,
);
apply_obfuscation_config_inner(
config,
settings,
close_msg_sender,
#[cfg(target_os = "android")]
tun_provider,
)
.await
.map(Some)
}

async fn apply_obfuscation_config_inner(
config: &mut Config,
settings: ObfuscationSettings,
close_msg_sender: sync_mpsc::Sender<CloseMsg>,
#[cfg(target_os = "android")] tun_provider: Arc<Mutex<TunProvider>>,
) -> Result<ObfuscatorHandle> {
log::trace!("Obfuscation settings: {settings:?}");

let obfuscator = create_obfuscator(&settings)
.await
.map_err(Error::ObfuscationError)?;

let packet_overhead = obfuscator.packet_overhead();

#[cfg(target_os = "android")]
bypass_vpn(tun_provider, obfuscator.remote_socket_fd()).await;

Expand All @@ -76,7 +62,10 @@ async fn apply_obfuscation_config_inner(
}
});

Ok(ObfuscatorHandle::new(obfuscation_task))
Ok(Some(ObfuscatorHandle {
obfuscation_task,
packet_overhead,
}))
}

/// Patch the first peer in the WireGuard configuration to use the local proxy endpoint
Expand Down Expand Up @@ -129,16 +118,17 @@ async fn bypass_vpn(
/// Simple wrapper that automatically cancels the future which runs an obfuscator.
pub struct ObfuscatorHandle {
obfuscation_task: tokio::task::JoinHandle<()>,
packet_overhead: u16,
}

impl ObfuscatorHandle {
pub fn new(obfuscation_task: tokio::task::JoinHandle<()>) -> Self {
Self { obfuscation_task }
}

pub fn abort(&self) {
self.obfuscation_task.abort();
}

pub fn packet_overhead(&self) -> u16 {
self.packet_overhead
}
}

impl Drop for ObfuscatorHandle {
Expand Down
5 changes: 5 additions & 0 deletions tunnel-obfuscation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ pub trait Obfuscator: Send {
/// Returns the file descriptor of the outbound socket.
#[cfg(target_os = "android")]
fn remote_socket_fd(&self) -> std::os::unix::io::RawFd;

/// The overhead (in bytes) of this obfuscation protocol.
///
/// This is used when deciding on MTUs.
fn packet_overhead(&self) -> u16;
}

#[derive(Debug)]
Expand Down
15 changes: 15 additions & 0 deletions tunnel-obfuscation/src/shadowsocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub enum Error {

pub struct Shadowsocks {
udp_client_addr: SocketAddr,
wireguard_endpoint: SocketAddr,
server: tokio::task::JoinHandle<Result<()>>,
// The receiver will implicitly shut down when this is dropped
_shutdown_tx: oneshot::Sender<()>,
Expand Down Expand Up @@ -101,6 +102,7 @@ impl Shadowsocks {

Ok(Shadowsocks {
udp_client_addr,
wireguard_endpoint: settings.wireguard_endpoint,
server,
_shutdown_tx: shutdown_tx,
#[cfg(target_os = "android")]
Expand Down Expand Up @@ -285,6 +287,19 @@ impl Obfuscator for Shadowsocks {
fn remote_socket_fd(&self) -> std::os::unix::io::RawFd {
self.outbound_fd
}

fn packet_overhead(&self) -> u16 {
// This math relies on the packet structure of Shadowsocks AEAD UDP packets.
// https://shadowsocks.org/doc/aead.html
// Those packets look like this: [salt][address][payload][tag]
debug_assert!(SHADOWSOCKS_CIPHER.is_aead());

let overhead = SHADOWSOCKS_CIPHER.salt_len()
+ Address::from(self.wireguard_endpoint).serialized_len()
+ SHADOWSOCKS_CIPHER.tag_len();

u16::try_from(overhead).expect("packet overhead is less than u16::MAX")
}
}

/// Return whether retrying is a lost cause
Expand Down
10 changes: 10 additions & 0 deletions tunnel-obfuscation/src/udp2tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,14 @@ impl Obfuscator for Udp2Tcp {
fn remote_socket_fd(&self) -> std::os::unix::io::RawFd {
self.instance.remote_tcp_fd()
}

fn packet_overhead(&self) -> u16 {
// TCP header - UDP header + udp-over-tcp header
let overhead = 60 - 20 +
// the payload size is prepended to each obfuscated packet
// TODO: Make `HEADER_LEN` constant public in udp-over-tcp lib and use it instead
size_of::<u16>();

u16::try_from(overhead).expect("packet overhead is less than u16::MAX")
}
}

0 comments on commit f72944e

Please sign in to comment.