Added statistics file

This commit is contained in:
Dennis Schwerdel 2019-01-09 17:45:12 +01:00
parent 6a3208949e
commit fe25ecec1d
12 changed files with 294 additions and 32 deletions

View File

@ -38,7 +38,7 @@ start() {
# 2 if daemon could not be started # 2 if daemon could not be started
for net in $NETWORKS; do for net in $NETWORKS; do
[ -f "$NETCONFIGS/$net.net" ] || continue [ -f "$NETCONFIGS/$net.net" ] || continue
start-stop-daemon --start --pidfile /run/$NAME-$net.pid --name $NAME -- "$DAEMON --daemon --config $NETCONFIGS/$net.net --pid-file /run/$NAME-$net.pid" start-stop-daemon --start --pidfile /run/$NAME-$net.pid --name $NAME -- "$DAEMON --daemon --config $NETCONFIGS/$net.net --log-file /var/log/$NAME-$net.log --ststs-file /var/log/$NAME-$net.stats --pid-file /run/$NAME-$net.pid"
done done
return 0 return 0
} }

View File

@ -4,7 +4,7 @@ Before=systemd-user-sessions.service
[Service] [Service]
Type=forking Type=forking
ExecStart=/usr/bin/vpncloud --config /etc/vpncloud/%i.net --daemon --log-file /var/log/vpncloud-%i.log --pid-file /run/vpncloud-%i.run 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.run
WorkingDirectory=/etc/vpncloud WorkingDirectory=/etc/vpncloud
PIDFile=/run/vpncloud-%i.run PIDFile=/run/vpncloud-%i.run

View File

