2021-05-19 07:54:07 +00:00
|
|
|
use crate::{Error, IoErrorContext, NetworkOpt};
|
2021-03-29 17:22:14 +00:00
|
|
|
use ipnetwork::IpNetwork;
|
|
|
|
use std::{
|
|
|
|
net::{IpAddr, SocketAddr},
|
|
|
|
process::{self, Command},
|
|
|
|
};
|
2021-05-19 07:54:07 +00:00
|
|
|
use wgctrl::{Backend, Device, DeviceUpdate, InterfaceName, PeerConfigBuilder};
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
fn cmd(bin: &str, args: &[&str]) -> Result<process::Output, Error> {
|
|
|
|
let output = Command::new(bin).args(args).output()?;
|
2021-05-19 18:16:19 +00:00
|
|
|
log::debug!("{} {}", bin, args.join(" "));
|
|
|
|
log::debug!("status code {:?}", output.status.code());
|
2021-05-19 18:18:19 +00:00
|
|
|
log::trace!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
|
|
log::trace!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
2021-03-29 17:22:14 +00:00
|
|
|
if output.status.success() {
|
|
|
|
Ok(output)
|
|
|
|
} else {
|
|
|
|
Err(format!(
|
|
|
|
"failed to run {} {} command: {}",
|
|
|
|
bin,
|
|
|
|
args.join(" "),
|
|
|
|
String::from_utf8_lossy(&output.stderr)
|
|
|
|
)
|
|
|
|
.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_os = "macos")]
|
2021-04-09 01:28:37 +00:00
|
|
|
pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error> {
|
|
|
|
let real_interface =
|
|
|
|
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
|
2021-03-31 16:16:00 +00:00
|
|
|
|
|
|
|
if addr.is_ipv4() {
|
|
|
|
cmd(
|
|
|
|
"ifconfig",
|
|
|
|
&[
|
|
|
|
&real_interface,
|
|
|
|
"inet",
|
|
|
|
&addr.to_string(),
|
|
|
|
&addr.ip().to_string(),
|
|
|
|
"alias",
|
|
|
|
],
|
|
|
|
)?;
|
|
|
|
} else {
|
|
|
|
cmd(
|
|
|
|
"ifconfig",
|
|
|
|
&[&real_interface, "inet6", &addr.to_string(), "alias"],
|
|
|
|
)?;
|
|
|
|
}
|
2021-03-29 17:22:14 +00:00
|
|
|
cmd("ifconfig", &[&real_interface, "mtu", "1420"])?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
2021-04-09 01:28:37 +00:00
|
|
|
pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error> {
|
|
|
|
let interface = interface.to_string();
|
2021-03-29 17:22:14 +00:00
|
|
|
cmd(
|
|
|
|
"ip",
|
2021-04-09 01:28:37 +00:00
|
|
|
&["address", "replace", &addr.to_string(), "dev", &interface],
|
2021-03-29 17:22:14 +00:00
|
|
|
)?;
|
2021-05-08 17:17:23 +00:00
|
|
|
cmd(
|
2021-03-29 17:22:14 +00:00
|
|
|
"ip",
|
2021-04-09 01:28:37 +00:00
|
|
|
&["link", "set", "mtu", "1420", "up", "dev", &interface],
|
2021-05-08 17:17:23 +00:00
|
|
|
)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn up(
|
2021-04-09 01:28:37 +00:00
|
|
|
interface: &InterfaceName,
|
2021-03-29 17:22:14 +00:00
|
|
|
private_key: &str,
|
|
|
|
address: IpNetwork,
|
|
|
|
listen_port: Option<u16>,
|
|
|
|
peer: Option<(&str, IpAddr, SocketAddr)>,
|
2021-05-19 07:54:07 +00:00
|
|
|
network: NetworkOpt,
|
2021-03-29 17:22:14 +00:00
|
|
|
) -> Result<(), Error> {
|
2021-05-19 07:54:07 +00:00
|
|
|
let mut device = DeviceUpdate::new();
|
2021-03-29 17:22:14 +00:00
|
|
|
if let Some((public_key, address, endpoint)) = peer {
|
|
|
|
let prefix = if address.is_ipv4() { 32 } else { 128 };
|
|
|
|
let peer_config = PeerConfigBuilder::new(&wgctrl::Key::from_base64(&public_key)?)
|
|
|
|
.add_allowed_ip(address, prefix)
|
|
|
|
.set_endpoint(endpoint);
|
|
|
|
device = device.add_peer(peer_config);
|
|
|
|
}
|
|
|
|
if let Some(listen_port) = listen_port {
|
|
|
|
device = device.set_listen_port(listen_port);
|
|
|
|
}
|
|
|
|
device
|
|
|
|
.set_private_key(wgctrl::Key::from_base64(&private_key).unwrap())
|
2021-05-19 07:54:07 +00:00
|
|
|
.apply(interface, network.backend)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
set_addr(interface, address)?;
|
2021-05-19 07:54:07 +00:00
|
|
|
if !network.no_routing {
|
2021-05-11 17:31:47 +00:00
|
|
|
add_route(interface, address)?;
|
|
|
|
}
|
2021-05-19 18:16:19 +00:00
|
|
|
cmd("ip", &["link"])?;
|
2021-03-29 17:22:14 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
pub fn set_listen_port(
|
|
|
|
interface: &InterfaceName,
|
|
|
|
listen_port: Option<u16>,
|
|
|
|
backend: Backend,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let mut device = DeviceUpdate::new();
|
2021-03-29 17:22:14 +00:00
|
|
|
if let Some(listen_port) = listen_port {
|
|
|
|
device = device.set_listen_port(listen_port);
|
|
|
|
} else {
|
|
|
|
device = device.randomize_listen_port();
|
|
|
|
}
|
2021-05-19 07:54:07 +00:00
|
|
|
device.apply(interface, backend)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
pub fn down(interface: &InterfaceName, backend: Backend) -> Result<(), Error> {
|
2021-05-19 18:16:19 +00:00
|
|
|
Ok(Device::get(interface, backend)
|
|
|
|
.with_str(interface.as_str_lossy())?
|
|
|
|
.delete()
|
|
|
|
.with_str(interface.as_str_lossy())?)
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Add a route in the OS's routing table to get traffic flowing through this interface.
|
|
|
|
/// Returns an error if the process doesn't exit successfully, otherwise returns
|
|
|
|
/// true if the route was changed, false if the route already exists.
|
2021-04-09 01:28:37 +00:00
|
|
|
pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
if cfg!(target_os = "macos") {
|
|
|
|
let real_interface =
|
2021-04-09 01:28:37 +00:00
|
|
|
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
|
2021-03-29 17:22:14 +00:00
|
|
|
let output = cmd(
|
|
|
|
"route",
|
|
|
|
&[
|
|
|
|
"-n",
|
|
|
|
"add",
|
2021-03-31 16:16:00 +00:00
|
|
|
if cidr.is_ipv4() { "-inet" } else { "-inet6" },
|
2021-03-29 17:22:14 +00:00
|
|
|
&cidr.to_string(),
|
|
|
|
"-interface",
|
|
|
|
&real_interface,
|
|
|
|
],
|
|
|
|
)?;
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
if !output.status.success() {
|
|
|
|
Err(format!(
|
|
|
|
"failed to add route for device {} ({}): {}",
|
|
|
|
&interface, real_interface, stderr
|
|
|
|
)
|
|
|
|
.into())
|
|
|
|
} else {
|
|
|
|
Ok(!stderr.contains("File exists"))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// TODO(mcginty): use the netlink interface on linux to modify routing table.
|
|
|
|
let _ = cmd(
|
|
|
|
"ip",
|
2021-04-09 01:28:37 +00:00
|
|
|
&[
|
|
|
|
"route",
|
|
|
|
"add",
|
2021-05-19 18:16:19 +00:00
|
|
|
&IpNetwork::new(cidr.network(), cidr.prefix())?.to_string(),
|
2021-04-09 01:28:37 +00:00
|
|
|
"dev",
|
|
|
|
&interface.to_string(),
|
|
|
|
],
|
2021-03-29 17:22:14 +00:00
|
|
|
);
|
|
|
|
Ok(false)
|
|
|
|
}
|
|
|
|
}
|