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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|
use anyhow::{Context, Result, anyhow, bail};
use futures::{FutureExt, TryFutureExt, select};
use nix::{
sys::{
signal::{Signal, kill},
socket::SockaddrStorage,
},
unistd::Pid,
};
use std::{
convert::Infallible,
net::{Ipv4Addr, SocketAddrV4},
};
use talpid_types::drop_guard::on_drop;
use tokio::{io::AsyncWriteExt, process::Command, time::sleep};
// Private key of the wireguard remote peer on host.
const CUSTOM_TUN_REMOTE_PRIVKEY: &str = "gLvQuyqazziyf+pUCAFUgTnWIwn6fPE5MOReOqPEGHU=";
// Public key of the wireguard remote peer on host.
data_encoding_macro::base64_array!(
"pub const CUSTOM_TUN_REMOTE_PUBKEY" = "7svBwGBefP7KVmH/yes+pZCfO6uSOYeGieYYa1+kZ0E="
);
// Private key of the wireguard local peer on guest.
const CUSTOM_TUN_LOCAL_PUBKEY: &str = "h6elqt3dfamtS/p9jxJ8bIYs8UW9YHfTFhvx0fabTFo=";
// Private key of the wireguard local peer on guest.
data_encoding_macro::base64_array!(
"pub const CUSTOM_TUN_LOCAL_PRIVKEY" = "mPue6Xt0pdz4NRAhfQSp/SLKo7kV7DW+2zvBq0N9iUI="
);
/// Port of the wireguard remote peer as defined in `setup-network.sh`.
pub const CUSTOM_TUN_REMOTE_REAL_PORT: u16 = 51820;
/// Tunnel address of the wireguard local peer as defined in `setup-network.sh`.
pub const CUSTOM_TUN_LOCAL_TUN_ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 15, 2);
/// Tunnel address of the wireguard remote peer as defined in `setup-network.sh`.
pub const CUSTOM_TUN_REMOTE_TUN_ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 15, 1);
/// Gateway (and default DNS resolver) of the wireguard tunnel.
pub const CUSTOM_TUN_GATEWAY: Ipv4Addr = CUSTOM_TUN_REMOTE_TUN_ADDR;
/// Name of the wireguard interface on the host
pub const CUSTOM_TUN_INTERFACE_NAME: &str = "utun123";
use std::time::Duration;
/// Timeout for wireguard-go to create an interface
const INTERFACE_SETUP_TIMEOUT: Duration = Duration::from_secs(5);
/// Set up WireGuard relay and dummy hosts.
pub async fn setup_test_network() -> Result<()> {
log::debug!("Setting up test network");
enable_forwarding().await?;
create_wireguard_interface()
.await
.context("Failed to create WireGuard interface")?;
Ok(())
}
/// Returns the interface name and IP address of the bridge gateway, which is the (first) bridge
/// network that the given `guest_ip` belongs to.
pub(crate) fn find_vm_bridge(guest_ip: &Ipv4Addr) -> Result<(String, Ipv4Addr)> {
let to_sock_addr = |addr: Option<SockaddrStorage>| {
addr.as_ref()
.and_then(|addr| addr.as_sockaddr_in())
.map(|addr| *SocketAddrV4::from(*addr).ip())
};
nix::ifaddrs::getifaddrs()
.unwrap()
.filter(|addr| addr.interface_name.starts_with("bridge"))
.filter_map(|addr| {
let address = to_sock_addr(addr.address);
let netmask = to_sock_addr(addr.netmask);
address
.zip(netmask)
.map(|(address, netmask)| (addr.interface_name, address, netmask))
})
.find_map(|(interface_name, address, netmask)| {
ipnetwork::Ipv4Network::with_netmask(address, netmask)
.ok()
.filter(|ip_v4_network| ip_v4_network.contains(*guest_ip))
.map(|_| (interface_name.clone(), address))
})
.ok_or_else(|| anyhow!("Failed to identify bridge used by tart -- not running?"))
}
async fn enable_forwarding() -> Result<()> {
// Enable forwarding
let mut cmd = Command::new("/usr/bin/sudo");
cmd.args(["/usr/sbin/sysctl", "net.inet.ip.forwarding=1"]);
let output = cmd.output().await.context("Run sysctl")?;
if !output.status.success() {
return Err(anyhow!("sysctl failed: {}", output.status.code().unwrap()));
}
Ok(())
}
async fn create_wireguard_interface() -> Result<()> {
log::debug!("Creating custom WireGuard tunnel");
// Create a future that spawns wireguard-go, and SIGTERMs it when dropped.
let wireguard_go = async move {
let mut cmd = Command::new("/usr/bin/sudo");
cmd.args(["wireguard-go", "-f", CUSTOM_TUN_INTERFACE_NAME]);
// We don't want to SIGKILL sudo, as that would leave wireguard-go orphaned and running
cmd.kill_on_drop(false);
let child = cmd.spawn().context("Failed to spawn wireguard-go")?;
let pid = child.id().context("wireguard-go exited prematurely")?;
let pid = Pid::from_raw(pid as libc::pid_t);
let _term_on_drop = on_drop(|| {
if let Err(e) = kill(pid, Signal::SIGTERM) {
log::warn!("Failed to kill wireguard-go ({pid}): {e}");
}
});
let output = child.wait_with_output().await.context("Run wireguard-go")?;
if output.status.success() {
bail!("wireguard-go exited prematurely")
} else {
bail!("wireguard-go failed with status {:?}", output.status.code())
}
};
// Spawn wireguard-go using a tokio task. The task will hang around until this process exits.
let wireguard_go = tokio::spawn(wireguard_go.inspect_err(|e| log::error!("{e}")));
// Create a future that waits until the tunnel interface appears.
let tunnel_check = async move {
loop {
let mut cmd = Command::new("/sbin/ifconfig");
cmd.arg(CUSTOM_TUN_INTERFACE_NAME);
let output = cmd
.output()
.await
.context("Check if wireguard tunnel exists")?;
if output.status.success() {
log::debug!("Created custom WireGuard tunnel interface");
return Ok(());
}
sleep(Duration::from_secs(1)).await;
}
};
// Wait until...
select! {
// ...the utun-interface is created
result = tunnel_check.fuse() => result,
// ...or wireguard-go exits with an error
result = wireguard_go.fuse() => result
.context("wireguard-go task panicked")?
.map(|never: Infallible| match never {}), // this task never exits with Ok
// ...or we hit the timeout
_timeout = sleep(INTERFACE_SETUP_TIMEOUT).fuse() => {
bail!("WireGuard interface setup timed out");
}
}
}
pub async fn configure_tunnel() -> Result<()> {
// Check if the tunnel device is configured
let mut cmd = Command::new("/usr/sbin/ipconfig");
cmd.args(["getifaddr", CUSTOM_TUN_INTERFACE_NAME]);
let output = cmd
.output()
.await
.context("Check if wireguard tunnel has IP")?;
if output.status.success() {
log::debug!("Tunnel {CUSTOM_TUN_INTERFACE_NAME} already configured");
return Ok(());
}
// Set wireguard config
let mut tempfile = async_tempfile::TempFile::new()
.await
.context("Failed to create temporary wireguard config")?;
tempfile
.write_all(
format!(
"
[Interface]
PrivateKey = {CUSTOM_TUN_REMOTE_PRIVKEY}
ListenPort = {CUSTOM_TUN_REMOTE_REAL_PORT}
[Peer]
PublicKey = {CUSTOM_TUN_LOCAL_PUBKEY}
AllowedIPs = {CUSTOM_TUN_LOCAL_TUN_ADDR}
"
)
.as_bytes(),
)
.await
.context("Failed to write wireguard config")?;
let mut cmd = Command::new("/usr/bin/sudo");
cmd.args([
"wg",
"setconf",
CUSTOM_TUN_INTERFACE_NAME,
tempfile.file_path().to_str().unwrap(),
]);
let output = cmd.output().await.context("Run wg")?;
if !output.status.success() {
return Err(anyhow!("wg failed: {}", output.status.code().unwrap()));
}
// Set tunnel IP address
let mut cmd = Command::new("/usr/bin/sudo");
cmd.args([
"/usr/sbin/ipconfig",
"set",
CUSTOM_TUN_INTERFACE_NAME,
"manual",
&CUSTOM_TUN_REMOTE_TUN_ADDR.to_string(),
]);
let status = cmd.status().await.context("Run ipconfig")?;
if !status.success() {
return Err(anyhow!("ipconfig failed: {}", status.code().unwrap()));
}
Ok(())
}
|