2016-02-05 15:58:32 +00:00
|
|
|
// VpnCloud - Peer-to-Peer VPN
|
2019-02-19 21:04:21 +00:00
|
|
|
// Copyright (C) 2015-2019 Dennis Schwerdel
|
2016-02-05 15:58:32 +00:00
|
|
|
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
|
|
|
|
2015-11-25 20:05:11 +00:00
|
|
|
#![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;
|
2017-07-22 14:34:20 +00:00
|
|
|
#[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;
|
2015-11-25 20:05:11 +00:00
|
|
|
#[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;
|
|
|
|
#[cfg(feature = "bench")] mod benches;
|
|
|
|
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;
|
2016-06-27 13:43:30 +00:00
|
|
|
pub mod ethernet;
|
|
|
|
pub mod ip;
|
2019-12-04 08:32:35 +00:00
|
|
|
pub mod net;
|
2016-06-30 08:05:37 +00:00
|
|
|
pub mod poll;
|
2016-08-10 09:34:13 +00:00
|
|
|
pub mod port_forwarding;
|
2019-01-09 16:45:12 +00:00
|
|
|
pub mod traffic;
|
2019-12-04 08:32:35 +00:00
|
|
|
pub mod types;
|
|
|
|
pub mod udpmessage;
|
2015-11-19 15:34:20 +00:00
|
|
|
|
|
|
|
use docopt::Docopt;
|
|
|
|
|
2019-12-04 08:32:35 +00:00
|
|
|
use std::{
|
2019-12-06 08:55:24 +00:00
|
|
|
fs::{self, File, Permissions},
|
2019-12-04 08:32:35 +00:00
|
|
|
io::{self, Write},
|
|
|
|
net::UdpSocket,
|
2019-12-06 08:55:24 +00:00
|
|
|
os::unix::fs::PermissionsExt,
|
2019-12-04 08:32:35 +00:00
|
|
|
path::Path,
|
|
|
|
process::Command,
|
|
|
|
str::FromStr,
|
|
|
|
sync::Mutex
|
|
|
|
};
|
2015-11-20 17:40:23 +00:00
|
|
|
|
2019-12-04 08:32:35 +00:00
|
|
|
use crate::{
|
|
|
|
cloud::GenericCloud,
|
|
|
|
config::Config,
|
|
|
|
crypto::{Crypto, CryptoMethod},
|
|
|
|
device::{Device, TunTapDevice, Type},
|
|
|
|
ethernet::SwitchTable,
|
|
|
|
ip::RoutingTable,
|
|
|
|
port_forwarding::PortForwarding,
|
|
|
|
types::{Error, HeaderMagic, Mode, Protocol, Range},
|
|
|
|
util::{Duration, SystemTimeSource}
|
|
|
|
};
|
2016-08-08 07:34:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
const VERSION: u8 = 1;
|
|
|
|
const MAGIC: HeaderMagic = *b"vpn\x01";
|
2015-11-19 15:34:20 +00:00
|
|
|
|
2016-08-08 14:30:22 +00:00
|
|
|
static USAGE: &'static str = include_str!("usage.txt");
|
|
|
|
|
2019-01-01 23:35:14 +00:00
|
|
|
|
2017-07-22 14:34:20 +00:00
|
|
|
#[derive(Deserialize, Debug, Default)]
|
2016-08-08 14:30:22 +00:00
|
|
|
pub struct Args {
|
|
|
|
flag_config: Option<String>,
|
|
|
|
flag_type: Option<Type>,
|
2019-02-12 18:30:38 +00:00
|
|
|
flag_device_path: Option<String>,
|
2016-08-08 14:30:22 +00:00
|
|
|
flag_mode: Option<Mode>,
|
|
|
|
flag_shared_key: Option<String>,
|
|
|
|
flag_crypto: Option<CryptoMethod>,
|
|
|
|
flag_subnet: Vec<String>,
|
|
|
|
flag_device: Option<String>,
|
|
|
|
flag_listen: Option<u16>,
|
|
|
|
flag_network_id: Option<String>,
|
|
|
|
flag_magic: Option<String>,
|
|
|
|
flag_connect: Vec<String>,
|
|
|
|
flag_peer_timeout: Option<Duration>,
|
2019-01-10 18:36:50 +00:00
|
|
|
flag_keepalive: Option<Duration>,
|
2016-08-08 14:30:22 +00:00
|
|
|
flag_dst_timeout: Option<Duration>,
|
2019-02-19 21:04:21 +00:00
|
|
|
flag_beacon_store: Option<String>,
|
|
|
|
flag_beacon_load: Option<String>,
|
|
|
|
flag_beacon_interval: Option<Duration>,
|
2016-08-08 14:30:22 +00:00
|
|
|
flag_verbose: bool,
|
|
|
|
flag_quiet: bool,
|
|
|
|
flag_ifup: Option<String>,
|
|
|
|
flag_ifdown: Option<String>,
|
2016-08-10 09:34:13 +00:00
|
|
|
flag_version: bool,
|
2016-11-23 14:21:22 +00:00
|
|
|
flag_no_port_forwarding: bool,
|
|
|
|
flag_daemon: bool,
|
|
|
|
flag_pid_file: Option<String>,
|
2019-01-09 16:45:12 +00:00
|
|
|
flag_stats_file: Option<String>,
|
2016-11-23 14:21:22 +00:00
|
|
|
flag_user: Option<String>,
|
|
|
|
flag_group: Option<String>,
|
|
|
|
flag_log_file: Option<String>
|
2016-08-08 14:30:22 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 14:21:22 +00:00
|
|
|
struct DualLogger {
|
|
|
|
file: Mutex<Option<File>>
|
|
|
|
}
|
|
|
|
|
|
|
|
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)?;
|
2019-12-04 08:32:35 +00:00
|
|
|
Ok(DualLogger { file: Mutex::new(Some(file)) })
|
2016-11-23 14:21:22 +00:00
|
|
|
} else {
|
2019-12-04 08:32:35 +00:00
|
|
|
Ok(DualLogger { file: Mutex::new(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());
|
|
|
|
let mut file = self.file.lock().expect("Lock poisoned");
|
2016-11-25 06:15:19 +00:00
|
|
|
if let Some(ref mut file) = *file {
|
2016-11-23 14:21:22 +00:00
|
|
|
let time = time::strftime("%F %T", &time::now()).expect("Failed to format timestamp");
|
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) {
|
|
|
|
let mut file = self.file.lock().expect("Lock poisoned");
|
|
|
|
if let Some(ref mut file) = *file {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-05-04 06:22:24 +00:00
|
|
|
enum AnyTable {
|
2019-02-24 19:01:32 +00:00
|
|
|
Switch(SwitchTable<SystemTimeSource>),
|
2017-05-04 06:22:24 +00:00
|
|
|
Routing(RoutingTable)
|
|
|
|
}
|
|
|
|
|
|
|
|
enum AnyCloud<P: Protocol> {
|
2019-02-26 00:21:15 +00:00
|
|
|
Switch(GenericCloud<TunTapDevice, P, SwitchTable<SystemTimeSource>, UdpSocket, SystemTimeSource>),
|
|
|
|
Routing(GenericCloud<TunTapDevice, P, RoutingTable, UdpSocket, SystemTimeSource>)
|
2017-05-04 06:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<P: Protocol> AnyCloud<P> {
|
2019-02-24 19:01:32 +00:00
|
|
|
#[allow(unknown_lints, clippy::too_many_arguments)]
|
2019-12-04 08:32:35 +00:00
|
|
|
fn new(
|
|
|
|
config: &Config, device: TunTapDevice, table: AnyTable, learning: bool, broadcast: bool, addresses: Vec<Range>,
|
2019-12-06 08:55:24 +00:00
|
|
|
crypto: Crypto, port_forwarding: Option<PortForwarding>, stats_file: Option<File>
|
2019-12-04 08:32:35 +00:00
|
|
|
) -> Self
|
|
|
|
{
|
2017-05-04 06:22:24 +00:00
|
|
|
match table {
|
2019-12-04 08:32:35 +00:00
|
|
|
AnyTable::Switch(t) => {
|
|
|
|
AnyCloud::Switch(GenericCloud::<
|
|
|
|
TunTapDevice,
|
|
|
|
P,
|
|
|
|
SwitchTable<SystemTimeSource>,
|
|
|
|
UdpSocket,
|
|
|
|
SystemTimeSource
|
|
|
|
>::new(
|
2019-12-06 08:55:24 +00:00
|
|
|
config,
|
|
|
|
device,
|
|
|
|
t,
|
|
|
|
learning,
|
|
|
|
broadcast,
|
|
|
|
addresses,
|
|
|
|
crypto,
|
|
|
|
port_forwarding,
|
|
|
|
stats_file
|
2019-12-04 08:32:35 +00:00
|
|
|
))
|
|
|
|
}
|
|
|
|
AnyTable::Routing(t) => {
|
|
|
|
AnyCloud::Routing(GenericCloud::<TunTapDevice, P, RoutingTable, UdpSocket, SystemTimeSource>::new(
|
|
|
|
config,
|
|
|
|
device,
|
|
|
|
t,
|
|
|
|
learning,
|
|
|
|
broadcast,
|
|
|
|
addresses,
|
|
|
|
crypto,
|
2019-12-06 08:55:24 +00:00
|
|
|
port_forwarding,
|
|
|
|
stats_file
|
2019-12-04 08:32:35 +00:00
|
|
|
))
|
|
|
|
}
|
2017-05-04 06:22:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ifname(&self) -> &str {
|
|
|
|
match *self {
|
|
|
|
AnyCloud::Switch(ref c) => c.ifname(),
|
|
|
|
AnyCloud::Routing(ref c) => c.ifname()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(&mut self) {
|
|
|
|
match *self {
|
|
|
|
AnyCloud::Switch(ref mut c) => c.run(),
|
|
|
|
AnyCloud::Routing(ref mut c) => c.run()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn connect(&mut self, a: &str) -> Result<(), Error> {
|
|
|
|
match *self {
|
|
|
|
AnyCloud::Switch(ref mut c) => c.connect(a),
|
|
|
|
AnyCloud::Routing(ref mut c) => c.connect(a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_reconnect_peer(&mut self, a: String) {
|
|
|
|
match *self {
|
|
|
|
AnyCloud::Switch(ref mut c) => c.add_reconnect_peer(a),
|
|
|
|
AnyCloud::Routing(ref mut c) => c.add_reconnect_peer(a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-12-04 08:32:35 +00:00
|
|
|
fn run<P: Protocol>(config: Config) {
|
|
|
|
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
|
|
|
|
);
|
2015-11-23 00:40:47 +00:00
|
|
|
info!("Opened device {}", device.ifname());
|
2016-08-08 14:30:22 +00:00
|
|
|
let mut ranges = Vec::with_capacity(config.subnets.len());
|
|
|
|
for s in &config.subnets {
|
|
|
|
ranges.push(try_fail!(Range::from_str(s), "Invalid subnet format: {} ({})", s));
|
2015-11-22 19:02:02 +00:00
|
|
|
}
|
2016-08-08 14:30:22 +00:00
|
|
|
let dst_timeout = config.dst_timeout;
|
2017-05-04 06:22:24 +00:00
|
|
|
let (learning, broadcasting, table) = match config.mode {
|
2019-12-04 08:32:35 +00:00
|
|
|
Mode::Normal => {
|
|
|
|
match config.device_type {
|
|
|
|
Type::Tap => (true, true, AnyTable::Switch(SwitchTable::new(dst_timeout, 10))),
|
|
|
|
Type::Tun => (false, false, AnyTable::Routing(RoutingTable::new())),
|
|
|
|
Type::Dummy => (false, false, AnyTable::Switch(SwitchTable::new(dst_timeout, 10)))
|
|
|
|
}
|
|
|
|
}
|
2017-05-04 06:22:24 +00:00
|
|
|
Mode::Router => (false, false, AnyTable::Routing(RoutingTable::new())),
|
|
|
|
Mode::Switch => (true, true, AnyTable::Switch(SwitchTable::new(dst_timeout, 10))),
|
|
|
|
Mode::Hub => (false, true, AnyTable::Switch(SwitchTable::new(dst_timeout, 10)))
|
2015-11-23 00:40:47 +00:00
|
|
|
};
|
2016-08-08 14:30:22 +00:00
|
|
|
let crypto = match config.shared_key {
|
2019-01-10 18:36:50 +00:00
|
|
|
Some(ref key) => Crypto::from_shared_key(config.crypto, key),
|
2015-11-23 14:40:04 +00:00
|
|
|
None => Crypto::None
|
|
|
|
};
|
2019-12-04 08:32:35 +00:00
|
|
|
let port_forwarding = if config.port_forwarding { PortForwarding::new(config.port) } else { None };
|
2019-12-06 08:55:24 +00:00
|
|
|
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);
|
|
|
|
}
|
2019-12-06 08:55:24 +00:00
|
|
|
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 =
|
|
|
|
AnyCloud::<P>::new(&config, device, table, learning, broadcasting, ranges, crypto, port_forwarding, stats_file);
|
2016-08-08 14:30:22 +00:00
|
|
|
if let Some(script) = config.ifup {
|
2017-05-04 05:26:21 +00:00
|
|
|
run_script(&script, cloud.ifname());
|
2015-11-25 11:29:12 +00:00
|
|
|
}
|
2016-08-08 14:30:22 +00:00
|
|
|
for addr in config.peers {
|
2016-05-11 08:54:00 +00:00
|
|
|
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
|
|
|
|
cloud.add_reconnect_peer(addr);
|
2015-11-25 11:29:12 +00:00
|
|
|
}
|
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);
|
|
|
|
}
|
|
|
|
try_fail!(daemonize.start(), "Failed to daemonize: {}");
|
2019-12-06 07:54:27 +00:00
|
|
|
} 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
|
|
|
}
|
2015-11-25 11:29:12 +00:00
|
|
|
cloud.run();
|
2016-08-08 14:30:22 +00:00
|
|
|
if let Some(script) = config.ifdown {
|
2017-05-04 05:26:21 +00:00
|
|
|
run_script(&script, cloud.ifname());
|
2015-11-25 11:29:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
2017-07-22 14:34:20 +00:00
|
|
|
let args: Args = Docopt::new(USAGE).and_then(|d| d.deserialize()).unwrap_or_else(|e| e.exit());
|
2015-11-25 11:29:12 +00:00
|
|
|
if args.flag_version {
|
2019-12-04 08:32:35 +00:00
|
|
|
println!("VpnCloud v{}, protocol version {}", env!("CARGO_PKG_VERSION"), VERSION);
|
|
|
|
return
|
2015-11-25 11:29:12 +00:00
|
|
|
}
|
2019-01-01 23:35:14 +00:00
|
|
|
let logger = try_fail!(DualLogger::new(args.flag_log_file.as_ref()), "Failed to open logfile: {}");
|
|
|
|
log::set_boxed_logger(Box::new(logger)).unwrap();
|
|
|
|
assert!(!args.flag_verbose || !args.flag_quiet);
|
2019-12-04 08:32:35 +00:00
|
|
|
log::set_max_level(if args.flag_verbose {
|
|
|
|
log::LevelFilter::Debug
|
|
|
|
} else if args.flag_quiet {
|
|
|
|
log::LevelFilter::Error
|
|
|
|
} else {
|
|
|
|
log::LevelFilter::Info
|
|
|
|
});
|
2016-08-08 14:30:22 +00:00
|
|
|
let mut config = Config::default();
|
|
|
|
if let Some(ref file) = args.flag_config {
|
|
|
|
info!("Reading config file '{}'", file);
|
2017-07-22 14:34:20 +00:00
|
|
|
let f = try_fail!(File::open(file), "Failed to open config file: {:?}");
|
|
|
|
let config_file = try_fail!(serde_yaml::from_reader(f), "Failed to load config file: {:?}");
|
2016-08-08 14:30:22 +00:00
|
|
|
config.merge_file(config_file)
|
|
|
|
}
|
|
|
|
config.merge_args(args);
|
|
|
|
debug!("Config: {:?}", config);
|
|
|
|
match config.device_type {
|
|
|
|
Type::Tap => run::<ethernet::Frame>(config),
|
2019-01-10 18:36:50 +00:00
|
|
|
Type::Tun => run::<ip::Packet>(config),
|
|
|
|
Type::Dummy => run::<ethernet::Frame>(config)
|
2015-11-25 11:29:12 +00:00
|
|
|
}
|
2015-11-22 19:02:02 +00:00
|
|
|
}
|