Skip to content

Commit bb4319c

Browse files
committed
test(shared): add unit tests for tun MTU resolution and validate android input
Add comprehensive tests covering the `resolve_tun_mtu()` priority chain: explicit config > env var > default, with out-of-range and non-numeric values falling through gracefully. Remove unused `DEFAULT_TUN_MTU` import in linux vpn_core. Harden android SettingsScreen with a `parseValidatedMtu()` helper that rejects values outside 1280–1360 with a user-facing Toast instead of silently passing invalid input through the onBack callback.
1 parent f48e36e commit bb4319c

4 files changed

Lines changed: 99 additions & 5 deletions

File tree

android/app/src/main/java/com/mavi/vpn/ui/screens/SettingsScreen.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.mavi.vpn.data.InstalledApp
2323
import com.mavi.vpn.ui.components.drawableToBitmap
2424
import com.mavi.vpn.ui.components.toImageBitmap
2525
import com.mavi.vpn.viewmodel.VpnViewModel
26+
import android.widget.Toast
2627
import kotlinx.coroutines.Dispatchers
2728
import kotlinx.coroutines.withContext
2829

@@ -43,6 +44,16 @@ fun SettingsScreen(
4344
var censorshipResistant by remember { mutableStateOf(initialCensorshipResistant) }
4445
var http3Framing by remember { mutableStateOf(initialHttp3Framing) }
4546
var vpnMtuText by remember { mutableStateOf(if (initialVpnMtu > 0) initialVpnMtu.toString() else "") }
47+
48+
fun parseValidatedMtu(): Int {
49+
if (vpnMtuText.isBlank()) return 0
50+
val value = vpnMtuText.toIntOrNull() ?: return 0
51+
if (value !in 1280..1360) {
52+
Toast.makeText(context, "VPN MTU must be between 1280 and 1360", Toast.LENGTH_SHORT).show()
53+
return 0
54+
}
55+
return value
56+
}
4657

4758
val selectedPackages = remember {
4859
mutableStateListOf<String>().apply {
@@ -91,7 +102,7 @@ fun SettingsScreen(
91102
verticalAlignment = Alignment.CenterVertically,
92103
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)
93104
) {
94-
IconButton(onClick = { onBack(mode, selectedPackages.joinToString(","), censorshipResistant, http3Framing, vpnMtuText.toIntOrNull() ?: 0) }) {
105+
IconButton(onClick = { onBack(mode, selectedPackages.joinToString(","), censorshipResistant, http3Framing, parseValidatedMtu()) }) {
95106
Icon(Icons.Default.ArrowBack, contentDescription = "Back", tint = Color.White)
96107
}
97108
Text(
@@ -275,7 +286,7 @@ fun SettingsScreen(
275286
Spacer(modifier = Modifier.height(16.dp))
276287

277288
Button(
278-
onClick = { onBack(mode, selectedPackages.joinToString(","), censorshipResistant, http3Framing, vpnMtuText.toIntOrNull() ?: 0) },
289+
onClick = { onBack(mode, selectedPackages.joinToString(","), censorshipResistant, http3Framing, parseValidatedMtu()) },
279290
modifier = Modifier.fillMaxWidth().height(50.dp),
280291
shape = RoundedCornerShape(12.dp)
281292
) {

gui/src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ mod tests {
406406
assert!(!conn.http3_framing);
407407
assert!(!conn.censorship_resistant);
408408
assert!(conn.kc_auth.is_none());
409+
assert!(conn.vpn_mtu.is_none());
409410
}
410411

411412
#[test]

linux/src/vpn_core.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use shared::{
1414
icmp,
1515
ipc::Config,
1616
masque::{self, CAPSULE_MAVI_CONFIG},
17-
ControlMessage, DEFAULT_TUN_MTU, QUIC_OVERHEAD_BYTES, resolve_tun_mtu,
17+
ControlMessage, QUIC_OVERHEAD_BYTES, resolve_tun_mtu,
1818
};
1919
use std::net::{Ipv4Addr, Ipv6Addr};
2020
use std::sync::atomic::{AtomicBool, Ordering};

shared/src/lib.rs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ pub const MAX_TUN_MTU: u16 = 1360;
3232
/// Resolve the inner TUN MTU from an explicit config value, the `VPN_MTU`
3333
/// environment variable, or the compiled-in default (1280). The explicit
3434
/// `vpn_mtu` parameter takes highest priority; it must be within the
35-
/// 1280–1360 range. Invalid values fall through to the next source with a
36-
/// warning.
35+
/// 1280–1360 range. Invalid or out-of-range values are silently ignored and
36+
/// fall through to the next source.
3737
///
3838
/// QUIC Payload MTU is always derived as `tun_mtu + QUIC_OVERHEAD_BYTES`
3939
/// (i.e. +80) and must never be set independently.
@@ -312,4 +312,86 @@ mod tests {
312312
bincode::serde::decode_from_slice(&[], bincode::config::standard());
313313
assert!(result.is_err());
314314
}
315+
316+
#[test]
317+
fn resolve_tun_mtu_none_returns_default() {
318+
// Env var tests run in a single function to avoid parallel race conditions
319+
// on std::env::set_var/remove_var.
320+
let prev = std::env::var("VPN_MTU").ok();
321+
std::env::remove_var("VPN_MTU");
322+
assert_eq!(resolve_tun_mtu(None), DEFAULT_TUN_MTU);
323+
// Restore
324+
if let Some(v) = prev {
325+
std::env::set_var("VPN_MTU", v);
326+
}
327+
}
328+
329+
#[test]
330+
fn resolve_tun_mtu_explicit_valid() {
331+
assert_eq!(resolve_tun_mtu(Some(1300)), 1300);
332+
assert_eq!(resolve_tun_mtu(Some(1280)), 1280);
333+
assert_eq!(resolve_tun_mtu(Some(1360)), 1360);
334+
}
335+
336+
#[test]
337+
fn resolve_tun_mtu_explicit_out_of_range_falls_through() {
338+
let prev = std::env::var("VPN_MTU").ok();
339+
std::env::remove_var("VPN_MTU");
340+
assert_eq!(resolve_tun_mtu(Some(500)), DEFAULT_TUN_MTU);
341+
assert_eq!(resolve_tun_mtu(Some(2000)), DEFAULT_TUN_MTU);
342+
assert_eq!(resolve_tun_mtu(Some(0)), DEFAULT_TUN_MTU);
343+
if let Some(v) = prev {
344+
std::env::set_var("VPN_MTU", v);
345+
}
346+
}
347+
348+
#[test]
349+
fn resolve_tun_mtu_env_var_fallback() {
350+
let prev = std::env::var("VPN_MTU").ok();
351+
std::env::set_var("VPN_MTU", "1300");
352+
assert_eq!(resolve_tun_mtu(None), 1300);
353+
if let Some(v) = prev {
354+
std::env::set_var("VPN_MTU", v);
355+
} else {
356+
std::env::remove_var("VPN_MTU");
357+
}
358+
}
359+
360+
#[test]
361+
fn resolve_tun_mtu_explicit_takes_priority_over_env() {
362+
let prev = std::env::var("VPN_MTU").ok();
363+
std::env::set_var("VPN_MTU", "1300");
364+
assert_eq!(resolve_tun_mtu(Some(1340)), 1340);
365+
if let Some(v) = prev {
366+
std::env::set_var("VPN_MTU", v);
367+
} else {
368+
std::env::remove_var("VPN_MTU");
369+
}
370+
}
371+
372+
#[test]
373+
fn resolve_tun_mtu_invalid_env_falls_through() {
374+
let prev = std::env::var("VPN_MTU").ok();
375+
std::env::set_var("VPN_MTU", "not_a_number");
376+
assert_eq!(resolve_tun_mtu(None), DEFAULT_TUN_MTU);
377+
std::env::set_var("VPN_MTU", "99999");
378+
assert_eq!(resolve_tun_mtu(None), DEFAULT_TUN_MTU);
379+
if let Some(v) = prev {
380+
std::env::set_var("VPN_MTU", v);
381+
} else {
382+
std::env::remove_var("VPN_MTU");
383+
}
384+
}
385+
386+
#[test]
387+
fn resolve_tun_mtu_invalid_env_with_valid_explicit() {
388+
let prev = std::env::var("VPN_MTU").ok();
389+
std::env::set_var("VPN_MTU", "bad");
390+
assert_eq!(resolve_tun_mtu(Some(1300)), 1300);
391+
if let Some(v) = prev {
392+
std::env::set_var("VPN_MTU", v);
393+
} else {
394+
std::env::remove_var("VPN_MTU");
395+
}
396+
}
315397
}

0 commit comments

Comments
 (0)