Implemented beacon support

This commit is contained in:
Dennis Schwerdel 2019-02-19 22:04:21 +01:00
parent 2c818d7079
commit 537cb55c41
21 changed files with 323 additions and 44 deletions

3
.gitignore vendored
View File

@ -5,5 +5,4 @@ vpncloud-oldnodes
deb/vpncloud/vpncloud deb/vpncloud/vpncloud
deb/vpncloud/vpncloud.1* deb/vpncloud/vpncloud.1*
Stats.ods Stats.ods
.sodium-build dist
wiki

View File

@ -4,7 +4,9 @@ This project follows [semantic versioning](http://semver.org).
### UNRELEASED ### UNRELEASED
- [added] Added ability to publish small beacons for rendezvous
- [changed] Allow to build binary without manpage - [changed] Allow to build binary without manpage
- [fixed] Fixed bug that could cause repeated initialization messages
### v0.9.1 (2019-02-16) ### v0.9.1 (2019-02-16)

View File

@ -1,8 +1,8 @@
VpnCloud - Peer-to-Peer VPN VpnCloud - Peer-to-Peer VPN
--------------------------- ---------------------------
[![Build Status](https://travis-ci.org/dswd/vpncloud.rs.svg?branch=master)](https://travis-ci.org/dswd/vpncloud.rs) [![Build Status](https://travis-ci.org/dswd/vpncloud.svg?branch=master)](https://travis-ci.org/dswd/vpncloud)
[![Coverage Status](https://coveralls.io/repos/dswd/vpncloud.rs/badge.svg?branch=master&service=github)](https://coveralls.io/github/dswd/vpncloud.rs?branch=master) [![Coverage Status](https://coveralls.io/repos/dswd/vpncloud/badge.svg?branch=master&service=github)](https://coveralls.io/github/dswd/vpncloud?branch=master)
**VpnCloud** is a simple VPN over UDP. It creates a virtual network interface on **VpnCloud** is a simple VPN over UDP. It creates a virtual network interface on
the host and forwards all received data via UDP to the destination. VpnCloud the host and forwards all received data via UDP to the destination. VpnCloud

View File

@ -1,7 +1,20 @@
// VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2019-2019 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md)
use base_62; use base_62;
use ring::digest; use ring::digest;
use std::num::Wrapping; use std::num::Wrapping;
use std::path::Path;
use std::io::{self, Write, Read};
use std::fs::{self, Permissions, File};
use std::os::unix::fs::PermissionsExt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::mem;
use std::thread;
use std::process::{Command, Stdio};
use super::util::{now, Encoder}; use super::util::{now, Encoder};
use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr, SocketAddrV6, Ipv6Addr}; use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr, SocketAddrV6, Ipv6Addr};
@ -19,16 +32,27 @@ fn now_hour_16() -> u16 {
((now() / 3600) & 0xffff) as u16 ((now() / 3600) & 0xffff) as u16
} }
struct FutureResult<T> {
has_result: AtomicBool,
result: Mutex<T>
}
#[derive(Clone)]
pub struct BeaconSerializer { pub struct BeaconSerializer {
magic: Vec<u8>, magic: Vec<u8>,
shared_key: Vec<u8> shared_key: Vec<u8>,
future_peers: Arc<FutureResult<Vec<SocketAddr>>>,
} }
impl BeaconSerializer { impl BeaconSerializer {
pub fn new(magic: &[u8], shared_key: &[u8]) -> Self { pub fn new(magic: &[u8], shared_key: &[u8]) -> Self {
BeaconSerializer { BeaconSerializer {
magic: magic.to_owned(), magic: magic.to_owned(),
shared_key: shared_key.to_owned() shared_key: shared_key.to_owned(),
future_peers: Arc::new(FutureResult {
has_result: AtomicBool::new(false),
result: Mutex::new(Vec::new())
})
} }
} }
@ -162,6 +186,35 @@ impl BeaconSerializer {
self.encode_internal(peers, now_hour_16()) self.encode_internal(peers, now_hour_16())
} }
pub fn write_to_file<P: AsRef<Path>>(&self, peers: &[SocketAddr], path: P) -> Result<(), io::Error> {
let beacon = self.encode(peers);
debug!("Beacon: {}", beacon);
let mut f = try!(File::create(&path));
try!(writeln!(&mut f, "{}", beacon));
try!(fs::set_permissions(&path, Permissions::from_mode(0o644)));
Ok(())
}
pub fn write_to_cmd(&self, peers: &[SocketAddr], cmd: &str) -> Result<(), io::Error> {
let begin = self.begin();
let data = self.peerlist_encode(peers, now_hour_16());
let end = self.end();
let beacon = format!("{}{}{}", begin, data, end);
debug!("Calling beacon command: {}", cmd);
let process = try!(Command::new("sh").args(&["-c", cmd])
.env("begin", begin).env("data", data).env("end", end).env("beacon", beacon)
.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn());
thread::spawn(move || {
let output = process.wait_with_output().expect("Failed to wait on child");
if !output.status.success() {
error!("Beacon command failed: {}", String::from_utf8_lossy(&output.stderr));
} else {
debug!("Beacon command succeeded");
}
});
Ok(())
}
fn decode_internal(&self, data: &str, ttl_hours: Option<u16>, now_hour: u16) -> Vec<SocketAddr> { fn decode_internal(&self, data: &str, ttl_hours: Option<u16>, now_hour: u16) -> Vec<SocketAddr> {
let data = base_62_sanitize(data); let data = base_62_sanitize(data);
let mut peers = Vec::new(); let mut peers = Vec::new();
@ -185,6 +238,48 @@ impl BeaconSerializer {
pub fn decode(&self, data: &str, ttl_hours: Option<u16>) -> Vec<SocketAddr> { pub fn decode(&self, data: &str, ttl_hours: Option<u16>) -> Vec<SocketAddr> {
self.decode_internal(data, ttl_hours, now_hour_16()) self.decode_internal(data, ttl_hours, now_hour_16())
} }
pub fn read_from_file<P: AsRef<Path>>(&self, path: P, ttl_hours: Option<u16>) -> Result<Vec<SocketAddr>, io::Error> {
let mut f = try!(File::open(&path));
let mut contents = String::new();
try!(f.read_to_string(&mut contents));
Ok(self.decode(&contents, ttl_hours))
}
pub fn read_from_cmd(&self, cmd: &str, ttl_hours: Option<u16>) -> Result<(), io::Error> {
let begin = self.begin();
let end = self.end();
debug!("Calling beacon command: {}", cmd);
let process = try!(Command::new("sh").args(&["-c", cmd])
.env("begin", begin).env("end", end)
.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn());
let this = self.clone();
thread::spawn(move || {
let output = process.wait_with_output().expect("Failed to wait on child");
if output.status.success() {
let data = String::from_utf8_lossy(&output.stdout);
let mut peers = this.decode(&data, ttl_hours);
debug!("Beacon command succeeded with {} peers", peers.len());
mem::swap(&mut peers, &mut this.future_peers.result.lock().expect("Lock poisoned"));
this.future_peers.has_result.store(true, Ordering::Relaxed);
} else {
error!("Beacon command failed: {}", String::from_utf8_lossy(&output.stderr));
}
});
//TODO: implement
Ok(())
}
pub fn get_cmd_results(&self) -> Option<Vec<SocketAddr>> {
if self.future_peers.has_result.load(Ordering::Relaxed) {
let mut peers = Vec::new();
mem::swap(&mut peers, &mut self.future_peers.result.lock().expect("Lock poisoned"));
self.future_peers.has_result.store(false, Ordering::Relaxed);
Some(peers)
} else {
None
}
}
} }

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use test::Bencher; use test::Bencher;

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
#include <stdint.h> #include <stdint.h>

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};
@ -140,6 +140,7 @@ impl PeerList {
peer.alt_addrs.retain(|i| i != &addr); peer.alt_addrs.retain(|i| i != &addr);
peer.alt_addrs.push(old_addr); peer.alt_addrs.push(old_addr);
self.peers.insert(addr, peer); self.peers.insert(addr, peer);
self.addresses.insert(addr, node_id);
} }
#[inline] #[inline]
@ -202,6 +203,7 @@ pub struct ReconnectEntry {
pub struct GenericCloud<P: Protocol, T: Table> { pub struct GenericCloud<P: Protocol, T: Table> {
config: Config,
magic: HeaderMagic, magic: HeaderMagic,
node_id: NodeId, node_id: NodeId,
peers: PeerList, peers: PeerList,
@ -209,7 +211,7 @@ pub struct GenericCloud<P: Protocol, T: Table> {
learning: bool, learning: bool,
broadcast: bool, broadcast: bool,
reconnect_peers: Vec<ReconnectEntry>, reconnect_peers: Vec<ReconnectEntry>,
blacklist_peers: Vec<SocketAddr>, own_addresses: Vec<SocketAddr>,
table: T, table: T,
socket4: UdpSocket, socket4: UdpSocket,
socket6: UdpSocket, socket6: UdpSocket,
@ -220,9 +222,9 @@ pub struct GenericCloud<P: Protocol, T: Table> {
buffer_out: [u8; 64*1024], buffer_out: [u8; 64*1024],
next_housekeep: Time, next_housekeep: Time,
next_stats_out: Time, next_stats_out: Time,
next_beacon: Time,
port_forwarding: Option<PortForwarding>, port_forwarding: Option<PortForwarding>,
traffic: TrafficStats, traffic: TrafficStats,
stats_file: Option<String>,
beacon_serializer: BeaconSerializer, beacon_serializer: BeaconSerializer,
_dummy_p: PhantomData<P>, _dummy_p: PhantomData<P>,
} }
@ -251,7 +253,7 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
learning, learning,
broadcast, broadcast,
reconnect_peers: Vec::new(), reconnect_peers: Vec::new(),
blacklist_peers: Vec::new(), own_addresses: Vec::new(),
table, table,
socket4, socket4,
socket6, socket6,
@ -261,11 +263,12 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
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,
next_beacon: now(),
port_forwarding, port_forwarding,
traffic: TrafficStats::default(), traffic: TrafficStats::default(),
stats_file: config.stats_file.clone(),
beacon_serializer: BeaconSerializer::new(&config.get_magic(), crypto.get_key()), beacon_serializer: BeaconSerializer::new(&config.get_magic(), crypto.get_key()),
crypto, crypto,
config: config.clone(),
_dummy_p: PhantomData, _dummy_p: PhantomData,
} }
} }
@ -356,14 +359,14 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
}) })
} }
/// Returns whether the address is blacklisted /// Returns whether the address is of this node
/// ///
/// # Errors /// # Errors
/// Returns an `Error::SocketError` if the given address is a name that failed to resolve to /// Returns an `Error::SocketError` if the given address is a name that failed to resolve to
/// actual addresses. /// actual addresses.
fn is_blacklisted<Addr: ToSocketAddrs+fmt::Debug>(&self, addr: Addr) -> Result<bool, Error> { fn is_own_address<Addr: ToSocketAddrs+fmt::Debug>(&self, addr: Addr) -> Result<bool, Error> {
for addr in try!(resolve(&addr)) { for addr in try!(resolve(&addr)) {
if self.blacklist_peers.contains(&addr) { if self.own_addresses.contains(&addr) {
return Ok(true); return Ok(true);
} }
} }
@ -379,7 +382,7 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
/// # Errors /// # Errors
/// This method returns `Error::NameError` if the address is a name that fails to resolve. /// This method returns `Error::NameError` if the address is a name that fails to resolve.
pub fn connect<Addr: ToSocketAddrs+fmt::Debug+Clone>(&mut self, addr: Addr) -> Result<(), Error> { pub fn connect<Addr: ToSocketAddrs+fmt::Debug+Clone>(&mut self, addr: Addr) -> Result<(), Error> {
if try!(self.peers.is_connected(addr.clone())) || try!(self.is_blacklisted(addr.clone())) { if try!(self.peers.is_connected(addr.clone())) || try!(self.is_own_address(addr.clone())) {
return Ok(()) return Ok(())
} }
debug!("Connecting to {:?}", addr); debug!("Connecting to {:?}", addr);
@ -394,6 +397,25 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
Ok(()) Ok(())
} }
/// Connects to a node given by its address
///
/// This method connects to node by sending a `Message::Init` to it. If `addr` is a name that
/// resolves to multiple addresses, one message is sent to each of them.
/// If the node is already a connected peer or the address is blacklisted, no message is sent.
///
/// # Errors
/// This method returns `Error::NameError` if the address is a name that fails to resolve.
fn connect_sock(&mut self, addr: SocketAddr) -> Result<(), Error> {
if self.peers.contains_addr(&addr) || self.own_addresses.contains(&addr) {
return Ok(())
}
debug!("Connecting to {:?}", addr);
let subnets = self.addresses.clone();
let node_id = self.node_id;
let mut msg = Message::Init(0, node_id, subnets.clone());
self.send_msg(addr, &mut msg)
}
/// Run all periodic housekeeping tasks /// Run all periodic housekeeping tasks
/// ///
/// This method executes several tasks: /// This method executes several tasks:
@ -426,10 +448,6 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
// ...and send them to all peers // ...and send them to all peers
let mut msg = Message::Peers(peers); let mut msg = Message::Peers(peers);
try!(self.broadcast_msg(&mut msg)); try!(self.broadcast_msg(&mut msg));
// Output beacon
let beacon = self.beacon_serializer.encode(&self.peers.subset(3));
//TODO: publish beacon
debug!("Beacon: {}", beacon);
// Reschedule for next update // Reschedule for next update
self.next_peerlist = now + Time::from(self.update_freq); self.next_peerlist = now + Time::from(self.update_freq);
} }
@ -478,21 +496,65 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
self.next_stats_out = now + STATS_INTERVAL; self.next_stats_out = now + STATS_INTERVAL;
self.traffic.period(Some(60)); self.traffic.period(Some(60));
} }
if let Some(peers) = self.beacon_serializer.get_cmd_results() {
debug!("Loaded beacon with peers: {:?}", peers);
for peer in peers {
try!(self.connect_sock(peer));
}
}
if self.next_beacon < now {
try!(self.store_beacon());
try!(self.load_beacon());
self.next_beacon = now + Time::from(self.config.beacon_interval);
}
Ok(())
}
/// Stores the beacon
fn store_beacon(&mut self) -> Result<(), Error> {
if let Some(ref path) = self.config.beacon_store {
let peers: Vec<_> = self.own_addresses.choose_multiple(&mut thread_rng(),3).cloned().collect();
if path.starts_with('|') {
try!(self.beacon_serializer.write_to_cmd(&peers, &path[1..]).map_err(|e| Error::Beacon("Failed to call beacon command", e)));
} else {
try!(self.beacon_serializer.write_to_file(&peers, &path).map_err(|e| Error::Beacon("Failed to write beacon to file", e)));
}
}
Ok(())
}
/// Loads the beacon
fn load_beacon(&mut self) -> Result<(), Error> {
let peers;
if let Some(ref path) = self.config.beacon_load {
if path.starts_with('|') {
try!(self.beacon_serializer.read_from_cmd(&path[1..], Some(50)).map_err(|e| Error::Beacon("Failed to call beacon command", e)));
return Ok(())
} else {
peers = try!(self.beacon_serializer.read_from_file(&path, Some(50)).map_err(|e| Error::Beacon("Failed to read beacon from file", e)));
}
} else {
return Ok(())
}
debug!("Loaded beacon with peers: {:?}", peers);
for peer in peers {
try!(self.connect_sock(peer));
}
Ok(()) Ok(())
} }
/// 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.stats_file.is_none() { return Ok(()) } if self.config.stats_file.is_none() { return Ok(()) }
debug!("Writing out stats"); debug!("Writing out stats");
let mut f = try!(File::create(self.stats_file.as_ref().unwrap())); let mut f = try!(File::create(self.config.stats_file.as_ref().unwrap()));
try!(self.peers.write_out(&mut f)); try!(self.peers.write_out(&mut f));
try!(writeln!(&mut f)); try!(writeln!(&mut f));
try!(self.table.write_out(&mut f)); try!(self.table.write_out(&mut f));
try!(writeln!(&mut f)); try!(writeln!(&mut f));
try!(self.traffic.write_out(&mut f)); try!(self.traffic.write_out(&mut f));
try!(writeln!(&mut f)); try!(writeln!(&mut f));
try!(fs::set_permissions(self.stats_file.as_ref().unwrap(), Permissions::from_mode(0o644))); try!(fs::set_permissions(self.config.stats_file.as_ref().unwrap(), Permissions::from_mode(0o644)));
Ok(()) Ok(())
} }
@ -525,7 +587,7 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
// 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);
try!(self.connect(&addr)); try!(self.connect_sock(addr));
} }
}, },
None => { None => {
@ -594,16 +656,14 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
Message::Peers(peers) => { Message::Peers(peers) => {
// Connect to sender if not connected // Connect to sender if not connected
if !self.peers.contains_addr(&peer) { if !self.peers.contains_addr(&peer) {
try!(self.connect(&peer)); try!(self.connect_sock(peer));
} }
if let Some(node_id) = self.peers.get_node_id(&peer) { if let Some(node_id) = self.peers.get_node_id(&peer) {
self.peers.make_primary(node_id, peer); self.peers.make_primary(node_id, peer);
} }
// Connect to all peers in the message // Connect to all peers in the message
for p in &peers { for p in &peers {
if ! self.peers.contains_addr(p) && ! self.blacklist_peers.contains(p) { try!(self.connect_sock(*p));
try!(self.connect(p));
}
} }
// Refresh peer // Refresh peer
self.peers.refresh(&peer); self.peers.refresh(&peer);
@ -611,7 +671,7 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
Message::Init(stage, node_id, ranges) => { Message::Init(stage, node_id, ranges) => {
// Avoid connecting to self // Avoid connecting to self
if node_id == self.node_id { if node_id == self.node_id {
self.blacklist_peers.push(peer); self.own_addresses.push(peer);
return Ok(()) return Ok(())
} }
// Add sender as peer or as alternative address to existing peer // Add sender as peer or as alternative address to existing peer
@ -647,8 +707,15 @@ impl<P: Protocol, T: Table> GenericCloud<P, T> {
/// `handle_net_message` method. It will also read from the device and call /// `handle_net_message` method. It will also read from the device and call
/// `handle_interface_data` for each packet read. /// `handle_interface_data` for each packet read.
/// Also, this method will call `housekeep` every second. /// Also, this method will call `housekeep` every second.
#[allow(unknown_lints,clippy::cyclomatic_complexity)] #[allow(unknown_lints, clippy::cyclomatic_complexity)]
pub fn run(&mut self) { pub fn run(&mut self) {
match self.address() {
Err(err) => error!("Failed to obtain local addresses: {}", err),
Ok((v4, v6)) => {
self.own_addresses.push(v4);
self.own_addresses.push(v6);
}
}
let dummy_time = Instant::now(); let dummy_time = Instant::now();
let trap = Trap::trap(&[Signal::SIGINT, Signal::SIGTERM, Signal::SIGQUIT]); let trap = Trap::trap(&[Signal::SIGINT, Signal::SIGTERM, Signal::SIGQUIT]);
let mut poll_handle = try_fail!(Poll::new(3), "Failed to create poll handle: {}"); let mut poll_handle = try_fail!(Poll::new(3), "Failed to create poll handle: {}");

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use super::{MAGIC, Args}; use super::{MAGIC, Args};
@ -30,6 +30,9 @@ pub struct Config {
pub peers: Vec<String>, pub peers: Vec<String>,
pub peer_timeout: Duration, pub peer_timeout: Duration,
pub keepalive: Option<Duration>, pub keepalive: Option<Duration>,
pub beacon_store: Option<String>,
pub beacon_load: Option<String>,
pub beacon_interval: Duration,
pub mode: Mode, pub mode: Mode,
pub dst_timeout: Duration, pub dst_timeout: Duration,
pub subnets: Vec<String>, pub subnets: Vec<String>,
@ -49,6 +52,7 @@ impl Default for Config {
crypto: CryptoMethod::ChaCha20, shared_key: None, crypto: CryptoMethod::ChaCha20, shared_key: None,
magic: None, magic: None,
port: 3210, peers: vec![], peer_timeout: 1800, keepalive: None, port: 3210, peers: vec![], peer_timeout: 1800, keepalive: None,
beacon_store: None, beacon_load: None, beacon_interval: 3600,
mode: Mode::Normal, dst_timeout: 300, mode: Mode::Normal, dst_timeout: 300,
subnets: vec![], subnets: vec![],
port_forwarding: true, port_forwarding: true,
@ -99,6 +103,15 @@ impl Config {
if let Some(val) = file.keepalive { if let Some(val) = file.keepalive {
self.keepalive = Some(val); self.keepalive = Some(val);
} }
if let Some(val) = file.beacon_store {
self.beacon_store = Some(val);
}
if let Some(val) = file.beacon_load {
self.beacon_load = Some(val);
}
if let Some(val) = file.beacon_interval {
self.beacon_interval = val;
}
if let Some(val) = file.mode { if let Some(val) = file.mode {
self.mode = val; self.mode = val;
} }
@ -164,6 +177,15 @@ impl Config {
if let Some(val) = args.flag_keepalive { if let Some(val) = args.flag_keepalive {
self.keepalive = Some(val); self.keepalive = Some(val);
} }
if let Some(val) = args.flag_beacon_store {
self.beacon_store = Some(val);
}
if let Some(val) = args.flag_beacon_load {
self.beacon_load = Some(val);
}
if let Some(val) = args.flag_beacon_interval {
self.beacon_interval = val;
}
if let Some(val) = args.flag_mode { if let Some(val) = args.flag_mode {
self.mode = val; self.mode = val;
} }
@ -233,6 +255,9 @@ pub struct ConfigFile {
pub peers: Option<Vec<String>>, pub peers: Option<Vec<String>>,
pub peer_timeout: Option<Duration>, pub peer_timeout: Option<Duration>,
pub keepalive: Option<Duration>, pub keepalive: Option<Duration>,
pub beacon_store: Option<String>,
pub beacon_load: Option<String>,
pub beacon_interval: Option<Duration>,
pub mode: Option<Mode>, pub mode: Option<Mode>,
pub dst_timeout: Option<Duration>, pub dst_timeout: Option<Duration>,
pub subnets: Option<Vec<String>>, pub subnets: Option<Vec<String>>,
@ -262,6 +287,9 @@ peers:
peer_timeout: 1800 peer_timeout: 1800
keepalive: 840 keepalive: 840
dst_timeout: 300 dst_timeout: 300
beacon_store: /run/vpncloud.beacon.out
beacon_load: /run/vpncloud.beacon.in
beacon_interval: 3600
mode: normal mode: normal
subnets: subnets:
- 10.0.1.0/24 - 10.0.1.0/24
@ -284,6 +312,9 @@ stats_file: /var/log/vpncloud.stats
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
peer_timeout: Some(1800), peer_timeout: Some(1800),
keepalive: Some(840), keepalive: Some(840),
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: Some(3600),
mode: Some(Mode::Normal), mode: Some(Mode::Normal),
dst_timeout: Some(300), dst_timeout: Some(300),
subnets: Some(vec!["10.0.1.0/24".to_string()]), subnets: Some(vec!["10.0.1.0/24".to_string()]),
@ -311,6 +342,9 @@ fn config_merge() {
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
peer_timeout: Some(1800), peer_timeout: Some(1800),
keepalive: Some(840), keepalive: Some(840),
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: Some(7200),
mode: Some(Mode::Normal), mode: Some(Mode::Normal),
dst_timeout: Some(300), dst_timeout: Some(300),
subnets: Some(vec!["10.0.1.0/24".to_string()]), subnets: Some(vec!["10.0.1.0/24".to_string()]),
@ -334,6 +368,9 @@ fn config_merge() {
peer_timeout: 1800, peer_timeout: 1800,
keepalive: Some(840), keepalive: Some(840),
dst_timeout: 300, dst_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: 7200,
mode: Mode::Normal, mode: Mode::Normal,
port_forwarding: true, port_forwarding: true,
subnets: vec!["10.0.1.0/24".to_string()], subnets: vec!["10.0.1.0/24".to_string()],
@ -356,6 +393,9 @@ fn config_merge() {
flag_peer_timeout: Some(1801), flag_peer_timeout: Some(1801),
flag_keepalive: Some(850), flag_keepalive: Some(850),
flag_dst_timeout: Some(301), flag_dst_timeout: Some(301),
flag_beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
flag_beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
flag_beacon_interval: Some(3600),
flag_mode: Some(Mode::Switch), flag_mode: Some(Mode::Switch),
flag_subnet: vec![], flag_subnet: vec![],
flag_connect: vec!["another:3210".to_string()], flag_connect: vec!["another:3210".to_string()],
@ -381,6 +421,9 @@ fn config_merge() {
peer_timeout: 1801, peer_timeout: 1801,
keepalive: Some(850), keepalive: Some(850),
dst_timeout: 301, dst_timeout: 301,
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
beacon_interval: 3600,
mode: Mode::Switch, mode: Mode::Switch,
port_forwarding: false, port_forwarding: false,
subnets: vec!["10.0.1.0/24".to_string()], subnets: vec!["10.0.1.0/24".to_string()],

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::net::SocketAddr; use std::net::SocketAddr;

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::net::SocketAddr; use std::net::SocketAddr;

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
#![cfg_attr(feature = "bench", feature(test))] #![cfg_attr(feature = "bench", feature(test))]
@ -82,6 +82,9 @@ pub struct Args {
flag_peer_timeout: Option<Duration>, flag_peer_timeout: Option<Duration>,
flag_keepalive: Option<Duration>, flag_keepalive: Option<Duration>,
flag_dst_timeout: Option<Duration>, flag_dst_timeout: Option<Duration>,
flag_beacon_store: Option<String>,
flag_beacon_load: Option<String>,
flag_beacon_interval: Option<Duration>,
flag_verbose: bool, flag_verbose: bool,
flag_quiet: bool, flag_quiet: bool,
flag_ifup: Option<String>, flag_ifup: Option<String>,

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use libc; use libc;

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::net::{SocketAddrV4, UdpSocket, SocketAddr}; use std::net::{SocketAddrV4, UdpSocket, SocketAddr};

View File

@ -1,3 +1,7 @@
// VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2018-2019 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md)
use std::net::SocketAddr; use std::net::SocketAddr;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{self, Write}; use std::io::{self, Write};

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::net::{SocketAddr, Ipv4Addr, Ipv6Addr}; use std::net::{SocketAddr, Ipv4Addr, Ipv6Addr};
@ -227,7 +227,8 @@ pub enum 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) File(&'static str, io::Error),
Beacon(&'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> {
@ -238,7 +239,8 @@ 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) Error::File(msg, ref err) => write!(formatter, "{}: {:?}", msg, err),
Error::Beacon(msg, ref err) => write!(formatter, "{}: {:?}", msg, err)
} }
} }
} }

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::fmt; use std::fmt;

View File

@ -23,6 +23,9 @@ Options:
--keepalive <secs> Periodically send message to keep --keepalive <secs> Periodically send message to keep
connections alive. connections alive.
--dst-timeout <secs> Switch table entry timeout in seconds. --dst-timeout <secs> Switch table entry timeout in seconds.
--beacon-store <path|command> The file or command to store the beacon.
--beacon-load <path|command> The file or command to load the beacon.
--beacon-interval <secs> Beacon store/load interval in seconds.
--ifup <command> A command to setup the network interface. --ifup <command> A command to setup the network interface.
--ifdown <command> A command to bring down the network --ifdown <command> A command to bring down the network
interface. interface.

View File

@ -1,5 +1,5 @@
// VpnCloud - Peer-to-Peer VPN // VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2017 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)
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};

View File

@ -86,6 +86,32 @@ vpncloud(1) -- Peer-to-peer VPN
mode. Addresses that have not been seen for the given period of time will mode. Addresses that have not been seen for the given period of time will
be forgotten. [default: `300`] be forgotten. [default: `300`]
* `--beacon-store <path|command>`:
Periodically store beacons containing the address of this node in the given
file or via the given command. If the parameter value starts with a pipe
character (`|`), the rest of the value is interpreted as a shell command.
Otherwise the value is interpreted as a file to write the beacon to.
If this parameter is not given, beacon storage is disabled.
Please see the section **BEACONS** for more information.
* `--beacon-load <path|command>`:
Periodically load beacons containing the addresses of other nodes from the
given file or via the given command. If the parameter value starts with a
pipe character (`|`), the rest of the value is interpreted as a shell
command. Otherwise the value is interpreted as a file to read the beacon
from.
If this parameter is not given, beacon loading is disabled.
Please see the section **BEACONS** for more information.
* `--beacon-interval <secs>`:
Beacon storage/loading interval in seconds. If configured to do so via
`--beacon-store` and `--beacon-load`, the node will periodically store its
beacon and load beacons of other nodes. This parameter defines the interval
in seconds. [default: `3600`]
* `--ifup <command>`: * `--ifup <command>`:
A command to setup the network interface. The command will be run (as A command to setup the network interface. The command will be run (as
@ -283,6 +309,9 @@ detailed descriptions of the options.
* `port`: The port number on which to listen for data. Same as `--listen` * `port`: The port number on which to listen for data. Same as `--listen`
* `peers`: A list of addresses to connect to. See `--connect` * `peers`: A list of addresses to connect to. See `--connect`
* `peer_timeout`: Peer timeout in seconds. Same as`--peer-timeout` * `peer_timeout`: Peer timeout in seconds. Same as`--peer-timeout`
* `beacon_store`: Path or command to store beacons. Same as `--beacon-store`
* `beacon_load`: Path or command to load beacons. Same as `--beacon-load`
* `beacon_interval`: Interval for loading and storing beacons in seconds. Same as `--beacon-interval`
* `mode`: The mode of the VPN. Same as `--mode` * `mode`: The mode of the VPN. Same as `--mode`
* `dst_timeout`: Switch table entry timeout in seconds. Same as `--dst-timeout` * `dst_timeout`: Switch table entry timeout in seconds. Same as `--dst-timeout`
* `subnets`: A list of local subnets to use. See `--subnet` * `subnets`: A list of local subnets to use. See `--subnet`
@ -314,6 +343,38 @@ group: nogroup
pid_file: /run/vpncloud.pid pid_file: /run/vpncloud.pid
## BEACONS
Beacons are short character sequences that contain a timestamp and a list of
addresses. They can be published and retrieved by other nodes to find peers
without the need for static addresses.
The beacons are short (less than 100 characters), encrypted and encoded with
printable characters to allow publishing them in various places on the
internet, e.g.:
- On shared drives or synchronized folders (e.g. on Dropbox)
- Via a dedicated database
- Via a general purpose message board of message service (e.g. Twitter)
The beacons are very robust. They only consist of alphanumeric characters
and can be interleaved with non-alphanumeric characters (e.g. whitespace).
Also the beacons contain a prefix and suffix that depends on the configured
network magic and secret key (if set) so that all nodes can find beacons in
a long text.
When beacons are stored or loaded via a command (using the pipe character `|`),
the command is interpreted using the configured shell `sh`. This command has
access to the following environment variables:
* `$begin`: The prefix of the beacon.
* `$end`: The suffix of the beacon.
* `$data` (only on store): The middle part of the beacon. Do not use this
without prefix and suffix!
* `$beacon` (only on store): The full beacon consisting of prefix, data and
suffix.
The commands are called in separate threads, so even longer running commands
will not block the node.
## NETWORK PROTOCOL ## NETWORK PROTOCOL
The protocol of VpnCloud is kept as simple as possible to allow other The protocol of VpnCloud is kept as simple as possible to allow other