summaryrefslogtreecommitdiffhomepage
path: root/mullvad-leak-checker/src/util.rs
blob: 677a63a807310024149908a1563e1b834c7b9add (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
use crate::Interface;
use std::net::IpAddr;

/// IP version, v4 or v6, with some associated data.
#[derive(Clone, Copy)]
pub enum Ip<V4 = (), V6 = ()> {
    V4(V4),
    V6(V6),
}

impl Ip {
    pub const fn v4() -> Self {
        Ip::V4(())
    }

    pub const fn v6() -> Self {
        Ip::V6(())
    }
}

#[cfg(target_os = "windows")]
pub fn get_interface_ip(interface: &Interface, ip_version: Ip) -> anyhow::Result<IpAddr> {
    use anyhow::{Context, anyhow};
    use talpid_windows::net::{AddressFamily, get_ip_address_for_interface, luid_from_alias};

    let interface_luid = match interface {
        Interface::Name(name) => luid_from_alias(name)?,
        Interface::Luid(luid) => *luid,
    };

    let address_family = match ip_version {
        Ip::V4(..) => AddressFamily::Ipv4,
        Ip::V6(..) => AddressFamily::Ipv6,
    };

    get_ip_address_for_interface(address_family, interface_luid)
        .with_context(|| anyhow!("Failed to get IP for interface {interface:?}"))?
        .ok_or(anyhow!("No IP for interface {interface:?}"))
}

#[cfg(unix)]
pub fn get_interface_ip(interface: &Interface, ip_version: Ip) -> anyhow::Result<IpAddr> {
    #[cfg(target_os = "macos")]
    let interface_name;

    let interface_name = match interface {
        Interface::Name(name) => name.as_str(),

        #[cfg(target_os = "macos")]
        &Interface::Index(index) => {
            use anyhow::{Context, anyhow};
            use std::ffi::c_uint;

            // nix getifaddrs provides no way of getting an interface by index, so we need to get
            // the interface name
            interface_name = nix::net::if_::if_indextoname(c_uint::from(index))
                .with_context(|| anyhow!("Failed to get name of iface with index {index}"))?;

            interface_name
                .to_str()
                .context("Network interface name was not UTF-8")?
        }
    };

    for interface_address in nix::ifaddrs::getifaddrs()? {
        if interface_address.interface_name != interface_name {
            continue;
        };
        let Some(address) = interface_address.address else {
            continue;
        };

        match ip_version {
            Ip::V4(()) => {
                if let Some(address) = address.as_sockaddr_in() {
                    return Ok(IpAddr::V4(address.ip()));
                };
            }
            Ip::V6(()) => {
                if let Some(address) = address.as_sockaddr_in6() {
                    return Ok(IpAddr::V6(address.ip()));
                };
            }
        }
    }

    anyhow::bail!("Interface {interface:?} has no valid IP to bind to");
}