vpncloud/src/main.rs

293 lines
10 KiB
Rust
Raw Normal View History

// VpnCloud - Peer-to-Peer VPN
2020-05-28 07:03:48 +00:00
// Copyright (C) 2015-2020 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md)
#![cfg_attr(feature = "bench", feature(test))]
2016-06-26 17:18:38 +00:00
2015-11-19 15:34:20 +00:00
#[macro_use] extern crate log;
#[macro_use] extern crate serde_derive;
2019-03-01 22:25:42 +00:00
2019-02-21 15:57:25 +00:00
#[cfg(test)] extern crate tempfile;
#[cfg(feature = "bench")] extern crate test;
2015-11-19 15:34:20 +00:00
2019-12-04 08:32:35 +00:00
#[macro_use]
pub mod util;
#[cfg(test)]
#[macro_use]
mod tests;
pub mod beacon;
pub mod cloud;
pub mod config;
2016-06-27 13:43:30 +00:00
pub mod crypto;
2019-12-04 08:32:35 +00:00
pub mod device;
2020-09-24 17:48:13 +00:00
pub mod error;
pub mod messages;
2019-12-04 08:32:35 +00:00
pub mod net;
2020-10-29 21:12:33 +00:00
pub mod oldconfig;
2020-09-24 17:48:13 +00:00
pub mod payload;
2016-06-30 08:05:37 +00:00
pub mod poll;
pub mod port_forwarding;
2020-09-24 17:48:13 +00:00
pub mod table;
2019-01-09 16:45:12 +00:00
pub mod traffic;
2019-12-04 08:32:35 +00:00
pub mod types;
2015-11-19 15:34:20 +00:00
2020-05-29 06:37:29 +00:00
use structopt::StructOpt;
2015-11-19 15:34:20 +00:00
2019-12-04 08:32:35 +00:00
use std::{
fs::{self, File, Permissions},
2019-12-04 08:32:35 +00:00
io::{self, Write},
2020-09-24 17:48:13 +00:00
net::{Ipv4Addr, UdpSocket},
os::unix::fs::PermissionsExt,
2019-12-04 08:32:35 +00:00
path::Path,
process::Command,
str::FromStr,
2020-06-03 13:49:06 +00:00
sync::Mutex,
thread
2019-12-04 08:32:35 +00:00
};
2015-11-20 17:40:23 +00:00
2019-12-04 08:32:35 +00:00
use crate::{
cloud::GenericCloud,
2020-09-24 17:48:13 +00:00
config::{Args, Config},
crypto::Crypto,
2019-12-04 08:32:35 +00:00
device::{Device, TunTapDevice, Type},
2020-10-29 21:12:33 +00:00
oldconfig::OldConfigFile,
2020-09-24 17:48:13 +00:00
payload::Protocol,
2020-10-29 21:12:33 +00:00
port_forwarding::PortForwarding,
2020-09-24 17:48:13 +00:00
util::SystemTimeSource
2019-12-04 08:32:35 +00:00
};
2016-11-23 14:21:22 +00:00
struct DualLogger {
2020-10-25 20:48:43 +00:00
file: Option<Mutex<File>>
2016-11-23 14:21:22 +00:00
}
impl DualLogger {
pub fn new<P: AsRef<Path>>(path: Option<P>) -> Result<Self, io::Error> {
if let Some(path) = path {
2019-12-19 15:08:51 +00:00
let path = path.as_ref();
if path.exists() {
fs::remove_file(path)?
}
2019-03-01 22:12:19 +00:00
let file = File::create(path)?;
2020-10-25 20:48:43 +00:00
Ok(DualLogger { file: Some(Mutex::new(file)) })
2016-11-23 14:21:22 +00:00
} else {
2020-10-25 20:48:43 +00:00
Ok(DualLogger { file: None })
2016-11-23 14:21:22 +00:00
}
}
}
2015-11-19 15:34:20 +00:00
2016-11-23 14:21:22 +00:00
impl log::Log for DualLogger {
2016-06-11 14:08:57 +00:00
#[inline]
2019-01-01 23:35:14 +00:00
fn enabled(&self, _metadata: &log::Metadata) -> bool {
2015-11-19 15:34:20 +00:00
true
}
2016-06-11 14:08:57 +00:00
#[inline]
2019-01-01 23:35:14 +00:00
fn log(&self, record: &log::Record) {
2015-11-19 15:34:20 +00:00
if self.enabled(record.metadata()) {
2016-11-23 14:21:22 +00:00
println!("{} - {}", record.level(), record.args());
2020-10-25 20:48:43 +00:00
if let Some(ref file) = self.file {
let mut file = file.lock().expect("Lock poisoned");
2020-11-07 11:04:25 +00:00
let time = time::OffsetDateTime::now_local().format("%F %H:%M:%S");
2019-12-04 08:32:35 +00:00
writeln!(file, "{} - {} - {}", time, record.level(), record.args())
.expect("Failed to write to logfile");
2016-11-23 14:21:22 +00:00
}
2015-11-19 15:34:20 +00:00
}
}
2019-01-01 23:35:14 +00:00
#[inline]
fn flush(&self) {
2020-10-25 20:48:43 +00:00
if let Some(ref file) = self.file {
let mut file = file.lock().expect("Lock poisoned");
2019-01-01 23:35:14 +00:00
try_fail!(file.flush(), "Logging error: {}");
}
}
2015-11-19 15:34:20 +00:00
}
2017-05-04 05:26:21 +00:00
fn run_script(script: &str, ifname: &str) {
2015-11-23 18:06:25 +00:00
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(&script).env("IFNAME", ifname);
debug!("Running script: {:?}", cmd);
match cmd.status() {
2019-12-04 08:32:35 +00:00
Ok(status) => {
if !status.success() {
error!("Script returned with error: {:?}", status.code())
}
}
2015-11-23 18:06:25 +00:00
Err(e) => error!("Failed to execute script {:?}: {}", script, e)
}
2015-11-19 15:34:20 +00:00
}
2020-09-24 17:48:13 +00:00
fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> {
let (ip_str, len_str) = match addr.find('/') {
Some(pos) => (&addr[..pos], &addr[pos + 1..]),
None => (addr, "24")
};
let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?;
if prefix_len > 32 {
return Err(format!("Invalid prefix length: {}", prefix_len))
}
let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?;
let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap());
Ok((ip, netmask))
}
2020-09-24 17:48:13 +00:00
fn setup_device(config: &Config) -> TunTapDevice {
let device = try_fail!(
TunTapDevice::new(&config.device_name, config.device_type, config.device_path.as_ref().map(|s| s as &str)),
"Failed to open virtual {} interface {}: {}",
config.device_type,
config.device_name
);
info!("Opened device {}", device.ifname());
if let Err(err) = device.set_mtu(None) {
error!("Error setting optimal MTU on {}: {}", device.ifname(), err);
}
2020-09-24 17:48:13 +00:00
if let Some(ip) = &config.ip {
let (ip, netmask) = try_fail!(parse_ip_netmask(ip), "Invalid ip address given: {}");
info!("Configuring device with ip {}, netmask {}", ip, netmask);
try_fail!(device.configure(ip, netmask), "Failed to configure device: {}");
}
2020-09-24 17:48:13 +00:00
if let Some(script) = &config.ifup {
run_script(script, device.ifname());
}
2020-09-24 17:48:13 +00:00
if config.fix_rp_filter {
try_fail!(device.fix_rp_filter(), "Failed to change rp_filter settings: {}");
}
2020-09-24 17:48:13 +00:00
if let Ok(val) = device.get_rp_filter() {
if val != 1 {
2020-10-31 09:12:19 +00:00
warn!("Your networking configuration might be affected by a vulnerability (https://vpncloud.ddswd.de/docs/security/cve-2019-14899/), please change your rp_filter setting to 1 (currently {}).", val);
}
}
2020-09-24 17:48:13 +00:00
device
}
2019-12-20 12:54:58 +00:00
#[allow(clippy::cognitive_complexity)]
2019-12-04 08:32:35 +00:00
fn run<P: Protocol>(config: Config) {
2020-09-24 17:48:13 +00:00
let device = setup_device(&config);
let port_forwarding = if config.port_forwarding { PortForwarding::new(config.listen.port()) } else { None };
let stats_file = match config.stats_file {
None => None,
Some(ref name) => {
2019-12-19 15:08:51 +00:00
let path = Path::new(name);
if path.exists() {
try_fail!(fs::remove_file(path), "Failed to remove file {}: {}", name);
}
let file = try_fail!(File::create(name), "Failed to create stats file: {}");
try_fail!(
fs::set_permissions(name, Permissions::from_mode(0o644)),
"Failed to set permissions on stats file: {}"
);
Some(file)
}
};
let mut cloud =
2020-09-24 17:48:13 +00:00
GenericCloud::<TunTapDevice, P, UdpSocket, SystemTimeSource>::new(&config, device, port_forwarding, stats_file);
for addr in config.peers {
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
cloud.add_reconnect_peer(addr);
}
2016-11-23 14:21:22 +00:00
if config.daemonize {
info!("Running process as daemon");
let mut daemonize = daemonize::Daemonize::new();
if let Some(user) = config.user {
daemonize = daemonize.user(&user as &str);
}
if let Some(group) = config.group {
daemonize = daemonize.group(&group as &str);
}
if let Some(pid_file) = config.pid_file {
daemonize = daemonize.pid_file(pid_file).chown_pid_file(true);
2020-06-03 13:49:06 +00:00
// Give child process some time to write PID file
daemonize = daemonize.exit_action(|| thread::sleep(std::time::Duration::from_millis(10)));
2016-11-23 14:21:22 +00:00
}
try_fail!(daemonize.start(), "Failed to daemonize: {}");
} else if config.user.is_some() || config.group.is_some() {
info!("Dropping privileges");
let mut pd = privdrop::PrivDrop::default();
if let Some(user) = config.user {
pd = pd.user(user);
}
if let Some(group) = config.group {
pd = pd.group(group);
}
try_fail!(pd.apply(), "Failed to drop privileges: {}");
2016-11-23 14:21:22 +00:00
}
cloud.run();
if let Some(script) = config.ifdown {
2017-05-04 05:26:21 +00:00
run_script(&script, cloud.ifname());
}
}
fn main() {
2020-05-29 06:37:29 +00:00
let args: Args = Args::from_args();
if args.version {
2020-09-24 17:48:13 +00:00
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
return
}
if args.genkey {
2020-09-28 10:50:08 +00:00
let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref());
2020-09-24 17:48:13 +00:00
println!("Private key: {}\nPublic key: {}\n", privkey, pubkey);
println!(
"Attention: Keep the private key secret and use only the public key on other nodes to establish trust."
);
2019-12-04 08:32:35 +00:00
return
}
2020-05-29 06:37:29 +00:00
let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}");
2019-01-01 23:35:14 +00:00
log::set_boxed_logger(Box::new(logger)).unwrap();
2020-05-29 06:37:29 +00:00
assert!(!args.verbose || !args.quiet);
log::set_max_level(if args.verbose {
2019-12-04 08:32:35 +00:00
log::LevelFilter::Debug
2020-05-29 06:37:29 +00:00
} else if args.quiet {
2019-12-04 08:32:35 +00:00
log::LevelFilter::Error
} else {
log::LevelFilter::Info
});
2020-10-29 21:12:33 +00:00
if args.migrate_config {
let file = args.config.unwrap();
info!("Trying to convert from old config format");
let f = try_fail!(File::open(&file), "Failed to open config file: {:?}");
let config_file_old: OldConfigFile =
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
let new_config = config_file_old.convert();
info!("Successfully converted from old format");
info!("Renaming original file to {}.orig", file);
try_fail!(fs::rename(&file, format!("{}.orig", file)), "Failed to rename original file: {:?}");
info!("Writing new config back into {}", file);
let f = try_fail!(File::create(&file), "Failed to open config file: {:?}");
2020-10-31 09:12:19 +00:00
try_fail!(
fs::set_permissions(&file, fs::Permissions::from_mode(0o600)),
"Failed to set permissions on file: {:?}"
);
2020-10-29 21:12:33 +00:00
try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
return
}
let mut config = Config::default();
2020-05-29 06:37:29 +00:00
if let Some(ref file) = args.config {
info!("Reading config file '{}'", file);
let f = try_fail!(File::open(file), "Failed to open config file: {:?}");
2020-10-29 21:12:33 +00:00
let config_file = match serde_yaml::from_reader(f) {
Ok(config) => config,
Err(err) => {
error!("Failed to read config file: {}", err);
info!("Trying to convert from old config format");
let f = try_fail!(File::open(file), "Failed to open config file: {:?}");
let config_file_old: OldConfigFile =
try_fail!(serde_yaml::from_reader(f), "Config file is neither version 2 nor version 1: {:?}");
let new_config = config_file_old.convert();
info!("Successfully converted from old format, please migrate your config using migrate-config");
new_config
}
};
config.merge_file(config_file)
}
config.merge_args(args);
debug!("Config: {:?}", config);
match config.device_type {
2020-09-24 17:48:13 +00:00
Type::Tap => run::<payload::Frame>(config),
Type::Tun => run::<payload::Packet>(config),
Type::Dummy => run::<payload::Frame>(config)
}
2015-11-22 19:02:02 +00:00
}