Compare commits

..

8 Commits

Author SHA1 Message Date
dswd ea9e3cd5e1
Create FUNDING.yml 2019-12-06 16:43:10 +01:00
Dennis Schwerdel 6c85e749d8 Only run coveralls on stable 2019-12-06 13:08:45 +01:00
Dennis Schwerdel bc994a959d Fixed tests 2019-12-06 10:23:50 +01:00
Dennis Schwerdel 5e7752b097 Added service restrictions to systemd 2019-12-06 10:21:07 +01:00
Dennis Schwerdel 55358b3561 Set keepalive to 120 secs when NAT is detected 2019-12-06 10:20:24 +01:00
Dennis Schwerdel cd09311059 Fixed problems on stats file when dropping perms 2019-12-06 09:55:24 +01:00
Dennis Schwerdel 04e2892c8e Also drop privileges in foreground mode 2019-12-06 08:54:27 +01:00
Dennis Schwerdel 7307b25405 Adding keepalive parameter to manpage 2019-12-06 08:48:36 +01:00
11 changed files with 132 additions and 38 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: dswd
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://paypal.me/dswd73']

View File

@ -27,7 +27,8 @@ script:
- | - |
cargo build && cargo test cargo build && cargo test
after_success: after_success:
- cargo coveralls - |
[ $TRAVIS_RUST_VERSION = stable ] && cargo coveralls
env: env:
global: global:
- TRAVIS_CARGO_NIGHTLY_FEATURE="" - TRAVIS_CARGO_NIGHTLY_FEATURE=""

View File