@ -5,13 +5,14 @@
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::net::UdpSocket; use std::net::UdpSocket;
use std::io; use std::io::{self, Write};
use std::fmt; use std::fmt;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use std::time::Instant; use std::time::Instant;
use std::cmp::min; use std::cmp::min;
use std::fs::File;
use fnv::FnvHasher; use fnv::FnvHasher;
use signal::{trap::Trap, Signal}; use signal::{trap::Trap, Signal};
@ -25,16 +26,24 @@ use super::crypto::Crypto;
use super::port_forwarding::PortForwarding; use super::port_forwarding::PortForwarding;
use super::util::{now, Time, Duration, resolve}; use super::util::{now, Time, Duration, resolve};
use super::poll::{Poll, Flags}; use super::poll::{Poll, Flags};
use super::traffic::TrafficStats;
type Hash = BuildHasherDefault<FnvHasher>; pub type Hash = BuildHasherDefault<FnvHasher>;
const MAX_RECONNECT_INTERVAL: u16 = 3600; const MAX_RECONNECT_INTERVAL: u16 = 3600;
const RESOLVE_INTERVAL: Time = 300; const RESOLVE_INTERVAL: Time = 300;
pub const STATS_INTERVAL: Time = 60;
struct PeerData {
timeout: Time,
node_id: NodeId,
alt_addrs: Vec<SocketAddr>,
}
struct PeerList { struct PeerList {
timeout: Duration, timeout: Duration,
peers: HashMap<SocketAddr, (Time, NodeId, Vec<SocketAddr>), Hash>, peers: HashMap<SocketAddr, PeerData, Hash>,
nodes: HashMap<NodeId, SocketAddr, Hash>, nodes: HashMap<NodeId, SocketAddr, Hash>,
addresses: HashSet<SocketAddr, Hash> addresses: HashSet<SocketAddr, Hash>
} }
@ -52,17 +61,17 @@ impl PeerList {
fn timeout(&mut self) -> Vec<SocketAddr> { fn timeout(&mut self) -> Vec<SocketAddr> {
let now = now(); let now = now();
let mut del: Vec<SocketAddr> = Vec::new(); let mut del: Vec<SocketAddr> = Vec::new();
for (&addr, &(timeout, _nodeid, ref _alt_addrs)) in &self.peers { for (&addr, ref data) in &self.peers {
if timeout < now { if data.timeout < now {
del.push(addr); del.push(addr);
} }
} }
for addr in &del { for addr in &del {
info!("Forgot peer: {}", addr); info!("Forgot peer: {}", addr);
if let Some((_timeout, nodeid, alt_addrs)) = self.peers.remove(addr) { if let Some(data) = self.peers.remove(addr) {
self.nodes.remove(&nodeid); self.nodes.remove(&data.node_id);
self.addresses.remove(addr); self.addresses.remove(addr);
for addr in &alt_addrs { for addr in &data.alt_addrs {
self.addresses.remove(addr); self.addresses.remove(addr);
} }
} }
@ -95,23 +104,27 @@ impl PeerList {
fn add(&mut self, node_id: NodeId, addr: SocketAddr) { fn add(&mut self, node_id: NodeId, addr: SocketAddr) {
if self.nodes.insert(node_id, addr).is_none() { if self.nodes.insert(node_id, addr).is_none() {
info!("New peer: {}", addr); info!("New peer: {}", addr);
self.peers.insert(addr, (now()+Time::from(self.timeout), node_id, vec![])); self.peers.insert(addr, PeerData {
timeout: now() + Time::from(self.timeout),
node_id,
alt_addrs: vec![]
});
self.addresses.insert(addr); self.addresses.insert(addr);
} }
} }
#[inline] #[inline]
fn refresh(&mut self, addr: &SocketAddr) { fn refresh(&mut self, addr: &SocketAddr) {
if let Some(&mut (ref mut timeout, _node_id, ref _alt_addrs)) = self.peers.get_mut(addr) { if let Some(ref mut data) = self.peers.get_mut(addr) {
*timeout = now()+Time::from(self.timeout); data.timeout = now()+Time::from(self.timeout);
} }
} }
#[inline] #[inline]
fn add_alt_addr(&mut self, node_id: NodeId, addr: SocketAddr) { fn add_alt_addr(&mut self, node_id: NodeId, addr: SocketAddr) {
if let Some(main_addr) = self.nodes.get(&node_id) { if let Some(main_addr) = self.nodes.get(&node_id) {
if let Some(&mut (_timeout, _node_id, ref mut alt_addrs)) = self.peers.get_mut(main_addr) { if let Some(ref mut data) = self.peers.get_mut(main_addr) {
alt_addrs.push(addr); data.alt_addrs.push(addr);
self.addresses.insert(addr); self.addresses.insert(addr);
} else { } else {
error!("Main address for node is not connected"); error!("Main address for node is not connected");
@ -144,15 +157,24 @@ impl PeerList {
#[inline] #[inline]
fn remove(&mut self, addr: &SocketAddr) { fn remove(&mut self, addr: &SocketAddr) {
if let Some((_timeout, node_id, alt_addrs)) = self.peers.remove(addr) { if let Some(data) = self.peers.remove(addr) {
info!("Removed peer: {}", addr); info!("Removed peer: {}", addr);
self.nodes.remove(&node_id); self.nodes.remove(&data.node_id);
self.addresses.remove(addr); self.addresses.remove(addr);
for addr in alt_addrs { for addr in data.alt_addrs {
self.addresses.remove(&addr); self.addresses.remove(&addr);
} }
} }
} }
#[inline]
fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
try!(writeln!(out, "Peers:"));
for (addr, data) in &self.peers {
try!(writeln!(out, " - {} (ttl: {} s)", addr, data.timeout-now()));
}
Ok(())
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -184,7 +206,10 @@ pub struct GenericCloud<P: Protocol, T: Table> {
update_freq: Duration, update_freq: Duration,
buffer_out: [u8; 64*1024], buffer_out: [u8; 64*1024],
next_housekeep: Time, next_housekeep: Time,
next_stats_out: Time,
port_forwarding: Option<PortForwarding>, port_forwarding: Option<PortForwarding>,
traffic: TrafficStats,
stats_file: Option<String>,
_dummy_p: PhantomData<P>, _dummy_p: PhantomData<P>,
} }
@ -192,7 +217,8 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
#[allow(unknown_lints,clippy::too_many_arguments)] #[allow(unknown_lints,clippy::too_many_arguments)]
pub fn new(magic: HeaderMagic, device: Device, listen: u16, table: T, pub fn new(magic: HeaderMagic, device: Device, listen: u16, table: T,
peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec<Range>, peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec<Range>,
crypto: Crypto, port_forwarding: Option<PortForwarding>) -> Self { crypto: Crypto, port_forwarding: Option<PortForwarding>, stats_file: Option<String>
) -> Self {
let socket4 = match UdpBuilder::new_v4().expect("Failed to obtain ipv4 socket builder") let socket4 = match UdpBuilder::new_v4().expect("Failed to obtain ipv4 socket builder")
.reuse_address(true).expect("Failed to set so_reuseaddr").bind(("0.0.0.0", listen)) { .reuse_address(true).expect("Failed to set so_reuseaddr").bind(("0.0.0.0", listen)) {
Ok(socket) => socket, Ok(socket) => socket,
@ -222,7 +248,10 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
update_freq: peer_timeout/2-60, update_freq: peer_timeout/2-60,
buffer_out: [0; 64*1024], buffer_out: [0; 64*1024],
next_housekeep: now(), next_housekeep: now(),
next_stats_out: now() + STATS_INTERVAL,
port_forwarding, port_forwarding,
traffic: TrafficStats::new(),
stats_file,
_dummy_p: PhantomData, _dummy_p: PhantomData,
} }
} }
@ -244,6 +273,7 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
// Encrypt and encode once and send several times // Encrypt and encode once and send several times
let msg_data = encode(msg, &mut self.buffer_out, self.magic, &mut self.crypto); let msg_data = encode(msg, &mut self.buffer_out, self.magic, &mut self.crypto);
for addr in self.peers.peers.keys() { for addr in self.peers.peers.keys() {
self.traffic.count_out_traffic(*addr, msg_data.len());
let socket = match *addr { let socket = match *addr {
SocketAddr::V4(_) => &self.socket4, SocketAddr::V4(_) => &self.socket4,
SocketAddr::V6(_) => &self.socket6 SocketAddr::V6(_) => &self.socket6
@ -267,6 +297,7 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
debug!("Sending {:?} to {}", msg, addr); debug!("Sending {:?} to {}", msg, addr);
// Encrypt and encode // Encrypt and encode
let msg_data = encode(msg, &mut self.buffer_out, self.magic, &mut self.crypto); let msg_data = encode(msg, &mut self.buffer_out, self.magic, &mut self.crypto);
self.traffic.count_out_traffic(addr, msg_data.len());
let socket = match addr { let socket = match addr {
SocketAddr::V4(_) => &self.socket4, SocketAddr::V4(_) => &self.socket4,
SocketAddr::V6(_) => &self.socket6 SocketAddr::V6(_) => &self.socket6
@ -423,6 +454,26 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
// Schedule next connection attempt // Schedule next connection attempt
entry.next = now + Time::from(entry.timeout); entry.next = now + Time::from(entry.timeout);
} }
if self.next_stats_out < now {
// Write out the statistics
try!(self.write_out_stats().map_err(|err| Error::File("Failed to write stats file", err)));
self.next_stats_out = now + STATS_INTERVAL;
self.traffic.period(Some(60));
}
Ok(())
}
/// Calculates, resets and writes out the statistics to a file
fn write_out_stats(&mut self) -> Result<(), io::Error> {
if self.stats_file.is_none() { return Ok(()) }
debug!("Writing out stats");
let mut f = try!(File::create(self.stats_file.as_ref().unwrap()));
try!(self.peers.write_out(&mut f));
try!(writeln!(&mut f));
try!(self.table.write_out(&mut f));
try!(writeln!(&mut f));
try!(self.traffic.write_out(&mut f));
try!(writeln!(&mut f));
Ok(()) Ok(())
} }
@ -445,12 +496,13 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
pub fn handle_interface_data(&mut self, payload: &mut [u8], start: usize, end: usize) -> Result<(), Error> { pub fn handle_interface_data(&mut self, payload: &mut [u8], start: usize, end: usize) -> Result<(), Error> {
let (src, dst) = try!(P::parse(&payload[start..end])); let (src, dst) = try!(P::parse(&payload[start..end]));
debug!("Read data from interface: src: {}, dst: {}, {} bytes", src, dst, end-start); debug!("Read data from interface: src: {}, dst: {}, {} bytes", src, dst, end-start);
self.traffic.count_out_payload(dst, src, end-start);
match self.table.lookup(&dst) { match self.table.lookup(&dst) {
Some(addr) => { // Peer found for destination Some(addr) => { // Peer found for destination
debug!("Found destination for {} => {}", dst, addr); debug!("Found destination for {} => {}", dst, addr);
try!(self.send_msg(addr, &mut Message::Data(payload, start, end))); try!(self.send_msg(addr, &mut Message::Data(payload, start, end)));
if !self.peers.contains_addr(&addr) { if !self.peers.contains_addr(&addr) {
// If the peer is not actually conected, remove the entry in the table and try // If the peer is not actually connected, remove the entry in the table and try
// to reconnect. // to reconnect.
warn!("Destination for {} not found in peers: {}", dst, addr); warn!("Destination for {} not found in peers: {}", dst, addr);
self.table.remove(&dst); self.table.remove(&dst);
@ -507,8 +559,9 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
debug!("Received {:?} from {}", msg, peer); debug!("Received {:?} from {}", msg, peer);
match msg { match msg {
Message::Data(payload, start, end) => { Message::Data(payload, start, end) => {
let (src, _dst) = try!(P::parse(&payload[start..end])); let (src, dst) = try!(P::parse(&payload[start..end]));
debug!("Writing data to device: {} bytes", end-start); debug!("Writing data to device: {} bytes", end-start);
self.traffic.count_in_payload(src, dst, end-start);
if let Err(e) = self.device.write(&mut payload[..end], start) { if let Err(e) = self.device.write(&mut payload[..end], start) {
error!("Failed to send via device: {}", e); error!("Failed to send via device: {}", e);
return Err(e); return Err(e);
@ -605,6 +658,7 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
fd if fd == socket6_fd => try_fail!(self.socket6.recv_from(&mut buffer), "Failed to read from ipv6 network socket: {}"), fd if fd == socket6_fd => try_fail!(self.socket6.recv_from(&mut buffer), "Failed to read from ipv6 network socket: {}"),
_ => unreachable!() _ => unreachable!()
}; };
self.traffic.count_in_traffic(src, size);
if let Err(e) = decode(&mut buffer[..size], self.magic, &mut self.crypto).and_then(|msg| self.handle_net_message(src, msg)) { if let Err(e) = decode(&mut buffer[..size], self.magic, &mut self.crypto).and_then(|msg| self.handle_net_message(src, msg)) {
error!("Error: {}, from: {}", e, src); error!("Error: {}, from: {}", e, src);
} }

View File

@ -31,6 +31,7 @@ pub struct Config {
pub port_forwarding: bool, pub port_forwarding: bool,
pub daemonize: bool, pub daemonize: bool,
pub pid_file: Option<String>, pub pid_file: Option<String>,
pub stats_file: Option<String>,
pub user: Option<String>, pub user: Option<String>,
pub group: Option<String> pub group: Option<String>
} }
@ -48,6 +49,7 @@ impl Default for Config {
port_forwarding: true, port_forwarding: true,
daemonize: false, daemonize: false,
pid_file: None, pid_file: None,
stats_file: None,
user: None, user: None,
group: None group: None
} }
@ -101,6 +103,9 @@ impl Config {
if let Some(val) = file.pid_file { if let Some(val) = file.pid_file {
self.pid_file = Some(val); self.pid_file = Some(val);
} }
if let Some(val) = file.stats_file {
self.stats_file = Some(val);
}
if let Some(val) = file.user { if let Some(val) = file.user {
self.user = Some(val); self.user = Some(val);
} }
@ -158,6 +163,9 @@ impl Config {
if let Some(val) = args.flag_pid_file { if let Some(val) = args.flag_pid_file {
self.pid_file = Some(val); self.pid_file = Some(val);
} }
if let Some(val) = args.flag_stats_file {
self.stats_file = Some(val);
}
if let Some(val) = args.flag_user { if let Some(val) = args.flag_user {
self.user = Some(val); self.user = Some(val);
} }
@ -204,6 +212,7 @@ pub struct ConfigFile {
pub subnets: Option<Vec<String>>, pub subnets: Option<Vec<String>>,
pub port_forwarding: Option<bool>, pub port_forwarding: Option<bool>,
pub pid_file: Option<String>, pub pid_file: Option<String>,
pub stats_file: Option<String>,
pub user: Option<String>, pub user: Option<String>,
pub group: Option<String>, pub group: Option<String>,
} }

View File

@ -6,6 +6,7 @@ use std::net::SocketAddr;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use std::io::{self, Write};
use fnv::FnvHasher; use fnv::FnvHasher;
@ -99,6 +100,16 @@ impl Table for SwitchTable {
} }
} }
/// Write out the table
fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
let now = now();
try!(writeln!(out, "Switch table:"));
for (addr, val) in &self.table {
try!(writeln!(out, " - {} => {} (ttl: {} s)", addr, val.address, val.timeout - now));
}
Ok(())
}
/// Learns the given address, inserting it in the hash map /// Learns the given address, inserting it in the hash map
#[inline] #[inline]
fn learn(&mut self, key: Address, _prefix_len: Option<u8>, addr: SocketAddr) { fn learn(&mut self, key: Address, _prefix_len: Option<u8>, addr: SocketAddr) {

View File

@ -5,6 +5,7 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use std::collections::{hash_map, HashMap}; use std::collections::{hash_map, HashMap};
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use std::io::{self, Write};
use fnv::FnvHasher; use fnv::FnvHasher;
@ -53,7 +54,7 @@ impl Protocol for Packet {
struct RoutingEntry { struct RoutingEntry {
address: SocketAddr, address: SocketAddr,
bytes: [u8; 16], bytes: Address,
prefix_len: u8 prefix_len: u8
} }
@ -90,7 +91,7 @@ impl Table for RoutingTable {
let mut group_bytes = [0; 16]; let mut group_bytes = [0; 16];
group_bytes[..group_len].copy_from_slice(&addr.data[..group_len]); group_bytes[..group_len].copy_from_slice(&addr.data[..group_len]);
// Create an entry // Create an entry
let routing_entry = RoutingEntry{address, bytes: addr.data, prefix_len}; let routing_entry = RoutingEntry{address, bytes: addr, prefix_len};
// Add the entry to the routing table, creating a new list of the prefix group is empty. // Add the entry to the routing table, creating a new list of the prefix group is empty.
match self.0.entry(group_bytes) { match self.0.entry(group_bytes) {
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(routing_entry), hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(routing_entry),
@ -120,7 +121,7 @@ impl Table for RoutingTable {
// Calculate the match length of the address and the prefix // Calculate the match length of the address and the prefix
let mut match_len = 0; let mut match_len = 0;
for j in 0..addr.len as usize { for j in 0..addr.len as usize {
let b = addr.data[j] ^ entry.bytes[j]; let b = addr.data[j] ^ entry.bytes.data[j];
if b == 0 { if b == 0 {
match_len += 8; match_len += 8;
} else { } else {
@ -146,6 +147,17 @@ impl Table for RoutingTable {
//nothing to do //nothing to do
} }
/// Write out the table
fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
try!(writeln!(out, "Routing table:"));
for entries in self.0.values() {
for entry in entries {
try!(writeln!(out, " - {}/{} => {}", entry.bytes, entry.prefix_len, entry.address));
}
}
Ok(())
}
/// Removes an address from the map and returns whether something has been removed /// Removes an address from the map and returns whether something has been removed
#[inline] #[inline]
fn remove(&mut self, _addr: &Address) -> bool { fn remove(&mut self, _addr: &Address) -> bool {

View File

@ -34,6 +34,7 @@ pub mod device;
pub mod poll; pub mod poll;
pub mod config; pub mod config;
pub mod port_forwarding; pub mod port_forwarding;
pub mod traffic;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
#[cfg(feature = "bench")] mod benches; #[cfg(feature = "bench")] mod benches;
@ -86,6 +87,7 @@ pub struct Args {
flag_no_port_forwarding: bool, flag_no_port_forwarding: bool,
flag_daemon: bool, flag_daemon: bool,
flag_pid_file: Option<String>, flag_pid_file: Option<String>,
flag_stats_file: Option<String>,
flag_user: Option<String>, flag_user: Option<String>,
flag_group: Option<String>, flag_group: Option<String>,
flag_log_file: Option<String> flag_log_file: Option<String>
@ -159,13 +161,13 @@ impl<P: Protocol> AnyCloud<P> {
#[allow(unknown_lints,clippy::too_many_arguments)] #[allow(unknown_lints,clippy::too_many_arguments)]
fn new(magic: HeaderMagic, device: Device, listen: u16, table: AnyTable, fn new(magic: HeaderMagic, device: Device, listen: u16, table: AnyTable,
peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec<Range>, peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec<Range>,
crypto: Crypto, port_forwarding: Option<PortForwarding>) -> Self { crypto: Crypto, port_forwarding: Option<PortForwarding>, stats_file: Option<String>) -> Self {
match table { match table {
AnyTable::Switch(t) => AnyCloud::Switch(GenericCloud::<P, SwitchTable>::new( AnyTable::Switch(t) => AnyCloud::Switch(GenericCloud::<P, SwitchTable>::new(
magic, device, listen, t, peer_timeout, learning, broadcast, addresses, crypto, port_forwarding magic, device, listen, t, peer_timeout, learning, broadcast, addresses, crypto, port_forwarding, stats_file
)), )),
AnyTable::Routing(t) => AnyCloud::Routing(GenericCloud::<P, RoutingTable>::new( AnyTable::Routing(t) => AnyCloud::Routing(GenericCloud::<P, RoutingTable>::new(
magic, device, listen, t, peer_timeout, learning, broadcast, addresses, crypto, port_forwarding magic, device, listen, t, peer_timeout, learning, broadcast, addresses, crypto, port_forwarding, stats_file
)) ))
} }
} }
@ -230,7 +232,7 @@ fn run<P: Protocol> (config: Config) {
} else { } else {
None None
}; };
let mut cloud = AnyCloud::<P>::new(magic, device, config.port, table, peer_timeout, learning, broadcasting, ranges, crypto, port_forwarding); let mut cloud = AnyCloud::<P>::new(magic, device, config.port, table, peer_timeout, learning, broadcasting, ranges, crypto, port_forwarding, config.stats_file);
if let Some(script) = config.ifup { if let Some(script) = config.ifup {
run_script(&script, cloud.ifname()); run_script(&script, cloud.ifname());
} }

134
src/traffic.rs Normal file
View File

@ -0,0 +1,134 @@
use std::net::SocketAddr;
use std::collections::HashMap;
use std::io::{self, Write};
use super::types::Address;
use super::cloud::Hash;
use super::util::Bytes;
pub struct TrafficEntry {
pub out_bytes_total: u64,
pub out_packets_total: usize,
pub out_bytes: u64,
pub out_packets: usize,
pub in_bytes_total: u64,
pub in_packets_total: usize,
pub in_bytes: u64,
pub in_packets: usize,
pub idle_periods: usize
}
impl TrafficEntry {
pub fn new() -> Self {
TrafficEntry {
out_bytes_total: 0,
out_packets_total: 0,
out_bytes: 0,
out_packets: 0,
in_bytes_total: 0,
in_packets_total: 0,
in_bytes: 0,
in_packets: 0,
idle_periods: 0
}
}
#[inline]
fn count_out(&mut self, bytes: usize) {
self.out_packets += 1;
self.out_bytes += bytes as u64;
}
#[inline]
fn count_in(&mut self, bytes: usize) {
self.in_packets += 1;
self.in_bytes += bytes as u64;
}
fn period(&mut self) {
self.out_bytes_total += self.out_bytes;
self.out_packets_total += self.out_packets;
self.in_bytes_total += self.in_bytes;
self.in_packets_total += self.in_packets;
if self.in_packets == 0 && self.out_packets == 0 {
self.idle_periods += 1;
} else {
self.idle_periods = 0;
}
self.out_packets = 0;
self.in_packets = 0;
self.out_bytes = 0;
self.in_bytes = 0;
}
}
pub struct TrafficStats {
peers: HashMap<SocketAddr, TrafficEntry, Hash>,
payload: HashMap<(Address, Address), TrafficEntry, Hash>
}
impl TrafficStats {
pub fn new() -> Self {
Self { peers: Default::default(), payload: Default::default() }
}
#[inline]
pub fn count_out_traffic(&mut self, peer: SocketAddr, bytes: usize) {
self.peers.entry(peer).or_insert_with(TrafficEntry::new).count_out(bytes);
}
#[inline]
pub fn count_in_traffic(&mut self, peer: SocketAddr, bytes: usize) {
self.peers.entry(peer).or_insert_with(TrafficEntry::new).count_in(bytes);
}
#[inline]
pub fn count_out_payload(&mut self, remote: Address, local: Address, bytes: usize) {
self.payload.entry((remote, local)).or_insert_with(TrafficEntry::new).count_out(bytes);
}
#[inline]
pub fn count_in_payload(&mut self, remote: Address, local: Address, bytes: usize) {
self.payload.entry((remote, local)).or_insert_with(TrafficEntry::new).count_in(bytes);
}
pub fn period(&mut self, cleanup_idle: Option<usize>) {
for entry in self.peers.values_mut() {
entry.period();
}
for entry in self.payload.values_mut() {
entry.period();
}
if let Some(periods) = cleanup_idle {
self.peers.retain(|_, entry| entry.idle_periods < periods);
self.payload.retain(|_, entry| entry.idle_periods < periods);
}
}
pub fn get_peer_traffic(&self) -> impl Iterator<Item=(&SocketAddr, &TrafficEntry)> {
self.peers.iter()
}
pub fn get_payload_traffic(&self) -> impl Iterator<Item=(&(Address, Address), &TrafficEntry)> {
self.payload.iter()
}
#[inline]
pub fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
try!(writeln!(out, "Peer traffic:"));
let mut peers: Vec<_> = self.get_peer_traffic().collect();
peers.sort_unstable_by_key(|(_, data)| (data.out_bytes + data.in_bytes));
for (addr, data) in peers.iter().rev() {
try!(writeln!(out, " - {}: in={}/s, out={}/s", addr, Bytes(data.in_bytes/60), Bytes(data.out_bytes/60)));
}
try!(writeln!(out));
try!(writeln!(out, "Payload traffic:"));
let mut payload: Vec<_> = self.get_payload_traffic().collect();
payload.sort_unstable_by_key(|(_, data)| (data.out_bytes + data.in_bytes));
for ((remote, local), data) in payload.iter().rev() {
try!(writeln!(out, " - {} <-> {}: in={}/s, out={}/s", remote, local, Bytes(data.in_bytes/60), Bytes(data.out_bytes/60)));
}
Ok(())
}
}

View File

@ -6,7 +6,7 @@ use std::net::{SocketAddr, Ipv4Addr, Ipv6Addr};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::io; use std::io::{self, Write};
use super::util::{bytes_to_hex, Encoder}; use super::util::{bytes_to_hex, Encoder};
@ -210,6 +210,7 @@ pub trait Table {
fn learn(&mut self, Address, Option<u8>, SocketAddr); fn learn(&mut self, Address, Option<u8>, SocketAddr);
fn lookup(&mut self, &Address) -> Option<SocketAddr>; fn lookup(&mut self, &Address) -> Option<SocketAddr>;
fn housekeep(&mut self); fn housekeep(&mut self);
fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error>;
fn remove(&mut self, &Address) -> bool; fn remove(&mut self, &Address) -> bool;
fn remove_all(&mut self, &SocketAddr); fn remove_all(&mut self, &SocketAddr);
} }
@ -225,7 +226,8 @@ pub enum Error {
Socket(&'static str, io::Error), Socket(&'static str, io::Error),
Name(String), Name(String),
TunTapDev(&'static str, io::Error), TunTapDev(&'static str, io::Error),
Crypto(&'static str) Crypto(&'static str),
File(&'static str, io::Error)
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
@ -236,6 +238,7 @@ impl fmt::Display for Error {
Error::Crypto(msg) => write!(formatter, "{}", msg), Error::Crypto(msg) => write!(formatter, "{}", msg),
Error::Name(ref name) => write!(formatter, "failed to resolve name '{}'", name), Error::Name(ref name) => write!(formatter, "failed to resolve name '{}'", name),
Error::WrongHeaderMagic(net) => write!(formatter, "wrong header magic: {}", bytes_to_hex(&net)), Error::WrongHeaderMagic(net) => write!(formatter, "wrong header magic: {}", bytes_to_hex(&net)),
Error::File(msg, ref err) => write!(formatter, "{}: {:?}", msg, err)
} }
} }
} }

View File

@ -28,6 +28,7 @@ Options:
--user <user> Run as other user when daemonizing. --user <user> Run as other user when daemonizing.
--group <group> Run as other group when daemonizing. --group <group> Run as other group when daemonizing.
--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.
--no-port-forwarding Disable automatic port forward. --no-port-forwarding Disable automatic port forward.
--daemon Run the process in the background. --daemon Run the process in the background.
-v, --verbose Print debug information. -v, --verbose Print debug information.

View File

@ -136,3 +136,33 @@ pub fn resolve<Addr: ToSocketAddrs+fmt::Debug>(addr: Addr) -> Result<Vec<SocketA
addrs.dedup(); addrs.dedup();
Ok(addrs) Ok(addrs)
} }
pub struct Bytes(pub u64);
impl fmt::Display for Bytes {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let mut size = self.0 as f32;
if size >= 512.0 {
size /= 1024.0;
} else {
return write!(formatter, "{:.0} B", size);
}
if size >= 512.0 {
size /= 1024.0;
} else {
return write!(formatter, "{:.1} KiB", size);
}
if size >= 512.0 {
size /= 1024.0;
} else {
return write!(formatter, "{:.1} MiB", size);
}
if size >= 512.0 {
size /= 1024.0;
} else {
return write!(formatter, "{:.1} GiB", size);
}
write!(formatter, "{:.1} TiB", size)
}
}

View File

@ -118,6 +118,11 @@ vpncloud(1) -- Peer-to-peer VPN
If set, print logs also to the given file. The file will be created and If set, print logs also to the given file. The file will be created and
truncated if is exists. truncated if is exists.
* `--stats-file <file>`:
If set, periodically write statistics on peers and current traffic to the
given file. The file will be periodically overwritten with new data.
* `--daemon`: * `--daemon`:
Spawn a background process instead of running the process in the foreground. Spawn a background process instead of running the process in the foreground.
@ -280,6 +285,7 @@ detailed descriptions of the options.
* `user`: The name of a user to run the background process under. See `--user` * `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` * `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` * `pid_file`: The path of the pid file to create. See `--pid-file`
* `stats_file`: The path of the statistics file. See `--stats-file`
### Example ### Example
@ -422,5 +428,5 @@ alive.
## COPYRIGHT ## COPYRIGHT
Copyright (C) 2015-2016 Dennis Schwerdel Copyright (C) 2015-2019 Dennis Schwerdel
This software is licensed under GPL-3 or newer (see LICENSE.md) This software is licensed under GPL-3 or newer (see LICENSE.md)