Skip to content

Commit

Permalink
Make the daemon aware if ipv4 and/or ipv6 is available
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkusPettersson98 authored and Pururun committed Oct 22, 2024
1 parent 081f9ab commit 730feb1
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,55 @@ import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import kotlin.properties.Delegates.observable
import co.touchlab.kermit.Logger
import java.net.Inet4Address
import java.net.Inet6Address
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import net.mullvad.talpid.model.NetworkInfo

class ConnectivityListener {
private val availableNetworks = HashSet<Network>()
private val availableNetworks = MutableStateFlow(emptySet<Network>())

private val callback =
object : NetworkCallback() {
override fun onAvailable(network: Network) {
availableNetworks.add(network)
isConnected = true
availableNetworks.update { it + network }
val info = availableNetworks.value.info()
notifyConnectivityChange(info.hasIpV4, info.hasIpV6, senderAddress)
}

override fun onLost(network: Network) {
availableNetworks.remove(network)
isConnected = availableNetworks.isNotEmpty()
availableNetworks.update { it - network }
val info = availableNetworks.value.info()
notifyConnectivityChange(info.hasIpV4, info.hasIpV6, senderAddress)
}
}

private lateinit var connectivityManager: ConnectivityManager

// Used by JNI
var isConnected by
observable(false) { _, oldValue, newValue ->
if (newValue != oldValue) {
if (senderAddress != 0L) {
notifyConnectivityChange(newValue, senderAddress)
}
}
}

var senderAddress = 0L

// Used by jni
@Suppress("unused") fun isConnected(): Boolean = availableNetworks.value.info().isConnected

fun Set<Network>.info(): NetworkInfo {
return this.map { network ->
val addresses =
connectivityManager.getLinkProperties(network)?.linkAddresses ?: emptyList()
NetworkInfo(
hasIpV4 = addresses.any { it.address is Inet4Address },
hasIpV6 = addresses.any { it.address is Inet6Address },
)
}
.reduceOrNull { acc, networkInfo ->
NetworkInfo(
hasIpV4 = acc.hasIpV4 || networkInfo.hasIpV4,
hasIpV6 = acc.hasIpV6 || networkInfo.hasIpV6,
)
} ?: NetworkInfo(hasIpV4 = false, hasIpV6 = false)
}

fun register(context: Context) {
val request =
NetworkRequest.Builder()
Expand All @@ -64,7 +81,7 @@ class ConnectivityListener {
senderAddress = 0L
}

private external fun notifyConnectivityChange(isConnected: Boolean, senderAddress: Long)
private external fun notifyConnectivityChange(ipv4: Boolean, ipv6: Boolean, senderAddress: Long)

private external fun destroySender(senderAddress: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.mullvad.talpid.model

data class NetworkInfo(val hasIpV4: Boolean, val hasIpV6: Boolean) {
val isConnected = hasIpV4 || hasIpV6
}
13 changes: 13 additions & 0 deletions mullvad-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,13 @@ pub enum DaemonCommand {
ExportJsonSettings(ResponseTx<String, settings::patch::Error>),
/// Request the current feature indicators.
GetFeatureIndicators(oneshot::Sender<FeatureIndicators>),
/// Update the IPv4 and IPv6 connectivity status.
#[cfg(target_os = "android")]
UpdateNetworkAvailability(
ResponseTx<(), Error>,
bool, // IPv4
bool, // IPv6
),
}

/// All events that can happen in the daemon. Sent from various threads and exposed interfaces.
Expand Down Expand Up @@ -1336,6 +1343,12 @@ impl Daemon {
ApplyJsonSettings(tx, blob) => self.on_apply_json_settings(tx, blob).await,
ExportJsonSettings(tx) => self.on_export_json_settings(tx),
GetFeatureIndicators(tx) => self.on_get_feature_indicators(tx),
#[cfg(target_os = "android")]
UpdateNetworkAvailability(
_tx,
_ipv4, // IPv4
_ipv6, // IPv6
) => {}
}
}

Expand Down
6 changes: 4 additions & 2 deletions mullvad-daemon/src/tunnel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,13 @@ impl InnerParametersGenerator {
async fn generate(
&mut self,
retry_attempt: u32,
ipv4: bool,
ipv6: bool,
) -> Result<TunnelParameters, Error> {
let data = self.device().await?;
let selected_relay = self
.relay_selector
.get_relay(retry_attempt as usize, RuntimeParameters { ipv6 })?;
.get_relay(retry_attempt as usize, RuntimeParameters { ipv4, ipv6 })?;

match selected_relay {
#[cfg(not(target_os = "android"))]
Expand Down Expand Up @@ -299,13 +300,14 @@ impl TunnelParametersGenerator for ParametersGenerator {
fn generate(
&mut self,
retry_attempt: u32,
ipv4: bool,
ipv6: bool,
) -> Pin<Box<dyn Future<Output = Result<TunnelParameters, ParameterGenerationError>>>> {
let generator = self.0.clone();
Box::pin(async move {
let mut inner = generator.lock().await;
inner
.generate(retry_attempt, ipv6)
.generate(retry_attempt, ipv4, ipv6)
.await
.inspect_err(|error| {
log::error!(
Expand Down
43 changes: 40 additions & 3 deletions mullvad-relay-selector/src/relay_selector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,28 @@ pub struct AdditionalWireguardConstraints {
/// Values which affect the choice of relay but are only known at runtime.
#[derive(Clone, Debug)]
pub struct RuntimeParameters {
/// Whether IPv4 is available
pub ipv4: bool,
/// Whether IPv6 is available
pub ipv6: bool,
}

impl RuntimeParameters {
/// Return whether a given [query][`RelayQuery`] is valid given the current runtime parameters
pub fn compatible(&self, query: &RelayQuery) -> bool {
if !self.ipv4 {
let must_use_ipv4 = matches!(
query.wireguard_constraints().ip_version,
Constraint::Only(talpid_types::net::IpVersion::V4)
);
if must_use_ipv4 {
log::trace!(
"{query:?} is incompatible with {self:?} due to IPv4 not being available"
);
return false;
}
}

if !self.ipv6 {
let must_use_ipv6 = matches!(
query.wireguard_constraints().ip_version,
Expand All @@ -168,7 +183,10 @@ impl RuntimeParameters {
#[allow(clippy::derivable_impls)]
impl Default for RuntimeParameters {
fn default() -> Self {
RuntimeParameters { ipv6: false }
RuntimeParameters {
ipv4: false,
ipv6: false,
}
}
}

Expand Down Expand Up @@ -528,13 +546,32 @@ impl RelaySelector {
SpecializedSelectorConfig::Normal(normal_config) => {
let parsed_relays = &self.parsed_relays.lock().unwrap();
// Merge user preferences with the relay selector's default preferences.
let query = Self::pick_and_merge_query(
let mut query = Self::pick_and_merge_query(
retry_attempt,
retry_order,
runtime_params,
runtime_params.clone(),
&normal_config,
parsed_relays,
)?;
// TODO: Remove
// Dirty hack to set IPv4 or IPv6 if one or the other is available.
dbg!(&query);
#[cfg(target_os = "android")]
{
let mut wireguard_constraints = query.wireguard_constraints().clone();
if wireguard_constraints.ip_version.is_any() {
if runtime_params.ipv4 && !runtime_params.ipv6 {
wireguard_constraints.ip_version =
Constraint::Only(talpid_types::net::IpVersion::V4)
}
if runtime_params.ipv6 && !runtime_params.ipv4 {
wireguard_constraints.ip_version =
Constraint::Only(talpid_types::net::IpVersion::V6)
}
}
query.set_wireguard_constraints(wireguard_constraints);
}
dbg!(&query);
Self::get_relay_inner(&query, parsed_relays, normal_config.custom_lists)
}
}
Expand Down
8 changes: 7 additions & 1 deletion mullvad-relay-selector/tests/relay_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,13 @@ fn test_retry_order() {
let relay_selector = default_relay_selector();
for (retry_attempt, query) in RETRY_ORDER.iter().enumerate() {
let relay = relay_selector
.get_relay(retry_attempt, RuntimeParameters { ipv6: true })
.get_relay(
retry_attempt,
RuntimeParameters {
ipv4: true,
ipv6: true,
},
)
.unwrap_or_else(|_| panic!("Retry attempt {retry_attempt} did not yield any relay"));
// For each relay, cross-check that the it has the expected tunnel protocol
let tunnel_type = tunnel_type(&unwrap_relay(relay.clone()));
Expand Down
79 changes: 49 additions & 30 deletions talpid-core/src/offline/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,33 +94,37 @@ impl MonitorHandle {
#[allow(clippy::unused_async)]
pub async fn connectivity(&self) -> Connectivity {
self.get_is_connected()
.map(|connected| Connectivity::Status { connected })
.unwrap_or_else(|error| {
.map(|connected| Connectivity::Status {
ipv4: connected,
ipv6: connected,
})
.inspect_err(|error| {
log::error!(
"{}",
error.display_chain_with_msg("Failed to check connectivity status")
);
Connectivity::PresumeOnline
})
.unwrap_or(Connectivity::PresumeOnline)
}

fn get_is_connected(&self) -> Result<bool, Error> {
let is_connected = self.call_method(
"isConnected",
"()Z",
&[],
JavaType::Primitive(Primitive::Boolean),
)?;

match is_connected {
JValue::Bool(JNI_TRUE) => Ok(true),
JValue::Bool(_) => Ok(false),
value => Err(Error::InvalidMethodResult(
"ConnectivityListener",
"isConnected",
format!("{:?}", value),
)),
}
Ok(true)
// let is_connected = self.call_method(
// "isConnected",
// "()Z",
// &[],
// JavaType::Primitive(Primitive::Boolean),
// )?;

// match is_connected {
// JValue::Bool(JNI_TRUE) => Ok(true),
// JValue::Bool(_) => Ok(false),
// value => Err(Error::InvalidMethodResult(
// "ConnectivityListener",
// "isConnected",
// format!("{:?}", value),
// )),
// }
}

fn set_sender(&self, sender: Weak<UnboundedSender<Connectivity>>) -> Result<(), Error> {
Expand Down Expand Up @@ -172,17 +176,26 @@ impl MonitorHandle {
pub extern "system" fn Java_net_mullvad_talpid_ConnectivityListener_notifyConnectivityChange(
_: JNIEnv<'_>,
_: JObject<'_>,
connected: jboolean,
ipv4: jboolean,
ipv6: jboolean,
sender_address: jlong,
) {
let connected = JNI_TRUE == connected;
let sender_ref = Box::leak(unsafe { get_sender_from_address(sender_address) });
if let Some(sender) = sender_ref.upgrade() {
if sender
.unbounded_send(Connectivity::Status { connected })
.is_err()
{
log::warn!("Failed to send offline change event");
let ipv4 = JNI_TRUE == ipv4;
let ipv6 = JNI_TRUE == ipv6;
let sender_ptr = unsafe { get_sender_from_address(sender_address) }.map(Box::leak);
match sender_ptr {
Some(sender_ref) => {
if let Some(sender) = sender_ref.upgrade() {
if sender
.unbounded_send(Connectivity::Status { ipv4, ipv6 })
.is_err()
{
log::warn!("Failed to send offline change event");
}
}
}
None => {
log::error!("sender was null pointer");
}
}
}
Expand All @@ -198,8 +211,14 @@ pub extern "system" fn Java_net_mullvad_talpid_ConnectivityListener_destroySende
let _ = unsafe { get_sender_from_address(sender_address) };
}

unsafe fn get_sender_from_address(address: jlong) -> Box<Weak<UnboundedSender<Connectivity>>> {
Box::from_raw(address as *mut Weak<UnboundedSender<Connectivity>>)
unsafe fn get_sender_from_address(
address: jlong,
) -> Option<Box<Weak<UnboundedSender<Connectivity>>>> {
let raw = address as *mut Weak<UnboundedSender<Connectivity>>;
if raw.is_null() {
return None;
}
Some(Box::from_raw(raw))
}

#[allow(clippy::unused_async)]
Expand Down
4 changes: 2 additions & 2 deletions talpid-core/src/offline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl MonitorHandle {
}

pub async fn spawn_monitor(
sender: UnboundedSender<Connectivity>,
offline_tx: UnboundedSender<Connectivity>,
#[cfg(not(target_os = "android"))] route_manager: RouteManagerHandle,
#[cfg(target_os = "linux")] fwmark: Option<u32>,
#[cfg(target_os = "android")] android_context: AndroidContext,
Expand All @@ -50,7 +50,7 @@ pub async fn spawn_monitor(
None
} else {
imp::spawn_monitor(
sender,
offline_tx,
#[cfg(not(target_os = "android"))]
route_manager,
#[cfg(target_os = "linux")]
Expand Down
12 changes: 7 additions & 5 deletions talpid-core/src/tunnel_state_machine/connecting_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ impl ConnectingState {
}
return ErrorState::enter(shared_values, ErrorStateCause::IsOffline);
}
match shared_values.runtime.block_on(
shared_values
.tunnel_parameters_generator
.generate(retry_attempt, shared_values.connectivity.has_ipv6()),
) {
match shared_values
.runtime
.block_on(shared_values.tunnel_parameters_generator.generate(
retry_attempt,
shared_values.connectivity.has_ipv4(),
shared_values.connectivity.has_ipv6(),
)) {
Err(err) => {
ErrorState::enter(shared_values, ErrorStateCause::TunnelParameterError(err))
}
Expand Down
1 change: 1 addition & 0 deletions talpid-core/src/tunnel_state_machine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ pub trait TunnelParametersGenerator: Send + 'static {
fn generate(
&mut self,
retry_attempt: u32,
ipv4: bool,
ipv6: bool,
) -> Pin<Box<dyn Future<Output = Result<TunnelParameters, ParameterGenerationError>>>>;
}
Expand Down
Loading

0 comments on commit 730feb1

Please sign in to comment.