From 88f5272023e7f9ec0787b8de8a4ec10a1ff79b73 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Wed, 23 Nov 2016 15:21:22 +0100 Subject: [PATCH] Daemonizing and proper systemd support --- CHANGELOG.md | 4 ++ Cargo.lock | 17 +++++++-- Cargo.toml | 4 +- deb/.gitignore | 4 +- deb/vpncloud/Makefile | 2 + deb/vpncloud/debian/compat | 2 +- deb/vpncloud/debian/control | 2 +- deb/vpncloud/debian/vpncloud.service | 11 +++--- deb/vpncloud/vpncloud-control | 2 +- src/cloud.rs | 2 +- src/config.rs | 36 +++++++++++++++++- src/main.rs | 57 +++++++++++++++++++++++----- src/usage.txt | 6 +++ vpncloud.md | 40 ++++++++++++++++++- 14 files changed, 157 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f546d..28fbab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project follows [semantic versioning](http://semver.org). - [added] Support for automatic port forwarding via UPnP - [added] Added `-s` shorthand for `--subnet` - [added] Support for YAML config file via `--config` +- [added] Support for running in the background +- [added] Support for dropping permissions +- [added] Support for writing a pid file +- [added] Support for writing logs to logfile - [changed] Not overriding recently learnt addresses in switch mode - [changed] Caching resolved addresses to increase performance - [changed] Configurable magic header is now used instead of Network-ID (**incompatible**) diff --git a/Cargo.lock b/Cargo.lock index 9297d4d..5a2dda1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,6 +4,7 @@ version = "0.7.0" dependencies = [ "aligned_alloc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "daemonize 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", @@ -11,11 +12,10 @@ dependencies = [ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", - "signal 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "signal 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -63,6 +63,14 @@ dependencies = [ "url 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "daemonize" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "docopt" version = "0.6.86" @@ -272,7 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "signal" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", @@ -422,6 +430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626" +"checksum daemonize 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0239832c1b4ca406d5ec73728cf4c7336d25cf85dd32db9e047e9e706ee0e935" "checksum docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)" = "4a7ef30445607f6fc8720f0a0a2c7442284b629cf0d049286860fae23e71c4d9" "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" "checksum gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "553f11439bdefe755bf366b264820f1da70f3aaf3924e594b886beb9c831bcf5" @@ -448,7 +457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)" = "bff9fc1c79f2dec76b253273d07682e94a978bd8f132ded071188122b2af9818" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" -"checksum signal 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "beb615e58999635b6063277cf520f2d88824955c1056cf4f166b0f55b218512d" +"checksum signal 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "904a4bba60e8e7a53b7a7eec8f59084a9ceafe3df5aa9d24846a83a5e351aa34" "checksum siphasher 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c3c58c9ac43c530919fe6bd8ef11ae2612f64c2bf8eab9346f5b71ce0617f2" "checksum solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "172382bac9424588d7840732b250faeeef88942e37b6e35317dce98cafdd75b2" "checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c" diff --git a/Cargo.toml b/Cargo.toml index 384c010..68ac1ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,7 @@ time = "0.1" docopt = "0.6" rustc-serialize = "0.3" log = "0.3" -signal = "0.2" -nix = "0.6" +signal = "0.3" libc = "0.2" aligned_alloc = "0.1" rand = "0.3" @@ -26,6 +25,7 @@ bitflags = "0.7" yaml-rust = "0.3" igd = "0.5" siphasher = "0.1" +daemonize = "0.2" [build-dependencies] gcc = "0.3" diff --git a/deb/.gitignore b/deb/.gitignore index ea1bd47..be0a54e 100644 --- a/deb/.gitignore +++ b/deb/.gitignore @@ -1,9 +1,7 @@ vpncloud/debian/files vpncloud/debian/vpncloud +vpncloud/debian/debhelper* vpncloud/vpncloud -vpncloud-nocrypto/debian/vpncloud* -vpncloud-nocrypto/debian/files -vpncloud-nocrypto/vpncloud *.deb *.build *.changes diff --git a/deb/vpncloud/Makefile b/deb/vpncloud/Makefile index bb29538..dbb132f 100644 --- a/deb/vpncloud/Makefile +++ b/deb/vpncloud/Makefile @@ -11,3 +11,5 @@ install: install -d $(DESTDIR)/usr/bin install -m 755 vpncloud $(DESTDIR)/usr/bin/vpncloud install -m 755 vpncloud-control $(DESTDIR)/usr/bin/vpncloud-control + install -d $(DESTDIR)/lib/systemd/system + install -m 644 vpncloud@.service $(DESTDIR)/lib/systemd/system/vpncloud@.service diff --git a/deb/vpncloud/debian/compat b/deb/vpncloud/debian/compat index 7f8f011..ec63514 100644 --- a/deb/vpncloud/debian/compat +++ b/deb/vpncloud/debian/compat @@ -1 +1 @@ -7 +9 diff --git a/deb/vpncloud/debian/control b/deb/vpncloud/debian/control index 4305ed1..0c77bdc 100644 --- a/deb/vpncloud/debian/control +++ b/deb/vpncloud/debian/control @@ -2,7 +2,7 @@ Source: vpncloud Section: misc Priority: extra Maintainer: Dennis Schwerdel -Build-Depends: debhelper (>= 7), ruby-ronn +Build-Depends: debhelper (>= 9), ruby-ronn Standards-Version: 3.8.3 Package: vpncloud diff --git a/deb/vpncloud/debian/vpncloud.service b/deb/vpncloud/debian/vpncloud.service index ee175a6..e7d4cbf 100644 --- a/deb/vpncloud/debian/vpncloud.service +++ b/deb/vpncloud/debian/vpncloud.service @@ -1,11 +1,10 @@ [Unit] -Description=VpnCloud networks +Description=VpnCloud +Before=systemd-user-sessions.service [Service] -Type=forking -ExecStart=/usr/bin/vpncloud-control start -ExecStop=/usr/bin/vpncloud-control stop -RemainAfterExit=yes +ExecStartPre=/bin/echo "Please use instantiated services (vpncloud@NAME) instead." +ExecStart=/bin/false [Install] -WantedBy=multi-user.target +WantedBy= diff --git a/deb/vpncloud/vpncloud-control b/deb/vpncloud/vpncloud-control index 9da89c0..0970c6d 100755 --- a/deb/vpncloud/vpncloud-control +++ b/deb/vpncloud/vpncloud-control @@ -38,7 +38,7 @@ start() { # 2 if daemon could not be started for net in $NETWORKS; do [ -f "$NETCONFIGS/$net.net" ] || continue - start-stop-daemon --start --pidfile /run/$NAME-$net.pid --make-pidfile --name $NAME --background --startas /bin/sh -- -c "exec $DAEMON --config $NETCONFIGS/$net.net >/var/log/vpncloud-$net.log 2>&1" + start-stop-daemon --start --pidfile /run/$NAME-$net.pid --name $NAME -- "$DAEMON --daemon --config $NETCONFIGS/$net.net --pid-file /run/$NAME-$net.pid" done return 0 } diff --git a/src/cloud.rs b/src/cloud.rs index 9cf1121..75d19a6 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -14,7 +14,7 @@ use std::time::Instant; use std::cmp::{min, max}; use fnv::FnvHasher; -use nix::sys::signal::{SIGTERM, SIGQUIT, SIGINT}; +use libc::{SIGTERM, SIGQUIT, SIGINT}; use signal::trap::Trap; use rand::{random, sample, thread_rng}; use net2::UdpBuilder; diff --git a/src/config.rs b/src/config.rs index 1fceb81..c200109 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,6 +25,10 @@ pub struct Config { pub dst_timeout: Duration, pub subnets: Vec, pub port_forwarding: bool, + pub daemonize: bool, + pub pid_file: Option, + pub user: Option, + pub group: Option } impl Default for Config { @@ -37,7 +41,11 @@ impl Default for Config { port: 3210, peers: vec![], peer_timeout: 1800, mode: Mode::Normal, dst_timeout: 300, subnets: vec![], - port_forwarding: true + port_forwarding: true, + daemonize: false, + pid_file: None, + user: None, + group: None } } } @@ -86,6 +94,15 @@ impl Config { if let Some(val) = file.port_forwarding { self.port_forwarding = val; } + if let Some(val) = file.pid_file { + self.pid_file = Some(val); + } + if let Some(val) = file.user { + self.user = Some(val); + } + if let Some(val) = file.group { + self.group = Some(val); + } } pub fn merge_args(&mut self, mut args: Args) { @@ -131,6 +148,18 @@ impl Config { if args.flag_no_port_forwarding { self.port_forwarding = false; } + if args.flag_daemon { + self.daemonize = true; + } + if let Some(val) = args.flag_pid_file { + self.pid_file = Some(val); + } + if let Some(val) = args.flag_user { + self.user = Some(val); + } + if let Some(val) = args.flag_group { + self.group = Some(val); + } } pub fn get_magic(&self) -> HeaderMagic { @@ -169,5 +198,8 @@ pub struct ConfigFile { pub mode: Option, pub dst_timeout: Option, pub subnets: Option>, - pub port_forwarding: Option + pub port_forwarding: Option, + pub pid_file: Option, + pub user: Option, + pub group: Option, } diff --git a/src/main.rs b/src/main.rs index 5e54fef..d1da5c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,6 @@ extern crate time; extern crate docopt; extern crate rustc_serialize; extern crate signal; -extern crate nix; extern crate libc; extern crate aligned_alloc; extern crate rand; @@ -19,6 +18,7 @@ extern crate net2; extern crate yaml_rust; extern crate igd; extern crate siphasher; +extern crate daemonize; #[cfg(feature = "bench")] extern crate test; #[macro_use] pub mod util; @@ -38,8 +38,12 @@ pub mod port_forwarding; use docopt::Docopt; +use std::sync::Mutex; use std::str::FromStr; use std::process::Command; +use std::fs::File; +use std::path::Path; +use std::io::{self, Write}; use device::{Device, Type}; use ethernet::SwitchTable; @@ -77,13 +81,31 @@ pub struct Args { flag_ifup: Option, flag_ifdown: Option, flag_version: bool, - flag_no_port_forwarding: bool + flag_no_port_forwarding: bool, + flag_daemon: bool, + flag_pid_file: Option, + flag_user: Option, + flag_group: Option, + flag_log_file: Option } -struct SimpleLogger; +struct DualLogger { + file: Mutex> +} -impl log::Log for SimpleLogger { +impl DualLogger { + pub fn new>(path: Option

) -> Result { + if let Some(path) = path { + let file = try!(File::create(path)); + Ok(DualLogger{file: Mutex::new(Some(file))}) + } else { + Ok(DualLogger{file: Mutex::new(None)}) + } + } +} + +impl log::Log for DualLogger { #[inline] fn enabled(&self, _metadata: &log::LogMetadata) -> bool { true @@ -92,11 +114,12 @@ impl log::Log for SimpleLogger { #[inline] fn log(&self, record: &log::LogRecord) { if self.enabled(record.metadata()) { - println!("{} - {} - {}", - time::strftime("%F %T", &time::now()).expect("Failed to format timestamp"), - record.level(), - record.args() - ); + println!("{} - {}", record.level(), record.args()); + let mut file = self.file.lock().expect("Lock poisoned"); + if let &mut Some(ref mut file) = &mut file as &mut Option { + let time = time::strftime("%F %T", &time::now()).expect("Failed to format timestamp"); + writeln!(file, "{} - {} - {}", time, record.level(), record.args()).expect("Failed to write to logfile"); + } } } } @@ -153,6 +176,20 @@ fn run (config: Config) { try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr); cloud.add_reconnect_peer(addr); } + 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: {}"); + } cloud.run(); if let Some(script) = config.ifdown { run_script(script, cloud.ifname()); @@ -180,7 +217,7 @@ fn main() { } else { max_log_level.set(log::LogLevelFilter::Info); } - Box::new(SimpleLogger) + Box::new(try_fail!(DualLogger::new(args.flag_log_file.as_ref()), "Failed to open logfile: {}")) }).unwrap(); let mut config = Config::default(); if let Some(ref file) = args.flag_config { diff --git a/src/usage.txt b/src/usage.txt index 7ae2ec0..ebf5936 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -23,7 +23,13 @@ Options: --ifup A command to setup the network interface. --ifdown A command to bring down the network interface. + --pid-file Store the process id in this file when + daemonizing. + --user Run as other user when daemonizing. + --group Run as other group when daemonizing. + --log-file Print logs also to this file. --no-port-forwarding Disable automatic port forward. + --daemon Run the process in the background. -v, --verbose Print debug information. -q, --quiet Only print errors and warnings. -h, --help Display the help. diff --git a/vpncloud.md b/vpncloud.md index 9682b23..0824755 100644 --- a/vpncloud.md +++ b/vpncloud.md @@ -88,6 +88,8 @@ vpncloud(1) -- Peer-to-peer VPN parameter to `sh -c`) when the device has been created to configure it. The name of the allocated device will be available via the environment variable `IFNAME`. + Please note that this command is executed with the full permissions of the + caller. * `--ifdown `: @@ -95,6 +97,36 @@ vpncloud(1) -- Peer-to-peer VPN parameter to `sh -c`) to remove any configuration from the device. The name of the allocated device will be available via the environment variable `IFNAME`. + Please note that this command is executed with the (limited) permissions of + the user and group given as `--user` and `--group`. + + * `--pid-file `: + + Store the process id in this file when running in the background. If set, + the given file will be created containing the process id of the new + background process. This option is only used when running in background. + + * `--user `: + * `--group `: + + Change the user and/or group of the process once all the setup has been + done and before spawning the background process. This option is only used + when running in background. + + * `--log-file `: + + If set, print logs also to the given file. The file will be created and + truncated if is exists. + + * `--daemon`: + + Spawn a background process instead of running the process in the foreground. + If this flag is set, the process will first carry out all the + initialization, then drop permissions if `--user` or `--group` is used and + then spawn a background process and write its process id to a file if + `--pid-file` is set. Then, the main process will exit and the background + process continues to provide the VPN. At the time, when the main process + exits, the interface exists and is properly configured to be used. * `--no-port-forwarding`: @@ -245,6 +277,9 @@ detailed descriptions of the options. * `dst_timeout`: Switch table entry timeout in seconds. Same as `--dst-timeout` * `subnets`: A list of local subnets to use. See `--subnet` * `port_forwarding`: Whether to activate port forwardig. See `--no-port-forwarding` +* `user`: The name of a user to run the background process under. See `--user` +* `group`: The name of a group to run the background process under. See `--group` +* `pid_file`: The path of the pid file to create. See `--pid-file` ### Example @@ -263,6 +298,9 @@ mode: normal subnets: - 10.0.1.0/24 port_forwarding: true +user: nobody +group: nogroup +pid_file: /run/vpncloud.pid ## NETWORK PROTOCOL @@ -297,7 +335,7 @@ Every packet sent over UDP contains the following header (in order): `libsodium::crypto_aead_aes256gcm` method, using the 8 byte header as additional data. - * 2 `reserved bytes` that are currently unused + * 2 `reserved bytes` that are currently unused and set to 0 * 1 byte for the `message type`