summaryrefslogtreecommitdiffhomepage
path: root/test/test-manager/src/tests/split_tunnel.rs
blob: 74d43c9d8cdf428c2f5a077e1773f7a8a5b1f33a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use anyhow::Context;
use mullvad_management_interface::MullvadProxyClient;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use test_macro::test_function;
use test_rpc::{ServiceClient, meta::OsVersion};

use super::{
    TestContext,
    helpers::{self, ConnChecker},
    ui,
};

const LEAK_DESTINATION: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 1337);

/// Test that split tunneling works by asserting the following:
/// - Splitting a process shouldn't do anything if tunnel is not connected.
/// - A split process should never push traffic through the tunnel.
/// - Splitting/unsplitting should work regardless if process is running.
#[test_function]
pub async fn test_split_tunnel(
    _ctx: TestContext,
    rpc: ServiceClient,
    mut mullvad_client: MullvadProxyClient,
) -> anyhow::Result<()> {
    // Skip test on macOS 12, since the feature is unsupported
    if is_macos_12_or_lower(&rpc).await? {
        return Ok(());
    }

    let mut checker = ConnChecker::new(rpc.clone(), mullvad_client.clone(), LEAK_DESTINATION);

    // Test that program is behaving when we are disconnected
    (checker.spawn().await?.assert_insecure().await)
        .with_context(|| "Test disconnected and unsplit")?;
    checker.split().await?;
    (checker.spawn().await?.assert_insecure().await)
        .with_context(|| "Test disconnected and split")?;
    checker.unsplit().await?;

    // Test that program is behaving being split/unsplit while running and we are disconnected
    let mut handle = checker.spawn().await?;
    handle.split().await?;
    (handle.assert_insecure().await)
        .with_context(|| "Test disconnected and being split while running")?;
    handle.unsplit().await?;
    (handle.assert_insecure().await)
        .with_context(|| "Test disconnected and being unsplit while running")?;
    drop(handle);

    helpers::connect_and_wait(&mut mullvad_client).await?;

    // Test running an unsplit program
    checker
        .spawn()
        .await?
        .assert_secure()
        .await
        .with_context(|| "Test connected and unsplit")?;

    // Test running a split program
    checker.split().await?;

    checker
        .spawn()
        .await?
        .assert_insecure()
        .await
        .with_context(|| "Test connected and split")?;

    checker.unsplit().await?;

    // Test splitting and unsplitting a program while it's running
    let mut handle = checker.spawn().await?;
    (handle.assert_secure().await).with_context(|| "Test connected and unsplit (again)")?;
    handle.split().await?;
    (handle.assert_insecure().await)
        .with_context(|| "Test connected and being split while running")?;
    handle.unsplit().await?;
    (handle.assert_secure().await)
        .with_context(|| "Test connected and being unsplit while running")?;

    Ok(())
}

/// Test that split tunneling works by asserting the following:
/// - Splitting a process shouldn't do anything if tunnel is not connected.
/// - A split process should never push traffic through the tunnel.
/// - Splitting/unsplitting should work regardless if process is running.
#[test_function(target_os = "macos")]
pub async fn test_split_tunnel_ui(
    _ctx: TestContext,
    rpc: ServiceClient,
    _: MullvadProxyClient,
) -> anyhow::Result<()> {
    // Skip test on macOS 12, since the feature is unsupported
    if is_macos_12_or_lower(&rpc).await? {
        return Ok(());
    }

    let ui_result = ui::run_test(&rpc, &["macos-split-tunneling.spec"])
        .await
        .unwrap();
    assert!(ui_result.success());

    Ok(())
}

async fn is_macos_12_or_lower(rpc: &ServiceClient) -> anyhow::Result<bool> {
    match rpc.get_os_version().await.context("Detect OS version")? {
        OsVersion::Macos(version) if version.major <= 12 => Ok(true),
        _ => Ok(false),
    }
}