client: support running as non-root (#94)
shared(wg): use netlink instead of execve calls to "ip" hostsfile: write to hostsfile in-placepull/121/head
parent
6a60643d7d
commit
449b4b8278
|
@ -153,13 +153,13 @@ dependencies = [
|
|||
name = "client"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
"dialoguer",
|
||||
"hostsfile",
|
||||
"indoc",
|
||||
"ipnetwork",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"regex",
|
||||
"serde",
|
||||
|
@ -382,10 +382,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||
|
||||
[[package]]
|
||||
name = "hostsfile"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
version = "1.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
|
@ -576,6 +573,54 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac48279d5062bdf175bdbcb6b58ff1d6b0ecd54b951f7a0ff4bc0550fe903ccb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"netlink-packet-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-route"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da1bf86b4324996fb58f8e17752b2a06176f4c5efc013928060ac94a3a329b71"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"netlink-packet-core",
|
||||
"netlink-packet-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-utils"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c2afb159d0e3ac700e85f0df25b8438b99d43ed0c0b685242fcdf1b5673e54d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"paste",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d61c5374735aa0cd07cb7fd820b656062b187b5588d79517f72956b57c6de9ef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
|
@ -636,6 +681,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
|
@ -930,12 +981,17 @@ dependencies = [
|
|||
name = "shared"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
"dialoguer",
|
||||
"indoc",
|
||||
"ipnetwork",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"netlink-packet-core",
|
||||
"netlink-packet-route",
|
||||
"netlink-sys",
|
||||
"publicip",
|
||||
"regex",
|
||||
"serde",
|
||||
|
@ -943,6 +999,7 @@ dependencies = [
|
|||
"toml",
|
||||
"url",
|
||||
"wgctrl",
|
||||
"wgctrl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -14,13 +14,13 @@ name = "innernet"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
colored = "2"
|
||||
dialoguer = "0.8"
|
||||
hostsfile = { path = "../hostsfile" }
|
||||
indoc = "1"
|
||||
ipnetwork = { git = "https://github.com/mcginty/ipnetwork" } # pending https://github.com/achanda/ipnetwork/pull/129
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
regex = { version = "1", default-features = false, features = ["std"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Error;
|
||||
use colored::*;
|
||||
use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::{ensure_dirs_exist, Cidr, IoErrorContext, Peer, WrappedIoError, CLIENT_DATA_DIR};
|
||||
use std::{
|
||||
|
@ -35,13 +35,7 @@ impl DataStore {
|
|||
.open(path)
|
||||
.with_path(path)?;
|
||||
|
||||
if shared::chmod(&file, 0o600).with_path(path)? {
|
||||
println!(
|
||||
"{} updated permissions for {} to 0600.",
|
||||
"[!]".yellow(),
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
shared::warn_on_dangerous_mode(path).with_path(path)?;
|
||||
|
||||
let mut json = String::new();
|
||||
file.read_to_string(&mut json).with_path(path)?;
|
||||
|
@ -94,9 +88,7 @@ impl DataStore {
|
|||
for new_peer in current_peers.iter() {
|
||||
if let Some(existing_peer) = peers.iter_mut().find(|p| p.ip == new_peer.ip) {
|
||||
if existing_peer.public_key != new_peer.public_key {
|
||||
return Err(
|
||||
"PINNING ERROR: New peer has same IP but different public key.".into(),
|
||||
);
|
||||
bail!("PINNING ERROR: New peer has same IP but different public key.");
|
||||
} else {
|
||||
*existing_peer = new_peer.clone();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use anyhow::{anyhow, bail};
|
||||
use colored::*;
|
||||
use dialoguer::{Confirm, Input};
|
||||
use hostsfile::HostsBuilder;
|
||||
|
@ -6,10 +7,10 @@ use shared::{
|
|||
interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts,
|
||||
Association, AssociationContents, Cidr, CidrTree, DeleteCidrOpts, EndpointContents,
|
||||
InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, RedeemContents, RenamePeerOpts,
|
||||
State, CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT,
|
||||
State, WrappedIoError, CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
fmt, io,
|
||||
path::{Path, PathBuf},
|
||||
thread,
|
||||
time::{Duration, SystemTime},
|
||||
|
@ -245,7 +246,9 @@ fn update_hosts_file(
|
|||
&format!("{}.{}.wg", peer.contents.name, interface),
|
||||
);
|
||||
}
|
||||
hosts_builder.write_to(hosts_path)?;
|
||||
if let Err(e) = hosts_builder.write_to(&hosts_path).with_path(hosts_path) {
|
||||
log::warn!("failed to update hosts ({})", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -272,7 +275,7 @@ fn install(
|
|||
|
||||
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());
|
||||
bail!("An interface with this name already exists in innernet.");
|
||||
}
|
||||
|
||||
let iface = iface.parse()?;
|
||||
|
@ -423,11 +426,10 @@ fn fetch(
|
|||
|
||||
if !interface_up {
|
||||
if !bring_up_interface {
|
||||
return Err(format!(
|
||||
bail!(
|
||||
"Interface is not up. Use 'innernet up {}' instead",
|
||||
interface
|
||||
)
|
||||
.into());
|
||||
);
|
||||
}
|
||||
|
||||
log::info!("bringing up the interface.");
|
||||
|
@ -647,7 +649,7 @@ fn rename_peer(interface: &InterfaceName, opts: RenamePeerOpts) -> Result<(), Er
|
|||
.filter(|p| p.name == old_name)
|
||||
.map(|p| p.id)
|
||||
.next()
|
||||
.ok_or("Peer not found.")?;
|
||||
.ok_or(anyhow!("Peer not found."))?;
|
||||
|
||||
let _ = api.http_form("PUT", &format!("/admin/peers/{}", id), peer_request)?;
|
||||
log::info!("Peer renamed.");
|
||||
|
@ -687,11 +689,11 @@ fn add_association(interface: &InterfaceName, opts: AddAssociationOpts) -> Resul
|
|||
let cidr1 = cidrs
|
||||
.iter()
|
||||
.find(|c| &c.name == cidr1)
|
||||
.ok_or(format!("can't find cidr '{}'", cidr1))?;
|
||||
.ok_or(anyhow!("can't find cidr '{}'", cidr1))?;
|
||||
let cidr2 = cidrs
|
||||
.iter()
|
||||
.find(|c| &c.name == cidr2)
|
||||
.ok_or(format!("can't find cidr '{}'", cidr2))?;
|
||||
.ok_or(anyhow!("can't find cidr '{}'", cidr2))?;
|
||||
(cidr1, cidr2)
|
||||
} else if let Some((cidr1, cidr2)) = prompts::add_association(&cidrs[..])? {
|
||||
(cidr1, cidr2)
|
||||
|
@ -824,16 +826,18 @@ fn show(
|
|||
let devices = interfaces
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
DataStore::open(&name)
|
||||
.and_then(|store| {
|
||||
Ok((
|
||||
Device::get(&name, network.backend).with_str(name.as_str_lossy())?,
|
||||
store,
|
||||
))
|
||||
})
|
||||
.ok()
|
||||
match DataStore::open(&name) {
|
||||
Ok(store) => {
|
||||
let device = Device::get(&name, network.backend).with_str(name.as_str_lossy());
|
||||
Some(device.map(|device| (device, store)))
|
||||
},
|
||||
// Skip WireGuard interfaces that aren't managed by innernet.
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
|
||||
// Error on interfaces that *are* managed by innernet but are not readable.
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
if devices.is_empty() {
|
||||
log::info!("No innernet networks currently running.");
|
||||
|
@ -846,7 +850,7 @@ fn show(
|
|||
let me = peers
|
||||
.iter()
|
||||
.find(|p| p.public_key == device_info.public_key.as_ref().unwrap().to_base64())
|
||||
.ok_or("missing peer info")?;
|
||||
.ok_or(anyhow!("missing peer info"))?;
|
||||
|
||||
let mut peer_states = device_info
|
||||
.peers
|
||||
|
@ -858,7 +862,7 @@ fn show(
|
|||
peer,
|
||||
info: Some(info),
|
||||
}),
|
||||
None => Err(format!("peer {} isn't an innernet peer.", public_key)),
|
||||
None => Err(anyhow!("peer {} isn't an innernet peer.", public_key)),
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<PeerState>, _>>()?;
|
||||
|
@ -987,6 +991,9 @@ fn main() {
|
|||
if let Err(e) = run(opt) {
|
||||
println!();
|
||||
log::error!("{}\n", e);
|
||||
if let Some(e) = e.downcast_ref::<WrappedIoError>() {
|
||||
util::permissions_helptext(e);
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -998,10 +1005,6 @@ fn run(opt: Opts) -> Result<(), Error> {
|
|||
interface: None,
|
||||
});
|
||||
|
||||
if unsafe { libc::getuid() } != 0 && !matches!(command, Command::Completions { .. }) {
|
||||
return Err("innernet must run as root.".into());
|
||||
}
|
||||
|
||||
match command {
|
||||
Command::Install {
|
||||
invite,
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
use crate::{ClientError, Error};
|
||||
use colored::*;
|
||||
use indoc::eprintdoc;
|
||||
use log::{Level, LevelFilter};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use shared::{interface_config::ServerInfo, INNERNET_PUBKEY_HEADER};
|
||||
use std::time::Duration;
|
||||
use shared::{interface_config::ServerInfo, WrappedIoError, INNERNET_PUBKEY_HEADER};
|
||||
use std::{io, time::Duration};
|
||||
use ureq::{Agent, AgentBuilder};
|
||||
|
||||
static LOGGER: Logger = Logger;
|
||||
struct Logger;
|
||||
|
||||
const BASE_MODULES: &[&str] = &["innernet", "shared"];
|
||||
|
||||
fn target_is_base(target: &str) -> bool {
|
||||
BASE_MODULES
|
||||
.iter()
|
||||
.any(|module| module == &target || target.starts_with(&format!("{}::", module)))
|
||||
}
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||
metadata.level() <= log::max_level()
|
||||
&& (log::max_level() == LevelFilter::Trace
|
||||
|| metadata.target().starts_with("shared::")
|
||||
|| metadata.target() == "innernet")
|
||||
&& (log::max_level() == LevelFilter::Trace || target_is_base(metadata.target()))
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
|
@ -25,7 +33,7 @@ impl log::Log for Logger {
|
|||
Level::Debug => "[D]".blue(),
|
||||
Level::Trace => "[T]".purple(),
|
||||
};
|
||||
if record.level() <= LevelFilter::Debug && record.target() != "innernet" {
|
||||
if record.level() <= LevelFilter::Debug && !target_is_base(record.target()) {
|
||||
println!(
|
||||
"{} {} {}",
|
||||
level_str,
|
||||
|
@ -94,6 +102,41 @@ pub fn human_size(bytes: u64) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn permissions_helptext(e: &WrappedIoError) {
|
||||
if e.raw_os_error() == Some(1) {
|
||||
let current_exe = std::env::current_exe()
|
||||
.ok()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "<innernet path>".into());
|
||||
eprintdoc!(
|
||||
"{}: innernet can't access the device info.
|
||||
|
||||
You either need to run innernet as root, or give innernet CAP_NET_ADMIN capabilities:
|
||||
|
||||
sudo setcap cap_net_admin+eip {}
|
||||
",
|
||||
"ERROR".bold().red(),
|
||||
current_exe
|
||||
);
|
||||
} else if e.kind() == io::ErrorKind::PermissionDenied {
|
||||
eprintdoc!(
|
||||
"{}: innernet can't access its config/data folders.
|
||||
|
||||
You either need to run innernet as root, or give the user/group running innernet permissions
|
||||
to access {config} and {data}.
|
||||
|
||||
For non-root permissions, it's recommended to create an \"innernet\" group, and run for example:
|
||||
|
||||
sudo chgrp -R innernet {config} {data}
|
||||
sudo chmod -R g+rwX {config} {data}
|
||||
",
|
||||
"ERROR".bold().red(),
|
||||
config = shared::CLIENT_CONFIG_DIR.to_string_lossy(),
|
||||
data = shared::CLIENT_DATA_DIR.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Api<'a> {
|
||||
agent: Agent,
|
||||
server: &'a ServerInfo,
|
||||
|
|
|
@ -5,7 +5,6 @@ edition = "2018"
|
|||
license = "UNLICENSED"
|
||||
name = "hostsfile"
|
||||
publish = false
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
|
||||
[dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufRead, BufReader, Write},
|
||||
net::IpAddr,
|
||||
path::{Path, PathBuf},
|
||||
result,
|
||||
};
|
||||
use std::{collections::HashMap, fmt, fs::OpenOptions, io::{self, BufRead, BufReader, ErrorKind, Write}, net::IpAddr, path::{Path, PathBuf}, result};
|
||||
|
||||
pub type Result<T> = result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
|
@ -115,7 +107,12 @@ impl HostsBuilder {
|
|||
|
||||
/// Inserts a new section to the system's default hosts file. If there is a section with the
|
||||
/// same tag name already, it will be replaced with the new list instead.
|
||||
pub fn write(&self) -> Result<()> {
|
||||
pub fn write(&self) -> io::Result<()> {
|
||||
self.write_to(&Self::default_path()?)
|
||||
}
|
||||
|
||||
/// Returns the default hosts path based on the current OS.
|
||||
pub fn default_path() -> io::Result<PathBuf> {
|
||||
let hosts_file = if cfg!(unix) {
|
||||
PathBuf::from("/etc/hosts")
|
||||
} else if cfg!(windows) {
|
||||
|
@ -124,21 +121,24 @@ impl HostsBuilder {
|
|||
// the location depends on the environment variable %WinDir%.
|
||||
format!(
|
||||
"{}\\System32\\Drivers\\Etc\\hosts",
|
||||
std::env::var("WinDir")?
|
||||
std::env::var("WinDir").map_err(|_| io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"WinDir environment variable missing".to_owned()
|
||||
))?
|
||||
),
|
||||
)
|
||||
} else {
|
||||
return Err(Box::new(Error("unsupported operating system.".to_owned())));
|
||||
return Err(io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"unsupported operating system.".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
if !hosts_file.exists() {
|
||||
return Err(Box::new(Error(format!(
|
||||
"hosts file {:?} missing",
|
||||
&hosts_file
|
||||
))));
|
||||
return Err(ErrorKind::NotFound.into());
|
||||
}
|
||||
|
||||
self.write_to(&hosts_file)
|
||||
Ok(hosts_file)
|
||||
}
|
||||
|
||||
/// Inserts a new section to the specified hosts file. If there is a section with the same tag
|
||||
|
@ -146,7 +146,7 @@ impl HostsBuilder {
|
|||
///
|
||||
/// On Windows, the format of one hostname per line will be used, all other systems will use
|
||||
/// the same format as Unix and Unix-like systems (i.e. allow multiple hostnames per line).
|
||||
pub fn write_to<P: AsRef<Path>>(&self, hosts_path: P) -> Result<()> {
|
||||
pub fn write_to<P: AsRef<Path>>(&self, hosts_path: P) -> io::Result<()> {
|
||||
let hosts_path = hosts_path.as_ref();
|
||||
let begin_marker = format!("# DO NOT EDIT {} BEGIN", &self.tag);
|
||||
let end_marker = format!("# DO NOT EDIT {} END", &self.tag);
|
||||
|
@ -179,49 +179,44 @@ impl HostsBuilder {
|
|||
lines.len()
|
||||
},
|
||||
_ => {
|
||||
return Err(Box::new(Error(format!(
|
||||
"start or end marker missing in {:?}",
|
||||
&hosts_path
|
||||
))));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("start or end marker missing in {:?}", &hosts_path),
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
// The tempfile should be in the same filesystem as the hosts file.
|
||||
let hosts_dir = hosts_path
|
||||
.parent()
|
||||
.expect("hosts file must be an absolute file path");
|
||||
let temp_dir = tempfile::Builder::new().tempdir_in(hosts_dir)?;
|
||||
let temp_path = temp_dir.path().join("hosts");
|
||||
|
||||
// Copy the existing hosts file to preserve permissions.
|
||||
fs::copy(&hosts_path, &temp_path)?;
|
||||
|
||||
let mut file = File::create(&temp_path)?;
|
||||
let mut s = vec![];
|
||||
|
||||
for line in &lines[..insert] {
|
||||
writeln!(&mut file, "{}", line)?;
|
||||
writeln!(&mut s, "{}", line)?;
|
||||
}
|
||||
if !self.hostname_map.is_empty() {
|
||||
writeln!(&mut file, "{}", begin_marker)?;
|
||||
writeln!(&mut s, "{}", begin_marker)?;
|
||||
for (ip, hostnames) in &self.hostname_map {
|
||||
if cfg!(windows) {
|
||||
// windows only allows one hostname per line
|
||||
for hostname in hostnames {
|
||||
writeln!(&mut file, "{} {}", ip, hostname)?;
|
||||
writeln!(&mut s, "{} {}", ip, hostname)?;
|
||||
}
|
||||
} else {
|
||||
// assume the same format as Unix
|
||||
writeln!(&mut file, "{} {}", ip, hostnames.join(" "))?;
|
||||
writeln!(&mut s, "{} {}", ip, hostnames.join(" "))?;
|
||||
}
|
||||
}
|
||||
writeln!(&mut file, "{}", end_marker)?;
|
||||
writeln!(&mut s, "{}", end_marker)?;
|
||||
}
|
||||
for line in &lines[insert..] {
|
||||
writeln!(&mut file, "{}", line)?;
|
||||
writeln!(&mut s, "{}", line)?;
|
||||
}
|
||||
|
||||
// Move the file atomically to avoid a partial state.
|
||||
fs::rename(&temp_path, &hosts_path)?;
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(hosts_path)?
|
||||
.write_all(&s)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ name = "innernet-server"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
bytes = "1"
|
||||
colored = "2"
|
||||
dialoguer = "0.8"
|
||||
|
|
|
@ -229,7 +229,7 @@ mod tests {
|
|||
let old_peer = DatabasePeer::get(&server.db.lock(), test::DEVELOPER1_PEER_ID)?;
|
||||
|
||||
let change = PeerContents {
|
||||
name: "new-peer-name".parse()?,
|
||||
name: "new-peer-name".parse().unwrap(),
|
||||
..old_peer.contents.clone()
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::*;
|
||||
use anyhow::anyhow;
|
||||
use db::DatabaseCidr;
|
||||
use dialoguer::{theme::ColorfulTheme, Input};
|
||||
use indoc::printdoc;
|
||||
|
@ -63,7 +64,7 @@ fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(),
|
|||
parent: None,
|
||||
},
|
||||
)
|
||||
.map_err(|_| "failed to create root CIDR".to_string())?;
|
||||
.map_err(|_| anyhow!("failed to create root CIDR"))?;
|
||||
|
||||
let server_cidr = DatabaseCidr::create(
|
||||
&conn,
|
||||
|
@ -73,12 +74,12 @@ fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(),
|
|||
parent: Some(root_cidr.id),
|
||||
},
|
||||
)
|
||||
.map_err(|_| "failed to create innernet-server CIDR".to_string())?;
|
||||
.map_err(|_| anyhow!("failed to create innernet-server CIDR"))?;
|
||||
|
||||
let _me = DatabasePeer::create(
|
||||
&conn,
|
||||
PeerContents {
|
||||
name: SERVER_NAME.parse()?,
|
||||
name: SERVER_NAME.parse().map_err(|e: &str| anyhow!(e))?,
|
||||
ip: db_init_data.our_ip,
|
||||
cidr_id: server_cidr.id,
|
||||
public_key: db_init_data.public_key_base64,
|
||||
|
@ -90,7 +91,7 @@ fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(),
|
|||
invite_expires: None,
|
||||
},
|
||||
)
|
||||
.map_err(|_| "failed to create innernet peer.".to_string())?;
|
||||
.map_err(|_| anyhow!("failed to create innernet peer."))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -99,7 +100,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro
|
|||
let theme = ColorfulTheme::default();
|
||||
|
||||
shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(|_| {
|
||||
format!(
|
||||
anyhow!(
|
||||
"Failed to create config and database directories {}",
|
||||
"(are you not running as root?)".bold()
|
||||
)
|
||||
|
@ -139,7 +140,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro
|
|||
let endpoint: Endpoint = if let Some(endpoint) = opts.external_endpoint {
|
||||
endpoint
|
||||
} else if opts.auto_external_endpoint {
|
||||
let ip = publicip::get_any(Preference::Ipv4).ok_or("couldn't get external IP")?;
|
||||
let ip = publicip::get_any(Preference::Ipv4).ok_or(anyhow!("couldn't get external IP"))?;
|
||||
SocketAddr::new(ip, 51820).into()
|
||||
} else {
|
||||
prompts::ask_endpoint()?
|
||||
|
@ -152,7 +153,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro
|
|||
.with_prompt("Listen port")
|
||||
.default(51820)
|
||||
.interact()
|
||||
.map_err(|_| "failed to get listen port.")?
|
||||
.map_err(|_| anyhow!("failed to get listen port."))?
|
||||
};
|
||||
|
||||
let our_ip = root_cidr
|
||||
|
@ -185,7 +186,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro
|
|||
|
||||
let database_path = conf.database_path(&name);
|
||||
let conn = create_database(&database_path).map_err(|_| {
|
||||
format!(
|
||||
anyhow!(
|
||||
"failed to create database {}",
|
||||
"(are you not running as root?)".bold()
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use anyhow::{anyhow, bail};
|
||||
use colored::*;
|
||||
use dialoguer::Confirm;
|
||||
use hyper::{http, server::conn::AddrStream, Body, Request, Response};
|
||||
|
@ -261,14 +262,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
fn open_database_connection(
|
||||
interface: &InterfaceName,
|
||||
conf: &ServerConfig,
|
||||
) -> Result<rusqlite::Connection, Box<dyn std::error::Error>> {
|
||||
) -> Result<rusqlite::Connection, Error> {
|
||||
let database_path = conf.database_path(&interface);
|
||||
if !Path::new(&database_path).exists() {
|
||||
return Err(format!(
|
||||
bail!(
|
||||
"no database file found at {}",
|
||||
database_path.to_string_lossy()
|
||||
)
|
||||
.into());
|
||||
);
|
||||
}
|
||||
|
||||
let conn = Connection::open(&database_path)?;
|
||||
|
@ -337,7 +337,7 @@ fn rename_peer(
|
|||
let mut db_peer = DatabasePeer::list(&conn)?
|
||||
.into_iter()
|
||||
.find(|p| p.name == old_name)
|
||||
.ok_or( "Peer not found.")?;
|
||||
.ok_or(anyhow!("Peer not found."))?;
|
||||
let _peer = db_peer.update(&conn, peer_request)?;
|
||||
} else {
|
||||
println!("exited without creating peer.");
|
||||
|
|
|
@ -221,7 +221,7 @@ pub fn peer_contents(
|
|||
let public_key = KeyPair::generate().public;
|
||||
|
||||
Ok(PeerContents {
|
||||
name: name.parse()?,
|
||||
name: name.parse().map_err(|e: &str| anyhow!(e))?,
|
||||
ip: ip_str.parse()?,
|
||||
cidr_id,
|
||||
public_key: public_key.to_base64(),
|
||||
|
|
|
@ -7,11 +7,13 @@ publish = false
|
|||
version = "1.3.1"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
colored = "2.0"
|
||||
dialoguer = "0.8"
|
||||
indoc = "1"
|
||||
ipnetwork = { git = "https://github.com/mcginty/ipnetwork" } # pending https://github.com/achanda/ipnetwork/pull/129
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
publicip = { path = "../publicip" }
|
||||
regex = "1"
|
||||
|
@ -20,3 +22,9 @@ structopt = "0.3"
|
|||
toml = "0.5"
|
||||
url = "2"
|
||||
wgctrl = { path = "../wgctrl-rs" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
netlink-sys = "0.6"
|
||||
netlink-packet-core = "0.2"
|
||||
netlink-packet-route = "0.7"
|
||||
wgctrl-sys = { path = "../wgctrl-sys" }
|
||||
|
|
|
@ -116,14 +116,7 @@ impl InterfaceConfig {
|
|||
|
||||
pub fn from_interface(interface: &InterfaceName) -> Result<Self, Error> {
|
||||
let path = Self::build_config_file_path(interface)?;
|
||||
let file = File::open(&path).with_path(&path)?;
|
||||
if crate::chmod(&file, 0o600)? {
|
||||
println!(
|
||||
"{} updated permissions for {} to 0600.",
|
||||
"[!]".yellow(),
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
crate::warn_on_dangerous_mode(&path).with_path(&path)?;
|
||||
Self::from_file(path)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use colored::*;
|
||||
pub use anyhow::Error;
|
||||
use lazy_static::lazy_static;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
|
@ -9,6 +9,8 @@ use std::{
|
|||
};
|
||||
|
||||
pub mod interface_config;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod netlink;
|
||||
pub mod prompts;
|
||||
pub mod types;
|
||||
pub mod wg;
|
||||
|
@ -26,8 +28,6 @@ lazy_static! {
|
|||
pub const PERSISTENT_KEEPALIVE_INTERVAL_SECS: u16 = 25;
|
||||
pub const INNERNET_PUBKEY_HEADER: &str = "X-Innernet-Server-Key";
|
||||
|
||||
pub type Error = Box<dyn std::error::Error>;
|
||||
|
||||
pub fn ensure_dirs_exist(dirs: &[&Path]) -> Result<(), WrappedIoError> {
|
||||
for dir in dirs {
|
||||
match fs::create_dir(dir).with_path(dir) {
|
||||
|
@ -35,20 +35,29 @@ pub fn ensure_dirs_exist(dirs: &[&Path]) -> Result<(), WrappedIoError> {
|
|||
return Err(e);
|
||||
},
|
||||
_ => {
|
||||
let target_file = File::open(dir).with_path(dir)?;
|
||||
if chmod(&target_file, 0o700).with_path(dir)? {
|
||||
println!(
|
||||
"{} updated permissions for {} to 0700.",
|
||||
"[!]".yellow(),
|
||||
dir.display()
|
||||
);
|
||||
}
|
||||
warn_on_dangerous_mode(dir).with_path(dir)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn warn_on_dangerous_mode(path: &Path) -> Result<(), io::Error> {
|
||||
let file = File::open(path)?;
|
||||
let metadata = file.metadata()?;
|
||||
let permissions = metadata.permissions();
|
||||
let mode = permissions.mode() & 0o777;
|
||||
|
||||
if mode & 0o007 != 0 {
|
||||
log::warn!(
|
||||
"{} is world-accessible (mode is {:#05o}). This is probably not what you want.",
|
||||
path.to_string_lossy(),
|
||||
mode
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the permissions of a file or directory. Returns `Ok(true)` if
|
||||
/// permissions had to be changed, `Ok(false)` if permissions were already
|
||||
/// correct.
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
use crate::Error;
|
||||
use anyhow::anyhow;
|
||||
use ipnetwork::IpNetwork;
|
||||
use netlink_packet_core::{
|
||||
NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST,
|
||||
};
|
||||
use netlink_packet_route::{
|
||||
address, constants::*, link, route, AddressHeader, AddressMessage, LinkHeader, LinkMessage,
|
||||
RouteHeader, RouteMessage, RtnlMessage, RTN_UNICAST, RT_SCOPE_LINK, RT_TABLE_MAIN,
|
||||
};
|
||||
use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr};
|
||||
use std::io;
|
||||
use wgctrl::InterfaceName;
|
||||
|
||||
fn if_nametoindex(interface: &InterfaceName) -> Result<u32, Error> {
|
||||
match unsafe { libc::if_nametoindex(interface.as_ptr()) } {
|
||||
0 => Err(anyhow!("couldn't find interface '{}'.", interface)),
|
||||
index => Ok(index),
|
||||
}
|
||||
}
|
||||
|
||||
fn netlink_call(
|
||||
message: RtnlMessage,
|
||||
flags: Option<u16>,
|
||||
) -> Result<NetlinkMessage<RtnlMessage>, io::Error> {
|
||||
let mut req = NetlinkMessage::from(message);
|
||||
req.header.flags = flags.unwrap_or(NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
|
||||
req.finalize();
|
||||
let mut buf = [0; 4096];
|
||||
req.serialize(&mut buf);
|
||||
let len = req.buffer_len();
|
||||
|
||||
log::debug!("netlink request: {:?}", req);
|
||||
let socket = Socket::new(NETLINK_ROUTE).unwrap();
|
||||
let kernel_addr = SocketAddr::new(0, 0);
|
||||
socket.connect(&kernel_addr)?;
|
||||
let n_sent = socket.send(&buf[..len], 0).unwrap();
|
||||
if n_sent != len {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"failed to send netlink request",
|
||||
));
|
||||
}
|
||||
|
||||
let n_received = socket.recv(&mut buf[..], 0).unwrap();
|
||||
let response = NetlinkMessage::<RtnlMessage>::deserialize(&buf[..n_received])
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
log::trace!("netlink response: {:?}", response);
|
||||
if let NetlinkPayload::Error(e) = response.payload {
|
||||
return Err(e.to_io());
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), Error> {
|
||||
let index = if_nametoindex(interface)?;
|
||||
let message = LinkMessage {
|
||||
header: LinkHeader {
|
||||
index,
|
||||
flags: IFF_UP,
|
||||
..Default::default()
|
||||
},
|
||||
nlas: vec![link::nlas::Nla::Mtu(mtu)],
|
||||
};
|
||||
netlink_call(RtnlMessage::SetLink(message), None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error> {
|
||||
let index = if_nametoindex(interface)?;
|
||||
let (family, nlas) = match addr {
|
||||
IpNetwork::V4(network) => {
|
||||
let addr_bytes = network.ip().octets().to_vec();
|
||||
(
|
||||
AF_INET as u8,
|
||||
vec![
|
||||
address::Nla::Local(addr_bytes.clone()),
|
||||
address::Nla::Address(addr_bytes),
|
||||
],
|
||||
)
|
||||
},
|
||||
IpNetwork::V6(network) => (
|
||||
AF_INET6 as u8,
|
||||
vec![address::Nla::Address(network.ip().octets().to_vec())],
|
||||
),
|
||||
};
|
||||
let message = AddressMessage {
|
||||
header: AddressHeader {
|
||||
index,
|
||||
family,
|
||||
prefix_len: addr.prefix(),
|
||||
scope: RT_SCOPE_UNIVERSE,
|
||||
..Default::default()
|
||||
},
|
||||
nlas,
|
||||
};
|
||||
netlink_call(
|
||||
RtnlMessage::NewAddress(message),
|
||||
Some(NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE | NLM_F_CREATE),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, Error> {
|
||||
let if_index = if_nametoindex(interface)?;
|
||||
let (address_family, dst) = match cidr {
|
||||
IpNetwork::V4(network) => (AF_INET as u8, network.network().octets().to_vec()),
|
||||
IpNetwork::V6(network) => (AF_INET6 as u8, network.network().octets().to_vec()),
|
||||
};
|
||||
let message = RouteMessage {
|
||||
header: RouteHeader {
|
||||
table: RT_TABLE_MAIN,
|
||||
protocol: RTPROT_BOOT,
|
||||
scope: RT_SCOPE_LINK,
|
||||
kind: RTN_UNICAST,
|
||||
destination_prefix_length: cidr.prefix(),
|
||||
address_family,
|
||||
..Default::default()
|
||||
},
|
||||
nlas: vec![route::Nla::Destination(dst), route::Nla::Oif(if_index)],
|
||||
};
|
||||
|
||||
match netlink_call(RtnlMessage::NewRoute(message), None) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(false),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint,
|
||||
Error, Hostname, Peer, PeerContents, RenamePeerOpts, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use colored::*;
|
||||
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
|
||||
use ipnetwork::IpNetwork;
|
||||
|
@ -21,7 +22,7 @@ pub fn add_cidr(cidrs: &[Cidr], request: &AddCidrOpts) -> Result<Option<CidrCont
|
|||
cidrs
|
||||
.iter()
|
||||
.find(|cidr| &cidr.name == parent_name)
|
||||
.ok_or("No parent CIDR with that name exists.")?
|
||||
.ok_or(anyhow!("No parent CIDR with that name exists."))?
|
||||
} else {
|
||||
choose_cidr(cidrs, "Parent CIDR")?
|
||||
};
|
||||
|
@ -74,7 +75,7 @@ pub fn delete_cidr(cidrs: &[Cidr], peers: &[Peer], request: &DeleteCidrOpts) ->
|
|||
cidrs
|
||||
.iter()
|
||||
.find(|cidr| &cidr.name == name)
|
||||
.ok_or_else(|| format!("CIDR {} doesn't exist or isn't eligible for deletion", name))?
|
||||
.ok_or_else(|| anyhow!("CIDR {} doesn't exist or isn't eligible for deletion", name))?
|
||||
} else {
|
||||
let cidr_index = Select::with_theme(&*THEME)
|
||||
.with_prompt("Delete CIDR")
|
||||
|
@ -92,7 +93,7 @@ pub fn delete_cidr(cidrs: &[Cidr], peers: &[Peer], request: &DeleteCidrOpts) ->
|
|||
{
|
||||
Ok(cidr.id)
|
||||
} else {
|
||||
Err("Canceled".into())
|
||||
Err(anyhow!("Canceled"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,7 +194,7 @@ pub fn add_peer(
|
|||
leaves
|
||||
.iter()
|
||||
.find(|cidr| &cidr.name == parent_name)
|
||||
.ok_or("No eligible CIDR with that name exists.")?
|
||||
.ok_or(anyhow!("No eligible CIDR with that name exists."))?
|
||||
} else {
|
||||
choose_cidr(&leaves[..], "Eligible CIDRs for peer")?
|
||||
};
|
||||
|
@ -241,7 +242,7 @@ pub fn add_peer(
|
|||
} else {
|
||||
Input::with_theme(&*THEME)
|
||||
.with_prompt("Invite expires after")
|
||||
.default("14d".parse()?)
|
||||
.default("14d".parse().map_err(|s: &str| anyhow!(s))?)
|
||||
.interact()?
|
||||
};
|
||||
|
||||
|
@ -287,7 +288,7 @@ pub fn rename_peer(
|
|||
eligible_peers
|
||||
.into_iter()
|
||||
.find(|p| &p.name == name)
|
||||
.ok_or_else(|| format!("Peer '{}' does not exist", name))?
|
||||
.ok_or_else(|| anyhow!("Peer '{}' does not exist", name))?
|
||||
.clone()
|
||||
} else {
|
||||
let peer_index = Select::with_theme(&*THEME)
|
||||
|
|
|
@ -4,6 +4,7 @@ use regex::Regex;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io,
|
||||
net::{IpAddr, SocketAddr, ToSocketAddrs},
|
||||
ops::Deref,
|
||||
path::Path,
|
||||
|
@ -121,14 +122,14 @@ impl fmt::Display for Endpoint {
|
|||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub fn resolve(&self) -> Result<SocketAddr, String> {
|
||||
let mut addrs = self
|
||||
.to_string()
|
||||
.to_socket_addrs()
|
||||
.map_err(|e| e.to_string())?;
|
||||
addrs
|
||||
.next()
|
||||
.ok_or_else(|| "failed to resolve address".to_string())
|
||||
pub fn resolve(&self) -> Result<SocketAddr, io::Error> {
|
||||
let mut addrs = self.to_string().to_socket_addrs()?;
|
||||
addrs.next().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::AddrNotAvailable,
|
||||
"failed to resolve address".to_string(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
106
shared/src/wg.rs
106
shared/src/wg.rs
|
@ -1,13 +1,11 @@
|
|||
use crate::{Error, IoErrorContext, NetworkOpt};
|
||||
use ipnetwork::IpNetwork;
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
process::{self, Command},
|
||||
};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
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()?;
|
||||
#[cfg(target_os = "macos")]
|
||||
fn cmd(bin: &str, args: &[&str]) -> Result<std::process::Output, Error> {
|
||||
let output = std::process::Command::new(bin).args(args).output()?;
|
||||
log::debug!("cmd: {} {}", bin, args.join(" "));
|
||||
log::debug!("status: {:?}", output.status.code());
|
||||
log::trace!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||
|
@ -15,13 +13,12 @@ fn cmd(bin: &str, args: &[&str]) -> Result<process::Output, Error> {
|
|||
if output.status.success() {
|
||||
Ok(output)
|
||||
} else {
|
||||
Err(format!(
|
||||
Err(anyhow::anyhow!(
|
||||
"failed to run {} {} command: {}",
|
||||
bin,
|
||||
args.join(" "),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
)
|
||||
.into())
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,30 +37,30 @@ pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error>
|
|||
&addr.ip().to_string(),
|
||||
"alias",
|
||||
],
|
||||
)?;
|
||||
)
|
||||
.map(|_output| ())
|
||||
} else {
|
||||
cmd(
|
||||
"ifconfig",
|
||||
&[&real_interface, "inet6", &addr.to_string(), "alias"],
|
||||
)?;
|
||||
)
|
||||
.map(|_output| ())
|
||||
}
|
||||
cmd("ifconfig", &[&real_interface, "mtu", "1420"])?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), Error> {
|
||||
let real_interface =
|
||||
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
|
||||
cmd("ifconfig", &[&real_interface, "mtu", &mtu.to_string()])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), Error> {
|
||||
let interface = interface.to_string();
|
||||
cmd(
|
||||
"ip",
|
||||
&["address", "replace", &addr.to_string(), "dev", &interface],
|
||||
)?;
|
||||
cmd(
|
||||
"ip",
|
||||
&["link", "set", "mtu", "1420", "up", "dev", &interface],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
pub use super::netlink::set_addr;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use super::netlink::set_up;
|
||||
|
||||
pub fn up(
|
||||
interface: &InterfaceName,
|
||||
|
@ -88,6 +85,7 @@ pub fn up(
|
|||
.set_private_key(wgctrl::Key::from_base64(&private_key).unwrap())
|
||||
.apply(interface, network.backend)?;
|
||||
set_addr(interface, address)?;
|
||||
set_up(interface, 1420)?;
|
||||
if !network.no_routing {
|
||||
add_route(interface, address)?;
|
||||
}
|
||||
|
@ -120,43 +118,31 @@ pub fn down(interface: &InterfaceName, backend: Backend) -> Result<(), Error> {
|
|||
/// Add a route in the OS's routing table to get traffic flowing through this interface.
|
||||
/// Returns an error if the process doesn't exit successfully, otherwise returns
|
||||
/// true if the route was changed, false if the route already exists.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result<bool, Error> {
|
||||
if cfg!(target_os = "macos") {
|
||||
let real_interface =
|
||||
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
|
||||
let output = cmd(
|
||||
"route",
|
||||
&[
|
||||
"-n",
|
||||
"add",
|
||||
if cidr.is_ipv4() { "-inet" } else { "-inet6" },
|
||||
&cidr.to_string(),
|
||||
"-interface",
|
||||
&real_interface,
|
||||
],
|
||||
)?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if !output.status.success() {
|
||||
Err(format!(
|
||||
"failed to add route for device {} ({}): {}",
|
||||
&interface, real_interface, stderr
|
||||
)
|
||||
.into())
|
||||
} else {
|
||||
Ok(!stderr.contains("File exists"))
|
||||
}
|
||||
let real_interface =
|
||||
wgctrl::backends::userspace::resolve_tun(interface).with_str(interface.to_string())?;
|
||||
let output = cmd(
|
||||
"route",
|
||||
&[
|
||||
"-n",
|
||||
"add",
|
||||
if cidr.is_ipv4() { "-inet" } else { "-inet6" },
|
||||
&cidr.to_string(),
|
||||
"-interface",
|
||||
&real_interface,
|
||||
],
|
||||
)?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if !output.status.success() {
|
||||
Err(anyhow::anyhow!(
|
||||
"failed to add route for device {} ({}): {}",
|
||||
&interface, real_interface, stderr
|
||||
))
|
||||
} else {
|
||||
// TODO(mcginty): use the netlink interface on linux to modify routing table.
|
||||
let _ = cmd(
|
||||
"ip",
|
||||
&[
|
||||
"route",
|
||||
"add",
|
||||
&IpNetwork::new(cidr.network(), cidr.prefix())?.to_string(),
|
||||
"dev",
|
||||
&interface.to_string(),
|
||||
],
|
||||
);
|
||||
Ok(false)
|
||||
Ok(!stderr.contains("File exists"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use super::netlink::add_route;
|
||||
|
|
|
@ -165,7 +165,7 @@ impl InterfaceName {
|
|||
|
||||
#[cfg(target_os = "linux")]
|
||||
/// Returns a pointer to the inner byte buffer for FFI calls.
|
||||
pub(crate) fn as_ptr(&self) -> *const c_char {
|
||||
pub fn as_ptr(&self) -> *const c_char {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue