Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the daemon aware if ipv4 and/or ipv6 is available #7038

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading