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
parent
170c8267bf
commit
3892a99156
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"] }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue