Daemonizing and proper systemd support

pull/11/merge
Dennis Schwerdel 2016-11-23 15:21:22 +01:00
parent 4497ddaf13
commit 88f5272023
14 changed files with 157 additions and 32 deletions

View File

@ -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**)

17
Cargo.lock generated
View File

@ -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"

View File

@ -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"

4
deb/.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -1 +1 @@
7
9

View File

@ -2,7 +2,7 @@ Source: vpncloud
Section: misc
Priority: extra
Maintainer: Dennis Schwerdel <schwerdel@informatik.uni-kl.de>
Build-Depends: debhelper (>= 7), ruby-ronn
Build-Depends: debhelper (>= 9), ruby-ronn
Standards-Version: 3.8.3
Package: vpncloud

View File

@ -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=

View File

@ -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
}

View File

@ -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;

View File

@ -25,6 +25,10 @@ pub struct Config {
pub dst_timeout: Duration,
pub subnets: Vec<String>,
pub port_forwarding: bool,
pub daemonize: bool,
pub pid_file: Option<String>,
pub user: Option<String>,
pub group: Option<String>
}
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<Mode>,
pub dst_timeout: Option<Duration>,
pub subnets: Option<Vec<String>>,
pub port_forwarding: Option<bool>
pub port_forwarding: Option<bool>,
pub pid_file: Option<String>,
pub user: Option<String>,
pub group: Option<String>,
}

View File

@ -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<String>,
flag_ifdown: Option<String>,
flag_version: bool,
flag_no_port_forwarding: bool
flag_no_port_forwarding: bool,
flag_daemon: bool,
flag_pid_file: Option<String>,
flag_user: Option<String>,
flag_group: Option<String>,
flag_log_file: Option<String>
}
struct SimpleLogger;
struct DualLogger {
file: Mutex<Option<File>>
}
impl log::Log for SimpleLogger {
impl DualLogger {
pub fn new<P: AsRef<Path>>(path: Option<P>) -> Result<Self, io::Error> {
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<File> {
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<T: Protocol> (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 {

View File

@ -23,7 +23,13 @@ Options:
--ifup <command> A command to setup the network interface.
--ifdown <command> A command to bring down the network
interface.
--pid-file <file> Store the process id in this file when
daemonizing.
--user <user> Run as other user when daemonizing.
--group <group> Run as other group when daemonizing.
--log-file <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.

View File

@ -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 <command>`:
@ -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 <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 <user>`:
* `--group <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 <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`