Skip to main content

isoloom_render/network/
more_addresses.rs

1//! A VM's further addresses on a network (`lan: [10, 11]`), written once for every VM target:
2//! each on the interface that holds the machine's primary address there, since the hypervisor
3//! or the cloud configures only that one. A cloud that filters by address has been told of them
4//! by its own module (a secondary private address, an alias range).
5
6use isoloom_plan::more_addresses::MoreAddresses;
7use isoloom_spec::shell::quote;
8
9/// Shell that puts each further address on the interface holding the primary one. Idempotent
10/// (`replace`): a transport runs it again at every boot and whenever a link comes up.
11#[must_use]
12pub fn linux(addresses: &[MoreAddresses]) -> String {
13    addresses
14        .iter()
15        .map(|a| {
16            let find = quote(&format!(" inet {}/", a.primary));
17            let added: String = a
18                .more
19                .iter()
20                .map(|more| format!("ip addr replace {more}/{} dev \"$dev\"\n", a.prefix))
21                .collect::<Vec<_>>()
22                .concat();
23            format!("dev=$(ip -o -4 addr show | grep -F {find} | awk '{{print $2; exit}}')\n{added}")
24        })
25        .collect::<Vec<_>>()
26        .concat()
27}
28
29/// The same in PowerShell for a Windows VM: the interface found by the primary address, each
30/// further one added once (Windows keeps them across restarts) and never used as a source, so
31/// the machine's own connections still leave from its primary address.
32#[must_use]
33pub fn windows(addresses: &[MoreAddresses]) -> String {
34    addresses
35        .iter()
36        .flat_map(|a| {
37            a.more.iter().map(move |more| {
38                format!(
39                    "if (-not (Get-NetIPAddress -IPAddress '{more}' -ErrorAction SilentlyContinue)) {{ New-NetIPAddress -InterfaceIndex (Get-NetIPAddress -IPAddress '{}').InterfaceIndex -IPAddress '{more}' -PrefixLength {} -SkipAsSource $true | Out-Null }}",
40                    a.primary, a.prefix
41                )
42            })
43        })
44        .collect::<Vec<_>>()
45        .join("\n")
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    fn web() -> Vec<MoreAddresses> {
53        vec![MoreAddresses {
54            primary: "10.0.0.10".parse().unwrap(),
55            prefix: 24,
56            more: vec!["10.0.0.11".parse().unwrap(), "10.0.0.12".parse().unwrap()],
57        }]
58    }
59
60    #[test]
61    fn each_address_lands_on_the_interface_of_the_primary_one() {
62        let sh = linux(&web());
63        assert!(sh.starts_with("dev=$(ip -o -4 addr show | grep -F ' inet 10.0.0.10/'"), "{sh}");
64        assert!(
65            sh.contains("ip addr replace 10.0.0.11/24 dev \"$dev\"\nip addr replace 10.0.0.12/24 dev \"$dev\"\n"),
66            "{sh}"
67        );
68        let ps = windows(&web());
69        assert_eq!(ps.lines().count(), 2, "{ps}");
70        assert!(
71            ps.contains("(Get-NetIPAddress -IPAddress '10.0.0.10').InterfaceIndex -IPAddress '10.0.0.11' -PrefixLength 24 -SkipAsSource $true"),
72            "{ps}"
73        );
74    }
75}