wgctrl: use wireguard backends explicitly (with OS-specific defaults) (#85)

Based on the conversation from #5 (comment) - this changes innernet's behavior on Linux from automatically falling back to the userspace, instead requiring --backend userspace to be specified.

This should help people avoid weird situations in environments like Docker.
pull/88/head
Jake McGinty 2021-05-19 16:54:07 +09:00 committed by GitHub
parent 170c8267bf
commit 3892a99156
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 450 additions and 415 deletions

41
Cargo.lock generated
View File

@ -278,31 +278,32 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
[[package]]
name = "futures-task"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
[[package]]
name = "futures-util"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
dependencies = [
"autocfg",
"futures-core",
"futures-task",
"pin-project-lite",
@ -409,9 +410,9 @@ dependencies = [
[[package]]
name = "httparse"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437"
checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
[[package]]
name = "httpdate"
@ -862,18 +863,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.125"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.125"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
@ -1048,9 +1049,9 @@ dependencies = [
[[package]]
name = "terminal_size"
version = "0.1.16"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
@ -1102,9 +1103,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.5.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
checksum = "bd3076b5c8cc18138b8f8814895c11eb4de37114a5d127bafdc5e55798ceef37"
dependencies = [
"autocfg",
"libc",
@ -1116,9 +1117,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37"
dependencies = [
"proc-macro2",
"quote",

View File

@ -1,7 +1,7 @@
use crate::Error;
use colored::*;
use serde::{Deserialize, Serialize};
use shared::{ensure_dirs_exist, Cidr, IoErrorContext, Peer, WrappedIoError, CLIENT_DATA_PATH};
use shared::{ensure_dirs_exist, Cidr, IoErrorContext, Peer, WrappedIoError, CLIENT_DATA_DIR};
use std::{
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
@ -54,13 +54,13 @@ impl DataStore {
}
pub fn get_path(interface: &InterfaceName) -> PathBuf {
CLIENT_DATA_PATH
CLIENT_DATA_DIR
.join(interface.to_string())
.with_extension("json")
}
fn _open(interface: &InterfaceName, create: bool) -> Result<Self, WrappedIoError> {
ensure_dirs_exist(&[*CLIENT_DATA_PATH])?;
ensure_dirs_exist(&[*CLIENT_DATA_DIR])?;
Self::open_with_path(Self::get_path(interface), create)
}

View File

@ -5,7 +5,7 @@ use indoc::printdoc;
use shared::{
interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts,
Association, AssociationContents, Cidr, CidrTree, EndpointContents, InstallOpts, Interface,
IoErrorContext, Peer, RedeemContents, RoutingOpt, State, CLIENT_CONFIG_PATH,
IoErrorContext, NetworkOpt, Peer, RedeemContents, State, CLIENT_CONFIG_DIR,
REDEEM_TRANSITION_WAIT,
};
use std::{
@ -15,7 +15,7 @@ use std::{
time::{Duration, SystemTime},
};
use structopt::StructOpt;
use wgctrl::{DeviceConfigBuilder, DeviceInfo, InterfaceName, PeerConfigBuilder, PeerInfo};
use wgctrl::{Device, DeviceUpdate, InterfaceName, PeerConfigBuilder, PeerInfo};
mod data_store;
mod util;
@ -41,6 +41,9 @@ macro_rules! println_pad {
struct Opts {
#[structopt(subcommand)]
command: Option<Command>,
#[structopt(flatten)]
network: NetworkOpt,
}
#[derive(Debug, StructOpt)]
@ -73,9 +76,6 @@ enum Command {
#[structopt(flatten)]
opts: InstallOpts,
#[structopt(flatten)]
routing: RoutingOpt,
},
/// Enumerate all innernet connections.
@ -107,9 +107,6 @@ enum Command {
#[structopt(flatten)]
hosts: HostsOpt,
#[structopt(flatten)]
routing: RoutingOpt,
interface: Interface,
},
@ -119,9 +116,6 @@ enum Command {
#[structopt(flatten)]
hosts: HostsOpt,
#[structopt(flatten)]
routing: RoutingOpt,
},
/// Uninstall an innernet network.
@ -233,9 +227,9 @@ fn install(
invite: &Path,
hosts_file: Option<PathBuf>,
opts: InstallOpts,
routing: RoutingOpt,
network: NetworkOpt,
) -> Result<(), Error> {
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_PATH])?;
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_DIR])?;
let config = InterfaceConfig::from_file(invite)?;
let iface = if opts.default_name {
@ -249,15 +243,15 @@ fn install(
.interact()?
};
let target_conf = CLIENT_CONFIG_PATH.join(&iface).with_extension("conf");
let target_conf = CLIENT_CONFIG_DIR.join(&iface).with_extension("conf");
if target_conf.exists() {
return Err("An interface with this name already exists in innernet.".into());
}
let iface = iface.parse()?;
redeem_invite(&iface, config, target_conf, routing).map_err(|e| {
redeem_invite(&iface, config, target_conf, network).map_err(|e| {
println!("{} bringing down the interface.", "[*]".dimmed());
if let Err(e) = wg::down(&iface) {
if let Err(e) = wg::down(&iface, network.backend) {
println!("{} failed to bring down interface: {}.", "[*]".yellow(), e.to_string());
};
println!("{} Failed to redeem invite. Now's a good time to make sure the server is started and accessible!", "[!]".red());
@ -266,7 +260,7 @@ fn install(
let mut fetch_success = false;
for _ in 0..3 {
if fetch(&iface, false, hosts_file.clone(), routing).is_ok() {
if fetch(&iface, false, hosts_file.clone(), network).is_ok() {
fetch_success = true;
break;
}
@ -320,7 +314,7 @@ fn redeem_invite(
iface: &InterfaceName,
mut config: InterfaceConfig,
target_conf: PathBuf,
routing: RoutingOpt,
network: NetworkOpt,
) -> Result<(), Error> {
println!("{} bringing up the interface.", "[*]".dimmed());
let resolved_endpoint = config.server.external_endpoint.resolve()?;
@ -334,7 +328,7 @@ fn redeem_invite(
config.server.internal_endpoint.ip(),
resolved_endpoint,
)),
!routing.no_routing,
network,
)?;
println!("{} Generating new keypair.", "[*]".dimmed());
@ -365,9 +359,9 @@ fn redeem_invite(
"{} Changing keys and waiting for server's WireGuard interface to transition.",
"[*]".dimmed(),
);
DeviceConfigBuilder::new()
DeviceUpdate::new()
.set_private_key(keypair.private)
.apply(&iface)?;
.apply(&iface, network.backend)?;
thread::sleep(*REDEEM_TRANSITION_WAIT);
Ok(())
@ -377,7 +371,7 @@ fn up(
interface: &InterfaceName,
loop_interval: Option<Duration>,
hosts_path: Option<PathBuf>,
routing: RoutingOpt,
routing: NetworkOpt,
) -> Result<(), Error> {
loop {
fetch(interface, true, hosts_path.clone(), routing)?;
@ -394,10 +388,10 @@ fn fetch(
interface: &InterfaceName,
bring_up_interface: bool,
hosts_path: Option<PathBuf>,
routing: RoutingOpt,
network: NetworkOpt,
) -> Result<(), Error> {
let config = InterfaceConfig::from_interface(interface)?;
let interface_up = if let Ok(interfaces) = DeviceInfo::enumerate() {
let interface_up = if let Ok(interfaces) = Device::list(network.backend) {
interfaces.iter().any(|name| name == interface)
} else {
false
@ -424,7 +418,7 @@ fn fetch(
config.server.internal_endpoint.ip(),
resolved_endpoint,
)),
!routing.no_routing,
network,
)?
}
@ -432,7 +426,8 @@ fn fetch(
let mut store = DataStore::open_or_create(&interface)?;
let State { peers, cidrs } = Api::new(&config.server).http("GET", "/user/state")?;
let device_info = DeviceInfo::get_by_name(&interface).with_str(interface.as_str_lossy())?;
let device_info =
Device::get(&interface, network.backend).with_str(interface.as_str_lossy())?;
let interface_public_key = device_info
.public_key
.as_ref()
@ -467,7 +462,7 @@ fn fetch(
})
.collect::<Vec<PeerConfigBuilder>>();
let mut device_config_builder = DeviceConfigBuilder::new();
let mut device_config_builder = DeviceUpdate::new();
let mut device_config_changed = false;
if !peer_configs_diff.is_empty() {
@ -491,7 +486,7 @@ fn fetch(
}
if device_config_changed {
device_config_builder.apply(&interface)?;
device_config_builder.apply(&interface, network.backend)?;
if let Some(path) = hosts_path {
update_hosts_file(interface, path, &peers)?;
@ -512,7 +507,7 @@ fn fetch(
Ok(())
}
fn uninstall(interface: &InterfaceName) -> Result<(), Error> {
fn uninstall(interface: &InterfaceName, network: NetworkOpt) -> Result<(), Error> {
if Confirm::with_theme(&*prompts::THEME)
.with_prompt(&format!(
"Permanently delete network \"{}\"?",
@ -522,7 +517,7 @@ fn uninstall(interface: &InterfaceName) -> Result<(), Error> {
.interact()?
{
println!("{} bringing down interface (if up).", "[*]".dimmed());
wg::down(interface).ok();
wg::down(interface, network.backend).ok();
let config = InterfaceConfig::get_path(interface);
let data = DataStore::get_path(interface);
std::fs::remove_file(&config)
@ -701,11 +696,15 @@ fn list_associations(interface: &InterfaceName) -> Result<(), Error> {
Ok(())
}
fn set_listen_port(interface: &InterfaceName, unset: bool) -> Result<(), Error> {
fn set_listen_port(
interface: &InterfaceName,
unset: bool,
network: NetworkOpt,
) -> Result<(), Error> {
let mut config = InterfaceConfig::from_interface(interface)?;
if let Some(listen_port) = prompts::set_listen_port(&config.interface, unset)? {
wg::set_listen_port(interface, listen_port)?;
wg::set_listen_port(interface, listen_port, network.backend)?;
println!("{} the interface is updated", "[*]".dimmed(),);
config.interface.listen_port = listen_port;
@ -718,14 +717,18 @@ fn set_listen_port(interface: &InterfaceName, unset: bool) -> Result<(), Error>
Ok(())
}
fn override_endpoint(interface: &InterfaceName, unset: bool) -> Result<(), Error> {
fn override_endpoint(
interface: &InterfaceName,
unset: bool,
network: NetworkOpt,
) -> Result<(), Error> {
let config = InterfaceConfig::from_interface(interface)?;
if !unset && config.interface.listen_port.is_none() {
println!(
"{}: you need to set a listen port for your interface first.",
"note".bold().yellow()
);
set_listen_port(interface, unset)?;
set_listen_port(interface, unset, network)?;
}
if let Some(endpoint) = prompts::override_endpoint(unset)? {
@ -742,9 +745,16 @@ fn override_endpoint(interface: &InterfaceName, unset: bool) -> Result<(), Error
Ok(())
}
fn show(short: bool, tree: bool, interface: Option<Interface>) -> Result<(), Error> {
let interfaces =
interface.map_or_else(DeviceInfo::enumerate, |interface| Ok(vec![*interface]))?;
fn show(
short: bool,
tree: bool,
interface: Option<Interface>,
network: NetworkOpt,
) -> Result<(), Error> {
let interfaces = interface.map_or_else(
|| Device::list(network.backend),
|interface| Ok(vec![*interface]),
)?;
let devices = interfaces
.into_iter()
@ -752,7 +762,7 @@ fn show(short: bool, tree: bool, interface: Option<Interface>) -> Result<(), Err
DataStore::open(&name)
.and_then(|store| {
Ok((
DeviceInfo::get_by_name(&name).with_str(name.as_str_lossy())?,
Device::get(&name, network.backend).with_str(name.as_str_lossy())?,
store,
))
})
@ -826,7 +836,7 @@ fn print_tree(cidr: &CidrTree, peers: &[PeerState], level: usize) {
}
}
fn print_interface(device_info: &DeviceInfo, short: bool) -> Result<(), Error> {
fn print_interface(device_info: &Device, short: bool) -> Result<(), Error> {
if short {
let listen_port_str = device_info
.listen_port
@ -930,32 +940,26 @@ fn run(opt: Opts) -> Result<(), Error> {
invite,
hosts,
opts,
routing,
} => install(&invite, hosts.into(), opts, routing)?,
} => install(&invite, hosts.into(), opts, opt.network)?,
Command::Show {
short,
tree,
interface,
} => show(short, tree, interface)?,
Command::Fetch {
interface,
hosts,
routing,
} => fetch(&interface, false, hosts.into(), routing)?,
} => show(short, tree, interface, opt.network)?,
Command::Fetch { interface, hosts } => fetch(&interface, false, hosts.into(), opt.network)?,
Command::Up {
interface,
daemon,
hosts,
routing,
interval,
} => up(
&interface,
daemon.then(|| Duration::from_secs(interval)),
hosts.into(),
routing,
opt.network,
)?,
Command::Down { interface } => wg::down(&interface)?,
Command::Uninstall { interface } => uninstall(&interface)?,
Command::Down { interface } => wg::down(&interface, opt.network.backend)?,
Command::Uninstall { interface } => uninstall(&interface, opt.network)?,
Command::AddPeer { interface, opts } => add_peer(&interface, opts)?,
Command::AddCidr { interface, opts } => add_cidr(&interface, opts)?,
Command::DisablePeer { interface } => enable_or_disable_peer(&interface, false)?,
@ -963,8 +967,12 @@ fn run(opt: Opts) -> Result<(), Error> {
Command::AddAssociation { interface, opts } => add_association(&interface, opts)?,
Command::DeleteAssociation { interface } => delete_association(&interface)?,
Command::ListAssociations { interface } => list_associations(&interface)?,
Command::SetListenPort { interface, unset } => set_listen_port(&interface, unset)?,
Command::OverrideEndpoint { interface, unset } => override_endpoint(&interface, unset)?,
Command::SetListenPort { interface, unset } => {
set_listen_port(&interface, unset, opt.network)?
},
Command::OverrideEndpoint { interface, unset } => {
override_endpoint(&interface, unset, opt.network)?
},
}
Ok(())

View File

@ -8,7 +8,7 @@ use crate::{
};
use hyper::{Body, Method, Request, Response, StatusCode};
use shared::PeerContents;
use wgctrl::DeviceConfigBuilder;
use wgctrl::DeviceUpdate;
pub async fn routes(
req: Request<Body>,
@ -49,9 +49,9 @@ mod handlers {
if cfg!(not(test)) {
// Update the current WireGuard interface with the new peers.
DeviceConfigBuilder::new()
DeviceUpdate::new()
.add_peer((&*peer).into())
.apply(&session.context.interface)
.apply(&session.context.interface, session.context.backend)
.map_err(|_| ServerError::WireGuard)?;
log::info!("updated WireGuard interface, adding {}", &*peer);
}

View File

@ -4,11 +4,11 @@ use crate::{
api::inject_endpoints,
db::{DatabaseCidr, DatabasePeer},
util::{form_body, json_response, status_response},
ServerError, Session,
Context, ServerError, Session,
};
use hyper::{Body, Method, Request, Response, StatusCode};
use shared::{EndpointContents, PeerContents, RedeemContents, State, REDEEM_TRANSITION_WAIT};
use wgctrl::DeviceConfigBuilder;
use wgctrl::DeviceUpdate;
pub async fn routes(
req: Request<Body>,
@ -83,7 +83,9 @@ mod handlers {
selected_peer.redeem(&conn, &form.public_key)?;
if cfg!(not(test)) {
let interface = session.context.interface;
let Context {
interface, backend, ..
} = session.context;
// If we were to modify the WireGuard interface immediately, the HTTP response wouldn't
// get through. Instead, we need to wait a reasonable amount for the HTTP response to
@ -102,10 +104,10 @@ mod handlers {
&*selected_peer,
old_public_key.to_base64()
);
DeviceConfigBuilder::new()
DeviceUpdate::new()
.remove_peer_by_key(&old_public_key)
.add_peer((&*selected_peer).into())
.apply(&interface)
.apply(&interface, backend)
.map_err(|e| log::error!("{:?}", e))
.ok();
});

View File

@ -6,7 +6,7 @@ use ipnetwork::IpNetwork;
use parking_lot::{Mutex, RwLock};
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use shared::{AddCidrOpts, AddPeerOpts, IoErrorContext, RoutingOpt, INNERNET_PUBKEY_HEADER};
use shared::{AddCidrOpts, AddPeerOpts, IoErrorContext, NetworkOpt, INNERNET_PUBKEY_HEADER};
use std::{
collections::{HashMap, VecDeque},
convert::TryInto,
@ -21,7 +21,7 @@ use std::{
};
use structopt::StructOpt;
use subtle::ConstantTimeEq;
use wgctrl::{DeviceConfigBuilder, DeviceInfo, InterfaceName, Key, PeerConfigBuilder};
use wgctrl::{Backend, Device, DeviceUpdate, InterfaceName, Key, PeerConfigBuilder};
pub mod api;
pub mod db;
@ -45,6 +45,9 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
struct Opt {
#[structopt(subcommand)]
command: Command,
#[structopt(flatten)]
network: NetworkOpt,
}
#[derive(Debug, StructOpt)]
@ -64,7 +67,7 @@ enum Command {
interface: Interface,
#[structopt(flatten)]
routing: RoutingOpt,
network: NetworkOpt,
},
/// Add a peer to an existing network.
@ -92,6 +95,7 @@ pub struct Context {
pub db: Db,
pub endpoints: Arc<RwLock<HashMap<String, SocketAddr>>>,
pub interface: InterfaceName,
pub backend: Backend,
pub public_key: Key,
}
@ -209,10 +213,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
if let Err(e) = initialize::init_wizard(&conf, opts) {
println!("{}: {}.", "creation failed".red(), e);
}
}
Command::Uninstall { interface } => uninstall(&interface, &conf)?,
Command::Serve { interface, routing } => serve(*interface, &conf, routing).await?,
Command::AddPeer { interface, args } => add_peer(&interface, &conf, args)?,
},
Command::Uninstall { interface } => uninstall(&interface, &conf, opt.network)?,
Command::Serve {
interface,
network: routing,
} => serve(*interface, &conf, routing).await?,
Command::AddPeer { interface, args } => add_peer(&interface, &conf, args, opt.network)?,
Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?,
}
@ -243,6 +250,7 @@ fn add_peer(
interface: &InterfaceName,
conf: &ServerConfig,
opts: AddPeerOpts,
network: NetworkOpt,
) -> Result<(), Error> {
let config = ConfigFile::from_file(conf.config_path(interface))?;
let conn = open_database_connection(interface, conf)?;
@ -255,11 +263,11 @@ fn add_peer(
if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree, &opts)? {
let peer = DatabasePeer::create(&conn, peer_request)?;
if cfg!(not(test)) && DeviceInfo::get_by_name(interface).is_ok() {
if cfg!(not(test)) && Device::get(interface, network.backend).is_ok() {
// Update the current WireGuard interface with the new peers.
DeviceConfigBuilder::new()
DeviceUpdate::new()
.add_peer((&*peer).into())
.apply(interface)
.apply(interface, network.backend)
.map_err(|_| ServerError::WireGuard)?;
println!("adding to WireGuard interface: {}", &*peer);
@ -309,7 +317,11 @@ fn add_cidr(
Ok(())
}
fn uninstall(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error> {
fn uninstall(
interface: &InterfaceName,
conf: &ServerConfig,
network: NetworkOpt,
) -> Result<(), Error> {
if Confirm::with_theme(&*prompts::THEME)
.with_prompt(&format!(
"Permanently delete network \"{}\"?",
@ -319,7 +331,7 @@ fn uninstall(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error
.interact()?
{
println!("{} bringing down interface (if up).", "[*]".dimmed());
wg::down(interface).ok();
wg::down(interface, network.backend).ok();
let config = conf.config_path(interface);
let data = conf.database_path(interface);
std::fs::remove_file(&config)
@ -339,7 +351,7 @@ fn uninstall(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error
Ok(())
}
fn spawn_endpoint_refresher(interface: InterfaceName) -> Endpoints {
fn spawn_endpoint_refresher(interface: InterfaceName, network: NetworkOpt) -> Endpoints {
let endpoints = Arc::new(RwLock::new(HashMap::new()));
tokio::task::spawn({
let endpoints = endpoints.clone();
@ -347,7 +359,7 @@ fn spawn_endpoint_refresher(interface: InterfaceName) -> Endpoints {
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
if let Ok(info) = DeviceInfo::get_by_name(&interface) {
if let Ok(info) = Device::get(&interface, network.backend) {
for peer in info.peers {
if let Some(endpoint) = peer.config.endpoint {
endpoints
@ -378,7 +390,7 @@ fn spawn_expired_invite_sweeper(db: Db) {
async fn serve(
interface: InterfaceName,
conf: &ServerConfig,
routing: RoutingOpt,
network: NetworkOpt,
) -> Result<(), Error> {
let config = ConfigFile::from_file(conf.config_path(&interface))?;
let conn = open_database_connection(&interface, conf)?;
@ -396,18 +408,18 @@ async fn serve(
IpNetwork::new(config.address, config.network_cidr_prefix)?,
Some(config.listen_port),
None,
!routing.no_routing,
network,
)?;
DeviceConfigBuilder::new()
DeviceUpdate::new()
.add_peers(&peer_configs)
.apply(&interface)?;
.apply(&interface, network.backend)?;
log::info!("{} peers added to wireguard interface.", peers.len());
let public_key = wgctrl::Key::from_base64(&config.private_key)?.generate_public();
let db = Arc::new(Mutex::new(conn));
let endpoints = spawn_endpoint_refresher(interface);
let endpoints = spawn_endpoint_refresher(interface, network);
spawn_expired_invite_sweeper(db.clone());
let context = Context {
@ -415,6 +427,7 @@ async fn serve(
endpoints,
interface,
public_key,
backend: network.backend,
};
log::info!("innernet-server {} starting.", VERSION);

View File

@ -12,7 +12,7 @@ use serde::Serialize;
use shared::{Cidr, CidrContents, Error, PeerContents};
use std::{collections::HashMap, net::SocketAddr, path::PathBuf, sync::Arc};
use tempfile::TempDir;
use wgctrl::{InterfaceName, Key, KeyPair};
use wgctrl::{Backend, InterfaceName, Key, KeyPair};
pub const ROOT_CIDR: &str = "10.80.0.0/15";
pub const SERVER_CIDR: &str = "10.80.0.1/32";
@ -137,6 +137,10 @@ impl Server {
interface: self.interface.clone(),
endpoints: self.endpoints.clone(),
public_key: self.public_key.clone(),
#[cfg(target_os = "linux")]
backend: Backend::Kernel,
#[cfg(not(target_os = "linux"))]
backend: Backend::Userspace,
}
}

View File

@ -1,4 +1,4 @@
use crate::{ensure_dirs_exist, Endpoint, Error, IoErrorContext, CLIENT_CONFIG_PATH};
use crate::{ensure_dirs_exist, Endpoint, Error, IoErrorContext, CLIENT_CONFIG_DIR};
use colored::*;
use indoc::writedoc;
use ipnetwork::IpNetwork;
@ -128,13 +128,13 @@ impl InterfaceConfig {
}
pub fn get_path(interface: &InterfaceName) -> PathBuf {
CLIENT_CONFIG_PATH
CLIENT_CONFIG_DIR
.join(interface.to_string())
.with_extension("conf")
}
fn build_config_file_path(interface: &InterfaceName) -> Result<PathBuf, Error> {
ensure_dirs_exist(&[*CLIENT_CONFIG_PATH])?;
ensure_dirs_exist(&[*CLIENT_CONFIG_DIR])?;
Ok(Self::get_path(interface))
}
}

View File

@ -16,8 +16,8 @@ pub mod wg;
pub use types::*;
lazy_static! {
pub static ref CLIENT_CONFIG_PATH: &'static Path = Path::new("/etc/innernet");
pub static ref CLIENT_DATA_PATH: &'static Path = Path::new("/var/lib/innernet");
pub static ref CLIENT_CONFIG_DIR: &'static Path = Path::new("/etc/innernet");
pub static ref CLIENT_DATA_DIR: &'static Path = Path::new("/var/lib/innernet");
pub static ref SERVER_CONFIG_DIR: &'static Path = Path::new("/etc/innernet-server");
pub static ref SERVER_DATABASE_DIR: &'static Path = Path::new("/var/lib/innernet-server");
pub static ref REDEEM_TRANSITION_WAIT: Duration = Duration::from_secs(5);
@ -28,15 +28,12 @@ pub const INNERNET_PUBKEY_HEADER: &str = "X-Innernet-Server-Key";
pub type Error = Box<dyn std::error::Error>;
pub static WG_MANAGE_DIR: &str = "/etc/innernet";
pub static WG_DIR: &str = "/etc/wireguard";
pub fn ensure_dirs_exist(dirs: &[&Path]) -> Result<(), WrappedIoError> {
for dir in dirs {
match fs::create_dir(dir).with_path(dir) {
Err(e) if e.kind() != io::ErrorKind::AlreadyExists => {
return Err(e);
}
},
_ => {
let target_file = File::open(dir).with_path(dir)?;
if chmod(&target_file, 0o700).with_path(dir)? {
@ -46,7 +43,7 @@ pub fn ensure_dirs_exist(dirs: &[&Path]) -> Result<(), WrappedIoError> {
dir.display()
);
}
}
},
}
}
Ok(())

View File

@ -13,7 +13,7 @@ use std::{
};
use structopt::StructOpt;
use url::Host;
use wgctrl::{InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder};
use wgctrl::{Backend, InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder};
#[derive(Debug, Clone)]
pub struct Interface {
@ -73,7 +73,7 @@ impl FromStr for Endpoint {
let port = port.parse().map_err(|_| "couldn't parse port")?;
let host = Host::parse(host).map_err(|_| "couldn't parse host")?;
Ok(Endpoint { host, port })
}
},
_ => Err("couldn't parse in form of 'host:port'"),
}
}
@ -347,11 +347,16 @@ pub struct AddAssociationOpts {
}
#[derive(Debug, Clone, Copy, StructOpt)]
pub struct RoutingOpt {
pub struct NetworkOpt {
#[structopt(long)]
/// Whether the routing should be done by innernet or is done by an
/// external tool like e.g. babeld.
pub no_routing: bool,
#[structopt(long, default_value, possible_values = Backend::variants())]
/// Specify a WireGuard backend to use.
/// If not set, innernet will auto-select based on availability.
pub backend: Backend,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]

View File

@ -1,10 +1,10 @@
use crate::{Error, IoErrorContext};
use crate::{Error, IoErrorContext, NetworkOpt};
use ipnetwork::IpNetwork;
use std::{
net::{IpAddr, SocketAddr},
process::{self, Command},
};
use wgctrl::{DeviceConfigBuilder, InterfaceName, PeerConfigBuilder};
use wgctrl::{Backend, Device, DeviceUpdate, InterfaceName, PeerConfigBuilder};
fn cmd(bin: &str, args: &[&str]) -> Result<process::Output, Error> {
let output = Command::new(bin).args(args).output()?;
@ -67,9 +67,9 @@ pub fn up(
address: IpNetwork,
listen_port: Option<u16>,
peer: Option<(&str, IpAddr, SocketAddr)>,
do_routing: bool,
network: NetworkOpt,
) -> Result<(), Error> {
let mut device = DeviceConfigBuilder::new();
let mut device = DeviceUpdate::new();
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)?)
@ -82,36 +82,32 @@ pub fn up(
}
device
.set_private_key(wgctrl::Key::from_base64(&private_key).unwrap())
.apply(interface)?;
.apply(interface, network.backend)?;
set_addr(interface, address)?;
if do_routing {
if !network.no_routing {
add_route(interface, address)?;
}
Ok(())
}
pub fn set_listen_port(interface: &InterfaceName, listen_port: Option<u16>) -> Result<(), Error> {
let mut device = DeviceConfigBuilder::new();
pub fn set_listen_port(
interface: &InterfaceName,
listen_port: Option<u16>,
backend: Backend,
) -> Result<(), Error> {
let mut device = DeviceUpdate::new();
if let Some(listen_port) = listen_port {
device = device.set_listen_port(listen_port);
} else {
device = device.randomize_listen_port();
}
device.apply(interface)?;
device.apply(interface, backend)?;
Ok(())
}
#[cfg(target_os = "linux")]
pub fn down(interface: &InterfaceName) -> Result<(), Error> {
Ok(wgctrl::delete_interface(&interface).with_str(interface.to_string())?)
}
#[cfg(not(target_os = "linux"))]
pub fn down(interface: &InterfaceName) -> Result<(), Error> {
wgctrl::backends::userspace::delete_interface(interface)
.with_str(interface.to_string())
.map_err(Error::from)
pub fn down(interface: &InterfaceName, backend: Backend) -> Result<(), Error> {
Ok(Device::get(interface, backend)?.delete()?)
}
/// Add a route in the OS's routing table to get traffic flowing through this interface.

View File

@ -1,5 +1,5 @@
use crate::{
device::AllowedIp, DeviceConfigBuilder, DeviceInfo, InterfaceName, InvalidInterfaceName,
device::AllowedIp, Backend, Device, DeviceUpdate, InterfaceName, InvalidInterfaceName,
InvalidKey, PeerConfig, PeerConfigBuilder, PeerInfo, PeerStats,
};
use wgctrl_sys::{timespec64, wg_device_flags as wgdf, wg_peer_flags as wgpf};
@ -9,15 +9,10 @@ use std::{
io,
net::{IpAddr, SocketAddr},
os::raw::c_char,
path::Path,
process::Command,
ptr, str,
sync::Once,
time::{Duration, SystemTime},
};
static MODPROBE: Once = Once::new();
impl<'a> From<&'a wgctrl_sys::wg_allowedip> for AllowedIp {
fn from(raw: &wgctrl_sys::wg_allowedip) -> AllowedIp {
let addr = match i32::from(raw.family) {
@ -69,11 +64,11 @@ impl<'a> From<&'a wgctrl_sys::wg_peer> for PeerInfo {
}
}
impl<'a> From<&'a wgctrl_sys::wg_device> for DeviceInfo {
fn from(raw: &wgctrl_sys::wg_device) -> DeviceInfo {
impl<'a> From<&'a wgctrl_sys::wg_device> for Device {
fn from(raw: &wgctrl_sys::wg_device) -> Device {
// SAFETY: The name string buffer came directly from wgctrl so its NUL terminated.
let name = unsafe { InterfaceName::from_wg(raw.name) };
DeviceInfo {
Device {
name,
public_key: if (raw.flags & wgdf::WGDEVICE_HAS_PUBLIC_KEY).0 > 0 {
Some(Key::from_raw(raw.public_key))
@ -95,6 +90,7 @@ impl<'a> From<&'a wgctrl_sys::wg_device> for DeviceInfo {
},
peers: parse_peers(&raw),
linked_name: None,
backend: Backend::Kernel,
__cant_construct_me: (),
}
}
@ -214,7 +210,7 @@ fn encode_endpoint(endpoint: Option<SocketAddr>) -> wgctrl_sys::wg_peer__bindgen
peer.addr4 = wgctrl_sys::sockaddr_in {
sin_family: libc::AF_INET as u16,
sin_addr: wgctrl_sys::in_addr {
s_addr: u32::from_be(s.ip().clone().into()),
s_addr: u32::from_be((*s.ip()).into()),
},
sin_port: u16::to_be(s.port()),
sin_zero: [0; 8],
@ -223,10 +219,12 @@ fn encode_endpoint(endpoint: Option<SocketAddr>) -> wgctrl_sys::wg_peer__bindgen
},
Some(SocketAddr::V6(s)) => {
let mut peer = wgctrl_sys::wg_peer__bindgen_ty_1::default();
let in6_addr = wgctrl_sys::in6_addr__bindgen_ty_1{__u6_addr8: s.ip().octets()};
let in6_addr = wgctrl_sys::in6_addr__bindgen_ty_1 {
__u6_addr8: s.ip().octets(),
};
peer.addr6 = wgctrl_sys::sockaddr_in6 {
sin6_family: libc::AF_INET6 as u16,
sin6_addr: wgctrl_sys::in6_addr{__in6_u: in6_addr},
sin6_addr: wgctrl_sys::in6_addr { __in6_u: in6_addr },
sin6_port: u16::to_be(s.port()),
sin6_flowinfo: 0,
sin6_scope_id: 0,
@ -238,7 +236,7 @@ fn encode_endpoint(endpoint: Option<SocketAddr>) -> wgctrl_sys::wg_peer__bindgen
}
fn encode_peers(
peers: Vec<PeerConfigBuilder>,
peers: &[PeerConfigBuilder],
) -> (*mut wgctrl_sys::wg_peer, *mut wgctrl_sys::wg_peer) {
let mut first_peer = ptr::null_mut();
let mut last_peer: *mut wgctrl_sys::wg_peer = ptr::null_mut();
@ -290,20 +288,6 @@ fn encode_peers(
(first_peer, last_peer)
}
pub fn exists() -> bool {
// Try to load the wireguard module if it isn't already.
// This is only called once per lifetime of the process.
MODPROBE.call_once(|| {
Command::new("/sbin/modprobe")
.arg("wireguard")
.output()
.ok();
});
// Check that the wireguard module is loaded.
Path::new("/sys/module/wireguard").is_dir()
}
pub fn enumerate() -> Result<Vec<InterfaceName>, io::Error> {
let base = unsafe { wgctrl_sys::wg_list_device_names() };
@ -337,8 +321,8 @@ pub fn enumerate() -> Result<Vec<InterfaceName>, io::Error> {
Ok(result)
}
pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<()> {
let (first_peer, last_peer) = encode_peers(builder.peers);
pub fn apply(builder: &DeviceUpdate, iface: &InterfaceName) -> io::Result<()> {
let (first_peer, last_peer) = encode_peers(&builder.peers);
let result = unsafe { wgctrl_sys::wg_add_device(iface.as_ptr()) };
match result {
@ -394,7 +378,7 @@ pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<
}
}
pub fn get_by_name(name: &InterfaceName) -> Result<DeviceInfo, io::Error> {
pub fn get_by_name(name: &InterfaceName) -> Result<Device, io::Error> {
let mut device: *mut wgctrl_sys::wg_device = ptr::null_mut();
let result = unsafe {
@ -405,7 +389,7 @@ pub fn get_by_name(name: &InterfaceName) -> Result<DeviceInfo, io::Error> {
};
let result = if result == 0 {
Ok(DeviceInfo::from(unsafe { &*device }))
Ok(Device::from(unsafe { &*device }))
} else {
Err(io::Error::last_os_error())
};
@ -539,4 +523,4 @@ mod tests {
assert_eq!(endpoint6, parse_endpoint(&encoded6));
Ok(())
}
}
}

View File

@ -1,4 +1,4 @@
use crate::{DeviceConfigBuilder, DeviceInfo, InterfaceName, PeerConfig, PeerInfo, PeerStats};
use crate::{Backend, Device, DeviceUpdate, InterfaceName, PeerConfig, PeerInfo, PeerStats};
#[cfg(target_os = "linux")]
use crate::Key;
@ -89,11 +89,11 @@ fn new_peer_info(public_key: Key) -> PeerInfo {
}
struct ConfigParser {
device_info: DeviceInfo,
device_info: Device,
current_peer: Option<PeerInfo>,
}
impl From<ConfigParser> for DeviceInfo {
impl From<ConfigParser> for Device {
fn from(parser: ConfigParser) -> Self {
parser.device_info
}
@ -102,7 +102,7 @@ impl From<ConfigParser> for DeviceInfo {
impl ConfigParser {
/// Returns `None` if an invalid device name was provided.
fn new(name: &InterfaceName) -> Self {
let device_info = DeviceInfo {
let device_info = Device {
name: *name,
public_key: None,
private_key: None,
@ -110,6 +110,7 @@ impl ConfigParser {
listen_port: None,
peers: vec![],
linked_name: resolve_tun(name).ok(),
backend: Backend::Userspace,
__cant_construct_me: (),
};
@ -229,7 +230,7 @@ impl ConfigParser {
}
}
pub fn get_by_name(name: &InterfaceName) -> Result<DeviceInfo, io::Error> {
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);
@ -263,7 +264,7 @@ fn get_userspace_implementation() -> String {
.unwrap_or_else(|_| "wireguard-go".to_string())
}
pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<()> {
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(_) => {
@ -302,7 +303,7 @@ pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<
request.push_str("replace_peers=true\n");
}
for peer in builder.peers {
for peer in &builder.peers {
request.push_str(&format!("public_key={}\n", hex::encode(peer.public_key.0)));
if peer.replace_allowed_ips {
@ -328,7 +329,7 @@ pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<
));
}
for allowed_ip in peer.allowed_ips {
for allowed_ip in &peer.allowed_ips {
request.push_str(&format!(
"allowed_ip={}/{}\n",
allowed_ip.address, allowed_ip.cidr

View File

@ -1,195 +1,9 @@
use crate::{
backends,
device::{AllowedIp, InterfaceName, PeerConfig},
key::{Key, KeyPair},
device::{AllowedIp, PeerConfig},
key::Key,
};
use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
/// Builds and represents a configuration that can be applied to a WireGuard interface.
///
/// This is the primary way of changing the settings of an interface.
///
/// Note that if an interface exists, the configuration is applied _on top_ of the existing
/// settings, and missing parts are not overwritten or set to defaults.
///
/// If this is not what you want, use [`delete_interface`](delete_interface)
/// to remove the interface entirely before applying the new configuration.
///
/// # Example
/// ```rust
/// # use wgctrl::*;
/// # use std::net::AddrParseError;
/// # fn try_main() -> Result<(), AddrParseError> {
/// let our_keypair = KeyPair::generate();
/// let peer_keypair = KeyPair::generate();
/// let server_addr = "192.168.1.1:51820".parse()?;
///
/// DeviceConfigBuilder::new()
/// .set_keypair(our_keypair)
/// .replace_peers()
/// .add_peer_with(&peer_keypair.public, |peer| {
/// peer.set_endpoint(server_addr)
/// .replace_allowed_ips()
/// .allow_all_ips()
/// }).apply(&"wg-example".parse().unwrap());
///
/// println!("Send these keys to your peer: {:#?}", peer_keypair);
///
/// # Ok(())
/// # }
/// # fn main() { try_main(); }
/// ```
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DeviceConfigBuilder {
pub(crate) public_key: Option<Key>,
pub(crate) private_key: Option<Key>,
pub(crate) fwmark: Option<u32>,
pub(crate) listen_port: Option<u16>,
pub(crate) peers: Vec<PeerConfigBuilder>,
pub(crate) replace_peers: bool,
}
impl DeviceConfigBuilder {
/// Creates a new `DeviceConfigBuilder` that does nothing when applied.
pub fn new() -> Self {
DeviceConfigBuilder {
public_key: None,
private_key: None,
fwmark: None,
listen_port: None,
peers: vec![],
replace_peers: false,
}
}
/// Sets a new keypair to be applied to the interface.
///
/// This is a convenience method that simply wraps
/// [`set_public_key`](DeviceConfigBuilder::set_public_key)
/// and [`set_private_key`](DeviceConfigBuilder::set_private_key).
pub fn set_keypair(self, keypair: KeyPair) -> Self {
self.set_public_key(keypair.public)
.set_private_key(keypair.private)
}
/// Specifies a new public key to be applied to the interface.
pub fn set_public_key(mut self, key: Key) -> Self {
self.public_key = Some(key);
self
}
/// Specifies that the public key for this interface should be unset.
pub fn unset_public_key(self) -> Self {
self.set_public_key(Key::zero())
}
/// Sets a new private key to be applied to the interface.
pub fn set_private_key(mut self, key: Key) -> Self {
self.private_key = Some(key);
self
}
/// Specifies that the private key for this interface should be unset.
pub fn unset_private_key(self) -> Self {
self.set_private_key(Key::zero())
}
/// Specifies the fwmark value that should be applied to packets coming from the interface.
pub fn set_fwmark(mut self, fwmark: u32) -> Self {
self.fwmark = Some(fwmark);
self
}
/// Specifies that fwmark should not be set on packets from the interface.
pub fn unset_fwmark(self) -> Self {
self.set_fwmark(0)
}
/// Specifies the port to listen for incoming packets on.
///
/// This is useful for a server configuration that listens on a fixed endpoint.
pub fn set_listen_port(mut self, port: u16) -> Self {
self.listen_port = Some(port);
self
}
/// Specifies that a random port should be used for incoming packets.
///
/// This is probably what you want in client configurations.
pub fn randomize_listen_port(self) -> Self {
self.set_listen_port(0)
}
/// Specifies a new peer configuration to be added to the interface.
///
/// See [`PeerConfigBuilder`](PeerConfigBuilder) for details on building
/// peer configurations. This method can be called more than once, and all
/// peers will be added to the configuration.
pub fn add_peer(mut self, peer: PeerConfigBuilder) -> Self {
self.peers.push(peer);
self
}
/// Specifies a new peer configuration using a builder function.
///
/// This is simply a convenience method to make adding peers more fluent.
/// This method can be called more than once, and all peers will be added
/// to the configuration.
pub fn add_peer_with(
self,
pubkey: &Key,
builder: impl Fn(PeerConfigBuilder) -> PeerConfigBuilder,
) -> Self {
self.add_peer(builder(PeerConfigBuilder::new(pubkey)))
}
/// Specifies multiple peer configurations to be added to the interface.
pub fn add_peers(mut self, peers: &[PeerConfigBuilder]) -> Self {
self.peers.extend_from_slice(peers);
self
}
/// Specifies that the peer configurations in this `DeviceConfigBuilder` should
/// replace the existing configurations on the interface, not modify or append to them.
pub fn replace_peers(mut self) -> Self {
self.replace_peers = true;
self
}
/// Specifies that the peer with this public key should be removed from the interface.
pub fn remove_peer_by_key(self, public_key: &Key) -> Self {
let mut peer = PeerConfigBuilder::new(public_key);
peer.remove_me = true;
self.add_peer(peer)
}
/// Build and apply the configuration to a WireGuard interface by name.
///
/// An interface with the provided name will be created if one does not exist already.
#[cfg(target_os = "linux")]
pub fn apply(self, iface: &InterfaceName) -> io::Result<()> {
if backends::kernel::exists() {
backends::kernel::apply(self, &iface)
} else {
backends::userspace::apply(self, iface)
}
}
#[cfg(not(target_os = "linux"))]
pub fn apply(self, iface: &InterfaceName) -> io::Result<()> {
backends::userspace::apply(self, iface)
}
}
impl Default for DeviceConfigBuilder {
fn default() -> Self {
Self::new()
}
}
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
/// Builds and represents a single peer in a WireGuard interface configuration.
///
@ -215,7 +29,7 @@ impl Default for DeviceConfigBuilder {
/// .add_allowed_ip("192.168.1.2".parse()?, 32);
///
/// // update our existing configuration with the new peer
/// DeviceConfigBuilder::new().add_peer(peer).apply(&"wg-example".parse().unwrap());
/// DeviceUpdate::new().add_peer(peer).apply(&"wg-example".parse().unwrap(), Backend::Userspace);
///
/// println!("Send these keys to your peer: {:#?}", peer_keypair);
///
@ -350,9 +164,3 @@ impl PeerConfigBuilder {
self
}
}
/// Deletes an existing WireGuard interface by name.
#[cfg(target_os = "linux")]
pub fn delete_interface(iface: &InterfaceName) -> io::Result<()> {
backends::kernel::delete_interface(iface)
}

View File

@ -1,21 +1,17 @@
use libc::c_char;
use crate::{backends, key::Key};
use crate::{backends, key::Key, Backend, KeyPair, PeerConfigBuilder};
use std::{
borrow::Cow,
ffi::CStr,
fmt,
fmt, io,
net::{IpAddr, SocketAddr},
str::FromStr,
time::SystemTime,
};
/// Represents an IP address a peer is allowed to have, in CIDR notation.
///
/// This may have unexpected semantics - refer to the
/// [WireGuard documentation](https://www.wireguard.com/#cryptokey-routing)
/// for more information on how routing is implemented.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AllowedIp {
/// The IP address.
@ -90,7 +86,7 @@ pub struct PeerInfo {
/// The peer statistics are retrieved once at construction time,
/// and need to be updated manually by calling [`get_by_name`](DeviceInfo::get_by_name).
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DeviceInfo {
pub struct Device {
/// The interface name of this device
pub name: InterfaceName,
/// The public encryption key of this interface (if present)
@ -105,6 +101,8 @@ pub struct DeviceInfo {
pub peers: Vec<PeerInfo>,
/// The associated "real name" of the interface (ex. "utun8" on macOS).
pub linked_name: Option<String>,
/// The backend the device exists on (userspace or kernel).
pub backend: Backend,
pub(crate) __cant_construct_me: (),
}
@ -227,57 +225,216 @@ impl From<InvalidInterfaceName> for std::io::Error {
impl std::error::Error for InvalidInterfaceName {}
impl DeviceInfo {
/// Enumerates all WireGuard interfaces currently present in the system
/// and returns their names.
impl Device {
/// Enumerates all WireGuard interfaces currently present in the system,
/// both with kernel and userspace backends.
///
/// You can use [`get_by_name`](DeviceInfo::get_by_name) to retrieve more
/// detailed information on each interface.
#[cfg(target_os = "linux")]
pub fn enumerate() -> Result<Vec<InterfaceName>, std::io::Error> {
if backends::kernel::exists() {
backends::kernel::enumerate()
} else {
backends::userspace::enumerate()
pub fn list(backend: Backend) -> Result<Vec<InterfaceName>, std::io::Error> {
match backend {
#[cfg(target_os = "linux")]
Backend::Kernel => backends::kernel::enumerate(),
Backend::Userspace => backends::userspace::enumerate(),
}
}
#[cfg(not(target_os = "linux"))]
pub fn enumerate() -> Result<Vec<InterfaceName>, std::io::Error> {
crate::backends::userspace::enumerate()
}
#[cfg(target_os = "linux")]
pub fn get_by_name(name: &InterfaceName) -> Result<Self, std::io::Error> {
if backends::kernel::exists() {
backends::kernel::get_by_name(name)
} else {
println!("kernel module not detected. falling back to userspace backend.");
backends::userspace::get_by_name(name)
pub fn get(name: &InterfaceName, backend: Backend) -> Result<Self, std::io::Error> {
match backend {
#[cfg(target_os = "linux")]
Backend::Kernel => backends::kernel::get_by_name(name),
Backend::Userspace => backends::userspace::get_by_name(name),
}
}
#[cfg(not(target_os = "linux"))]
pub fn get_by_name(name: &InterfaceName) -> Result<Self, std::io::Error> {
backends::userspace::get_by_name(name)
pub fn delete(self) -> Result<(), std::io::Error> {
match self.backend {
#[cfg(target_os = "linux")]
Backend::Kernel => backends::kernel::delete_interface(&self.name),
Backend::Userspace => backends::userspace::delete_interface(&self.name),
}
}
}
/// Builds and represents a configuration that can be applied to a WireGuard interface.
///
/// This is the primary way of changing the settings of an interface.
///
/// Note that if an interface exists, the configuration is applied _on top_ of the existing
/// settings, and missing parts are not overwritten or set to defaults.
///
/// If this is not what you want, use [`delete_interface`](delete_interface)
/// to remove the interface entirely before applying the new configuration.
///
/// # Example
/// ```rust
/// # use wgctrl::*;
/// # use std::net::AddrParseError;
/// # fn try_main() -> Result<(), AddrParseError> {
/// let our_keypair = KeyPair::generate();
/// let peer_keypair = KeyPair::generate();
/// let server_addr = "192.168.1.1:51820".parse()?;
///
/// DeviceUpdate::new()
/// .set_keypair(our_keypair)
/// .replace_peers()
/// .add_peer_with(&peer_keypair.public, |peer| {
/// peer.set_endpoint(server_addr)
/// .replace_allowed_ips()
/// .allow_all_ips()
/// }).apply(&"wg-example".parse().unwrap(), Backend::Userspace);
///
/// println!("Send these keys to your peer: {:#?}", peer_keypair);
///
/// # Ok(())
/// # }
/// # fn main() { try_main(); }
/// ```
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DeviceUpdate {
pub(crate) public_key: Option<Key>,
pub(crate) private_key: Option<Key>,
pub(crate) fwmark: Option<u32>,
pub(crate) listen_port: Option<u16>,
pub(crate) peers: Vec<PeerConfigBuilder>,
pub(crate) replace_peers: bool,
}
impl DeviceUpdate {
/// Creates a new `DeviceConfigBuilder` that does nothing when applied.
pub fn new() -> Self {
DeviceUpdate {
public_key: None,
private_key: None,
fwmark: None,
listen_port: None,
peers: vec![],
replace_peers: false,
}
}
#[cfg(target_os = "linux")]
pub fn delete(self) -> Result<(), std::io::Error> {
backends::kernel::delete_interface(&self.name)
/// Sets a new keypair to be applied to the interface.
///
/// This is a convenience method that simply wraps
/// [`set_public_key`](DeviceConfigBuilder::set_public_key)
/// and [`set_private_key`](DeviceConfigBuilder::set_private_key).
pub fn set_keypair(self, keypair: KeyPair) -> Self {
self.set_public_key(keypair.public)
.set_private_key(keypair.private)
}
#[cfg(not(target_os = "linux"))]
pub fn delete(self) -> Result<(), std::io::Error> {
backends::userspace::delete_interface(&self.name)
/// Specifies a new public key to be applied to the interface.
pub fn set_public_key(mut self, key: Key) -> Self {
self.public_key = Some(key);
self
}
/// Specifies that the public key for this interface should be unset.
pub fn unset_public_key(self) -> Self {
self.set_public_key(Key::zero())
}
/// Sets a new private key to be applied to the interface.
pub fn set_private_key(mut self, key: Key) -> Self {
self.private_key = Some(key);
self
}
/// Specifies that the private key for this interface should be unset.
pub fn unset_private_key(self) -> Self {
self.set_private_key(Key::zero())
}
/// Specifies the fwmark value that should be applied to packets coming from the interface.
pub fn set_fwmark(mut self, fwmark: u32) -> Self {
self.fwmark = Some(fwmark);
self
}
/// Specifies that fwmark should not be set on packets from the interface.
pub fn unset_fwmark(self) -> Self {
self.set_fwmark(0)
}
/// Specifies the port to listen for incoming packets on.
///
/// This is useful for a server configuration that listens on a fixed endpoint.
pub fn set_listen_port(mut self, port: u16) -> Self {
self.listen_port = Some(port);
self
}
/// Specifies that a random port should be used for incoming packets.
///
/// This is probably what you want in client configurations.
pub fn randomize_listen_port(self) -> Self {
self.set_listen_port(0)
}
/// Specifies a new peer configuration to be added to the interface.
///
/// See [`PeerConfigBuilder`](PeerConfigBuilder) for details on building
/// peer configurations. This method can be called more than once, and all
/// peers will be added to the configuration.
pub fn add_peer(mut self, peer: PeerConfigBuilder) -> Self {
self.peers.push(peer);
self
}
/// Specifies a new peer configuration using a builder function.
///
/// This is simply a convenience method to make adding peers more fluent.
/// This method can be called more than once, and all peers will be added
/// to the configuration.
pub fn add_peer_with(
self,
pubkey: &Key,
builder: impl Fn(PeerConfigBuilder) -> PeerConfigBuilder,
) -> Self {
self.add_peer(builder(PeerConfigBuilder::new(pubkey)))
}
/// Specifies multiple peer configurations to be added to the interface.
pub fn add_peers(mut self, peers: &[PeerConfigBuilder]) -> Self {
self.peers.extend_from_slice(peers);
self
}
/// Specifies that the peer configurations in this `DeviceConfigBuilder` should
/// replace the existing configurations on the interface, not modify or append to them.
pub fn replace_peers(mut self) -> Self {
self.replace_peers = true;
self
}
/// Specifies that the peer with this public key should be removed from the interface.
pub fn remove_peer_by_key(self, public_key: &Key) -> Self {
let mut peer = PeerConfigBuilder::new(public_key);
peer.remove_me = true;
self.add_peer(peer)
}
/// Build and apply the configuration to a WireGuard interface by name.
///
/// An interface with the provided name will be created if one does not exist already.
pub fn apply(self, iface: &InterfaceName, backend: Backend) -> io::Result<()> {
match backend {
#[cfg(target_os = "linux")]
Backend::Kernel => backends::kernel::apply(&self, &iface),
Backend::Userspace => backends::userspace::apply(&self, &iface),
}
}
}
impl Default for DeviceUpdate {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::{
DeviceConfigBuilder, InterfaceName, InvalidInterfaceName, KeyPair, PeerConfigBuilder,
};
use crate::{DeviceUpdate, InterfaceName, InvalidInterfaceName, KeyPair, PeerConfigBuilder};
const TEST_INTERFACE: &str = "wgctrl-test";
use super::*;
@ -289,14 +446,14 @@ mod tests {
}
let keypairs: Vec<_> = (0..10).map(|_| KeyPair::generate()).collect();
let mut builder = DeviceConfigBuilder::new();
let mut builder = DeviceUpdate::new();
for keypair in &keypairs {
builder = builder.add_peer(PeerConfigBuilder::new(&keypair.public))
}
let interface = TEST_INTERFACE.parse().unwrap();
builder.apply(&interface).unwrap();
builder.apply(&interface, Backend::Userspace).unwrap();
let device = DeviceInfo::get_by_name(&interface).unwrap();
let device = Device::get(&interface, Backend::Userspace).unwrap();
for keypair in &keypairs {
assert!(device

View File

@ -3,4 +3,63 @@ mod config;
mod device;
mod key;
use std::{
fmt::{self, Display, Formatter},
str::FromStr,
};
pub use crate::{config::*, device::*, key::*};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Backend {
#[cfg(target_os = "linux")]
Kernel,
Userspace,
}
impl Default for Backend {
fn default() -> Self {
#[cfg(target_os = "linux")]
{
Self::Kernel
}
#[cfg(not(target_os = "linux"))]
{
Self::Userspace
}
}
}
impl Display for Backend {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
#[cfg(target_os = "linux")]
Self::Kernel => write!(f, "kernel"),
Self::Userspace => write!(f, "userspace"),
}
}
}
impl FromStr for Backend {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
#[cfg(target_os = "linux")]
"kernel" => Ok(Self::Kernel),
"userspace" => Ok(Self::Userspace),
_ => Err(format!("valid values: {}.", Self::variants().join(", "))),
}
}
}
impl Backend {
pub fn variants() -> &'static [&'static str] {
#[cfg(target_os = "linux")]
{ &["kernel", "userspace"] }
#[cfg(not(target_os = "linux"))]
{ &["userspace"] }
}
}