mirror of https://github.com/dswd/vpncloud.git
Added statistics file
This commit is contained in:
parent
6a3208949e
commit
fe25ecec1d
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
92
src/cloud.rs
92
src/cloud.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
18
src/ip.rs
18
src/ip.rs
|
@ -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 {
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.
|
||||||
|
|
30
src/util.rs
30
src/util.rs
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue