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]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.14"
|
version = "0.3.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
|
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.14"
|
version = "0.3.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
|
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.14"
|
version = "0.3.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
|
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.14"
|
version = "0.3.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
|
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -409,9 +410,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.4.0"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437"
|
checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
|
@ -862,18 +863,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.125"
|
version = "1.0.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
|
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.125"
|
version = "1.0.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
|
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1048,9 +1049,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "terminal_size"
|
name = "terminal_size"
|
||||||
version = "0.1.16"
|
version = "0.1.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406"
|
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
@ -1102,9 +1103,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.5.0"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
|
checksum = "bd3076b5c8cc18138b8f8814895c11eb4de37114a5d127bafdc5e55798ceef37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1116,9 +1117,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
|
checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use serde::{Deserialize, Serialize};
|
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::{
|
use std::{
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::{Read, Seek, SeekFrom, Write},
|
io::{Read, Seek, SeekFrom, Write},
|
||||||
|
@ -54,13 +54,13 @@ impl DataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_path(interface: &InterfaceName) -> PathBuf {
|
pub fn get_path(interface: &InterfaceName) -> PathBuf {
|
||||||
CLIENT_DATA_PATH
|
CLIENT_DATA_DIR
|
||||||
.join(interface.to_string())
|
.join(interface.to_string())
|
||||||
.with_extension("json")
|
.with_extension("json")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _open(interface: &InterfaceName, create: bool) -> Result<Self, WrappedIoError> {
|
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)
|
Self::open_with_path(Self::get_path(interface), create)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use indoc::printdoc;
|
||||||
use shared::{
|
use shared::{
|
||||||
interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts,
|
interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts,
|
||||||
Association, AssociationContents, Cidr, CidrTree, EndpointContents, InstallOpts, Interface,
|
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,
|
REDEEM_TRANSITION_WAIT,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -15,7 +15,7 @@ use std::{
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use wgctrl::{DeviceConfigBuilder, DeviceInfo, InterfaceName, PeerConfigBuilder, PeerInfo};
|
use wgctrl::{Device, DeviceUpdate, InterfaceName, PeerConfigBuilder, PeerInfo};
|
||||||
|
|
||||||
mod data_store;
|
mod data_store;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -41,6 +41,9 @@ macro_rules! println_pad {
|
||||||
struct Opts {
|
struct Opts {
|
||||||
#[structopt(subcommand)]
|
#[structopt(subcommand)]
|
||||||
command: Option<Command>,
|
command: Option<Command>,
|
||||||
|
|
||||||
|
#[structopt(flatten)]
|
||||||
|
network: NetworkOpt,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
|
@ -73,9 +76,6 @@ enum Command {
|
||||||
|
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
opts: InstallOpts,
|
opts: InstallOpts,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
|
||||||
routing: RoutingOpt,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Enumerate all innernet connections.
|
/// Enumerate all innernet connections.
|
||||||
|
@ -107,9 +107,6 @@ enum Command {
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
hosts: HostsOpt,
|
hosts: HostsOpt,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
|
||||||
routing: RoutingOpt,
|
|
||||||
|
|
||||||
interface: Interface,
|
interface: Interface,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -119,9 +116,6 @@ enum Command {
|
||||||
|
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
hosts: HostsOpt,
|
hosts: HostsOpt,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
|
||||||
routing: RoutingOpt,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Uninstall an innernet network.
|
/// Uninstall an innernet network.
|
||||||
|
@ -233,9 +227,9 @@ fn install(
|
||||||
invite: &Path,
|
invite: &Path,
|
||||||
hosts_file: Option<PathBuf>,
|
hosts_file: Option<PathBuf>,
|
||||||
opts: InstallOpts,
|
opts: InstallOpts,
|
||||||
routing: RoutingOpt,
|
network: NetworkOpt,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_PATH])?;
|
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_DIR])?;
|
||||||
let config = InterfaceConfig::from_file(invite)?;
|
let config = InterfaceConfig::from_file(invite)?;
|
||||||
|
|
||||||
let iface = if opts.default_name {
|
let iface = if opts.default_name {
|
||||||
|
@ -249,15 +243,15 @@ fn install(
|
||||||
.interact()?
|
.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() {
|
if target_conf.exists() {
|
||||||
return Err("An interface with this name already exists in innernet.".into());
|
return Err("An interface with this name already exists in innernet.".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let iface = iface.parse()?;
|
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());
|
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 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());
|
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;
|
let mut fetch_success = false;
|
||||||
for _ in 0..3 {
|
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;
|
fetch_success = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -320,7 +314,7 @@ fn redeem_invite(
|
||||||
iface: &InterfaceName,
|
iface: &InterfaceName,
|
||||||
mut config: InterfaceConfig,
|
mut config: InterfaceConfig,
|
||||||
target_conf: PathBuf,
|
target_conf: PathBuf,
|
||||||
routing: RoutingOpt,
|
network: NetworkOpt,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
println!("{} bringing up the interface.", "[*]".dimmed());
|
println!("{} bringing up the interface.", "[*]".dimmed());
|
||||||
let resolved_endpoint = config.server.external_endpoint.resolve()?;
|
let resolved_endpoint = config.server.external_endpoint.resolve()?;
|
||||||
|
@ -334,7 +328,7 @@ fn redeem_invite(
|
||||||
config.server.internal_endpoint.ip(),
|
config.server.internal_endpoint.ip(),
|
||||||
resolved_endpoint,
|
resolved_endpoint,
|
||||||
)),
|
)),
|
||||||
!routing.no_routing,
|
network,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!("{} Generating new keypair.", "[*]".dimmed());
|
println!("{} Generating new keypair.", "[*]".dimmed());
|
||||||
|
@ -365,9 +359,9 @@ fn redeem_invite(
|
||||||
"{} Changing keys and waiting for server's WireGuard interface to transition.",
|
"{} Changing keys and waiting for server's WireGuard interface to transition.",
|
||||||
"[*]".dimmed(),
|
"[*]".dimmed(),
|
||||||
);
|
);
|
||||||
DeviceConfigBuilder::new()
|
DeviceUpdate::new()
|
||||||
.set_private_key(keypair.private)
|
.set_private_key(keypair.private)
|
||||||
.apply(&iface)?;
|
.apply(&iface, network.backend)?;
|
||||||
thread::sleep(*REDEEM_TRANSITION_WAIT);
|
thread::sleep(*REDEEM_TRANSITION_WAIT);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -377,7 +371,7 @@ fn up(
|
||||||
interface: &InterfaceName,
|
interface: &InterfaceName,
|
||||||
loop_interval: Option<Duration>,
|
loop_interval: Option<Duration>,
|
||||||
hosts_path: Option<PathBuf>,
|
hosts_path: Option<PathBuf>,
|
||||||
routing: RoutingOpt,
|
routing: NetworkOpt,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
loop {
|
loop {
|
||||||
fetch(interface, true, hosts_path.clone(), routing)?;
|
fetch(interface, true, hosts_path.clone(), routing)?;
|
||||||
|
@ -394,10 +388,10 @@ fn fetch(
|
||||||
interface: &InterfaceName,
|
interface: &InterfaceName,
|
||||||
bring_up_interface: bool,
|
bring_up_interface: bool,
|
||||||
hosts_path: Option<PathBuf>,
|
hosts_path: Option<PathBuf>,
|
||||||
routing: RoutingOpt,
|
network: NetworkOpt,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let config = InterfaceConfig::from_interface(interface)?;
|
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)
|
interfaces.iter().any(|name| name == interface)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -424,7 +418,7 @@ fn fetch(
|
||||||
config.server.internal_endpoint.ip(),
|
config.server.internal_endpoint.ip(),
|
||||||
resolved_endpoint,
|
resolved_endpoint,
|
||||||
)),
|
)),
|
||||||
!routing.no_routing,
|
network,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +426,8 @@ fn fetch(
|
||||||
let mut store = DataStore::open_or_create(&interface)?;
|
let mut store = DataStore::open_or_create(&interface)?;
|
||||||
let State { peers, cidrs } = Api::new(&config.server).http("GET", "/user/state")?;
|
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
|
let interface_public_key = device_info
|
||||||
.public_key
|
.public_key
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -467,7 +462,7 @@ fn fetch(
|
||||||
})
|
})
|
||||||
.collect::<Vec<PeerConfigBuilder>>();
|
.collect::<Vec<PeerConfigBuilder>>();
|
||||||
|
|
||||||
let mut device_config_builder = DeviceConfigBuilder::new();
|
let mut device_config_builder = DeviceUpdate::new();
|
||||||
let mut device_config_changed = false;
|
let mut device_config_changed = false;
|
||||||
|
|
||||||
if !peer_configs_diff.is_empty() {
|
if !peer_configs_diff.is_empty() {
|
||||||
|
@ -491,7 +486,7 @@ fn fetch(
|
||||||
}
|
}
|
||||||
|
|
||||||
if device_config_changed {
|
if device_config_changed {
|
||||||
device_config_builder.apply(&interface)?;
|
device_config_builder.apply(&interface, network.backend)?;
|
||||||
|
|
||||||
if let Some(path) = hosts_path {
|
if let Some(path) = hosts_path {
|
||||||
update_hosts_file(interface, path, &peers)?;
|
update_hosts_file(interface, path, &peers)?;
|
||||||
|
@ -512,7 +507,7 @@ fn fetch(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uninstall(interface: &InterfaceName) -> Result<(), Error> {
|
fn uninstall(interface: &InterfaceName, network: NetworkOpt) -> Result<(), Error> {
|
||||||
if Confirm::with_theme(&*prompts::THEME)
|
if Confirm::with_theme(&*prompts::THEME)
|
||||||
.with_prompt(&format!(
|
.with_prompt(&format!(
|
||||||
"Permanently delete network \"{}\"?",
|
"Permanently delete network \"{}\"?",
|
||||||
|
@ -522,7 +517,7 @@ fn uninstall(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
.interact()?
|
.interact()?
|
||||||
{
|
{
|
||||||
println!("{} bringing down interface (if up).", "[*]".dimmed());
|
println!("{} bringing down interface (if up).", "[*]".dimmed());
|
||||||
wg::down(interface).ok();
|
wg::down(interface, network.backend).ok();
|
||||||
let config = InterfaceConfig::get_path(interface);
|
let config = InterfaceConfig::get_path(interface);
|
||||||
let data = DataStore::get_path(interface);
|
let data = DataStore::get_path(interface);
|
||||||
std::fs::remove_file(&config)
|
std::fs::remove_file(&config)
|
||||||
|
@ -701,11 +696,15 @@ fn list_associations(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
Ok(())
|
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)?;
|
let mut config = InterfaceConfig::from_interface(interface)?;
|
||||||
|
|
||||||
if let Some(listen_port) = prompts::set_listen_port(&config.interface, unset)? {
|
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(),);
|
println!("{} the interface is updated", "[*]".dimmed(),);
|
||||||
|
|
||||||
config.interface.listen_port = listen_port;
|
config.interface.listen_port = listen_port;
|
||||||
|
@ -718,14 +717,18 @@ fn set_listen_port(interface: &InterfaceName, unset: bool) -> Result<(), Error>
|
||||||
Ok(())
|
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)?;
|
let config = InterfaceConfig::from_interface(interface)?;
|
||||||
if !unset && config.interface.listen_port.is_none() {
|
if !unset && config.interface.listen_port.is_none() {
|
||||||
println!(
|
println!(
|
||||||
"{}: you need to set a listen port for your interface first.",
|
"{}: you need to set a listen port for your interface first.",
|
||||||
"note".bold().yellow()
|
"note".bold().yellow()
|
||||||
);
|
);
|
||||||
set_listen_port(interface, unset)?;
|
set_listen_port(interface, unset, network)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(endpoint) = prompts::override_endpoint(unset)? {
|
if let Some(endpoint) = prompts::override_endpoint(unset)? {
|
||||||
|
@ -742,9 +745,16 @@ fn override_endpoint(interface: &InterfaceName, unset: bool) -> Result<(), Error
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show(short: bool, tree: bool, interface: Option<Interface>) -> Result<(), Error> {
|
fn show(
|
||||||
let interfaces =
|
short: bool,
|
||||||
interface.map_or_else(DeviceInfo::enumerate, |interface| Ok(vec![*interface]))?;
|
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
|
let devices = interfaces
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -752,7 +762,7 @@ fn show(short: bool, tree: bool, interface: Option<Interface>) -> Result<(), Err
|
||||||
DataStore::open(&name)
|
DataStore::open(&name)
|
||||||
.and_then(|store| {
|
.and_then(|store| {
|
||||||
Ok((
|
Ok((
|
||||||
DeviceInfo::get_by_name(&name).with_str(name.as_str_lossy())?,
|
Device::get(&name, network.backend).with_str(name.as_str_lossy())?,
|
||||||
store,
|
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 {
|
if short {
|
||||||
let listen_port_str = device_info
|
let listen_port_str = device_info
|
||||||
.listen_port
|
.listen_port
|
||||||
|
@ -930,32 +940,26 @@ fn run(opt: Opts) -> Result<(), Error> {
|
||||||
invite,
|
invite,
|
||||||
hosts,
|
hosts,
|
||||||
opts,
|
opts,
|
||||||
routing,
|
} => install(&invite, hosts.into(), opts, opt.network)?,
|
||||||
} => install(&invite, hosts.into(), opts, routing)?,
|
|
||||||
Command::Show {
|
Command::Show {
|
||||||
short,
|
short,
|
||||||
tree,
|
tree,
|
||||||
interface,
|
interface,
|
||||||
} => show(short, tree, interface)?,
|
} => show(short, tree, interface, opt.network)?,
|
||||||
Command::Fetch {
|
Command::Fetch { interface, hosts } => fetch(&interface, false, hosts.into(), opt.network)?,
|
||||||
interface,
|
|
||||||
hosts,
|
|
||||||
routing,
|
|
||||||
} => fetch(&interface, false, hosts.into(), routing)?,
|
|
||||||
Command::Up {
|
Command::Up {
|
||||||
interface,
|
interface,
|
||||||
daemon,
|
daemon,
|
||||||
hosts,
|
hosts,
|
||||||
routing,
|
|
||||||
interval,
|
interval,
|
||||||
} => up(
|
} => up(
|
||||||
&interface,
|
&interface,
|
||||||
daemon.then(|| Duration::from_secs(interval)),
|
daemon.then(|| Duration::from_secs(interval)),
|
||||||
hosts.into(),
|
hosts.into(),
|
||||||
routing,
|
opt.network,
|
||||||
)?,
|
)?,
|
||||||
Command::Down { interface } => wg::down(&interface)?,
|
Command::Down { interface } => wg::down(&interface, opt.network.backend)?,
|
||||||
Command::Uninstall { interface } => uninstall(&interface)?,
|
Command::Uninstall { interface } => uninstall(&interface, opt.network)?,
|
||||||
Command::AddPeer { interface, opts } => add_peer(&interface, opts)?,
|
Command::AddPeer { interface, opts } => add_peer(&interface, opts)?,
|
||||||
Command::AddCidr { interface, opts } => add_cidr(&interface, opts)?,
|
Command::AddCidr { interface, opts } => add_cidr(&interface, opts)?,
|
||||||
Command::DisablePeer { interface } => enable_or_disable_peer(&interface, false)?,
|
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::AddAssociation { interface, opts } => add_association(&interface, opts)?,
|
||||||
Command::DeleteAssociation { interface } => delete_association(&interface)?,
|
Command::DeleteAssociation { interface } => delete_association(&interface)?,
|
||||||
Command::ListAssociations { interface } => list_associations(&interface)?,
|
Command::ListAssociations { interface } => list_associations(&interface)?,
|
||||||
Command::SetListenPort { interface, unset } => set_listen_port(&interface, unset)?,
|
Command::SetListenPort { interface, unset } => {
|
||||||
Command::OverrideEndpoint { interface, unset } => override_endpoint(&interface, unset)?,
|
set_listen_port(&interface, unset, opt.network)?
|
||||||
|
},
|
||||||
|
Command::OverrideEndpoint { interface, unset } => {
|
||||||
|
override_endpoint(&interface, unset, opt.network)?
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
use shared::PeerContents;
|
use shared::PeerContents;
|
||||||
use wgctrl::DeviceConfigBuilder;
|
use wgctrl::DeviceUpdate;
|
||||||
|
|
||||||
pub async fn routes(
|
pub async fn routes(
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
|
@ -49,9 +49,9 @@ mod handlers {
|
||||||
|
|
||||||
if cfg!(not(test)) {
|
if cfg!(not(test)) {
|
||||||
// Update the current WireGuard interface with the new peers.
|
// Update the current WireGuard interface with the new peers.
|
||||||
DeviceConfigBuilder::new()
|
DeviceUpdate::new()
|
||||||
.add_peer((&*peer).into())
|
.add_peer((&*peer).into())
|
||||||
.apply(&session.context.interface)
|
.apply(&session.context.interface, session.context.backend)
|
||||||
.map_err(|_| ServerError::WireGuard)?;
|
.map_err(|_| ServerError::WireGuard)?;
|
||||||
log::info!("updated WireGuard interface, adding {}", &*peer);
|
log::info!("updated WireGuard interface, adding {}", &*peer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ use crate::{
|
||||||
api::inject_endpoints,
|
api::inject_endpoints,
|
||||||
db::{DatabaseCidr, DatabasePeer},
|
db::{DatabaseCidr, DatabasePeer},
|
||||||
util::{form_body, json_response, status_response},
|
util::{form_body, json_response, status_response},
|
||||||
ServerError, Session,
|
Context, ServerError, Session,
|
||||||
};
|
};
|
||||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
use shared::{EndpointContents, PeerContents, RedeemContents, State, REDEEM_TRANSITION_WAIT};
|
use shared::{EndpointContents, PeerContents, RedeemContents, State, REDEEM_TRANSITION_WAIT};
|
||||||
use wgctrl::DeviceConfigBuilder;
|
use wgctrl::DeviceUpdate;
|
||||||
|
|
||||||
pub async fn routes(
|
pub async fn routes(
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
|
@ -83,7 +83,9 @@ mod handlers {
|
||||||
selected_peer.redeem(&conn, &form.public_key)?;
|
selected_peer.redeem(&conn, &form.public_key)?;
|
||||||
|
|
||||||
if cfg!(not(test)) {
|
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
|
// 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
|
// get through. Instead, we need to wait a reasonable amount for the HTTP response to
|
||||||
|
@ -102,10 +104,10 @@ mod handlers {
|
||||||
&*selected_peer,
|
&*selected_peer,
|
||||||
old_public_key.to_base64()
|
old_public_key.to_base64()
|
||||||
);
|
);
|
||||||
DeviceConfigBuilder::new()
|
DeviceUpdate::new()
|
||||||
.remove_peer_by_key(&old_public_key)
|
.remove_peer_by_key(&old_public_key)
|
||||||
.add_peer((&*selected_peer).into())
|
.add_peer((&*selected_peer).into())
|
||||||
.apply(&interface)
|
.apply(&interface, backend)
|
||||||
.map_err(|e| log::error!("{:?}", e))
|
.map_err(|e| log::error!("{:?}", e))
|
||||||
.ok();
|
.ok();
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ use ipnetwork::IpNetwork;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use shared::{AddCidrOpts, AddPeerOpts, IoErrorContext, RoutingOpt, INNERNET_PUBKEY_HEADER};
|
use shared::{AddCidrOpts, AddPeerOpts, IoErrorContext, NetworkOpt, INNERNET_PUBKEY_HEADER};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
|
@ -21,7 +21,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use subtle::ConstantTimeEq;
|
use subtle::ConstantTimeEq;
|
||||||
use wgctrl::{DeviceConfigBuilder, DeviceInfo, InterfaceName, Key, PeerConfigBuilder};
|
use wgctrl::{Backend, Device, DeviceUpdate, InterfaceName, Key, PeerConfigBuilder};
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
@ -45,6 +45,9 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
struct Opt {
|
struct Opt {
|
||||||
#[structopt(subcommand)]
|
#[structopt(subcommand)]
|
||||||
command: Command,
|
command: Command,
|
||||||
|
|
||||||
|
#[structopt(flatten)]
|
||||||
|
network: NetworkOpt,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
|
@ -64,7 +67,7 @@ enum Command {
|
||||||
interface: Interface,
|
interface: Interface,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
routing: RoutingOpt,
|
network: NetworkOpt,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Add a peer to an existing network.
|
/// Add a peer to an existing network.
|
||||||
|
@ -92,6 +95,7 @@ pub struct Context {
|
||||||
pub db: Db,
|
pub db: Db,
|
||||||
pub endpoints: Arc<RwLock<HashMap<String, SocketAddr>>>,
|
pub endpoints: Arc<RwLock<HashMap<String, SocketAddr>>>,
|
||||||
pub interface: InterfaceName,
|
pub interface: InterfaceName,
|
||||||
|
pub backend: Backend,
|
||||||
pub public_key: Key,
|
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) {
|
if let Err(e) = initialize::init_wizard(&conf, opts) {
|
||||||
println!("{}: {}.", "creation failed".red(), e);
|
println!("{}: {}.", "creation failed".red(), e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Command::Uninstall { interface } => uninstall(&interface, &conf)?,
|
Command::Uninstall { interface } => uninstall(&interface, &conf, opt.network)?,
|
||||||
Command::Serve { interface, routing } => serve(*interface, &conf, routing).await?,
|
Command::Serve {
|
||||||
Command::AddPeer { interface, args } => add_peer(&interface, &conf, args)?,
|
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)?,
|
Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,6 +250,7 @@ fn add_peer(
|
||||||
interface: &InterfaceName,
|
interface: &InterfaceName,
|
||||||
conf: &ServerConfig,
|
conf: &ServerConfig,
|
||||||
opts: AddPeerOpts,
|
opts: AddPeerOpts,
|
||||||
|
network: NetworkOpt,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let config = ConfigFile::from_file(conf.config_path(interface))?;
|
let config = ConfigFile::from_file(conf.config_path(interface))?;
|
||||||
let conn = open_database_connection(interface, conf)?;
|
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)? {
|
if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree, &opts)? {
|
||||||
let peer = DatabasePeer::create(&conn, peer_request)?;
|
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.
|
// Update the current WireGuard interface with the new peers.
|
||||||
DeviceConfigBuilder::new()
|
DeviceUpdate::new()
|
||||||
.add_peer((&*peer).into())
|
.add_peer((&*peer).into())
|
||||||
.apply(interface)
|
.apply(interface, network.backend)
|
||||||
.map_err(|_| ServerError::WireGuard)?;
|
.map_err(|_| ServerError::WireGuard)?;
|
||||||
|
|
||||||
println!("adding to WireGuard interface: {}", &*peer);
|
println!("adding to WireGuard interface: {}", &*peer);
|
||||||
|
@ -309,7 +317,11 @@ fn add_cidr(
|
||||||
Ok(())
|
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)
|
if Confirm::with_theme(&*prompts::THEME)
|
||||||
.with_prompt(&format!(
|
.with_prompt(&format!(
|
||||||
"Permanently delete network \"{}\"?",
|
"Permanently delete network \"{}\"?",
|
||||||
|
@ -319,7 +331,7 @@ fn uninstall(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error
|
||||||
.interact()?
|
.interact()?
|
||||||
{
|
{
|
||||||
println!("{} bringing down interface (if up).", "[*]".dimmed());
|
println!("{} bringing down interface (if up).", "[*]".dimmed());
|
||||||
wg::down(interface).ok();
|
wg::down(interface, network.backend).ok();
|
||||||
let config = conf.config_path(interface);
|
let config = conf.config_path(interface);
|
||||||
let data = conf.database_path(interface);
|
let data = conf.database_path(interface);
|
||||||
std::fs::remove_file(&config)
|
std::fs::remove_file(&config)
|
||||||
|
@ -339,7 +351,7 @@ fn uninstall(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error
|
||||||
Ok(())
|
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()));
|
let endpoints = Arc::new(RwLock::new(HashMap::new()));
|
||||||
tokio::task::spawn({
|
tokio::task::spawn({
|
||||||
let endpoints = endpoints.clone();
|
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));
|
let mut interval = tokio::time::interval(Duration::from_secs(10));
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
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 {
|
for peer in info.peers {
|
||||||
if let Some(endpoint) = peer.config.endpoint {
|
if let Some(endpoint) = peer.config.endpoint {
|
||||||
endpoints
|
endpoints
|
||||||
|
@ -378,7 +390,7 @@ fn spawn_expired_invite_sweeper(db: Db) {
|
||||||
async fn serve(
|
async fn serve(
|
||||||
interface: InterfaceName,
|
interface: InterfaceName,
|
||||||
conf: &ServerConfig,
|
conf: &ServerConfig,
|
||||||
routing: RoutingOpt,
|
network: NetworkOpt,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let config = ConfigFile::from_file(conf.config_path(&interface))?;
|
let config = ConfigFile::from_file(conf.config_path(&interface))?;
|
||||||
let conn = open_database_connection(&interface, conf)?;
|
let conn = open_database_connection(&interface, conf)?;
|
||||||
|
@ -396,18 +408,18 @@ async fn serve(
|
||||||
IpNetwork::new(config.address, config.network_cidr_prefix)?,
|
IpNetwork::new(config.address, config.network_cidr_prefix)?,
|
||||||
Some(config.listen_port),
|
Some(config.listen_port),
|
||||||
None,
|
None,
|
||||||
!routing.no_routing,
|
network,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
DeviceConfigBuilder::new()
|
DeviceUpdate::new()
|
||||||
.add_peers(&peer_configs)
|
.add_peers(&peer_configs)
|
||||||
.apply(&interface)?;
|
.apply(&interface, network.backend)?;
|
||||||
|
|
||||||
log::info!("{} peers added to wireguard interface.", peers.len());
|
log::info!("{} peers added to wireguard interface.", peers.len());
|
||||||
|
|
||||||
let public_key = wgctrl::Key::from_base64(&config.private_key)?.generate_public();
|
let public_key = wgctrl::Key::from_base64(&config.private_key)?.generate_public();
|
||||||
let db = Arc::new(Mutex::new(conn));
|
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());
|
spawn_expired_invite_sweeper(db.clone());
|
||||||
|
|
||||||
let context = Context {
|
let context = Context {
|
||||||
|
@ -415,6 +427,7 @@ async fn serve(
|
||||||
endpoints,
|
endpoints,
|
||||||
interface,
|
interface,
|
||||||
public_key,
|
public_key,
|
||||||
|
backend: network.backend,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("innernet-server {} starting.", VERSION);
|
log::info!("innernet-server {} starting.", VERSION);
|
||||||
|
|
|
@ -12,7 +12,7 @@ use serde::Serialize;
|
||||||
use shared::{Cidr, CidrContents, Error, PeerContents};
|
use shared::{Cidr, CidrContents, Error, PeerContents};
|
||||||
use std::{collections::HashMap, net::SocketAddr, path::PathBuf, sync::Arc};
|
use std::{collections::HashMap, net::SocketAddr, path::PathBuf, sync::Arc};
|
||||||
use tempfile::TempDir;
|
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 ROOT_CIDR: &str = "10.80.0.0/15";
|
||||||
pub const SERVER_CIDR: &str = "10.80.0.1/32";
|
pub const SERVER_CIDR: &str = "10.80.0.1/32";
|
||||||
|
@ -137,6 +137,10 @@ impl Server {
|
||||||
interface: self.interface.clone(),
|
interface: self.interface.clone(),
|
||||||
endpoints: self.endpoints.clone(),
|
endpoints: self.endpoints.clone(),
|
||||||
public_key: self.public_key.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 colored::*;
|
||||||
use indoc::writedoc;
|
use indoc::writedoc;
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
|
@ -128,13 +128,13 @@ impl InterfaceConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_path(interface: &InterfaceName) -> PathBuf {
|
pub fn get_path(interface: &InterfaceName) -> PathBuf {
|
||||||
CLIENT_CONFIG_PATH
|
CLIENT_CONFIG_DIR
|
||||||
.join(interface.to_string())
|
.join(interface.to_string())
|
||||||
.with_extension("conf")
|
.with_extension("conf")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_config_file_path(interface: &InterfaceName) -> Result<PathBuf, Error> {
|
fn build_config_file_path(interface: &InterfaceName) -> Result<PathBuf, Error> {
|
||||||
ensure_dirs_exist(&[*CLIENT_CONFIG_PATH])?;
|
ensure_dirs_exist(&[*CLIENT_CONFIG_DIR])?;
|
||||||
Ok(Self::get_path(interface))
|
Ok(Self::get_path(interface))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ pub mod wg;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CLIENT_CONFIG_PATH: &'static Path = Path::new("/etc/innernet");
|
pub static ref CLIENT_CONFIG_DIR: &'static Path = Path::new("/etc/innernet");
|
||||||
pub static ref CLIENT_DATA_PATH: &'static Path = Path::new("/var/lib/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_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 SERVER_DATABASE_DIR: &'static Path = Path::new("/var/lib/innernet-server");
|
||||||
pub static ref REDEEM_TRANSITION_WAIT: Duration = Duration::from_secs(5);
|
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 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> {
|
pub fn ensure_dirs_exist(dirs: &[&Path]) -> Result<(), WrappedIoError> {
|
||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
match fs::create_dir(dir).with_path(dir) {
|
match fs::create_dir(dir).with_path(dir) {
|
||||||
Err(e) if e.kind() != io::ErrorKind::AlreadyExists => {
|
Err(e) if e.kind() != io::ErrorKind::AlreadyExists => {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let target_file = File::open(dir).with_path(dir)?;
|
let target_file = File::open(dir).with_path(dir)?;
|
||||||
if chmod(&target_file, 0o700).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()
|
dir.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use url::Host;
|
use url::Host;
|
||||||
use wgctrl::{InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder};
|
use wgctrl::{Backend, InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Interface {
|
pub struct Interface {
|
||||||
|
@ -73,7 +73,7 @@ impl FromStr for Endpoint {
|
||||||
let port = port.parse().map_err(|_| "couldn't parse port")?;
|
let port = port.parse().map_err(|_| "couldn't parse port")?;
|
||||||
let host = Host::parse(host).map_err(|_| "couldn't parse host")?;
|
let host = Host::parse(host).map_err(|_| "couldn't parse host")?;
|
||||||
Ok(Endpoint { host, port })
|
Ok(Endpoint { host, port })
|
||||||
}
|
},
|
||||||
_ => Err("couldn't parse in form of 'host:port'"),
|
_ => Err("couldn't parse in form of 'host:port'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,11 +347,16 @@ pub struct AddAssociationOpts {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, StructOpt)]
|
#[derive(Debug, Clone, Copy, StructOpt)]
|
||||||
pub struct RoutingOpt {
|
pub struct NetworkOpt {
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
/// Whether the routing should be done by innernet or is done by an
|
/// Whether the routing should be done by innernet or is done by an
|
||||||
/// external tool like e.g. babeld.
|
/// external tool like e.g. babeld.
|
||||||
pub no_routing: bool,
|
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)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::{Error, IoErrorContext};
|
use crate::{Error, IoErrorContext, NetworkOpt};
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use std::{
|
use std::{
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
process::{self, Command},
|
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> {
|
fn cmd(bin: &str, args: &[&str]) -> Result<process::Output, Error> {
|
||||||
let output = Command::new(bin).args(args).output()?;
|
let output = Command::new(bin).args(args).output()?;
|
||||||
|
@ -67,9 +67,9 @@ pub fn up(
|
||||||
address: IpNetwork,
|
address: IpNetwork,
|
||||||
listen_port: Option<u16>,
|
listen_port: Option<u16>,
|
||||||
peer: Option<(&str, IpAddr, SocketAddr)>,
|
peer: Option<(&str, IpAddr, SocketAddr)>,
|
||||||
do_routing: bool,
|
network: NetworkOpt,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut device = DeviceConfigBuilder::new();
|
let mut device = DeviceUpdate::new();
|
||||||
if let Some((public_key, address, endpoint)) = peer {
|
if let Some((public_key, address, endpoint)) = peer {
|
||||||
let prefix = if address.is_ipv4() { 32 } else { 128 };
|
let prefix = if address.is_ipv4() { 32 } else { 128 };
|
||||||
let peer_config = PeerConfigBuilder::new(&wgctrl::Key::from_base64(&public_key)?)
|
let peer_config = PeerConfigBuilder::new(&wgctrl::Key::from_base64(&public_key)?)
|
||||||
|
@ -82,36 +82,32 @@ pub fn up(
|
||||||
}
|
}
|
||||||
device
|
device
|
||||||
.set_private_key(wgctrl::Key::from_base64(&private_key).unwrap())
|
.set_private_key(wgctrl::Key::from_base64(&private_key).unwrap())
|
||||||
.apply(interface)?;
|
.apply(interface, network.backend)?;
|
||||||
set_addr(interface, address)?;
|
set_addr(interface, address)?;
|
||||||
if do_routing {
|
if !network.no_routing {
|
||||||
add_route(interface, address)?;
|
add_route(interface, address)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_listen_port(interface: &InterfaceName, listen_port: Option<u16>) -> Result<(), Error> {
|
pub fn set_listen_port(
|
||||||
let mut device = DeviceConfigBuilder::new();
|
interface: &InterfaceName,
|
||||||
|
listen_port: Option<u16>,
|
||||||
|
backend: Backend,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut device = DeviceUpdate::new();
|
||||||
if let Some(listen_port) = listen_port {
|
if let Some(listen_port) = listen_port {
|
||||||
device = device.set_listen_port(listen_port);
|
device = device.set_listen_port(listen_port);
|
||||||
} else {
|
} else {
|
||||||
device = device.randomize_listen_port();
|
device = device.randomize_listen_port();
|
||||||
}
|
}
|
||||||
device.apply(interface)?;
|
device.apply(interface, backend)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
pub fn down(interface: &InterfaceName, backend: Backend) -> Result<(), Error> {
|
||||||
pub fn down(interface: &InterfaceName) -> Result<(), Error> {
|
Ok(Device::get(interface, backend)?.delete()?)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a route in the OS's routing table to get traffic flowing through this interface.
|
/// Add a route in the OS's routing table to get traffic flowing through this interface.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
device::AllowedIp, DeviceConfigBuilder, DeviceInfo, InterfaceName, InvalidInterfaceName,
|
device::AllowedIp, Backend, Device, DeviceUpdate, InterfaceName, InvalidInterfaceName,
|
||||||
InvalidKey, PeerConfig, PeerConfigBuilder, PeerInfo, PeerStats,
|
InvalidKey, PeerConfig, PeerConfigBuilder, PeerInfo, PeerStats,
|
||||||
};
|
};
|
||||||
use wgctrl_sys::{timespec64, wg_device_flags as wgdf, wg_peer_flags as wgpf};
|
use wgctrl_sys::{timespec64, wg_device_flags as wgdf, wg_peer_flags as wgpf};
|
||||||
|
@ -9,15 +9,10 @@ use std::{
|
||||||
io,
|
io,
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
os::raw::c_char,
|
os::raw::c_char,
|
||||||
path::Path,
|
|
||||||
process::Command,
|
|
||||||
ptr, str,
|
ptr, str,
|
||||||
sync::Once,
|
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
static MODPROBE: Once = Once::new();
|
|
||||||
|
|
||||||
impl<'a> From<&'a wgctrl_sys::wg_allowedip> for AllowedIp {
|
impl<'a> From<&'a wgctrl_sys::wg_allowedip> for AllowedIp {
|
||||||
fn from(raw: &wgctrl_sys::wg_allowedip) -> AllowedIp {
|
fn from(raw: &wgctrl_sys::wg_allowedip) -> AllowedIp {
|
||||||
let addr = match i32::from(raw.family) {
|
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 {
|
impl<'a> From<&'a wgctrl_sys::wg_device> for Device {
|
||||||
fn from(raw: &wgctrl_sys::wg_device) -> DeviceInfo {
|
fn from(raw: &wgctrl_sys::wg_device) -> Device {
|
||||||
// SAFETY: The name string buffer came directly from wgctrl so its NUL terminated.
|
// SAFETY: The name string buffer came directly from wgctrl so its NUL terminated.
|
||||||
let name = unsafe { InterfaceName::from_wg(raw.name) };
|
let name = unsafe { InterfaceName::from_wg(raw.name) };
|
||||||
DeviceInfo {
|
Device {
|
||||||
name,
|
name,
|
||||||
public_key: if (raw.flags & wgdf::WGDEVICE_HAS_PUBLIC_KEY).0 > 0 {
|
public_key: if (raw.flags & wgdf::WGDEVICE_HAS_PUBLIC_KEY).0 > 0 {
|
||||||
Some(Key::from_raw(raw.public_key))
|
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),
|
peers: parse_peers(&raw),
|
||||||
linked_name: None,
|
linked_name: None,
|
||||||
|
backend: Backend::Kernel,
|
||||||
__cant_construct_me: (),
|
__cant_construct_me: (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +210,7 @@ fn encode_endpoint(endpoint: Option<SocketAddr>) -> wgctrl_sys::wg_peer__bindgen
|
||||||
peer.addr4 = wgctrl_sys::sockaddr_in {
|
peer.addr4 = wgctrl_sys::sockaddr_in {
|
||||||
sin_family: libc::AF_INET as u16,
|
sin_family: libc::AF_INET as u16,
|
||||||
sin_addr: wgctrl_sys::in_addr {
|
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_port: u16::to_be(s.port()),
|
||||||
sin_zero: [0; 8],
|
sin_zero: [0; 8],
|
||||||
|
@ -223,10 +219,12 @@ fn encode_endpoint(endpoint: Option<SocketAddr>) -> wgctrl_sys::wg_peer__bindgen
|
||||||
},
|
},
|
||||||
Some(SocketAddr::V6(s)) => {
|
Some(SocketAddr::V6(s)) => {
|
||||||
let mut peer = wgctrl_sys::wg_peer__bindgen_ty_1::default();
|
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 {
|
peer.addr6 = wgctrl_sys::sockaddr_in6 {
|
||||||
sin6_family: libc::AF_INET6 as u16,
|
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_port: u16::to_be(s.port()),
|
||||||
sin6_flowinfo: 0,
|
sin6_flowinfo: 0,
|
||||||
sin6_scope_id: 0,
|
sin6_scope_id: 0,
|
||||||
|
@ -238,7 +236,7 @@ fn encode_endpoint(endpoint: Option<SocketAddr>) -> wgctrl_sys::wg_peer__bindgen
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_peers(
|
fn encode_peers(
|
||||||
peers: Vec<PeerConfigBuilder>,
|
peers: &[PeerConfigBuilder],
|
||||||
) -> (*mut wgctrl_sys::wg_peer, *mut wgctrl_sys::wg_peer) {
|
) -> (*mut wgctrl_sys::wg_peer, *mut wgctrl_sys::wg_peer) {
|
||||||
let mut first_peer = ptr::null_mut();
|
let mut first_peer = ptr::null_mut();
|
||||||
let mut last_peer: *mut wgctrl_sys::wg_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)
|
(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> {
|
pub fn enumerate() -> Result<Vec<InterfaceName>, io::Error> {
|
||||||
let base = unsafe { wgctrl_sys::wg_list_device_names() };
|
let base = unsafe { wgctrl_sys::wg_list_device_names() };
|
||||||
|
|
||||||
|
@ -337,8 +321,8 @@ pub fn enumerate() -> Result<Vec<InterfaceName>, io::Error> {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<()> {
|
pub fn apply(builder: &DeviceUpdate, iface: &InterfaceName) -> io::Result<()> {
|
||||||
let (first_peer, last_peer) = encode_peers(builder.peers);
|
let (first_peer, last_peer) = encode_peers(&builder.peers);
|
||||||
|
|
||||||
let result = unsafe { wgctrl_sys::wg_add_device(iface.as_ptr()) };
|
let result = unsafe { wgctrl_sys::wg_add_device(iface.as_ptr()) };
|
||||||
match result {
|
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 mut device: *mut wgctrl_sys::wg_device = ptr::null_mut();
|
||||||
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
|
@ -405,7 +389,7 @@ pub fn get_by_name(name: &InterfaceName) -> Result<DeviceInfo, io::Error> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = if result == 0 {
|
let result = if result == 0 {
|
||||||
Ok(DeviceInfo::from(unsafe { &*device }))
|
Ok(Device::from(unsafe { &*device }))
|
||||||
} else {
|
} else {
|
||||||
Err(io::Error::last_os_error())
|
Err(io::Error::last_os_error())
|
||||||
};
|
};
|
||||||
|
@ -539,4 +523,4 @@ mod tests {
|
||||||
assert_eq!(endpoint6, parse_endpoint(&encoded6));
|
assert_eq!(endpoint6, parse_endpoint(&encoded6));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")]
|
#[cfg(target_os = "linux")]
|
||||||
use crate::Key;
|
use crate::Key;
|
||||||
|
@ -89,11 +89,11 @@ fn new_peer_info(public_key: Key) -> PeerInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConfigParser {
|
struct ConfigParser {
|
||||||
device_info: DeviceInfo,
|
device_info: Device,
|
||||||
current_peer: Option<PeerInfo>,
|
current_peer: Option<PeerInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ConfigParser> for DeviceInfo {
|
impl From<ConfigParser> for Device {
|
||||||
fn from(parser: ConfigParser) -> Self {
|
fn from(parser: ConfigParser) -> Self {
|
||||||
parser.device_info
|
parser.device_info
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ impl From<ConfigParser> for DeviceInfo {
|
||||||
impl ConfigParser {
|
impl ConfigParser {
|
||||||
/// Returns `None` if an invalid device name was provided.
|
/// Returns `None` if an invalid device name was provided.
|
||||||
fn new(name: &InterfaceName) -> Self {
|
fn new(name: &InterfaceName) -> Self {
|
||||||
let device_info = DeviceInfo {
|
let device_info = Device {
|
||||||
name: *name,
|
name: *name,
|
||||||
public_key: None,
|
public_key: None,
|
||||||
private_key: None,
|
private_key: None,
|
||||||
|
@ -110,6 +110,7 @@ impl ConfigParser {
|
||||||
listen_port: None,
|
listen_port: None,
|
||||||
peers: vec![],
|
peers: vec![],
|
||||||
linked_name: resolve_tun(name).ok(),
|
linked_name: resolve_tun(name).ok(),
|
||||||
|
backend: Backend::Userspace,
|
||||||
__cant_construct_me: (),
|
__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)?;
|
let mut sock = open_socket(name)?;
|
||||||
sock.write_all(b"get=1\n\n")?;
|
sock.write_all(b"get=1\n\n")?;
|
||||||
let mut reader = BufReader::new(sock);
|
let mut reader = BufReader::new(sock);
|
||||||
|
@ -263,7 +264,7 @@ fn get_userspace_implementation() -> String {
|
||||||
.unwrap_or_else(|_| "wireguard-go".to_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.
|
// If we can't open a configuration socket to an existing interface, try starting it.
|
||||||
let mut sock = match open_socket(iface) {
|
let mut sock = match open_socket(iface) {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -302,7 +303,7 @@ pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<
|
||||||
request.push_str("replace_peers=true\n");
|
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)));
|
request.push_str(&format!("public_key={}\n", hex::encode(peer.public_key.0)));
|
||||||
|
|
||||||
if peer.replace_allowed_ips {
|
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!(
|
request.push_str(&format!(
|
||||||
"allowed_ip={}/{}\n",
|
"allowed_ip={}/{}\n",
|
||||||
allowed_ip.address, allowed_ip.cidr
|
allowed_ip.address, allowed_ip.cidr
|
||||||
|
|
|
@ -1,195 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
backends,
|
device::{AllowedIp, PeerConfig},
|
||||||
device::{AllowedIp, InterfaceName, PeerConfig},
|
key::Key,
|
||||||
key::{Key, KeyPair},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds and represents a single peer in a WireGuard interface configuration.
|
/// 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);
|
/// .add_allowed_ip("192.168.1.2".parse()?, 32);
|
||||||
///
|
///
|
||||||
/// // update our existing configuration with the new peer
|
/// // 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);
|
/// println!("Send these keys to your peer: {:#?}", peer_keypair);
|
||||||
///
|
///
|
||||||
|
@ -350,9 +164,3 @@ impl PeerConfigBuilder {
|
||||||
self
|
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 libc::c_char;
|
||||||
|
|
||||||
use crate::{backends, key::Key};
|
use crate::{backends, key::Key, Backend, KeyPair, PeerConfigBuilder};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
ffi::CStr,
|
ffi::CStr,
|
||||||
fmt,
|
fmt, io,
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents an IP address a peer is allowed to have, in CIDR notation.
|
/// 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)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct AllowedIp {
|
pub struct AllowedIp {
|
||||||
/// The IP address.
|
/// The IP address.
|
||||||
|
@ -90,7 +86,7 @@ pub struct PeerInfo {
|
||||||
/// The peer statistics are retrieved once at construction time,
|
/// The peer statistics are retrieved once at construction time,
|
||||||
/// and need to be updated manually by calling [`get_by_name`](DeviceInfo::get_by_name).
|
/// and need to be updated manually by calling [`get_by_name`](DeviceInfo::get_by_name).
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct DeviceInfo {
|
pub struct Device {
|
||||||
/// The interface name of this device
|
/// The interface name of this device
|
||||||
pub name: InterfaceName,
|
pub name: InterfaceName,
|
||||||
/// The public encryption key of this interface (if present)
|
/// The public encryption key of this interface (if present)
|
||||||
|
@ -105,6 +101,8 @@ pub struct DeviceInfo {
|
||||||
pub peers: Vec<PeerInfo>,
|
pub peers: Vec<PeerInfo>,
|
||||||
/// The associated "real name" of the interface (ex. "utun8" on macOS).
|
/// The associated "real name" of the interface (ex. "utun8" on macOS).
|
||||||
pub linked_name: Option<String>,
|
pub linked_name: Option<String>,
|
||||||
|
/// The backend the device exists on (userspace or kernel).
|
||||||
|
pub backend: Backend,
|
||||||
|
|
||||||
pub(crate) __cant_construct_me: (),
|
pub(crate) __cant_construct_me: (),
|
||||||
}
|
}
|
||||||
|
@ -227,57 +225,216 @@ impl From<InvalidInterfaceName> for std::io::Error {
|
||||||
|
|
||||||
impl std::error::Error for InvalidInterfaceName {}
|
impl std::error::Error for InvalidInterfaceName {}
|
||||||
|
|
||||||
impl DeviceInfo {
|
impl Device {
|
||||||
/// Enumerates all WireGuard interfaces currently present in the system
|
/// Enumerates all WireGuard interfaces currently present in the system,
|
||||||
/// and returns their names.
|
/// both with kernel and userspace backends.
|
||||||
///
|
///
|
||||||
/// You can use [`get_by_name`](DeviceInfo::get_by_name) to retrieve more
|
/// You can use [`get_by_name`](DeviceInfo::get_by_name) to retrieve more
|
||||||
/// detailed information on each interface.
|
/// detailed information on each interface.
|
||||||
#[cfg(target_os = "linux")]
|
pub fn list(backend: Backend) -> Result<Vec<InterfaceName>, std::io::Error> {
|
||||||
pub fn enumerate() -> Result<Vec<InterfaceName>, std::io::Error> {
|
match backend {
|
||||||
if backends::kernel::exists() {
|
#[cfg(target_os = "linux")]
|
||||||
backends::kernel::enumerate()
|
Backend::Kernel => backends::kernel::enumerate(),
|
||||||
} else {
|
Backend::Userspace => backends::userspace::enumerate(),
|
||||||
backends::userspace::enumerate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
pub fn get(name: &InterfaceName, backend: Backend) -> Result<Self, std::io::Error> {
|
||||||
pub fn enumerate() -> Result<Vec<InterfaceName>, std::io::Error> {
|
match backend {
|
||||||
crate::backends::userspace::enumerate()
|
#[cfg(target_os = "linux")]
|
||||||
}
|
Backend::Kernel => backends::kernel::get_by_name(name),
|
||||||
|
Backend::Userspace => backends::userspace::get_by_name(name),
|
||||||
#[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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
pub fn delete(self) -> Result<(), std::io::Error> {
|
||||||
pub fn get_by_name(name: &InterfaceName) -> Result<Self, std::io::Error> {
|
match self.backend {
|
||||||
backends::userspace::get_by_name(name)
|
#[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")]
|
/// Sets a new keypair to be applied to the interface.
|
||||||
pub fn delete(self) -> Result<(), std::io::Error> {
|
///
|
||||||
backends::kernel::delete_interface(&self.name)
|
/// 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"))]
|
/// Specifies a new public key to be applied to the interface.
|
||||||
pub fn delete(self) -> Result<(), std::io::Error> {
|
pub fn set_public_key(mut self, key: Key) -> Self {
|
||||||
backends::userspace::delete_interface(&self.name)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{DeviceUpdate, InterfaceName, InvalidInterfaceName, KeyPair, PeerConfigBuilder};
|
||||||
DeviceConfigBuilder, InterfaceName, InvalidInterfaceName, KeyPair, PeerConfigBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TEST_INTERFACE: &str = "wgctrl-test";
|
const TEST_INTERFACE: &str = "wgctrl-test";
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -289,14 +446,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let keypairs: Vec<_> = (0..10).map(|_| KeyPair::generate()).collect();
|
let keypairs: Vec<_> = (0..10).map(|_| KeyPair::generate()).collect();
|
||||||
let mut builder = DeviceConfigBuilder::new();
|
let mut builder = DeviceUpdate::new();
|
||||||
for keypair in &keypairs {
|
for keypair in &keypairs {
|
||||||
builder = builder.add_peer(PeerConfigBuilder::new(&keypair.public))
|
builder = builder.add_peer(PeerConfigBuilder::new(&keypair.public))
|
||||||
}
|
}
|
||||||
let interface = TEST_INTERFACE.parse().unwrap();
|
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 {
|
for keypair in &keypairs {
|
||||||
assert!(device
|
assert!(device
|
||||||
|
|
|
@ -3,4 +3,63 @@ mod config;
|
||||||
mod device;
|
mod device;
|
||||||
mod key;
|
mod key;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Display, Formatter},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
pub use crate::{config::*, device::*, key::*};
|
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