blob: 93dd61b871cde79ef7ca29e0d985bccad0859007 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides the command "wireguard" for crosh which can configure and control a WireGuard service
// in Shill.
use std::{collections::HashMap, io};
use dbus::{
self,
arg::{ArgType, RefArg, Variant},
blocking::Connection,
};
use sys_util::error;
use system_api::client::OrgChromiumFlimflamManager;
use system_api::client::OrgChromiumFlimflamService;
use crate::{
dispatcher::{self, Arguments, Command, Dispatcher},
util::{is_consumer_device, DEFAULT_DBUS_TIMEOUT},
};
const USAGE: &str = r#"wireguard <cmd> [<args>]
Available subcommands:
list
Show all configured WireGuard services.
show <name>
Show the WireGuard service with name <name>.
new <name>
Create a new WireGuard service with name <name>.
del <name>
Delete the configured WireGuard service with name <name>.
set <name> [local-ip <ip>] [private-key] [mtu <mtu>] [dns <ip1>[,<ip2>]...]
[peer <base64-public-key> [remove]
[endpoint <hostname>/<ip>:<port>]
[preshared-key]
[allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]
[persistent-keepalive <interval seconds>] ]...
Configure properties for the WireGuard service with name <name>. Most options should
have the same meaning and usage as in `wireguard-tools` (and `wg-quick`). Exceptions
are:
- Only IPv4 is supported for the VPN overlay, so `local-ip`, `dns`, and `allowed-ips`
should be set to IPv4 address(es).
- If `dns` is not set, it will be defaulted to "8.8.8.8,8.8.4.4".
- If `mtu` is not set, it will be determined automatically. Set it to 0 to reset the
existing value.
- `endpoint` must be set before using `connect` command.
- `private-key` and `preshared-key` take no parameters. If they are used, this command
will prompt you to change the key (or remove the key) via stdin, to avoid leaving
them in the history of shell.
connect <name>
Connect to the configured WireGuard service with name <name>.
disconnect <name>
Disconnect from the configured WireGuard service with name <name>.
"#;
pub fn register(dispatcher: &mut Dispatcher) {
if let Ok(true) = is_consumer_device() {
dispatcher.register_command(
Command::new(
"wireguard".to_string(),
USAGE.to_string(),
"Utility to configure and control a WireGuard VPN service".to_string(),
)
.set_command_callback(Some(execute_wireguard)),
);
}
}
#[derive(Debug)]
enum Error {
InvalidArguments(String),
ServiceNotFound(String),
ServiceAlreadyExists(String),
ServiceNotConnectable {
service_name: String,
reason: String,
},
VpnDisabled,
WireGuardUnavailable,
Internal(String),
}
fn execute_wireguard(_cmd: &Command, args: &Arguments) -> Result<(), dispatcher::Error> {
let convert_err = |err: Error| match err {
Error::InvalidArguments(val) => dispatcher::Error::CommandInvalidArguments(val),
Error::ServiceNotFound(val) => {
println!("WireGuard service with name {} does not exist", val);
dispatcher::Error::CommandReturnedError
}
Error::ServiceAlreadyExists(val) => {
println!("WireGuard service with name {} already exists", val);
dispatcher::Error::CommandReturnedError
}
Error::ServiceNotConnectable {
service_name,
reason,
} => {
println!(
"WireGuard service with name {} is not connectable: {}",
service_name, reason
);
dispatcher::Error::CommandReturnedError
}
Error::VpnDisabled => {
println!("VPN is disabled on this device");
dispatcher::Error::CommandReturnedError
}
Error::WireGuardUnavailable => {
println!("WireGuard is not available on this device");
dispatcher::Error::CommandReturnedError
}
Error::Internal(val) => {
error!("ERROR: {}", val);
dispatcher::Error::CommandReturnedError
}
};
check_wireguard_support().map_err(convert_err)?;
let args: Vec<&str> = args.get_args().iter().map(String::as_str).collect();
match args.as_slice() {
[] => Err(Error::InvalidArguments("no command".to_string())),
["show"] | ["list"] => wireguard_list(),
["show", service_name] => wireguard_show(service_name),
["new", service_name] => wireguard_new(service_name),
["del", service_name] => wireguard_del(service_name),
["set", service_name, remaining @ ..] => wireguard_set(service_name, remaining),
["connect", service_name] => wireguard_connect(service_name),
["disconnect", service_name] => wireguard_disconnect(service_name),
[other, ..] => Err(Error::InvalidArguments(other.to_string())),
}
.map_err(convert_err)
}
// Represents the properties returned by GetProperties method from D-Bus.
type InputPropMap = HashMap<String, Variant<Box<dyn RefArg>>>;
// Represents the properties nested as a value of another property in the InputPropMap.
type InnerPropMap<'a> = HashMap<&'a str, &'a dyn RefArg>;
// Represents the properties used to configure a service via D-Bus.
type OutputPropMap = HashMap<&'static str, Variant<Box<dyn RefArg>>>;
// Helper functions for reading a value with a specific type from a property map.
trait GetPropExt {
fn get_arg(&self, key: &str) -> Result<&dyn RefArg, Error>;
fn get_str(&self, key: &str) -> Result<&str, Error> {
self.get_arg(key)?
.as_str()
.ok_or_else(|| get_prop_error(key, "str"))
}
fn get_string(&self, key: &str) -> Result<String, Error> {
Ok(self.get_str(key)?.to_string())
}
fn get_i32(&self, key: &str) -> Result<i32, Error> {
self.get_arg(key)?
.as_i64()
.map(|x| x as i32)
.ok_or_else(|| get_prop_error(key, "i32"))
}
fn get_strings(&self, key: &str) -> Result<Vec<String>, Error> {
self.get_arg(key)?
.as_iter()
.ok_or_else(|| get_prop_error(key, "vec"))?
.map(|arg| {
arg.as_str()
.ok_or_else(|| get_prop_error(key, "str"))
.map(|x| x.to_string())
})
.collect()
}
fn get_inner_prop_map(&self, key: &str) -> Result<InnerPropMap, Error> {
parse_arg_to_map(self.get_arg(key)?).ok_or_else(|| get_prop_error(key, "map"))
}
fn get_inner_prop_maps(&self, key: &str) -> Result<Vec<InnerPropMap>, Error> {
self.get_arg(key)?
.as_iter()
.ok_or_else(|| get_prop_error(key, "vec"))?
.map(|arg| parse_arg_to_map(arg).ok_or_else(|| get_prop_error(key, "map")))
.collect()
}
}
fn get_prop_error(k: &str, t: &str) -> Error {
Error::Internal(format!("Failed to parse properties {} with type {}", k, t))
}
fn parse_arg_to_map(arg: &dyn RefArg) -> Option<InnerPropMap> {
let mut kvs = HashMap::new();
let mut itr = arg.as_iter()?;
while let Some(val) = itr.next() {
let key = val.as_str()?;
let next = itr.next()?;
// Unwraps it if the value type is Variant, since it may affect iterating over the inner
// value if the inner type is dict.
if next.arg_type() == ArgType::Variant {
let mut inner_itr = next.as_iter().unwrap();
kvs.insert(key, inner_itr.next().unwrap());
} else {
kvs.insert(key, next);
}
}
Some(kvs)
}
impl GetPropExt for InputPropMap {
fn get_arg(&self, key: &str) -> Result<&dyn RefArg, Error> {
Ok(&self
.get(&key.to_string())
.ok_or_else(|| get_prop_error(key, "ref_arg"))?
.0)
}
}
impl GetPropExt for InnerPropMap<'_> {
fn get_arg(&self, key: &str) -> Result<&dyn RefArg, Error> {
Ok(self
.get(key)
.ok_or_else(|| get_prop_error(key, "ref_arg"))?)
}
}
// The following constants are defined in system_api/dbus/shill/dbus-constants.h.
// Also see shill/doc/manager-api.txt and shill/doc/service-api.txt for their meanings.
// Property names for manager.
const PROPERTY_PROHIBITED_TECHNOLOGIES: &str = "ProhibitedTechnologies";
const PROPERTY_SERVICES: &str = "Services";
const PROPERTY_SUPPORTED_VPN_TYPES: &str = "SupportedVPNTypes";
// Property names for service.
const PROPERTY_TYPE: &str = "Type";
const PROPERTY_NAME: &str = "Name";
const PROPERTY_PROVIDER_TYPE: &str = "Provider.Type";
const PROPERTY_PROVIDER_HOST: &str = "Provider.Host";
const PROPERTY_WIREGUARD_PRIVATE_KEY: &str = "WireGuard.PrivateKey";
const PROPERTY_WIREGUARD_PUBLIC_KEY: &str = "WireGuard.PublicKey";
const PROPERTY_WIREGUARD_PEERS: &str = "WireGuard.Peers";
const PROPERTY_STATIC_IP_CONFIG: &str = "StaticIPConfig";
const PROPERTY_SAVE_CREDENTIALS: &str = "SaveCredentials";
// Property names for WireGuard in "WireGuard.Peers".
const PROPERTY_PEER_PUBLIC_KEY: &str = "PublicKey";
const PROPERTY_PEER_PRESHARED_KEY: &str = "PresharedKey";
const PROPERTY_PEER_ENDPOINT: &str = "Endpoint";
const PROPERTY_PEER_ALLOWED_IPS: &str = "AllowedIPs";
const PROPERTY_PEER_PERSISTENT_KEEPALIVE: &str = "PersistentKeepalive";
// Property names in "StaticIPConfig"
const PROPERTY_ADDRESS: &str = "Address";
const PROPERTY_NAME_SERVERS: &str = "NameServers";
const PROPERTY_MTU: &str = "Mtu";
// Property values.
const TYPE_VPN: &str = "vpn";
const TYPE_WIREGUARD: &str = "wireguard";
// Represents a WireGuard service in Shill.
#[derive(Clone, Debug, PartialEq)]
struct WireGuardService {
path: Option<String>,
name: String,
local_ip: Option<String>,
private_key: Option<String>,
public_key: String,
mtu: Option<i32>,
name_servers: Option<Vec<String>>,
peers: Vec<WireGuardPeer>,
}
#[derive(Clone, Debug, PartialEq)]
struct WireGuardPeer {
public_key: String,
endpoint: String,
allowed_ips: String,
persistent_keepalive: String,
preshared_key: Option<String>,
}
impl WireGuardService {
fn parse_from_prop_map(service_properties: &InputPropMap) -> Result<Self, Error> {
let service_name = service_properties.get_str(PROPERTY_NAME)?;
let not_wg_service_err =
Error::ServiceNotFound(format!("{} is not a WireGuard VPN service", service_name));
let service_type = service_properties.get_str(PROPERTY_TYPE)?;
if service_type != TYPE_VPN {
return Err(not_wg_service_err);
}
// Reads address, dns servers, and mtu from StaticIPConfig. This property and all the
// sub-properties could be empty.
let mut local_ip = None;
let mut mtu = None;
let mut name_servers = None;
if let Ok(props) = service_properties.get_inner_prop_map(PROPERTY_STATIC_IP_CONFIG) {
// Note that the ok() below may potentially ignore some real parsing failures. It
// should not happen if shill is working correctly so we use ok() here for simplicity.
local_ip = props.get_string(PROPERTY_ADDRESS).ok();
name_servers = props.get_strings(PROPERTY_NAME_SERVERS).ok();
mtu = props.get_i32(PROPERTY_MTU).ok();
}
// The value of the "Provider" property is a map. Translates it into a HashMap at first.
let provider_properties = service_properties.get_inner_prop_map("Provider")?;
if provider_properties.get_str(PROPERTY_TYPE)? != TYPE_WIREGUARD {
return Err(not_wg_service_err);
}
let ret = WireGuardService {
path: None,
name: service_name.to_string(),
local_ip,
private_key: None,
public_key: provider_properties.get_string(PROPERTY_WIREGUARD_PUBLIC_KEY)?,
mtu,
name_servers,
peers: provider_properties
.get_inner_prop_maps(PROPERTY_WIREGUARD_PEERS)?
.into_iter()
.map(|p| {
Ok(WireGuardPeer {
public_key: p.get_string(PROPERTY_PEER_PUBLIC_KEY)?,
endpoint: p.get_string(PROPERTY_PEER_ENDPOINT)?,
allowed_ips: p.get_string(PROPERTY_PEER_ALLOWED_IPS)?,
persistent_keepalive: p.get_string(PROPERTY_PEER_PERSISTENT_KEEPALIVE)?,
preshared_key: None,
})
})
.collect::<Result<Vec<WireGuardPeer>, Error>>()?,
};
Ok(ret)
}
fn encode_into_prop_map(&self) -> OutputPropMap {
let mut properties: OutputPropMap = HashMap::new();
let mut insert_string_field = |k: &'static str, v: &str| {
properties.insert(k, Variant(Box::new(v.to_string())));
};
insert_string_field(PROPERTY_TYPE, TYPE_VPN);
insert_string_field(PROPERTY_NAME, &self.name);
insert_string_field(PROPERTY_PROVIDER_TYPE, TYPE_WIREGUARD);
insert_string_field(PROPERTY_PROVIDER_HOST, TYPE_WIREGUARD);
if let Some(val) = &self.private_key {
insert_string_field(PROPERTY_WIREGUARD_PRIVATE_KEY, val);
}
let mut static_ip_properties = HashMap::new();
let mut insert_ip_field = |k: &str, v: Box<dyn RefArg>| {
static_ip_properties.insert(k.to_string(), Variant(v));
};
if let Some(local_ip) = &self.local_ip {
insert_ip_field(PROPERTY_ADDRESS, Box::new(local_ip.clone()));
}
if let Some(name_servers) = &self.name_servers {
insert_ip_field(PROPERTY_NAME_SERVERS, Box::new(name_servers.clone()));
} else {
insert_ip_field(
PROPERTY_NAME_SERVERS,
Box::new(vec!["8.8.8.8".to_string(), "8.8.4.4".to_string()]),
);
}
if let Some(mtu) = self.mtu {
insert_ip_field(PROPERTY_MTU, Box::new(mtu));
}
if !static_ip_properties.is_empty() {
properties.insert(
PROPERTY_STATIC_IP_CONFIG,
Variant(Box::new(static_ip_properties)),
);
}
let mut peers_buf = Vec::new();
for peer in &self.peers {
let mut peer_properties = HashMap::new();
let mut insert_peer_field = |k: &str, v: &str| {
peer_properties.insert(k.to_string(), v.to_string());
};
insert_peer_field(PROPERTY_PEER_PUBLIC_KEY, &peer.public_key);
insert_peer_field(PROPERTY_PEER_ENDPOINT, &peer.endpoint);
insert_peer_field(PROPERTY_PEER_ALLOWED_IPS, &peer.allowed_ips);
insert_peer_field(
PROPERTY_PEER_PERSISTENT_KEEPALIVE,
&peer.persistent_keepalive,
);
if let Some(val) = &peer.preshared_key {
insert_peer_field(PROPERTY_PEER_PRESHARED_KEY, val);
}
peers_buf.push(peer_properties);
}
properties.insert(PROPERTY_WIREGUARD_PEERS, Variant(Box::new(peers_buf)));
// Always persists keys.
properties.insert(PROPERTY_SAVE_CREDENTIALS, Variant(Box::new(true)));
properties
}
// Updates the service by |args| from `wireguard set` command. If `private-key` or
// `preshared-key` is specified, keys will be read from |reader|, which is usually stdin.
fn update_from_args<R: io::BufRead>(
&mut self,
args: &[&str],
mut reader: R,
) -> Result<(), Error> {
use option_util::*;
// May point to a peer in |self.peers| which is being processed currently.
let mut current_peer = None;
// Indicates if |current_peer| is a peer added in this command.
let mut is_new_peer = false;
let mut iter = args.iter();
while let Some(key) = iter.next() {
let no_peer_err = Error::InvalidArguments(format!(
"Option '{}' should appear after a peer is specified",
key
));
// Forwards the iterator to read the value for the currently processing option.
let mut get_next_as_val = || {
iter.next().ok_or_else(|| {
Error::InvalidArguments(format!(
"Option '{}' expects one parameter but none is given",
key
))
})
};
match *key {
"local-ip" => self.local_ip = Some(parse_local_ip(get_next_as_val()?)?),
"dns" => self.name_servers = parse_dns(get_next_as_val()?)?,
"mtu" => self.mtu = parse_mtu(get_next_as_val()?)?,
"private-key" => {
let lines = [
"Change private key for this service (or press <Enter> directly",
"to let the system generate a random key).",
"New key: ",
];
let prompt = lines.join("\n");
self.private_key = Some(read_key(&prompt, &mut reader)?);
}
"peer" => {
let pubkey = parse_peer(get_next_as_val()?)?;
current_peer = self.peers.iter_mut().find(|x| x.public_key == pubkey);
is_new_peer = current_peer.is_none();
if is_new_peer {
self.peers.push(WireGuardPeer {
public_key: pubkey,
endpoint: "".to_string(),
allowed_ips: "".to_string(),
persistent_keepalive: "".to_string(),
preshared_key: None,
});
current_peer = self.peers.last_mut();
}
}
"remove" => {
if is_new_peer {
return Err(Error::InvalidArguments(
"Peer to be removed does not exist".to_string(),
));
}
let pubkey = current_peer.as_mut().ok_or(no_peer_err)?.public_key.clone();
current_peer = None;
self.peers.retain(|x| x.public_key != pubkey);
}
"endpoint" => {
current_peer.as_mut().ok_or(no_peer_err)?.endpoint =
parse_endpoint(get_next_as_val()?)?;
}
"allowed-ips" => {
current_peer.as_mut().ok_or(no_peer_err)?.allowed_ips =
parse_allowed_ips(get_next_as_val()?)?;
}
"preshared-key" => {
let peer = current_peer.as_mut().ok_or(no_peer_err)?;
let lines = [
format!("Change preshared key for {}", peer.public_key),
"(or press <Enter> directly to remove the key from the peer).".to_string(),
"New key: ".to_string(),
];
let prompt = lines.join("\n");
peer.preshared_key = Some(read_key(&prompt, &mut reader)?);
}
"persistent-keepalive" => {
current_peer
.as_mut()
.ok_or(no_peer_err)?
.persistent_keepalive = parse_persistent_keepalive(get_next_as_val()?)?;
}
_ => return Err(Error::InvalidArguments(format!("Unknown option `{}`", key))),
}
}
Ok(())
}
fn print(&self) {
// TODO(b/177877310): Print the connection state.
println!("name: {}", self.name);
// Always shows local ip since it's mandatory.
println!(
" local ip: {}",
self.local_ip.as_ref().unwrap_or(&"".to_string())
);
println!(" public key: {}", self.public_key);
println!(" private key: (hidden)");
if let Some(dns) = &self.name_servers {
println!(" name servers: {}", dns.join(", "));
}
if let Some(mtu) = self.mtu {
println!(" mtu: {}", mtu);
}
println!();
for p in &self.peers {
println!(" peer: {}", p.public_key);
println!(" preshared key: (hidden or not set)");
println!(" endpoint: {}", p.endpoint);
println!(" allowed ips: {}", p.allowed_ips);
println!(" persistent keepalive: {}", p.persistent_keepalive);
println!();
}
}
// Checks if every required field is filled. We do not check the service state here (i.e., if
// the service is "Idle", "Connected", or in other states), since shill will return a failure
// with a proper message immediately if the service is not connectable because of its state.
fn check_connectability(&self) -> Result<(), Error> {
for p in &self.peers {
// `endpoint` is the only required field.
if p.endpoint.is_empty() {
return Err(Error::ServiceNotConnectable {
service_name: self.name.clone(),
reason: format!("Peer {} does not have a valid endpoint", p.public_key),
});
}
}
Ok(())
}
}
mod option_util {
use std::{
io::{self, Write},
net::{Ipv4Addr, SocketAddr},
ops::Range,
};
use super::Error;
// Length of base64-encoded keys for WireGuard (Curve25519).
pub const WG_BASE64_KEYLEN: usize = 44;
fn is_valid_i32_in(val: &str, range: Range<i32>) -> bool {
if let Ok(v) = val.parse::<i32>() {
range.contains(&v)
} else {
false
}
}
fn is_valid_hostname(val: &str) -> bool {
// Some basic checks for a hostname, as per `man 7 hostname`.
if !(1..254).contains(&val.len()) {
return false;
}
for c in val.chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '.' => {}
_ => return false,
}
}
true
}
fn is_valid_wg_key(val: &str) -> bool {
if val.len() != WG_BASE64_KEYLEN {
return false;
}
// The last character must be the padding character given the key length.
if !val.ends_with('=') {
return false;
}
// Some basic checks for a base64 string.
for c in val[0..val.len() - 1].chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '/' | '+' => {}
_ => return false,
}
}
true
}
// Prompts |prompt| to the user, and reads a base64-encoded WireGuard key from |reader|.
pub(super) fn read_key<R: io::BufRead>(prompt: &str, reader: &mut R) -> Result<String, Error> {
let internal_err = |e| Error::Internal(format!("Read error: {}", e));
print!("{}", prompt);
io::stdout().flush().map_err(internal_err)?;
let mut buf = "".to_string();
reader.read_line(&mut buf).map_err(internal_err)?;
let buf = buf.trim().to_string();
if buf.is_empty() || is_valid_wg_key(&buf) {
Ok(buf)
} else {
Err(Error::InvalidArguments(
"The input is not a valid WireGuard key".to_string(),
))
}
}
pub(super) fn parse_local_ip(val: &str) -> Result<String, Error> {
val.parse::<Ipv4Addr>()
.map(|ip| ip.to_string())
.map_err(|_| {
Error::InvalidArguments(format!(
"'local-ip' should contain a valid IPv4 address, but got '{}'",
val
))
})
}
pub(super) fn parse_dns(val: &str) -> Result<Option<Vec<String>>, Error> {
if val.is_empty() {
return Ok(None);
}
val.split(',')
.map(|x| x.parse::<Ipv4Addr>())
.map(|x| x.map(|y| y.to_string()))
.collect::<Result<Vec<_>, _>>()
.map(Some)
.map_err(|_| {
Error::InvalidArguments(format!(
"'dns' should be a comma-separated list of valid IPv4 addresses, but got '{}'",
val
))
})
}
pub(super) fn parse_mtu(val: &str) -> Result<Option<i32>, Error> {
if val.is_empty() {
return Ok(None);
}
match val.parse::<i32>() {
Ok(0) => Ok(None),
// [576, 65536) as per wireguard-tools.
Ok(x) if (576..65536).contains(&x) => Ok(Some(x)),
_ => Err(Error::InvalidArguments(format!(
"'mtu' should be in range from 576 to 65535, but got '{}'",
val
))),
}
}
pub(super) fn parse_peer(val: &str) -> Result<String, Error> {
if is_valid_wg_key(val) {
Ok(val.to_string())
} else {
Err(Error::InvalidArguments(
"The given value after 'peer' is not a vaild public key".to_string(),
))
}
}
pub(super) fn parse_endpoint(val: &str) -> Result<String, Error> {
if let Ok(s) = val.parse::<SocketAddr>() {
return Ok(s.to_string());
}
let segments: Vec<&str> = val.split(':').collect();
match segments.as_slice() {
[h, p] if is_valid_hostname(h) && is_valid_i32_in(p, 1..65536) => Ok(val.to_string()),
_ => Err(Error::InvalidArguments(format!(
"'endpoint' should be an IP or hostname, followed by a colon and then a port number, but got '{}'",
val
))),
}
}
pub(super) fn parse_allowed_ips(val: &str) -> Result<String, Error> {
if val.is_empty() {
return Ok("".to_string());
}
// Currently, only IPv4 addresses are allowed.
let err = || {
Error::InvalidArguments(format!("'allowed-ips' should be a common-separated list of IPv4 addresses with CIDR notation, but got '{}'", val))
};
// Clears the bits after netmask (e.g., "192.168.1.1/24" => "192.168.1.0/24")
let formatter = |s: &str| -> Result<String, Error> {
let segments: Vec<&str> = s.split('/').collect();
if segments.len() != 2 {
return Err(err());
}
let addr = segments[0].parse::<Ipv4Addr>().map_err(|_| err())?.octets();
let prefix = segments[1].parse::<i32>().map_err(|_| err())?;
let addr_u32 = addr.iter().fold(0u32, |acc, x| (acc << 8) + (*x as u32));
match prefix {
0..=31 => {
let base_addr = Ipv4Addr::from(addr_u32 & !(0xFFFFFFFFu32 >> prefix));
Ok(format!("{}/{}", base_addr.to_string(), prefix))
}
32 => Ok(s.to_string()),
_ => Err(err()),
}
};
Ok(val
.split(',')
.map(formatter)
.collect::<Result<Vec<_>, _>>()?
.join(","))
}
pub(super) fn parse_persistent_keepalive(val: &str) -> Result<String, Error> {
match val {
"off" => Ok(val.to_string()),
v if is_valid_i32_in(v, 0..65536) => Ok(val.to_string()),
_ => Err(Error::InvalidArguments(format!(
"'persistent-keepalive' should be in range from 0 to 65535, but got '{}'",
val
))),
}
}
}
fn make_dbus_connection() -> Result<Connection, Error> {
Connection::new_system()
.map_err(|err| Error::Internal(format!("Failed to get D-Bus connection: {}", err)))
}
fn make_manager_proxy(connection: &Connection) -> dbus::blocking::Proxy<&Connection> {
connection.with_proxy("org.chromium.flimflam", "/", DEFAULT_DBUS_TIMEOUT)
}
fn make_service_proxy<'a>(
connection: &'a Connection,
service_path: &'a str,
) -> dbus::blocking::Proxy<'a, &'a Connection> {
connection.with_proxy("org.chromium.flimflam", service_path, DEFAULT_DBUS_TIMEOUT)
}
// Queries Shill to get all configured WireGuard services.
fn get_wireguard_services(connection: &Connection) -> Result<Vec<WireGuardService>, Error> {
let manager_proxy = make_manager_proxy(connection);
let manager_properties: InputPropMap =
OrgChromiumFlimflamManager::get_properties(&manager_proxy)
.map_err(|err| Error::Internal(format!("Failed to get Manager properties: {}", err)))?;
let mut wg_services = Vec::new();
// The "Services" property should contain a list of D-Bus paths.
let services = manager_properties.get_strings(PROPERTY_SERVICES)?;
for path in services {
let proxy = make_service_proxy(connection, &path);
let service_properties = OrgChromiumFlimflamService::get_properties(&proxy)
.map_err(|err| Error::Internal(format!("Failed to get service properties: {}", err)))?;
match WireGuardService::parse_from_prop_map(&service_properties) {
Ok(mut service) => {
service.path = Some(path.to_string());
wg_services.push(service)
}
Err(Error::ServiceNotFound(_)) => continue,
Err(other_err) => return Err(other_err),
};
}
Ok(wg_services)
}
fn get_wireguard_service_by_name(
connection: &Connection,
service_name: &str,
) -> Result<WireGuardService, Error> {
let services: Vec<WireGuardService> = get_wireguard_services(connection)?
.into_iter()
.filter(|x| x.name == service_name)
.collect();
match services.len() {
0 => Err(Error::ServiceNotFound(service_name.to_string())),
1 => Ok(services.into_iter().next().unwrap()),
_ => Err(Error::Internal(
"Found duplicated WireGuard services".to_string(),
)),
}
}
fn check_wireguard_support() -> Result<(), Error> {
let connection = make_dbus_connection()?;
let manager_proxy = make_manager_proxy(&connection);
let manager_properties: InputPropMap =
OrgChromiumFlimflamManager::get_properties(&manager_proxy)
.map_err(|err| Error::Internal(format!("Failed to get Manager properties: {}", err)))?;
let prohibited_techs = manager_properties.get_string(PROPERTY_PROHIBITED_TECHNOLOGIES)?;
if prohibited_techs.split(',').any(|x| x == TYPE_VPN) {
return Err(Error::VpnDisabled);
}
let supported_vpns = manager_properties.get_string(PROPERTY_SUPPORTED_VPN_TYPES)?;
if !supported_vpns.split(',').any(|x| x == TYPE_WIREGUARD) {
return Err(Error::WireGuardUnavailable);
}
Ok(())
}
fn wireguard_list() -> Result<(), Error> {
let connection = make_dbus_connection()?;
let mut services = get_wireguard_services(&connection)?;
services.sort_by(|a, b| a.name.cmp(&b.name));
for service in &services {
service.print()
}
Ok(())
}
fn wireguard_show(service_name: &str) -> Result<(), Error> {
let connection = make_dbus_connection()?;
get_wireguard_service_by_name(&connection, service_name)?.print();
Ok(())
}
fn wireguard_new(service_name: &str) -> Result<(), Error> {
let connection = make_dbus_connection()?;
// Checks if there is already a service with the given name.
match get_wireguard_service_by_name(&connection, service_name) {
Ok(_) => return Err(Error::ServiceAlreadyExists(service_name.to_string())),
Err(Error::ServiceNotFound(_)) => {}
Err(err) => return Err(err),
};
let manager_proxy = make_manager_proxy(&connection);
let service = WireGuardService {
path: None,
name: service_name.to_string(),
local_ip: None,
private_key: None,
public_key: "".to_string(),
name_servers: None,
mtu: None,
peers: Vec::new(),
};
manager_proxy
.configure_service(service.encode_into_prop_map())
.map_err(|err| Error::Internal(format!("Failed to configure service: {}", err)))?;
println!("Service {} created", service_name);
Ok(())
}
fn wireguard_set(service_name: &str, args: &[&str]) -> Result<(), Error> {
let connection = make_dbus_connection()?;
let mut wg_service = get_wireguard_service_by_name(&connection, service_name)?;
wg_service.update_from_args(args, io::stdin().lock())?;
let manager_proxy = make_manager_proxy(&connection);
let properties = wg_service.encode_into_prop_map();
manager_proxy.configure_service(properties).map_err(|err| {
error!("ERROR: Failed to configure service: {}", err);
Error::Internal("".to_string())
})?;
println!("Service {} updated", service_name);
Ok(())
}
fn wireguard_connect(service_name: &str) -> Result<(), Error> {
let connection = make_dbus_connection()?;
let service = get_wireguard_service_by_name(&connection, service_name)?;
service.check_connectability()?;
make_service_proxy(&connection, &service.path.unwrap())
.connect()
.map_err(|err| Error::Internal(format!("Failed to connect service: {}", err)))?;
println!("Connecting to {}..", service_name);
Ok(())
}
fn wireguard_disconnect(service_name: &str) -> Result<(), Error> {
let connection = make_dbus_connection()?;
let service = get_wireguard_service_by_name(&connection, service_name)?;
make_service_proxy(&connection, &service.path.unwrap())
.disconnect()
.map_err(|err| Error::Internal(format!("Failed to disconnect service: {}", err)))?;
println!("Disconnecting from {}..", service_name);
Ok(())
}
fn wireguard_del(service_name: &str) -> Result<(), Error> {
let connection = make_dbus_connection()?;
let service = get_wireguard_service_by_name(&connection, service_name)?;
make_service_proxy(&connection, &service.path.unwrap())
.remove()
.map_err(|err| Error::Internal(format!("Failed to delete service: {}", err)))?;
println!("Service {} was deleted", service_name);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
// Keys are generate randomly by `wireguard-tools`, only for test usages.
const GOOD_KEYS: [&str; 7] = [
"",
" ",
" \n",
"yJtfKXN4bvw/Dlgp4uRUrbBJZDQGojDL7J5mbdh+x2k=",
"yJtfKXN4bvw/Dlgp4uRUrbBJZDQGojDL7J5mbdh+x2k= ",
" yJtfKXN4bvw/Dlgp4uRUrbBJZDQGojDL7J5mbdh+x2k=",
" yJtfKXN4bvw/Dlgp4uRUrbBJZDQGojDL7J5mbdh+x2k=\n",
];
const BAD_KEYS: [&str; 4] = [
"abc",
"abc\n",
"yJtfKXN4bvw/Dlgp4uRUrbB\nJZDQGojDL7J5mbdh+x2k=",
"yJtfKXN4bvw/Dlgp4uRUrbBJZDQGojDL7J5mbdh+x2\n",
];
struct TestVars {
public_key_a: String,
public_key_b: String,
public_key_new: String,
peer_a: WireGuardPeer,
peer_b: WireGuardPeer,
service: WireGuardService,
}
impl TestVars {
fn new() -> Self {
// Keys are generated randomly by `wireguard-tools`, only for test usages.
let public_key_a: String = "JNy+A9BmLM3wAY/8JrL8KZ0pxIuwZ61FjsrK4DyNXgk=".to_string();
let public_key_b: String = "IYo3i7xxgCKXEc6Nvawlmg2wO0Qf5ojqvLjz386RmB4=".to_string();
let public_key_new: String = "LCDxqGY8J5+gXFGh1IqAsMC2CHF6w+oAuegB0nA8Ti4=".to_string();
let peer_a = WireGuardPeer {
public_key: public_key_a.clone(),
endpoint: "10.0.8.1:13579".to_string(),
allowed_ips: "0.0.0.0/0".to_string(),
persistent_keepalive: "".to_string(),
preshared_key: None,
};
let peer_b = WireGuardPeer {
public_key: public_key_b.clone(),
endpoint: "10.0.10.1:24680".to_string(),
allowed_ips: "10.8.0.0/16,192.168.100.0/24".to_string(),
persistent_keepalive: "3".to_string(),
preshared_key: None,
};
let service = WireGuardService {
path: None,
name: "wg_test".to_string(),
local_ip: Some("192.168.1.2".to_string()),
private_key: None,
public_key: "".to_string(),
name_servers: Some(vec!["4.3.2.1".to_string()]),
mtu: Some(1234),
peers: vec![peer_a.clone(), peer_b.clone()],
};
TestVars {
public_key_a,
public_key_b,
public_key_new,
peer_a,
peer_b,
service,
}
}
}
#[test]
fn test_update_local_ip() {
let vars = TestVars::new();
let cases = ["192.168.1.1", "10.8.0.3"];
for c in cases.iter() {
let mut expected = vars.service.clone();
expected.local_ip = Some(c.to_string());
let mut actual = vars.service.clone();
let args = ["local-ip", c];
actual.update_from_args(&args, "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_local_ip_invalid() {
let vars = TestVars::new();
let cases = ["", "192.168.1", "abcdabcd", "fe80::1", "1.2.3.4,1.2.3.5"];
for c in cases.iter() {
let mut actual = vars.service.clone();
let args = ["local-ip", c];
actual.update_from_args(&args, "".as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_private_key() {
let vars = TestVars::new();
for c in GOOD_KEYS.iter() {
let mut expected = vars.service.clone();
expected.private_key = Some(c.trim().to_string());
let mut actual = vars.service.clone();
let args = ["private-key"];
actual.update_from_args(&args, c.as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_private_key_invalid() {
let vars = TestVars::new();
for c in BAD_KEYS.iter() {
let mut actual = vars.service.clone();
let args = ["private-key"];
actual.update_from_args(&args, c.as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_dns() {
let vars = TestVars::new();
let cases = ["8.8.8.8,1.2.3.4", "8.8.8.8"];
for c in cases.iter() {
let mut expected = vars.service.clone();
expected.name_servers = Some(c.split(',').map(|x| x.to_string()).collect());
let mut actual = vars.service.clone();
actual.update_from_args(&["dns", c], "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_dns_default() {
let vars = TestVars::new();
let mut expected = vars.service.clone();
expected.name_servers = None;
let mut actual = vars.service;
actual
.update_from_args(&["dns", ""], "".as_bytes())
.unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_update_dns_invalid() {
let vars = TestVars::new();
let cases = ["192.168.1", "abcdabcd", "fe80::1", "8.8.8.8,abc"];
for c in cases.iter() {
let mut actual = vars.service.clone();
let args = ["dns", c];
actual.update_from_args(&args, "".as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_mtu() {
let vars = TestVars::new();
let cases = ["576", "1000", "65535"];
for c in cases.iter() {
let mut expected = vars.service.clone();
expected.mtu = Some(c.parse::<i32>().unwrap());
let mut actual = vars.service.clone();
actual.update_from_args(&["mtu", c], "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_mtu_default() {
let vars = TestVars::new();
let cases = ["0", ""];
for c in cases.iter() {
let mut expected = vars.service.clone();
expected.mtu = None;
let mut actual = vars.service.clone();
actual.update_from_args(&["mtu", c], "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_mtu_invalid() {
let vars = TestVars::new();
let cases = ["-1", "575", "1000.0", "65536", "abcde"];
for c in cases.iter() {
let mut actual = vars.service.clone();
let args = ["mtu", c];
actual.update_from_args(&args, "".as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_peer_endpoint() {
let vars = TestVars::new();
let cases = [
"192.168.1.1:1234",
"10.8.0.3:21",
"[fe80::1]:12345",
"www.example.com:65535",
"a-z.A-Z01234.xyz:1111",
];
for c in cases.iter() {
let mut expected = vars.service.clone();
let mut updated_peer_b = vars.peer_b.clone();
updated_peer_b.endpoint = c.to_string();
expected.peers = vec![vars.peer_a.clone(), updated_peer_b];
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "endpoint", c];
actual.update_from_args(&args, "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_peer_endpoint_invalid() {
let vars = TestVars::new();
let cases = [
"fe80::1:12345", // invalid IPv6 addr with port
"192.168.1.1:-1", // bad port
"10.8.0.3:65536",
"&example.com:1234", // bad hostname
"example,com:1234",
":1234",
];
for c in cases.iter() {
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "endpoint", c];
actual.update_from_args(&args, "".as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_peer_allowed_ips() {
let vars = TestVars::new();
// Inputs and expected outputs.
let cases = [
("", ""),
("0.0.0.0/0", "0.0.0.0/0"),
("192.168.12.13/0", "0.0.0.0/0"),
("192.168.0.1/32", "192.168.0.1/32"),
("192.168.1.0/24", "192.168.1.0/24"),
("192.168.1.1/24", "192.168.1.0/24"),
("192.168.0.0/16", "192.168.0.0/16"),
("192.168.12.13/16", "192.168.0.0/16"),
("192.168.128.0/20,10.0.0.0/8", "192.168.128.0/20,10.0.0.0/8"),
];
for c in cases.iter() {
let mut expected = vars.service.clone();
let mut updated_peer_b = vars.peer_b.clone();
updated_peer_b.allowed_ips = c.1.to_string();
expected.peers = vec![vars.peer_a.clone(), updated_peer_b];
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "allowed-ips", c.0];
actual.update_from_args(&args, "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_peer_allowed_ips_invalid() {
let vars = TestVars::new();
let cases = [
"fe80::0/32", // IPv6 is not supported
"192.168.1.0/-1", // bad CIDR
"192.168.1.0/256",
"192.168.128.0/20, 10.0.0.0/8", // additional space
"192.168.128.0/20;10.0.0.0/8", // bad separator
];
for c in cases.iter() {
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "allowed-ips", c];
actual.update_from_args(&args, "".as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_peer_persistent_keepalive() {
let vars = TestVars::new();
let cases = ["off", "0", "1", "300", "65535"];
for c in cases.iter() {
let mut expected = vars.service.clone();
let mut updated_peer_b = vars.peer_b.clone();
updated_peer_b.persistent_keepalive = c.to_string();
expected.peers = vec![vars.peer_a.clone(), updated_peer_b];
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "persistent-keepalive", c];
actual.update_from_args(&args, "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_peer_persistent_keepalive_invalid() {
let vars = TestVars::new();
let cases = ["-1", "1.0", "65536", "abcd", "192.168.1.1", "remove"];
for c in cases.iter() {
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "persistent-keepalive", c];
actual.update_from_args(&args, "".as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_peer_preshared_key() {
let vars = TestVars::new();
for c in GOOD_KEYS.iter() {
let mut expected = vars.service.clone();
let mut updated_peer_b = vars.peer_b.clone();
updated_peer_b.preshared_key = Some(c.trim().to_string());
expected.peers = vec![vars.peer_a.clone(), updated_peer_b];
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "preshared-key"];
actual.update_from_args(&args, c.as_bytes()).unwrap();
assert_eq!(expected, actual);
}
}
#[test]
fn test_update_peer_preshared_key_invalid() {
let vars = TestVars::new();
for c in BAD_KEYS.iter() {
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_b, "preshared-key"];
actual.update_from_args(&args, c.as_bytes()).unwrap_err();
}
}
#[test]
fn test_update_add_peer() {
let vars = TestVars::new();
let endpoint_new = "34.56.78.90:45678";
let mut expected = vars.service.clone();
expected.peers.push(WireGuardPeer {
public_key: vars.public_key_new.clone(),
endpoint: endpoint_new.to_string(),
allowed_ips: "".to_string(),
persistent_keepalive: "".to_string(),
preshared_key: None,
});
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_new, "endpoint", endpoint_new];
actual.update_from_args(&args, "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_update_remove_peer() {
let vars = TestVars::new();
let mut expected = vars.service.clone();
expected.peers = vec![vars.peer_b.clone()];
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_a, "remove"];
actual.update_from_args(&args, "".as_bytes()).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_update_remove_new_peer() {
let vars = TestVars::new();
let mut actual = vars.service.clone();
let args = ["peer", &vars.public_key_new, "remove"];
actual.update_from_args(&args, "".as_bytes()).unwrap_err();
}
#[test]
fn test_update_all_in_one() {
// Keys below generated randomly by `wireguard-tools`, only for test usages.
let vars = TestVars::new();
let local_ip = "192.168.99.2";
let private_key = "yJtfKXN4bvw/Dlgp4uRUrbBJZDQGojDL7J5mbdh+x2k=";
let endpoint_a = "34.56.78.90:45678";
let allowed_ips_a = "192.168.101.0/24";
let persistent_keepalive_a = "21";
let preshared_key_a = "";
let endpoint_new = "90.56.78.34:12345";
let allowed_ips_new = "192.168.102.0/24";
let persistent_keepalive_new = "off";
let preshared_key_new = "xZkhH/yBfi3NtT1MzX0iszT03dr1g/0URWEgG5hwCrQ=";
let updated_peer_a = WireGuardPeer {
public_key: vars.public_key_a.clone(),
endpoint: endpoint_a.to_string(),
allowed_ips: allowed_ips_a.to_string(),
persistent_keepalive: persistent_keepalive_a.to_string(),
preshared_key: Some(preshared_key_a.to_string()),
};
let new_peer = WireGuardPeer {
public_key: vars.public_key_new.clone(),
endpoint: endpoint_new.to_string(),
allowed_ips: allowed_ips_new.to_string(),
persistent_keepalive: persistent_keepalive_new.to_string(),
preshared_key: Some(preshared_key_new.to_string()),
};
let mut expected = vars.service.clone();
expected.local_ip = Some(local_ip.to_string());
expected.private_key = Some(private_key.to_string());
expected.name_servers = Some(vec!["8.8.8.8".to_string(), "1.2.3.4".to_string()]);
expected.mtu = Some(1000);
expected.peers = vec![updated_peer_a, new_peer];
let mut cmd = vec!["local-ip", local_ip];
cmd.extend_from_slice(&["private-key"]);
cmd.extend_from_slice(&["dns", "8.8.8.8,1.2.3.4", "mtu", "1000"]);
cmd.extend_from_slice(&["peer", &vars.public_key_b, "remove"]);
cmd.extend_from_slice(&["peer", &vars.public_key_a, "endpoint", endpoint_a]);
cmd.extend_from_slice(&["preshared-key"]);
cmd.extend_from_slice(&["allowed-ips", allowed_ips_a]);
cmd.extend_from_slice(&["persistent-keepalive", persistent_keepalive_a]);
cmd.extend_from_slice(&["peer", &vars.public_key_new, "endpoint", endpoint_new]);
cmd.extend_from_slice(&["allowed-ips", allowed_ips_new]);
cmd.extend_from_slice(&["persistent-keepalive", persistent_keepalive_new]);
cmd.extend_from_slice(&["preshared-key"]);
let input = format!(
"{}\n{}\n{}\n",
private_key, preshared_key_a, preshared_key_new
);
let mut actual = vars.service;
actual.update_from_args(&cmd, input.as_bytes()).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_check_connectability() {
let mut vars = TestVars::new();
let service = &mut vars.service;
service.check_connectability().unwrap();
service.peers[0].endpoint = "".to_string();
service.check_connectability().unwrap_err();
}
}