client: tighten some error types and apply helptext to io::Error

pull/121/head
Jake McGinty 2021-06-16 20:22:28 +09:00
parent 93b4b0b43c
commit 1aed782683
6 changed files with 75 additions and 43 deletions

View File

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use shared::{ensure_dirs_exist, Cidr, IoErrorContext, Peer, WrappedIoError, CLIENT_DATA_DIR}; use shared::{ensure_dirs_exist, Cidr, IoErrorContext, Peer, WrappedIoError, CLIENT_DATA_DIR};
use std::{ use std::{
fs::{File, OpenOptions}, fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write}, io::{self, Read, Seek, SeekFrom, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use wgctrl::InterfaceName; use wgctrl::InterfaceName;
@ -121,7 +121,7 @@ impl DataStore {
} }
} }
pub fn write(&mut self) -> Result<(), Error> { pub fn write(&mut self) -> Result<(), io::Error> {
self.file.seek(SeekFrom::Start(0))?; self.file.seek(SeekFrom::Start(0))?;
self.file.set_len(0)?; self.file.set_len(0)?;
self.file self.file

View File

@ -245,7 +245,7 @@ fn update_hosts_file(
interface: &InterfaceName, interface: &InterfaceName,
hosts_path: PathBuf, hosts_path: PathBuf,
peers: &[Peer], peers: &[Peer],
) -> Result<(), Error> { ) -> Result<(), WrappedIoError> {
log::info!("updating {} with the latest peers.", "/etc/hosts".yellow()); log::info!("updating {} with the latest peers.", "/etc/hosts".yellow());
let mut hosts_builder = HostsBuilder::new(format!("innernet {}", interface)); let mut hosts_builder = HostsBuilder::new(format!("innernet {}", interface));
@ -358,7 +358,11 @@ fn redeem_invite(
network: NetworkOpt, network: NetworkOpt,
) -> Result<(), Error> { ) -> Result<(), Error> {
log::info!("bringing up the interface."); log::info!("bringing up the interface.");
let resolved_endpoint = config.server.external_endpoint.resolve()?; let resolved_endpoint = config
.server
.external_endpoint
.resolve()
.with_str(config.server.external_endpoint.to_string())?;
wg::up( wg::up(
&iface, &iface,
&config.interface.private_key, &config.interface.private_key,
@ -370,7 +374,8 @@ fn redeem_invite(
resolved_endpoint, resolved_endpoint,
)), )),
network, network,
)?; )
.with_str(iface.to_string())?;
log::info!("Generating new keypair."); log::info!("Generating new keypair.");
let keypair = wgctrl::KeyPair::generate(); let keypair = wgctrl::KeyPair::generate();
@ -397,7 +402,8 @@ fn redeem_invite(
log::info!("Changing keys and waiting for server's WireGuard interface to transition.",); log::info!("Changing keys and waiting for server's WireGuard interface to transition.",);
DeviceUpdate::new() DeviceUpdate::new()
.set_private_key(keypair.private) .set_private_key(keypair.private)
.apply(&iface, network.backend)?; .apply(&iface, network.backend)
.with_str(iface.to_string())?;
thread::sleep(*REDEEM_TRANSITION_WAIT); thread::sleep(*REDEEM_TRANSITION_WAIT);
Ok(()) Ok(())
@ -442,7 +448,11 @@ fn fetch(
} }
log::info!("bringing up the interface."); log::info!("bringing up the interface.");
let resolved_endpoint = config.server.external_endpoint.resolve()?; let resolved_endpoint = config
.server
.external_endpoint
.resolve()
.with_str(config.server.external_endpoint.to_string())?;
wg::up( wg::up(
interface, interface,
&config.interface.private_key, &config.interface.private_key,
@ -454,7 +464,8 @@ fn fetch(
resolved_endpoint, resolved_endpoint,
)), )),
network, network,
)? )
.with_str(interface.to_string())?;
} }
log::info!("fetching state from server."); log::info!("fetching state from server.");
@ -521,7 +532,9 @@ fn fetch(
} }
if device_config_changed { if device_config_changed {
device_config_builder.apply(&interface, network.backend)?; device_config_builder
.apply(&interface, network.backend)
.with_str(interface.to_string())?;
if let Some(path) = hosts_path { if let Some(path) = hosts_path {
update_hosts_file(interface, path, &peers)?; update_hosts_file(interface, path, &peers)?;
@ -534,7 +547,7 @@ fn fetch(
} }
store.set_cidrs(cidrs); store.set_cidrs(cidrs);
store.update_peers(peers)?; store.update_peers(peers)?;
store.write()?; store.write().with_str(interface.to_string())?;
Ok(()) Ok(())
} }
@ -1020,6 +1033,9 @@ fn main() {
if let Some(e) = e.downcast_ref::<WrappedIoError>() { if let Some(e) = e.downcast_ref::<WrappedIoError>() {
util::permissions_helptext(e); util::permissions_helptext(e);
} }
if let Some(e) = e.downcast_ref::<io::Error>() {
util::permissions_helptext(e);
}
std::process::exit(1); std::process::exit(1);
} }
} }