@ -2,6 +2,15 @@
This project follows [semantic versioning](http://semver.org). This project follows [semantic versioning](http://semver.org).
### Unreleased
- [added] Added service restrictions to systemd
- [changed] Also drop privileges in foreground mode
- [changed] Set builders to Ubuntu 16.04 and CentOS 7
- [changed] Set keepalive to 120 secs when NAT is detected
- [fixed] Added parameter keepalive to manpage
- [fixed] Fixed problems on stats file when dropping permissions
### v1.1.0 (2019-12-04) ### v1.1.0 (2019-12-04)
- [added] Exchange peer timeout and adapt keepalive accordingly - [added] Exchange peer timeout and adapt keepalive accordingly

24
Cargo.lock generated
View File

@ -230,6 +230,18 @@ dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "nix"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "4.2.3" version = "4.2.3"
@ -254,6 +266,15 @@ name = "ppv-lite86"
version = "0.2.6" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "privdrop"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.6" version = "1.0.6"
@ -551,6 +572,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"privdrop 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)", "ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
@ -728,10 +750,12 @@ dependencies = [
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum privdrop 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "432c2e1a6d9d56e3c14710a9b807ade349c48ccd5647bcda9a175f40f81ca5a9"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"

View File

@ -28,6 +28,7 @@ igd = "0.9"
siphasher = "0.3" siphasher = "0.3"
daemonize = "0.4" daemonize = "0.4"
ring = "0.16" ring = "0.16"
privdrop = "0.3"
[build-dependencies] [build-dependencies]
cc = "^1" cc = "^1"

View File

@ -2,12 +2,22 @@
Description=VpnCloud network '%I' Description=VpnCloud network '%I'
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
Documentation=man:vpncloud(1)
[Service] [Service]
Type=forking Type=simple
ExecStart=/usr/bin/vpncloud --config /etc/vpncloud/%i.net --daemon --log-file /var/log/vpncloud-%i.log --stats-file /var/log/vpncloud-%i.stats --pid-file /run/vpncloud-%i.pid ExecStart=/usr/bin/vpncloud --config /etc/vpncloud/%i.net --log-file /var/log/vpncloud-%i.log --stats-file /var/log/vpncloud-%i.stats
WorkingDirectory=/etc/vpncloud WorkingDirectory=/etc/vpncloud
PIDFile=/run/vpncloud-%i.pid RestartSec=5s
Restart=on-failure
LimitNPROC=10
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
ReadWritePaths=/var/log/vpncloud-%i.log /var/log/vpncloud-%i.stats
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT
DeviceAllow=/dev/null rw
DeviceAllow=/dev/net/tun rw
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -6,12 +6,11 @@ use std::{
cmp::min, cmp::min,
collections::HashMap, collections::HashMap,
fmt, fmt,
fs::{self, File, Permissions}, fs::File,
hash::BuildHasherDefault, hash::BuildHasherDefault,
io::{self, Write}, io::{self, Write},
marker::PhantomData, marker::PhantomData,
net::{SocketAddr, ToSocketAddrs}, net::{SocketAddr, ToSocketAddrs}
os::unix::fs::PermissionsExt
}; };
use fnv::FnvHasher; use fnv::FnvHasher;
@ -230,6 +229,7 @@ pub struct GenericCloud<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSou
peer_timeout_publish: u16, peer_timeout_publish: u16,
update_freq: u16, update_freq: u16,
buffer_out: [u8; 64 * 1024], buffer_out: [u8; 64 * 1024],
stats_file: Option<File>,
next_housekeep: Time, next_housekeep: Time,
next_stats_out: Time, next_stats_out: Time,
next_beacon: Time, next_beacon: Time,
@ -244,7 +244,7 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
config: &Config, device: D, table: T, learning: bool, broadcast: bool, addresses: Vec<Range>, crypto: Crypto, config: &Config, device: D, table: T, learning: bool, broadcast: bool, addresses: Vec<Range>, crypto: Crypto,
port_forwarding: Option<PortForwarding> port_forwarding: Option<PortForwarding>, stats_file: Option<File>
) -> Self ) -> Self
{ {
let socket4 = match S::listen_v4("0.0.0.0", config.port) { let socket4 = match S::listen_v4("0.0.0.0", config.port) {
@ -256,11 +256,11 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
Err(err) => fail!("Failed to open ipv6 address ::{}: {}", config.port, err) Err(err) => fail!("Failed to open ipv6 address ::{}: {}", config.port, err)
}; };
let now = TS::now(); let now = TS::now();
let peer_timeout_publish = if socket4.detect_nat() { let update_freq = if socket4.detect_nat() && config.get_keepalive() > 120 {
info!("Private IP detected, setting published peer timeout to 300s"); info!("Private IP detected, setting keepalive interval to 120s");
300 120
} else { } else {
config.peer_timeout as u16 config.get_keepalive() as u16
}; };
let mut res = GenericCloud { let mut res = GenericCloud {
magic: config.get_magic(), magic: config.get_magic(),
@ -271,13 +271,14 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
broadcast, broadcast,
reconnect_peers: Vec::new(), reconnect_peers: Vec::new(),
own_addresses: Vec::new(), own_addresses: Vec::new(),
peer_timeout_publish, peer_timeout_publish: config.peer_timeout as u16,
table, table,
socket4, socket4,
socket6, socket6,
device, device,
next_peerlist: now, next_peerlist: now,
update_freq: config.get_keepalive() as u16, update_freq,
stats_file,
buffer_out: [0; 64 * 1024], buffer_out: [0; 64 * 1024],
next_housekeep: now, next_housekeep: now,
next_stats_out: now + STATS_INTERVAL, next_stats_out: now + STATS_INTERVAL,
@ -480,8 +481,8 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
let mut msg = Message::Peers(peers); let mut msg = Message::Peers(peers);
self.broadcast_msg(&mut msg)?; self.broadcast_msg(&mut msg)?;
// Reschedule for next update // Reschedule for next update
self.update_freq = min(self.config.get_keepalive() as u16, self.peers.min_peer_timeout()); let interval = min(self.update_freq as u16, self.peers.min_peer_timeout());
self.next_peerlist = now + Time::from(self.update_freq); self.next_peerlist = now + Time::from(interval);
} }
// Connect to those reconnect_peers that are due // Connect to those reconnect_peers that are due
for entry in self.reconnect_peers.clone() { for entry in self.reconnect_peers.clone() {
@ -526,7 +527,7 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
// Write out the statistics // Write out the statistics
self.write_out_stats().map_err(|err| Error::File("Failed to write stats file", err))?; self.write_out_stats().map_err(|err| Error::File("Failed to write stats file", err))?;
self.next_stats_out = now + STATS_INTERVAL; self.next_stats_out = now + STATS_INTERVAL;
self.traffic.period(Some(60)); self.traffic.period(Some(5));
} }
if let Some(peers) = self.beacon_serializer.get_cmd_results() { if let Some(peers) = self.beacon_serializer.get_cmd_results() {
debug!("Loaded beacon with peers: {:?}", peers); debug!("Loaded beacon with peers: {:?}", peers);
@ -586,18 +587,16 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
/// Calculates, resets and writes out the statistics to a file /// Calculates, resets and writes out the statistics to a file
fn write_out_stats(&mut self) -> Result<(), io::Error> { fn write_out_stats(&mut self) -> Result<(), io::Error> {
if self.config.stats_file.is_none() { if let Some(ref mut f) = self.stats_file {
return Ok(())
}
debug!("Writing out stats"); debug!("Writing out stats");
let mut f = File::create(self.config.stats_file.as_ref().unwrap())?; f.set_len(0)?;
self.peers.write_out(&mut f)?; self.peers.write_out(f)?;
writeln!(&mut f)?; writeln!(f)?;
self.table.write_out(&mut f)?; self.table.write_out(f)?;
writeln!(&mut f)?; writeln!(f)?;
self.traffic.write_out(&mut f)?; self.traffic.write_out(f)?;
writeln!(&mut f)?; writeln!(f)?;
fs::set_permissions(self.config.stats_file.as_ref().unwrap(), Permissions::from_mode(0o644))?; }
Ok(()) Ok(())
} }

View File

@ -33,9 +33,10 @@ pub mod udpmessage;
use docopt::Docopt; use docopt::Docopt;
use std::{ use std::{
fs::File, fs::{self, File, Permissions},
io::{self, Write}, io::{self, Write},
net::UdpSocket, net::UdpSocket,
os::unix::fs::PermissionsExt,
path::Path, path::Path,
process::Command, process::Command,
str::FromStr, str::FromStr,
@ -166,7 +167,7 @@ impl<P: Protocol> AnyCloud<P> {
#[allow(unknown_lints, clippy::too_many_arguments)] #[allow(unknown_lints, clippy::too_many_arguments)]
fn new( fn new(
config: &Config, device: TunTapDevice, table: AnyTable, learning: bool, broadcast: bool, addresses: Vec<Range>, config: &Config, device: TunTapDevice, table: AnyTable, learning: bool, broadcast: bool, addresses: Vec<Range>,
crypto: Crypto, port_forwarding: Option<PortForwarding> crypto: Crypto, port_forwarding: Option<PortForwarding>, stats_file: Option<File>
) -> Self ) -> Self
{ {
match table { match table {
@ -178,7 +179,15 @@ impl<P: Protocol> AnyCloud<P> {
UdpSocket, UdpSocket,
SystemTimeSource SystemTimeSource
>::new( >::new(
config, device, t, learning, broadcast, addresses, crypto, port_forwarding config,
device,
t,
learning,
broadcast,
addresses,
crypto,
port_forwarding,
stats_file
)) ))
} }
AnyTable::Routing(t) => { AnyTable::Routing(t) => {
@ -190,7 +199,8 @@ impl<P: Protocol> AnyCloud<P> {
broadcast, broadcast,
addresses, addresses,
crypto, crypto,
port_forwarding port_forwarding,
stats_file
)) ))
} }
} }
@ -256,7 +266,19 @@ fn run<P: Protocol>(config: Config) {
None => Crypto::None None => Crypto::None
}; };
let port_forwarding = if config.port_forwarding { PortForwarding::new(config.port) } else { None }; let port_forwarding = if config.port_forwarding { PortForwarding::new(config.port) } else { None };
let mut cloud = AnyCloud::<P>::new(&config, device, table, learning, broadcasting, ranges, crypto, port_forwarding); let stats_file = match config.stats_file {
None => None,
Some(ref 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 =
AnyCloud::<P>::new(&config, device, table, learning, broadcasting, ranges, crypto, port_forwarding, stats_file);
if let Some(script) = config.ifup { if let Some(script) = config.ifup {
run_script(&script, cloud.ifname()); run_script(&script, cloud.ifname());
} }
@ -277,6 +299,16 @@ fn run<P: Protocol>(config: Config) {
daemonize = daemonize.pid_file(pid_file).chown_pid_file(true); daemonize = daemonize.pid_file(pid_file).chown_pid_file(true);
} }
try_fail!(daemonize.start(), "Failed to daemonize: {}"); 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: {}");
} }
cloud.run(); cloud.run();
if let Some(script) = config.ifdown { if let Some(script) = config.ifdown {

View File

@ -80,7 +80,7 @@ fn create_tap_node(nat: bool) -> TapTestNode {
fn create_tap_node_with_config(nat: bool, mut config: Config) -> TapTestNode { fn create_tap_node_with_config(nat: bool, mut config: Config) -> TapTestNode {
MockSocket::set_nat(nat); MockSocket::set_nat(nat);
config.port = NEXT_PORT.with(|p| p.fetch_add(1, Ordering::Relaxed)) as u16; config.port = NEXT_PORT.with(|p| p.fetch_add(1, Ordering::Relaxed)) as u16;
TestNode::new(&config, MockDevice::new(), SwitchTable::new(1800, 10), true, true, vec![], Crypto::None, None) TestNode::new(&config, MockDevice::new(), SwitchTable::new(1800, 10), true, true, vec![], Crypto::None, None, None)
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -94,6 +94,7 @@ fn create_tun_node(nat: bool, addresses: Vec<Range>) -> TunTestNode {
false, false,
addresses, addresses,
Crypto::None, Crypto::None,
None,
None None
) )
} }

View File

@ -31,8 +31,8 @@ Options:
interface. interface.
--pid-file <file> Store the process id in this file when --pid-file <file> Store the process id in this file when
daemonizing. daemonizing.
--user <user> Run as other user when daemonizing. --user <user> Run as other user.
--group <group> Run as other group when daemonizing. --group <group> Run as other group.
--log-file <file> Print logs also to this file. --log-file <file> Print logs also to this file.
--stats-file <file> Print statistics to this file. --stats-file <file> Print statistics to this file.
--no-port-forwarding Disable automatic port forward. --no-port-forwarding Disable automatic port forward.

View File

@ -80,6 +80,12 @@ vpncloud(1) -- Peer-to-peer VPN
Peer timeout in seconds. The peers will exchange information periodically Peer timeout in seconds. The peers will exchange information periodically
and drop peers that are silent for this period of time. [default: `1800`] and drop peers that are silent for this period of time. [default: `1800`]
* `--keepalive <secs>`:
Interval of peer exchange messages in seconds. The peers will exchange
information periodically to keep connections alive. This setting overrides
how often this will happen. [default: `peer-timeout/2-60`]
* `--dst-timeout <secs>`: * `--dst-timeout <secs>`:
Switch table entry timeout in seconds. This parameter is only used in switch Switch table entry timeout in seconds. This parameter is only used in switch
@ -140,8 +146,7 @@ vpncloud(1) -- Peer-to-peer VPN
* `--group <group>`: * `--group <group>`:
Change the user and/or group of the process once all the setup has been 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 done.
when running in background.
* `--log-file <file>`: * `--log-file <file>`: