From 537cb55c41c32a7bf94608a5119748466be0f8e8 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Tue, 19 Feb 2019 22:04:21 +0100 Subject: [PATCH] Implemented beacon support --- .gitignore | 3 +- CHANGELOG.md | 2 + README.md | 4 +- src/beacon.rs | 99 +++++++++++++++++++++++++++++++++++- src/benches.rs | 2 +- src/c/tuntap.c | 2 +- src/cloud.rs | 113 ++++++++++++++++++++++++++++++++--------- src/config.rs | 45 +++++++++++++++- src/device.rs | 2 +- src/ethernet.rs | 2 +- src/ip.rs | 2 +- src/main.rs | 5 +- src/poll/epoll.rs | 2 +- src/poll/mod.rs | 2 +- src/port_forwarding.rs | 2 +- src/traffic.rs | 4 ++ src/types.rs | 8 +-- src/udpmessage.rs | 2 +- src/usage.txt | 3 ++ src/util.rs | 2 +- vpncloud.md | 61 ++++++++++++++++++++++ 21 files changed, 323 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index c19bf1b..aecf01e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ vpncloud-oldnodes deb/vpncloud/vpncloud deb/vpncloud/vpncloud.1* Stats.ods -.sodium-build -wiki +dist diff --git a/CHANGELOG.md b/CHANGELOG.md index 52312b3..50a38d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ This project follows [semantic versioning](http://semver.org). ### UNRELEASED +- [added] Added ability to publish small beacons for rendezvous - [changed] Allow to build binary without manpage +- [fixed] Fixed bug that could cause repeated initialization messages ### v0.9.1 (2019-02-16) diff --git a/README.md b/README.md index 6793bcc..feb9261 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ VpnCloud - Peer-to-Peer VPN --------------------------- -[![Build Status](https://travis-ci.org/dswd/vpncloud.rs.svg?branch=master)](https://travis-ci.org/dswd/vpncloud.rs) -[![Coverage Status](https://coveralls.io/repos/dswd/vpncloud.rs/badge.svg?branch=master&service=github)](https://coveralls.io/github/dswd/vpncloud.rs?branch=master) +[![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/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 the host and forwards all received data via UDP to the destination. VpnCloud diff --git a/src/beacon.rs b/src/beacon.rs index e8b372a..83705aa 100644 --- a/src/beacon.rs +++ b/src/beacon.rs @@ -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 ring::digest; 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 std::net::{SocketAddr, SocketAddrV4, Ipv4Addr, SocketAddrV6, Ipv6Addr}; @@ -19,16 +32,27 @@ fn now_hour_16() -> u16 { ((now() / 3600) & 0xffff) as u16 } +struct FutureResult { + has_result: AtomicBool, + result: Mutex +} + +#[derive(Clone)] pub struct BeaconSerializer { magic: Vec, - shared_key: Vec + shared_key: Vec, + future_peers: Arc>>, } impl BeaconSerializer { pub fn new(magic: &[u8], shared_key: &[u8]) -> Self { BeaconSerializer { 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()) } + pub fn write_to_file>(&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, now_hour: u16) -> Vec { let data = base_62_sanitize(data); let mut peers = Vec::new(); @@ -185,6 +238,48 @@ impl BeaconSerializer { pub fn decode(&self, data: &str, ttl_hours: Option) -> Vec { self.decode_internal(data, ttl_hours, now_hour_16()) } + + pub fn read_from_file>(&self, path: P, ttl_hours: Option) -> Result, 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) -> 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> { + 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 + } + } } diff --git a/src/benches.rs b/src/benches.rs index e152435..cede7c0 100644 --- a/src/benches.rs +++ b/src/benches.rs @@ -1,5 +1,5 @@ // 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) use test::Bencher; diff --git a/src/c/tuntap.c b/src/c/tuntap.c index d729cbf..a73b877 100644 --- a/src/c/tuntap.c +++ b/src/c/tuntap.c @@ -1,5 +1,5 @@ // 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) #include diff --git a/src/cloud.rs b/src/cloud.rs index 4a004df..cd9543b 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -1,5 +1,5 @@ // 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) use std::net::{SocketAddr, ToSocketAddrs}; @@ -140,6 +140,7 @@ impl PeerList { peer.alt_addrs.retain(|i| i != &addr); peer.alt_addrs.push(old_addr); self.peers.insert(addr, peer); + self.addresses.insert(addr, node_id); } #[inline] @@ -202,6 +203,7 @@ pub struct ReconnectEntry { pub struct GenericCloud { + config: Config, magic: HeaderMagic, node_id: NodeId, peers: PeerList, @@ -209,7 +211,7 @@ pub struct GenericCloud { learning: bool, broadcast: bool, reconnect_peers: Vec, - blacklist_peers: Vec, + own_addresses: Vec, table: T, socket4: UdpSocket, socket6: UdpSocket, @@ -220,9 +222,9 @@ pub struct GenericCloud { buffer_out: [u8; 64*1024], next_housekeep: Time, next_stats_out: Time, + next_beacon: Time, port_forwarding: Option, traffic: TrafficStats, - stats_file: Option, beacon_serializer: BeaconSerializer, _dummy_p: PhantomData

, } @@ -251,7 +253,7 @@ impl GenericCloud { learning, broadcast, reconnect_peers: Vec::new(), - blacklist_peers: Vec::new(), + own_addresses: Vec::new(), table, socket4, socket6, @@ -261,11 +263,12 @@ impl GenericCloud { buffer_out: [0; 64*1024], next_housekeep: now(), next_stats_out: now() + STATS_INTERVAL, + next_beacon: now(), port_forwarding, traffic: TrafficStats::default(), - stats_file: config.stats_file.clone(), beacon_serializer: BeaconSerializer::new(&config.get_magic(), crypto.get_key()), crypto, + config: config.clone(), _dummy_p: PhantomData, } } @@ -356,14 +359,14 @@ impl GenericCloud { }) } - /// Returns whether the address is blacklisted + /// Returns whether the address is of this node /// /// # Errors /// Returns an `Error::SocketError` if the given address is a name that failed to resolve to /// actual addresses. - fn is_blacklisted(&self, addr: Addr) -> Result { + fn is_own_address(&self, addr: Addr) -> Result { for addr in try!(resolve(&addr)) { - if self.blacklist_peers.contains(&addr) { + if self.own_addresses.contains(&addr) { return Ok(true); } } @@ -379,7 +382,7 @@ impl GenericCloud { /// # Errors /// This method returns `Error::NameError` if the address is a name that fails to resolve. pub fn connect(&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(()) } debug!("Connecting to {:?}", addr); @@ -394,6 +397,25 @@ impl GenericCloud { 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 /// /// This method executes several tasks: @@ -426,10 +448,6 @@ impl GenericCloud { // ...and send them to all peers let mut msg = Message::Peers(peers); 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 self.next_peerlist = now + Time::from(self.update_freq); } @@ -478,21 +496,65 @@ impl GenericCloud { self.next_stats_out = now + STATS_INTERVAL; 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(()) } /// 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(()) } + if self.config.stats_file.is_none() { return Ok(()) } 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!(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)); - 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(()) } @@ -525,7 +587,7 @@ impl GenericCloud { // to reconnect. warn!("Destination for {} not found in peers: {}", dst, addr); self.table.remove(&dst); - try!(self.connect(&addr)); + try!(self.connect_sock(addr)); } }, None => { @@ -594,16 +656,14 @@ impl GenericCloud { Message::Peers(peers) => { // Connect to sender if not connected 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) { self.peers.make_primary(node_id, peer); } // Connect to all peers in the message for p in &peers { - if ! self.peers.contains_addr(p) && ! self.blacklist_peers.contains(p) { - try!(self.connect(p)); - } + try!(self.connect_sock(*p)); } // Refresh peer self.peers.refresh(&peer); @@ -611,7 +671,7 @@ impl GenericCloud { Message::Init(stage, node_id, ranges) => { // Avoid connecting to self if node_id == self.node_id { - self.blacklist_peers.push(peer); + self.own_addresses.push(peer); return Ok(()) } // Add sender as peer or as alternative address to existing peer @@ -647,8 +707,15 @@ impl GenericCloud { /// `handle_net_message` method. It will also read from the device and call /// `handle_interface_data` for each packet read. /// 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) { + 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 trap = Trap::trap(&[Signal::SIGINT, Signal::SIGTERM, Signal::SIGQUIT]); let mut poll_handle = try_fail!(Poll::new(3), "Failed to create poll handle: {}"); diff --git a/src/config.rs b/src/config.rs index 28c25e2..03fcbc3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ // 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) use super::{MAGIC, Args}; @@ -30,6 +30,9 @@ pub struct Config { pub peers: Vec, pub peer_timeout: Duration, pub keepalive: Option, + pub beacon_store: Option, + pub beacon_load: Option, + pub beacon_interval: Duration, pub mode: Mode, pub dst_timeout: Duration, pub subnets: Vec, @@ -49,6 +52,7 @@ impl Default for Config { crypto: CryptoMethod::ChaCha20, shared_key: None, magic: 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, subnets: vec![], port_forwarding: true, @@ -99,6 +103,15 @@ impl Config { if let Some(val) = file.keepalive { 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 { self.mode = val; } @@ -164,6 +177,15 @@ impl Config { if let Some(val) = args.flag_keepalive { 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 { self.mode = val; } @@ -233,6 +255,9 @@ pub struct ConfigFile { pub peers: Option>, pub peer_timeout: Option, pub keepalive: Option, + pub beacon_store: Option, + pub beacon_load: Option, + pub beacon_interval: Option, pub mode: Option, pub dst_timeout: Option, pub subnets: Option>, @@ -262,6 +287,9 @@ peers: peer_timeout: 1800 keepalive: 840 dst_timeout: 300 +beacon_store: /run/vpncloud.beacon.out +beacon_load: /run/vpncloud.beacon.in +beacon_interval: 3600 mode: normal subnets: - 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()]), peer_timeout: Some(1800), 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), dst_timeout: Some(300), 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()]), peer_timeout: Some(1800), 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), dst_timeout: Some(300), subnets: Some(vec!["10.0.1.0/24".to_string()]), @@ -334,6 +368,9 @@ fn config_merge() { peer_timeout: 1800, keepalive: Some(840), 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, port_forwarding: true, subnets: vec!["10.0.1.0/24".to_string()], @@ -356,6 +393,9 @@ fn config_merge() { flag_peer_timeout: Some(1801), flag_keepalive: Some(850), 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_subnet: vec![], flag_connect: vec!["another:3210".to_string()], @@ -381,6 +421,9 @@ fn config_merge() { peer_timeout: 1801, keepalive: Some(850), 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, port_forwarding: false, subnets: vec!["10.0.1.0/24".to_string()], diff --git a/src/device.rs b/src/device.rs index 21f3ef9..ad3726a 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,5 +1,5 @@ // 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) use std::os::unix::io::{AsRawFd, RawFd}; diff --git a/src/ethernet.rs b/src/ethernet.rs index fb3e621..9845190 100644 --- a/src/ethernet.rs +++ b/src/ethernet.rs @@ -1,5 +1,5 @@ // 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) use std::net::SocketAddr; diff --git a/src/ip.rs b/src/ip.rs index 338493b..ddd2518 100644 --- a/src/ip.rs +++ b/src/ip.rs @@ -1,5 +1,5 @@ // 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) use std::net::SocketAddr; diff --git a/src/main.rs b/src/main.rs index e43e88a..49eaad1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ // 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) #![cfg_attr(feature = "bench", feature(test))] @@ -82,6 +82,9 @@ pub struct Args { flag_peer_timeout: Option, flag_keepalive: Option, flag_dst_timeout: Option, + flag_beacon_store: Option, + flag_beacon_load: Option, + flag_beacon_interval: Option, flag_verbose: bool, flag_quiet: bool, flag_ifup: Option, diff --git a/src/poll/epoll.rs b/src/poll/epoll.rs index aefe225..5ab3eba 100644 --- a/src/poll/epoll.rs +++ b/src/poll/epoll.rs @@ -1,5 +1,5 @@ // 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) use libc; diff --git a/src/poll/mod.rs b/src/poll/mod.rs index 77879ce..3f9282a 100644 --- a/src/poll/mod.rs +++ b/src/poll/mod.rs @@ -1,5 +1,5 @@ // 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) #[cfg(any(target_os = "linux", target_os = "android"))] diff --git a/src/port_forwarding.rs b/src/port_forwarding.rs index 7e53779..2c6834c 100644 --- a/src/port_forwarding.rs +++ b/src/port_forwarding.rs @@ -1,5 +1,5 @@ // 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) use std::net::{SocketAddrV4, UdpSocket, SocketAddr}; diff --git a/src/traffic.rs b/src/traffic.rs index 2fcc406..4d30353 100644 --- a/src/traffic.rs +++ b/src/traffic.rs @@ -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::collections::HashMap; use std::io::{self, Write}; diff --git a/src/types.rs b/src/types.rs index a3ca926..6ac1ac1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,5 @@ // 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) use std::net::{SocketAddr, Ipv4Addr, Ipv6Addr}; @@ -227,7 +227,8 @@ pub enum Error { Name(String), TunTapDev(&'static str, io::Error), Crypto(&'static str), - File(&'static str, io::Error) + File(&'static str, io::Error), + Beacon(&'static str, io::Error) } impl fmt::Display for 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::Name(ref name) => write!(formatter, "failed to resolve name '{}'", name), 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) } } } diff --git a/src/udpmessage.rs b/src/udpmessage.rs index 0014083..7cb9e6c 100644 --- a/src/udpmessage.rs +++ b/src/udpmessage.rs @@ -1,5 +1,5 @@ // 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) use std::fmt; diff --git a/src/usage.txt b/src/usage.txt index e095665..0a7a9ce 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -23,6 +23,9 @@ Options: --keepalive Periodically send message to keep connections alive. --dst-timeout Switch table entry timeout in seconds. + --beacon-store The file or command to store the beacon. + --beacon-load The file or command to load the beacon. + --beacon-interval Beacon store/load interval in seconds. --ifup A command to setup the network interface. --ifdown A command to bring down the network interface. diff --git a/src/util.rs b/src/util.rs index c9afa78..c69df8c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,5 @@ // 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) use std::net::{SocketAddr, ToSocketAddrs}; diff --git a/vpncloud.md b/vpncloud.md index 99725ed..8b8ca42 100644 --- a/vpncloud.md +++ b/vpncloud.md @@ -86,6 +86,32 @@ vpncloud(1) -- Peer-to-peer VPN mode. Addresses that have not been seen for the given period of time will be forgotten. [default: `300`] + * `--beacon-store `: + + 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 `: + + 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 `: + + 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 `: 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` * `peers`: A list of addresses to connect to. See `--connect` * `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` * `dst_timeout`: Switch table entry timeout in seconds. Same as `--dst-timeout` * `subnets`: A list of local subnets to use. See `--subnet` @@ -314,6 +343,38 @@ group: nogroup 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 The protocol of VpnCloud is kept as simple as possible to allow other