View File

@ -102,7 +102,7 @@ pub fn human_size(bytes: u64) -> String {
} }
} }
pub fn permissions_helptext(e: &WrappedIoError) { pub fn permissions_helptext(e: &io::Error) {
if e.raw_os_error() == Some(1) { if e.raw_os_error() == Some(1) {
let current_exe = std::env::current_exe() let current_exe = std::env::current_exe()
.ok() .ok()

View File

@ -128,7 +128,7 @@ impl InterfaceConfig {
.with_extension("conf") .with_extension("conf")
} }
fn build_config_file_path(interface: &InterfaceName) -> Result<PathBuf, Error> { fn build_config_file_path(interface: &InterfaceName) -> Result<PathBuf, WrappedIoError> {
ensure_dirs_exist(&[*CLIENT_CONFIG_DIR])?; ensure_dirs_exist(&[*CLIENT_CONFIG_DIR])?;
Ok(Self::get_path(interface)) Ok(Self::get_path(interface))
} }

View File

@ -1,5 +1,3 @@
use crate::Error;
use anyhow::anyhow;
use ipnetwork::IpNetwork; use ipnetwork::IpNetwork;
use netlink_packet_core::{ use netlink_packet_core::{
NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST, NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST,
@ -12,9 +10,12 @@ use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr};
use std::io; use std::io;
use wgctrl::InterfaceName; use wgctrl::InterfaceName;
fn if_nametoindex(interface: &InterfaceName) -> Result<u32, Error> { fn if_nametoindex(interface: &InterfaceName) -> Result<u32, io::Error> {
match unsafe { libc::if_nametoindex(interface.as_ptr()) } { match unsafe { libc::if_nametoindex(interface.as_ptr()) } {
0 => Err(anyhow!("couldn't find interface '{}'.", interface)), 0 => Err(io::Error::new(
io::ErrorKind::NotFound,
format!("couldn't find interface '{}'.", interface),
)),
index => Ok(index), index => Ok(index),
} }
} }
@ -52,7 +53,7 @@ fn netlink_call(
Ok(response) Ok(response)
} }
pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), Error> { pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), io::Error> {
let index = if_nametoindex(interface)?; let index = if_nametoindex(interface)?;
let message = LinkMessage { let message = LinkMessage {
header: LinkHeader { header: LinkHeader {
@ -66,7 +67,7 @@ pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), Error> {
Ok(()) Ok(())
} }
pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error> { pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), io::Error> {
let index = if_nametoindex(interface)?; let index = if_nametoindex(interface)?;
let (family, nlas) = match addr { let (family, nlas) = match addr {
IpNetwork::V4(network) => { IpNetwork::V4(network) => {
@ -101,7 +102,7 @@ pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error>
Ok(()) Ok(())
} }
pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, Error> { pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, io::Error> {
let if_index = if_nametoindex(interface)?; let if_index = if_nametoindex(interface)?;
let (address_family, dst) = match cidr { let (address_family, dst) = match cidr {
IpNetwork::V4(network) => (AF_INET as u8, network.network().octets().to_vec()), IpNetwork::V4(network) => (AF_INET as u8, network.network().octets().to_vec()),

View File

@ -1,10 +1,13 @@
use crate::{Error, IoErrorContext, NetworkOpt}; use crate::{Error, IoErrorContext, NetworkOpt};
use ipnetwork::IpNetwork; use ipnetwork::IpNetwork;
use std::net::{IpAddr, SocketAddr}; use std::{
io,
net::{IpAddr, SocketAddr},
};
use wgctrl::{Backend, Device, DeviceUpdate, InterfaceName, PeerConfigBuilder}; use wgctrl::{Backend, Device, DeviceUpdate, InterfaceName, PeerConfigBuilder};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn cmd(bin: &str, args: &[&str]) -> Result<std::process::Output, Error> { fn cmd(bin: &str, args: &[&str]) -> Result<std::process::Output, io::Error> {
let output = std::process::Command::new(bin).args(args).output()?; let output = std::process::Command::new(bin).args(args).output()?;
log::debug!("cmd: {} {}", bin, args.join(" ")); log::debug!("cmd: {} {}", bin, args.join(" "));
log::debug!("status: {:?}", output.status.code()); log::debug!("status: {:?}", output.status.code());
@ -13,19 +16,21 @@ fn cmd(bin: &str, args: &[&str]) -> Result<std::process::Output, Error> {
if output.status.success() { if output.status.success() {
Ok(output) Ok(output)
} else { } else {
Err(anyhow::anyhow!( Err(io::Error::new(
"failed to run {} {} command: {}", io::ErrorKind::Other,
bin, format!(
args.join(" "), "failed to run {} {} command: {}",
String::from_utf8_lossy(&output.stderr) bin,
args.join(" "),
String::from_utf8_lossy(&output.stderr)
),
)) ))
} }
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error> { pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), io::Error> {
let real_interface = let real_interface = wgctrl::backends::userspace::resolve_tun(interface)?;
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
if addr.is_ipv4() { if addr.is_ipv4() {
cmd( cmd(
@ -49,9 +54,8 @@ pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error>
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), Error> { pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), io::Error> {
let real_interface = let real_interface = wgctrl::backends::userspace::resolve_tun(interface)?;
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
cmd("ifconfig", &[&real_interface, "mtu", &mtu.to_string()])?; cmd("ifconfig", &[&real_interface, "mtu", &mtu.to_string()])?;
Ok(()) Ok(())
} }
@ -69,11 +73,17 @@ pub fn up(
listen_port: Option<u16>, listen_port: Option<u16>,
peer: Option<(&str, IpAddr, SocketAddr)>, peer: Option<(&str, IpAddr, SocketAddr)>,
network: NetworkOpt, network: NetworkOpt,
) -> Result<(), Error> { ) -> Result<(), io::Error> {
let mut device = DeviceUpdate::new(); let mut device = DeviceUpdate::new();
if let Some((public_key, address, endpoint)) = peer { if let Some((public_key, address, endpoint)) = peer {
let prefix = if address.is_ipv4() { 32 } else { 128 }; let prefix = if address.is_ipv4() { 32 } else { 128 };
let peer_config = PeerConfigBuilder::new(&wgctrl::Key::from_base64(&public_key)?) let peer_config =
PeerConfigBuilder::new(&wgctrl::Key::from_base64(&public_key).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"failed to parse base64 public key",
)
})?)
.add_allowed_ip(address, prefix) .add_allowed_ip(address, prefix)
.set_endpoint(endpoint); .set_endpoint(endpoint);
device = device.add_peer(peer_config); device = device.add_peer(peer_config);
@ -85,7 +95,12 @@ pub fn up(
.set_private_key(wgctrl::Key::from_base64(&private_key).unwrap()) .set_private_key(wgctrl::Key::from_base64(&private_key).unwrap())
.apply(interface, network.backend)?; .apply(interface, network.backend)?;
set_addr(interface, address)?; set_addr(interface, address)?;
set_up(interface, network.mtu.unwrap_or_else(|| if address.is_ipv4() { 1420 } else { 1400 }))?; set_up(
interface,
network
.mtu
.unwrap_or_else(|| if address.is_ipv4() { 1420 } else { 1400 }),
)?;
if !network.no_routing { if !network.no_routing {
add_route(interface, address)?; add_route(interface, address)?;
} }
@ -119,9 +134,8 @@ pub fn down(interface: &InterfaceName, backend: Backend) -> Result<(), Error> {
/// Returns an error if the process doesn't exit successfully, otherwise returns /// Returns an error if the process doesn't exit successfully, otherwise returns
/// true if the route was changed, false if the route already exists. /// true if the route was changed, false if the route already exists.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, Error> { pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, io::Error> {
let real_interface = let real_interface = wgctrl::backends::userspace::resolve_tun(interface)?;
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
let output = cmd( let output = cmd(
"route", "route",
&[ &[
@ -135,11 +149,12 @@ pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, Err
)?; )?;
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() { if !output.status.success() {
Err(anyhow::anyhow!( Err(io::Error::new(
"failed to add route for device {} ({}): {}", io::ErrorKind::Other,
&interface, format!(
real_interface, "failed to add route for device {} ({}): {}",
stderr &interface, real_interface, stderr
),
)) ))
} else { } else {
Ok(!stderr.contains("File exists")) Ok(!stderr.contains("File exists"))