summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-09-10 15:19:53 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-09-16 14:18:32 +0200
commitaa61b9a4fa047e6027edc2d980942d4a5993ba3c (patch)
treebbaa2cbe392dd81e8c753c21a73bbe9627abd467
parent31ad422b0edb0322e1667d4b5fc89a86ba375399 (diff)
downloadmullvadvpn-aa61b9a4fa047e6027edc2d980942d4a5993ba3c.tar.xz
mullvadvpn-aa61b9a4fa047e6027edc2d980942d4a5993ba3c.zip
Run VpnService.protect() on Shadowsocks socket before connecting
-rw-r--r--Cargo.lock1
-rw-r--r--tunnel-obfuscation/Cargo.toml3
-rw-r--r--tunnel-obfuscation/src/shadowsocks.rs79
3 files changed, 63 insertions, 20 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 17ab11fd77..28d627dcd0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4689,6 +4689,7 @@ version = "0.0.0"
dependencies = [
"async-trait",
"log",
+ "nix 0.23.2",
"shadowsocks",
"thiserror",
"tokio",
diff --git a/tunnel-obfuscation/Cargo.toml b/tunnel-obfuscation/Cargo.toml
index b5f71fdb62..2a984c0104 100644
--- a/tunnel-obfuscation/Cargo.toml
+++ b/tunnel-obfuscation/Cargo.toml
@@ -17,3 +17,6 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "net", "io-util"] }
udp-over-tcp = { git = "https://github.com/mullvad/udp-over-tcp", rev = "87936ac29b68b902565955f138ab02294bcc8593" }
shadowsocks = { workspace = true }
+
+[target.'cfg(target_os="linux")'.dependencies]
+nix = "0.23"
diff --git a/tunnel-obfuscation/src/shadowsocks.rs b/tunnel-obfuscation/src/shadowsocks.rs
index 96ed0b47ca..c529f65a19 100644
--- a/tunnel-obfuscation/src/shadowsocks.rs
+++ b/tunnel-obfuscation/src/shadowsocks.rs
@@ -1,15 +1,24 @@
+//! Shadowsocks obfuscation
+//!
+//! Note: It is important not to connect to the shadowsocks endpoint right away. The remote socket
+//! must be protected in `VpnService` so that the socket is not routed through the tunnel.
+
use super::Obfuscator;
use async_trait::async_trait;
+#[cfg(target_os = "linux")]
+use nix::sys::socket::{setsockopt, sockopt};
use shadowsocks::{
config::{ServerConfig, ServerType},
context::Context,
crypto::CipherKind,
- net::ConnectOpts,
- relay::{udprelay::proxy_socket::ProxySocketError, Address},
+ relay::{
+ udprelay::proxy_socket::{ProxySocketError, UdpSocketType},
+ Address,
+ },
ProxySocket,
};
-#[cfg(target_os = "android")]
-use std::os::unix::io::AsRawFd;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use std::os::fd::AsRawFd;
use std::{io, net::SocketAddr, sync::Arc};
use tokio::{net::UdpSocket, sync::oneshot};
@@ -23,6 +32,13 @@ pub enum Error {
/// Failed to bind local UDP socket
#[error("Failed to bind UDP socket")]
BindUdp(#[source] io::Error),
+ /// Failed to bind remote UDP socket
+ #[error("Failed to bind remote UDP socket")]
+ BindRemoteUdp(#[source] io::Error),
+ /// Failed to set fwmark
+ #[cfg(target_os = "linux")]
+ #[error("Failed to set fwmark")]
+ SetFwmark(#[source] nix::Error),
/// Missing UDP listener address
#[error("Failed to retrieve UDP socket bind address")]
GetUdpLocalAddress(#[source] io::Error),
@@ -34,7 +50,7 @@ pub enum Error {
CreateUdpStream(#[source] io::Error),
/// Failed to connect to Shadowsocks endpoint
#[error("Failed to connect to Shadowsocks endpoint")]
- ConnectShadowsocks(#[from] ProxySocketError),
+ ConnectShadowsocks(#[from] io::Error),
/// Failed to receive remote socket descriptor
#[error("Failed to receive remote socket descriptor")]
ReceiveRemoteFd,
@@ -66,18 +82,18 @@ impl Shadowsocks {
let (shutdown_tx, shutdown_rx) = oneshot::channel();
- let shadowsocks = connect_shadowsocks_client(
- settings.shadowsocks_endpoint,
+ let remote_socket = create_shadowsocks_socket(
#[cfg(target_os = "linux")]
settings.fwmark,
)
.await?;
#[cfg(target_os = "android")]
- let outbound_fd = shadowsocks.as_raw_fd();
+ let outbound_fd = remote_socket.as_raw_fd();
let server = tokio::spawn(run_forwarding(
- shadowsocks,
+ settings.shadowsocks_endpoint,
+ remote_socket,
local_udp_socket,
settings.wireguard_endpoint,
shutdown_rx,
@@ -94,7 +110,8 @@ impl Shadowsocks {
}
async fn run_forwarding(
- shadowsocks: ProxySocket,
+ shadowsocks_endpoint: SocketAddr,
+ remote_socket: UdpSocket,
local_udp_socket: UdpSocket,
wireguard_endpoint: SocketAddr,
shutdown_rx: oneshot::Receiver<()>,
@@ -103,9 +120,11 @@ async fn run_forwarding(
.await
.map_err(Error::WaitForUdpClient)?;
- let local_udp = Arc::new(local_udp_socket);
+ let shadowsocks = connect_shadowsocks(remote_socket, shadowsocks_endpoint).await?;
let shadowsocks = Arc::new(shadowsocks);
+ let local_udp = Arc::new(local_udp_socket);
+
let wg_addr = Address::SocketAddress(wireguard_endpoint);
let mut client = tokio::spawn(handle_outgoing(
@@ -129,22 +148,42 @@ async fn run_forwarding(
Ok(())
}
-async fn connect_shadowsocks_client(
+async fn connect_shadowsocks(
+ remote_socket: UdpSocket,
shadowsocks_endpoint: SocketAddr,
- #[cfg(target_os = "linux")] fwmark: Option<u32>,
-) -> std::result::Result<ProxySocket, ProxySocketError> {
+) -> std::result::Result<ProxySocket, Error> {
+ remote_socket
+ .connect(shadowsocks_endpoint)
+ .await
+ .map_err(Error::ConnectShadowsocks)?;
+
let ss_context = Context::new_shared(ServerType::Local);
let ss_config: ServerConfig = ServerConfig::new(
shadowsocks_endpoint,
SHADOWSOCKS_PASSWORD,
SHADOWSOCKS_CIPHER,
);
- let connect_opts = ConnectOpts {
- #[cfg(target_os = "linux")]
- fwmark,
- ..Default::default()
- };
- ProxySocket::connect_with_opts(ss_context, &ss_config, &connect_opts).await
+ Ok(ProxySocket::from_socket(
+ UdpSocketType::Client,
+ ss_context,
+ &ss_config,
+ remote_socket,
+ ))
+}
+
+async fn create_shadowsocks_socket(
+ #[cfg(target_os = "linux")] fwmark: Option<u32>,
+) -> std::result::Result<UdpSocket, Error> {
+ let socket = UdpSocket::bind("0.0.0.0:0")
+ .await
+ .map_err(Error::BindRemoteUdp)?;
+
+ #[cfg(target_os = "linux")]
+ if let Some(fwmark) = fwmark {
+ setsockopt(socket.as_raw_fd(), sockopt::Mark, &fwmark).map_err(Error::SetFwmark)?;
+ }
+
+ Ok(socket)
}
async fn create_local_udp_socket(ipv4: bool) -> Result<(UdpSocket, SocketAddr)> {