innernet/wgctrl-rs/src/backends/userspace.rs

509 lines
16 KiB
Rust

use crate::{Backend, Device, DeviceUpdate, InterfaceName, PeerConfig, PeerInfo, PeerStats};
#[cfg(target_os = "linux")]
use crate::Key;
use std::{
fs,
io::{self, prelude::*, BufReader},
os::unix::net::UnixStream,
path::{Path, PathBuf},
process::{Command, Output},
time::{Duration, SystemTime},
};
static VAR_RUN_PATH: &str = "/var/run/wireguard";
static RUN_PATH: &str = "/run/wireguard";
fn get_base_folder() -> io::Result<PathBuf> {
if Path::new(VAR_RUN_PATH).exists() {
Ok(Path::new(VAR_RUN_PATH).to_path_buf())
} else if Path::new(RUN_PATH).exists() {
Ok(Path::new(RUN_PATH).to_path_buf())
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
"WireGuard socket directory not found.",
))
}
}
fn get_namefile(name: &InterfaceName) -> io::Result<PathBuf> {
Ok(get_base_folder()?.join(&format!("{}.name", name.as_str_lossy())))
}
fn get_socketfile(name: &InterfaceName) -> io::Result<PathBuf> {
if cfg!(target_os = "linux") {
Ok(get_base_folder()?.join(&format!("{}.sock", name)))
} else {
Ok(get_base_folder()?.join(&format!("{}.sock", resolve_tun(name)?)))
}
}
fn open_socket(name: &InterfaceName) -> io::Result<UnixStream> {
UnixStream::connect(get_socketfile(name)?)
}
pub fn resolve_tun(name: &InterfaceName) -> io::Result<String> {
let namefile = get_namefile(name)?;
Ok(fs::read_to_string(namefile)
.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "WireGuard name file can't be read"))?
.trim()
.to_string())
}
pub fn delete_interface(name: &InterfaceName) -> io::Result<()> {
fs::remove_file(get_socketfile(name)?).ok();
fs::remove_file(get_namefile(name)?).ok();
Ok(())
}
pub fn enumerate() -> Result<Vec<InterfaceName>, io::Error> {
use std::ffi::OsStr;
let mut interfaces = vec![];
for entry in fs::read_dir(get_base_folder()?)? {
let path = entry?.path();
if path.extension() == Some(OsStr::new("name")) {
let stem = path
.file_stem()
.and_then(|stem| stem.to_str())
.and_then(|name| name.parse::<InterfaceName>().ok())
.filter(|iface| open_socket(iface).is_ok());
if let Some(iface) = stem {
interfaces.push(iface);
}
}
}
Ok(interfaces)
}
fn new_peer_info(public_key: Key) -> PeerInfo {
PeerInfo {
config: PeerConfig {
public_key,
preshared_key: None,
endpoint: None,
persistent_keepalive_interval: None,
allowed_ips: vec![],
__cant_construct_me: (),
},
stats: PeerStats {
last_handshake_time: None,
rx_bytes: 0,
tx_bytes: 0,
},
}
}
struct ConfigParser {
device_info: Device,
current_peer: Option<PeerInfo>,
}
impl From<ConfigParser> for Device {
fn from(parser: ConfigParser) -> Self {
parser.device_info
}
}
impl ConfigParser {
/// Returns `None` if an invalid device name was provided.
fn new(name: &InterfaceName) -> Self {
let device_info = Device {
name: *name,
public_key: None,
private_key: None,
fwmark: None,
listen_port: None,
peers: vec![],
linked_name: resolve_tun(name).ok(),
backend: Backend::Userspace,
__cant_construct_me: (),
};
Self {
device_info,
current_peer: None,
}
}
fn add_line(&mut self, line: &str) -> Result<(), std::io::Error> {
use io::ErrorKind::InvalidData;
let split: Vec<&str> = line.splitn(2, '=').collect();
match &split[..] {
[key, value] => self.add_pair(key, value),
_ => Err(InvalidData.into()),
}
}
fn add_pair(&mut self, key: &str, value: &str) -> Result<(), std::io::Error> {
use io::ErrorKind::InvalidData;
match key {
"private_key" => {
self.device_info.private_key = Some(Key::from_hex(value).map_err(|_| InvalidData)?);
self.device_info.public_key = self
.device_info
.private_key
.as_ref()
.map(|k| k.generate_public());
},
"listen_port" => {
self.device_info.listen_port = Some(value.parse().map_err(|_| InvalidData)?)
},
"fwmark" => self.device_info.fwmark = Some(value.parse().map_err(|_| InvalidData)?),
"public_key" => {
let new_peer = new_peer_info(Key::from_hex(value).map_err(|_| InvalidData)?);
if let Some(finished_peer) = self.current_peer.replace(new_peer) {
self.device_info.peers.push(finished_peer);
}
},
"preshared_key" => {
self.current_peer
.as_mut()
.ok_or(InvalidData)?
.config
.preshared_key = Some(Key::from_hex(value).map_err(|_| InvalidData)?);
},
"tx_bytes" => {
self.current_peer
.as_mut()
.ok_or(InvalidData)?
.stats
.tx_bytes = value.parse().map_err(|_| InvalidData)?
},
"rx_bytes" => {
self.current_peer
.as_mut()
.ok_or(InvalidData)?
.stats
.rx_bytes = value.parse().map_err(|_| InvalidData)?
},
"last_handshake_time_sec" => {
let handshake_seconds: u64 = value.parse().map_err(|_| InvalidData)?;
if handshake_seconds > 0 {
self.current_peer
.as_mut()
.ok_or(InvalidData)?
.stats
.last_handshake_time =
Some(SystemTime::UNIX_EPOCH + Duration::from_secs(handshake_seconds));
}
},
"allowed_ip" => {
self.current_peer
.as_mut()
.ok_or(InvalidData)?
.config
.allowed_ips
.push(value.parse().map_err(|_| InvalidData)?);
},
"persistent_keepalive_interval" => {
self.current_peer
.as_mut()
.ok_or(InvalidData)?
.config
.persistent_keepalive_interval = Some(value.parse().map_err(|_| InvalidData)?);
},
"endpoint" => {
self.current_peer
.as_mut()
.ok_or(InvalidData)?
.config
.endpoint = Some(value.parse().map_err(|_| InvalidData)?);
},
"errno" => {
// "errno" indicates an end of the stream, along with the error return code.
if value != "0" {
return Err(std::io::Error::from_raw_os_error(
value
.parse()
.expect("Unable to parse userspace wg errno return code"),
));
}
if let Some(finished_peer) = self.current_peer.take() {
self.device_info.peers.push(finished_peer);
}
},
"protocol_version" | "last_handshake_time_nsec" => {},
_ => println!("got unsupported info: {}={}", key, value),
}
Ok(())
}
}
pub fn get_by_name(name: &InterfaceName) -> Result<Device, io::Error> {
let mut sock = open_socket(name)?;
sock.write_all(b"get=1\n\n")?;
let mut reader = BufReader::new(sock);
let mut buf = String::new();
let mut parser = ConfigParser::new(name);
loop {
match reader.read_line(&mut buf)? {
0 | 1 if buf == "\n" => break,
_ => {
parser.add_line(buf.trim_end())?;
buf.clear();
},
};
}
Ok(parser.into())
}
/// Following the rough logic of wg-quick(8), use the wireguard-go userspace
/// implementation by default, but allow for an environment variable to choose
/// a different implementation.
///
/// wgctrl-rs will look for WG_USERSPACE_IMPLEMENTATION first, but will also
/// respect the WG_QUICK_USERSPACE_IMPLEMENTATION choice if the former isn't
/// available.
fn get_userspace_implementation() -> String {
std::env::var("WG_USERSPACE_IMPLEMENTATION")
.or_else(|_| std::env::var("WG_QUICK_USERSPACE_IMPLEMENTATION"))
.unwrap_or_else(|_| "wireguard-go".to_string())
}
fn start_userspace_wireguard(iface: &InterfaceName) -> io::Result<Output> {
let mut command = Command::new(&get_userspace_implementation());
let output = if cfg!(target_os = "linux") {
command.args(&[iface.to_string()]).output()?
} else {
command
.env(
"WG_TUN_NAME_FILE",
&format!("{}/{}.name", VAR_RUN_PATH, iface),
)
.args(&["utun"])
.output()?
};
if !output.status.success() {
Err(io::ErrorKind::AddrNotAvailable.into())
} else {
Ok(output)
}
}
pub fn apply(builder: &DeviceUpdate, iface: &InterfaceName) -> io::Result<()> {
// If we can't open a configuration socket to an existing interface, try starting it.
let mut sock = match open_socket(iface) {
Err(_) => {
fs::create_dir_all(VAR_RUN_PATH)?;
// Clear out any old namefiles if they didn't lead to a connected socket.
let _ = fs::remove_file(get_namefile(iface)?);
start_userspace_wireguard(iface)?;
std::thread::sleep(Duration::from_millis(100));
open_socket(iface)
.map_err(|e| io::Error::new(e.kind(), format!("failed to open socket ({})", e)))?
},
Ok(sock) => sock,
};
let mut request = String::from("set=1\n");
if let Some(ref k) = builder.private_key {
request.push_str(&format!("private_key={}\n", hex::encode(k.as_bytes())));
}
if let Some(f) = builder.fwmark {
request.push_str(&format!("fwmark={}\n", f));
}
if let Some(f) = builder.listen_port {
request.push_str(&format!("listen_port={}\n", f));
}
if builder.replace_peers {
request.push_str("replace_peers=true\n");
}
for peer in &builder.peers {
request.push_str(&format!("public_key={}\n", hex::encode(peer.public_key.as_bytes())));
if peer.replace_allowed_ips {
request.push_str("replace_allowed_ips=true\n");
}
if peer.remove_me {
request.push_str("remove=true\n");
}
if let Some(ref k) = peer.preshared_key {
request.push_str(&format!("preshared_key={}\n", hex::encode(k.as_bytes())));
}
if let Some(endpoint) = peer.endpoint {
request.push_str(&format!("endpoint={}\n", endpoint));
}
if let Some(keepalive_interval) = peer.persistent_keepalive_interval {
request.push_str(&format!(
"persistent_keepalive_interval={}\n",
keepalive_interval
));
}
for allowed_ip in &peer.allowed_ips {
request.push_str(&format!(
"allowed_ip={}/{}\n",
allowed_ip.address, allowed_ip.cidr
));
}
}
request.push('\n');
sock.write_all(request.as_bytes())?;
let mut reader = BufReader::new(sock);
let mut line = String::new();
reader.read_line(&mut line)?;
let split: Vec<&str> = line.trim_end().splitn(2, '=').collect();
match &split[..] {
["errno", "0"] => Ok(()),
["errno", val] => {
println!("ERROR {}", val);
Err(io::ErrorKind::InvalidInput.into())
},
_ => Err(io::ErrorKind::Other.into()),
}
}
/// Represents a WireGuard encryption key.
///
/// WireGuard makes no meaningful distinction between public,
/// private and preshared keys - any sequence of 32 bytes
/// can be used as either of those.
///
/// This means that you need to be careful when working with
/// `Key`s, especially ones created from external data.
#[cfg(not(target_os = "linux"))]
#[derive(PartialEq, Eq, Clone)]
pub struct Key([u8; 32]);
#[cfg(not(target_os = "linux"))]
impl Key {
/// Generates and returns a new private key.
pub fn generate_private() -> Self {
use rand_core::{OsRng, RngCore};
let mut bytes = [0u8; 32];
OsRng.fill_bytes(&mut bytes);
// Apply key clamping.
bytes[0] &= 248;
bytes[31] &= 127;
bytes[31] |= 64;
Self(bytes)
}
/// Generates and returns a new preshared key.
pub fn generate_preshared() -> Self {
use rand_core::{OsRng, RngCore};
let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key);
Self(key)
}
/// Generates a public key for this private key.
pub fn generate_public(&self) -> Self {
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
// https://github.com/dalek-cryptography/x25519-dalek/blob/1c39ff92e0dfc0b24aa02d694f26f3b9539322a5/src/x25519.rs#L150
let point = (&ED25519_BASEPOINT_TABLE * &Scalar::from_bits(self.0)).to_montgomery();
Self(point.to_bytes())
}
/// Generates an all-zero key.
pub fn zero() -> Self {
Self([0u8; 32])
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
/// Converts the key to a standardized base64 representation, as used by the `wg` utility and `wg-quick`.
pub fn to_base64(&self) -> String {
base64::encode(&self.0)
}
/// Converts a base64 representation of the key to the raw bytes.
///
/// This can fail, as not all text input is valid base64 - in this case
/// `Err(InvalidKey)` is returned.
pub fn from_base64(key: &str) -> Result<Self, crate::InvalidKey> {
use crate::InvalidKey;
let mut key_bytes = [0u8; 32];
let decoded_bytes = base64::decode(key).map_err(|_| InvalidKey)?;
if decoded_bytes.len() != 32 {
return Err(InvalidKey);
}
key_bytes.copy_from_slice(&decoded_bytes[..]);
Ok(Self(key_bytes))
}
pub fn from_hex(hex_str: &str) -> Result<Self, crate::InvalidKey> {
use crate::InvalidKey;
let mut sized_bytes = [0u8; 32];
hex::decode_to_slice(hex_str, &mut sized_bytes).map_err(|_| InvalidKey)?;
Ok(Self(sized_bytes))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_pubkey_generation() {
let privkey = "SGb+ojrRNDuMePufwtIYhXzA//k6wF3R21tEBgKlzlM=";
let pubkey = "DD5yKRfzExcV5+kDnTroDgCU15latdMjiQ59j1hEuk8=";
let private = Key::from_base64(privkey).unwrap();
let public = Key::generate_public(&private);
assert_eq!(public.to_base64(), pubkey);
}
#[test]
fn test_rng_sanity_private() {
let first = Key::generate_private();
assert!(first.as_bytes() != &[0u8; 32]);
for _ in 0..100_000 {
let key = Key::generate_private();
assert!(first != key);
assert!(key.as_bytes() != &[0u8; 32]);
}
}
#[test]
fn test_rng_sanity_preshared() {
let first = Key::generate_preshared();
assert!(first.as_bytes() != &[0u8; 32]);
for _ in 0..100_000 {
let key = Key::generate_preshared();
assert!(first != key);
assert!(key.as_bytes() != &[0u8; 32]);
}
}
}