Prepare for release

pull/184/head v2.2.0
Dennis Schwerdel 2 years ago
parent 665b190257
commit 4fb92a36a6
  1. 12
      .devcontainer/Dockerfile
  2. 12
      .whitesource
  3. 4
      CHANGELOG.md
  4. 2
      Cargo.lock
  5. 4
      Cargo.toml
  6. 2
      README.md
  7. 16
      assets/changelog.txt
  8. 8
      contrib/asciinema-recorder/Dockerfile
  9. 4
      contrib/asciinema-recorder/config
  10. 6
      contrib/asciinema-recorder/record.sh
  11. 11
      contrib/asciinema-recorder/recorder.sh
  12. 85
      src/beacon.rs
  13. 143
      src/cloud.rs
  14. 182
      src/config.rs
  15. 59
      src/crypto/common.rs
  16. 25
      src/crypto/core.rs
  17. 172
      src/crypto/init.rs
  18. 2
      src/crypto/mod.rs
  19. 20
      src/crypto/rotate.rs
  20. 54
      src/device.rs
  21. 3
      src/error.rs
  22. 2
      src/installer.rs
  23. 40
      src/main.rs
  24. 26
      src/messages.rs
  25. 16
      src/net.rs
  26. 12
      src/oldconfig.rs
  27. 16
      src/payload.rs
  28. 8
      src/poll/epoll.rs
  29. 3
      src/poll/mod.rs
  30. 26
      src/port_forwarding.rs
  31. 28
      src/table.rs
  32. 11
      src/tests/common.rs
  33. 10
      src/traffic.rs
  34. 35
      src/types.rs
  35. 117
      src/wizard.rs
  36. 13
      src/wsproxy.rs

@ -1,10 +1,18 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/rust/.devcontainer/base.Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1
FROM mcr.microsoft.com/vscode/devcontainers/rust:1
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends asciidoctor
RUN rm /etc/localtime && ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime
RUN cargo install cargo-outdated cargo-cache
RUN chown vscode: -R /usr/local/rustup /usr/local/cargo
USER vscode
RUN rustup default 1.51.0 \
&& rustup component add clippy rust-src rustfmt
RUN cargo install cargo-outdated cargo-cache \
&& cargo cache -a

@ -1,12 +0,0 @@
{
"scanSettings": {
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff"
},
"issueSettings": {
"minSeverityLevel": "LOW"
}
}

@ -2,7 +2,7 @@
This project follows [semantic versioning](http://semver.org).
### UNRELEASED
### v2.2.0 (2021-04-06)
- [added] Service target file (thanks to mnhauke)
- [added] Added interactive configuration wizard
@ -10,7 +10,7 @@ This project follows [semantic versioning](http://semver.org).
- [added] Building static binaries
- [added] Building i686 rpm
- [changed] Restructured example config
- [changed] Changed Rust version to 1.50.0
- [changed] Changed Rust version to 1.51.0
- [changed] Updated dependencies
- [changed] Change permissions of /etc/vpncloud

2
Cargo.lock generated

@ -1268,7 +1268,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "vpncloud"
version = "2.1.0"
version = "2.2.0"
dependencies = [
"byteorder",
"criterion",

@ -1,6 +1,6 @@
[package]
name = "vpncloud"
version = "2.1.0"
version = "2.2.0"
authors = ["Dennis Schwerdel <schwerdel@googlemail.com>"]
build = "build.rs"
license = "GPL-3.0"
@ -12,7 +12,7 @@ readme = "README.md"
edition = "2018"
[package.metadata]
toolchain = "1.50.0"
toolchain = "1.51.0"
upx_version = "3.96"
[dependencies]

@ -19,7 +19,7 @@ peers:
- REMOTE_HOST:PORT
```
For more information, please see the [Website](https://vpncloud.ddswd.de) or the [Forum](https://groups.google.com/forum/#!forum/vpncloud).
For more information, please see the [Website](https://vpncloud.ddswd.de) or the [Discussions group](https://github.com/dswd/vpncloud/discussions).
### Project Status

@ -1,3 +1,17 @@
vpncloud (2.2.0) stable; urgency=medium
* [added] Service target file (thanks to mnhauke)
* [added] Added interactive configuration wizard
* [added] Support for (un-)installation
* [added] Building static binaries
* [added] Building i686 rpm
* [changed] Restructured example config
* [changed] Changed Rust version to 1.51.0
* [changed] Updated dependencies
* [changed] Change permissions of /etc/vpncloud
-- Dennis Schwerdel <schwerdel+vpncloud@googlemail.com> Tue, 06 Apr 2021 12:27:00 +0200
vpncloud (2.1.0) stable; urgency=medium
* [added] Support for websocket proxy mode
@ -9,7 +23,7 @@ vpncloud (2.1.0) stable; urgency=medium
* [fixed] Added missing peer address propagation
* [fixed] Fixed problem with peer addresses without port
-- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 06 Feb 2020 13:13:00 +0100
-- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 06 Feb 2021 13:13:00 +0100
vpncloud (2.0.1) stable; urgency=medium

@ -1,9 +1,13 @@
FROM ubuntu
RUN apt-get update && apt-get install -y asciinema
RUN apt-get update && apt-get install -y asciinema locales bash iputils-ping
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN mkdir /root/.asciinema
RUN mkdir /etc/vpncloud
WORKDIR /data
ADD config /root/.asciinema/config
RUN echo 'PS1="\[\e[00;34m\]\[\e[01;31m\]\u\[\e[00;01;34m\]@\[\e[00;34m\]node\[\e[01;31m\]:\[\e[00;34m\]\w\[\e[01;31m\]> \[\e[00m\]"' >> /root/.bashrc
RUN echo 'PS1="\[\e[00;34m\]\[\e[01;31m\]\u\[\e[00;01;34m\]@\[\e[00;34m\]node\[\e[01;31m\]:\[\e[00;34m\]\w\[\e[01;31m\]> \[\e[00m\]"' >> /root/.bashrc

@ -1,3 +1,3 @@
[record]
command = /usr/bin/bash -l
idle_time_limit = 2.5
command = bash -l
idle_time_limit = 2.5

@ -1,6 +0,0 @@
#!/bin/bash
set -e
docker build -t asciinema-recorder .
docker run -it --rm --network host -v $(pwd)/../../target/release/:/usr/local/bin/ -v $(pwd):/data asciinema-recorder asciinema "$@"

@ -0,0 +1,11 @@
#!/bin/bash
set -e
cd $(dirname $0)
docker build -t asciinema-recorder .
docker run -it --rm --network host \
-v $(pwd):/data \
-v /etc/hosts:/etc/hosts \
asciinema-recorder

@ -15,16 +15,15 @@ use std::{
process::{Command, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex
Arc, Mutex,
},
thread
thread,
};
use super::util::{from_base62, to_base62, Encoder, TimeSource};
use smallvec::SmallVec;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
const TYPE_BEGIN: u8 = 0;
const TYPE_END: u8 = 1;
const TYPE_DATA: u8 = 2;
@ -40,14 +39,14 @@ fn sha512(data: &[u8]) -> SmallVec<[u8; 64]> {
struct FutureResult<T> {
has_result: AtomicBool,
result: Mutex<T>
result: Mutex<T>,
}
#[derive(Clone)]
pub struct BeaconSerializer<TS> {
shared_key: Vec<u8>,
future_peers: Arc<FutureResult<Vec<SocketAddr>>>,
_dummy_ts: PhantomData<TS>
_dummy_ts: PhantomData<TS>,
}
impl<TS: TimeSource> BeaconSerializer<TS> {
@ -55,7 +54,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
Self {
shared_key: shared_key.to_owned(),
future_peers: Arc::new(FutureResult { has_result: AtomicBool::new(false), result: Mutex::new(Vec::new()) }),
_dummy_ts: PhantomData
_dummy_ts: PhantomData,
}
}
@ -105,7 +104,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
fn decrypt_data(&self, data: &mut Vec<u8>) -> bool {
if data.is_empty() {
return false
return false;
}
let seed = data.pop().unwrap() ^ self.get_keystream(TYPE_SEED, 0, 0)[0];
self.mask_with_keystream(data as &mut [u8], TYPE_DATA, seed);
@ -122,7 +121,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
for p in peers {
match *p {
SocketAddr::V4(addr) => v4addrs.push(addr),
SocketAddr::V6(addr) => v6addrs.push(addr)
SocketAddr::V6(addr) => v6addrs.push(addr),
}
}
// Add count of v4 addresses
@ -158,23 +157,23 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
let mut peers = Vec::new();
let mut pos = 0;
if data.len() < 4 {
return peers
return peers;
}
if !self.decrypt_data(&mut data) {
return peers
return peers;
}
let then = Wrapping(Encoder::read_u16(&data[pos..=pos + 1]));
if let Some(ttl) = ttl_hours {
let now = Wrapping(Self::now_hour_16());
if now - then > Wrapping(ttl) && then - now > Wrapping(ttl) {
return peers
return peers;
}
}
pos += 2;
let v4count = data[pos] as usize;
pos += 1;
if v4count * 6 > data.len() - pos || (data.len() - pos - v4count * 6) % 18 > 0 {
return peers
return peers;
}
for _ in 0..v4count {
assert!(data.len() >= pos + 6);
@ -198,7 +197,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
Ipv6Addr::new(ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]),
port,
0,
0
0,
));
peers.push(addr);
}
@ -262,14 +261,14 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
peers.append(&mut self.peerlist_decode(&data[start_pos..end_pos], ttl_hours));
pos = start_pos
} else {
break
break;
}
}
peers
}
pub fn read_from_file<P: AsRef<Path>>(
&self, path: P, ttl_hours: Option<u16>
&self, path: P, ttl_hours: Option<u16>,
) -> Result<Vec<SocketAddr>, io::Error> {
let mut f = File::open(&path)?;
let mut contents = String::new();
@ -316,26 +315,22 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
}
}
#[cfg(test)] use crate::util::MockTimeSource;
#[cfg(test)] use std::str::FromStr;
#[cfg(test)] use std::time::Duration;
#[cfg(test)]
use crate::util::MockTimeSource;
#[cfg(test)]
use std::str::FromStr;
#[cfg(test)]
use std::time::Duration;
#[test]
fn encode() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let mut peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let mut peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
assert_eq!("WsHI31EWDMBYxvITiILIrm2k9gEik22E", ser.encode(&peers));
peers.push(SocketAddr::from_str("[::1]:5678").unwrap());
assert_eq!("WsHI3GXKaXCveo6uejmZizZ72kR6Y0L9T7h49TXONp1ugfKvvvEik22E", ser.encode(&peers));
let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:54").unwrap()
];
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:54").unwrap()];
assert_eq!("WsHI32gm9eMSHP3Lm1GXcdP7rD3ik22E", ser.encode(&peers));
}
@ -343,10 +338,7 @@ fn encode() {
fn decode() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let mut peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let mut peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
assert_eq!(format!("{:?}", peers), format!("{:?}", ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None)));
peers.push(SocketAddr::from_str("[::1]:5678").unwrap());
assert_eq!(
@ -359,10 +351,7 @@ fn decode() {
fn decode_split() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
assert_eq!(
format!("{:?}", peers),
format!("{:?}", ser.decode("WsHI3-1E.WD:MB Yx\tvI\nTi(IL)Ir[m2]k9ügEäik22E", None))
@ -377,10 +366,7 @@ fn decode_split() {
fn decode_offset() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
assert_eq!(
format!("{:?}", peers),
format!("{:?}", ser.decode("Hello World: WsHI31EWDMBYxvITiILIrm2k9gEik22E! End of the World", None))
@ -391,10 +377,7 @@ fn decode_offset() {
fn decode_multiple() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
assert_eq!(
format!("{:?}", peers),
format!("{:?}", ser.decode("WsHI31HVpqxFNMNSPrvik22E WsHI34yOBcZIulKdtn2ik22E", None))
@ -438,15 +421,11 @@ fn decode_invalid() {
assert_eq!(2, ser.decode("WsHI3WsHI31EWDMBYxvITiILIrm2k9gEik22Eik22E", None).len());
}
#[test]
fn encode_decode() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
let data = ser.encode(&peers);
let peers2 = ser.decode(&data, None);
assert_eq!(format!("{:?}", peers), format!("{:?}", peers2));
@ -456,10 +435,7 @@ fn encode_decode() {
fn encode_decode_file() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
let file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
assert!(ser.write_to_file(&peers, file.path()).is_ok());
let peers2 = ser.read_from_file(file.path(), None);
@ -471,10 +447,7 @@ fn encode_decode_file() {
fn encode_decode_cmd() {
MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()];
let file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
assert!(ser.write_to_cmd(&peers, &format!("echo $beacon > {}", file.path().display())).is_ok());
thread::sleep(Duration::from_millis(100));

@ -12,7 +12,7 @@ use std::{
marker::PhantomData,
net::{SocketAddr, ToSocketAddrs},
path::Path,
str::FromStr
str::FromStr,
};
use fnv::FnvHasher;
@ -27,7 +27,7 @@ use crate::{
error::Error,
messages::{
AddrList, NodeInfo, PeerInfo, MESSAGE_TYPE_CLOSE, MESSAGE_TYPE_DATA, MESSAGE_TYPE_KEEPALIVE,
MESSAGE_TYPE_NODE_INFO
MESSAGE_TYPE_NODE_INFO,
},
net::{mapped_addr, Socket},
payload::Protocol,
@ -36,7 +36,7 @@ use crate::{
table::ClaimTable,
traffic::TrafficStats,
types::{Address, Mode, NodeId, Range, RangeList},
util::{addr_nice, bytes_to_hex, resolve, CtrlC, Duration, MsgBuffer, StatsdMsg, Time, TimeSource}
util::{addr_nice, bytes_to_hex, resolve, CtrlC, Duration, MsgBuffer, StatsdMsg, Time, TimeSource},
};
pub type Hash = BuildHasherDefault<FnvHasher>;
@ -54,7 +54,7 @@ struct PeerData {
timeout: Time,
peer_timeout: u16,
node_id: NodeId,
crypto: PeerCrypto<NodeInfo>
crypto: PeerCrypto<NodeInfo>,
}
#[derive(Clone)]
@ -64,10 +64,9 @@ pub struct ReconnectEntry {
tries: u16,
timeout: u16,
next: Time,
final_timeout: Option<Time>
final_timeout: Option<Time>,
}
pub struct GenericCloud<D: Device, P: Protocol, S: Socket, TS: TimeSource> {
node_id: NodeId,
config: Config,
@ -95,22 +94,22 @@ pub struct GenericCloud<D: Device, P: Protocol, S: Socket, TS: TimeSource> {
traffic: TrafficStats,
beacon_serializer: BeaconSerializer<TS>,
_dummy_p: PhantomData<P>,
_dummy_ts: PhantomData<TS>
_dummy_ts: PhantomData<TS>,
}
impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS> {
#[allow(clippy::too_many_arguments)]
pub fn new(config: &Config, socket: S, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>) -> Self {
pub fn new(
config: &Config, socket: S, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>,
) -> Self {
let (learning, broadcast) = match config.mode {
Mode::Normal => {
match config.device_type {
Type::Tap => (true, true),
Type::Tun => (false, false)
}
}
Mode::Normal => match config.device_type {
Type::Tap => (true, true),
Type::Tun => (false, false),
},
Mode::Router => (false, false),
Mode::Switch => (true, true),
Mode::Hub => (false, true)
Mode::Hub => (false, true),
};
let mut claims = SmallVec::with_capacity(config.claims.len());
for s in &config.claims {
@ -126,7 +125,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
Err(Error::DeviceIo(_, e)) if e.kind() == io::ErrorKind::AddrNotAvailable => {
info!("No address set on interface.")
}
Err(e) => error!("{}", e)
Err(e) => error!("{}", e),
}
}
let now = TS::now();
@ -161,7 +160,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
crypto,
config: config.clone(),
_dummy_p: PhantomData,
_dummy_ts: PhantomData
_dummy_ts: PhantomData,
};
res.initialize();
res
@ -191,7 +190,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
match self.socket.send(msg_data.message(), *addr) {
Ok(written) if written == msg_data.len() => Ok(()),
Ok(_) => Err(Error::Socket("Sent out truncated packet")),
Err(e) => Err(Error::SocketIo("IOError when sending", e))
Err(e) => Err(Error::SocketIo("IOError when sending", e)),
}?
}
Ok(())
@ -205,7 +204,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
match self.socket.send(msg.message(), addr) {
Ok(written) if written == msg.len() => Ok(()),
Ok(_) => Err(Error::Socket("Sent out truncated packet")),
Err(e) => Err(Error::SocketIo("IOError when sending", e))
Err(e) => Err(Error::SocketIo("IOError when sending", e)),
}
}
@ -215,7 +214,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
debug!("Sending msg with {} bytes to {}", msg.len(), addr);
let peer = match self.peers.get_mut(&addr) {
Some(peer) => peer,
None => return Err(Error::Message("Sending to node that is not a peer"))
None => return Err(Error::Message("Sending to node that is not a peer")),
};
peer.crypto.send_message(type_, msg)?;
self.send_to(addr, msg)
@ -258,7 +257,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
timeout: 1,
resolved,
next: now,
final_timeout: None
final_timeout: None,
})
}
@ -277,14 +276,14 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
|| self.peers.contains_key(addr)
|| self.pending_inits.contains_key(addr)
{
return Ok(())
return Ok(());
}
}
if !addrs.is_empty() {
self.config.call_hook(
"peer_connecting",
vec![("PEER", format!("{:?}", addr_nice(addrs[0]))), ("IFNAME", self.device.ifname().to_owned())],
true
true,
);
}
// Send a message to each resolved address
@ -310,7 +309,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
peers,
claims: self.claims.clone(),
peer_timeout: Some(self.peer_timeout_publish),
addrs: self.own_addresses.clone()
addrs: self.own_addresses.clone(),
}
}
@ -320,7 +319,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
|| self.own_addresses.contains(&addr)
|| self.pending_inits.contains_key(&addr)
{
return Ok(())
return Ok(());
}
debug!("Connecting to {:?}", addr);
let payload = self.create_node_info();
@ -340,7 +339,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
Err(_) => del.push(addr),
Ok(MessageResult::None) => (),
Ok(MessageResult::Reply) => self.send_to(addr, &mut msg)?,
Ok(_) => unreachable!()
Ok(_) => unreachable!(),
}
}
for addr in self.peers.keys().copied().collect::<SmallVec<[SocketAddr; 16]>>() {
@ -349,7 +348,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
Err(_) => del.push(addr),
Ok(MessageResult::None) => (),
Ok(MessageResult::Reply) => self.send_to(addr, &mut msg)?,
Ok(_) => unreachable!()
Ok(_) => unreachable!(),
}
}
for addr in del {
@ -366,7 +365,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
// Connect to those reconnect_peers that are due
for entry in self.reconnect_peers.clone() {
if entry.next > now {
continue
continue;
}
self.connect(&entry.resolved as &[SocketAddr])?;
}
@ -377,7 +376,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
entry.tries = 0;
entry.timeout = 1;
entry.next = now + 1;
continue
continue;
}
}
// Resolve entries anew
@ -385,19 +384,17 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
if *next_resolve <= now {
match resolve(address as &str) {
Ok(addrs) => entry.resolved = addrs,
Err(_) => {
match resolve(&format!("{}:{}", address, DEFAULT_PORT)) {
Ok(addrs) => entry.resolved = addrs,
Err(err) => warn!("Failed to resolve {}: {}", address, err)
}
}
Err(_) => match resolve(&format!("{}:{}", address, DEFAULT_PORT)) {
Ok(addrs) => entry.resolved = addrs,
Err(err) => warn!("Failed to resolve {}: {}", address, err),
},
}
*next_resolve = now + RESOLVE_INTERVAL;
}
}
// Ignore if next attempt is already in the future
if entry.next > now {
continue
continue;
}
// Exponential back-off: every 10 tries, the interval doubles
entry.tries += 1;
@ -502,7 +499,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
self.beacon_serializer
.read_from_cmd(path, Some(50))
.map_err(|e| Error::BeaconIo("Failed to call beacon command", e))?;
return Ok(())
return Ok(());
} else {
peers = self
.beacon_serializer
@ -510,7 +507,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
.map_err(|e| Error::BeaconIo("Failed to read beacon from file", e))?;
}
} else {
return Ok(())
return Ok(());
}
debug!("Loaded beacon with peers: {:?}", peers);
for peer in peers {
@ -595,7 +592,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
match self.socket.send(msg_data, *addr) {
Ok(written) if written == msg_data.len() => Ok(()),
Ok(_) => Err(Error::Socket("Sent out truncated packet")),
Err(e) => Err(Error::SocketIo("IOError when sending", e))
Err(e) => Err(Error::SocketIo("IOError when sending", e)),
}?
} else {
error!("Failed to resolve statsd server {}", endpoint);
@ -648,17 +645,20 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
("CLAIMS", info.claims.iter().map(|r| format!("{:?}", r)).collect::<Vec<String>>().join(" ")),
("NODE_ID", bytes_to_hex(&info.node_id)),
],
true
true,
);
if let Some(init) = self.pending_inits.remove(&addr) {
self.peers.insert(addr, PeerData {
addrs: info.addrs.clone(),
crypto: init,
node_id: info.node_id,
peer_timeout: info.peer_timeout.unwrap_or(DEFAULT_PEER_TIMEOUT),
last_seen: TS::now(),
timeout: TS::now() + self.config.peer_timeout as Time
});
self.peers.insert(
addr,
PeerData {
addrs: info.addrs.clone(),
crypto: init,
node_id: info.node_id,
peer_timeout: info.peer_timeout.unwrap_or(DEFAULT_PEER_TIMEOUT),
last_seen: TS::now(),
timeout: TS::now() + self.config.peer_timeout as Time,
},
);
self.update_peer_info(addr, Some(info))?;
} else {
error!("No init for new peer {}", addr_nice(addr));
@ -677,7 +677,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
("IFNAME", self.device.ifname().to_owned()),
("NODE_ID", bytes_to_hex(&peer.node_id)),
],
true
true,
);
}
}
@ -686,16 +686,16 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
'outer: for peer in peers {
for addr in &peer.addrs {
if self.peers.contains_key(addr) {
continue 'outer
continue 'outer;
}
}
if let Some(node_id) = peer.node_id {
if self.node_id == node_id {
continue 'outer
continue 'outer;
}
for p in self.peers.values() {
if p.node_id == node_id {
continue 'outer
continue 'outer;
}
}
}
@ -710,7 +710,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
peer.timeout = TS::now() + self.config.peer_timeout as Time
} else {
error!("Received peer update from non peer {}", addr_nice(addr));
return Ok(())
return Ok(());
}
if let Some(info) = info {
debug!("Adding claims of peer {}: {:?}", addr_nice(addr), info.claims);
@ -729,7 +729,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
self.traffic.count_in_payload(src, dst, len);
if let Err(e) = self.device.write(data) {
error!("Failed to send via device: {}", e);
return Err(e)
return Err(e);
}
if self.learning {
// Learn single address
@ -739,7 +739,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
}
fn handle_message(
&mut self, src: SocketAddr, msg_result: MessageResult<NodeInfo>, data: &mut MsgBuffer
&mut self, src: SocketAddr, msg_result: MessageResult<NodeInfo>, data: &mut MsgBuffer,
) -> Result<(), Error> {
// HOT PATH
match msg_result {
@ -756,7 +756,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
Ok(val) => val,
Err(err) => {
self.traffic.count_invalid_protocol(data.len());
return Err(err)
return Err(err);
}
};
self.update_peer_info(src, Some(info))?
@ -772,7 +772,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
_ => {
// COLD PATH
self.traffic.count_invalid_protocol(data.len());
return Err(Error::Message("Unknown message type"))
return Err(Error::Message("Unknown message type"));
}
}
}
@ -824,14 +824,14 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
("PEER", format!("{:?}", addr_nice(src))),
("IFNAME", self.device.ifname().to_owned()),
],
true
true,
);
self.pending_inits.insert(src, init);
Ok(res)
}
Err(err) => {
self.traffic.count_invalid_protocol(data.len());
return Err(err)
return Err(err);
}
}
}
@ -842,14 +842,14 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
// COLD PATH
info!("Ignoring non-init message from unknown peer {}", addr_nice(src));
self.traffic.count_invalid_protocol(data.len());
return Ok(())
return Ok(());
};
// HOT PATH
match msg_result {
Ok(val) => {
// HOT PATH
self.handle_message(src, val, data)
},
}
Err(err) => {
// COLD PATH
self.traffic.count_invalid_protocol(data.len());
@ -877,7 +877,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
self.config.call_hook(
"peer_disconnected",
vec![("PEER", format!("{:?}", addr_nice(src))), ("IFNAME", self.device.ifname().to_owned())],
true
true,
);
}
Err(e @ Error::CryptoInit(_)) => {
@ -910,7 +910,10 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
/// Also, this method will call `housekeep` every second.
pub fn run(&mut self) {
let ctrlc = CtrlC::new();
let waiter = try_fail!(WaitImpl::new(self.socket.as_raw_fd(), self.device.as_raw_fd(), 1000), "Failed to setup poll: {}");
let waiter = try_fail!(
WaitImpl::new(self.socket.as_raw_fd(), self.device.as_raw_fd(), 1000),
"Failed to setup poll: {}"
);
let mut buffer = MsgBuffer::new(SPACE_BEFORE);
let mut poll_error = false;
self.config.call_hook("vpn_started", vec![("IFNAME", self.device.ifname())], true);
@ -927,13 +930,13 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
}
WaitResult::Timeout => {}
WaitResult::Socket => self.handle_socket_event(&mut buffer),
WaitResult::Device => self.handle_device_event(&mut buffer)
WaitResult::Device => self.handle_device_event(&mut buffer),
}
if self.next_housekeep < TS::now() {
// COLD PATH
poll_error = false;
if ctrlc.was_pressed() {
break
break;
}
if let Err(e) = self.housekeep() {
error!("{}", e)
@ -957,10 +960,12 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
}
}
#[cfg(test)] use super::device::MockDevice;
#[cfg(test)] use super::net::MockSocket;
#[cfg(test)] use super::util::MockTimeSource;
#[cfg(test)]
use super::device::MockDevice;
#[cfg(test)]
use super::net::MockSocket;
#[cfg(test)]
use super::util::MockTimeSource;
#[cfg(test)]
impl<P: Protocol> GenericCloud<MockDevice, P, MockSocket, MockTimeSource> {

@ -2,22 +2,15 @@
// Copyright (C) 2015-2021 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md)
use super::{device::Type, types::Mode, util::Duration, util::run_cmd};
use super::{device::Type, types::Mode, util::run_cmd, util::Duration};
pub use crate::crypto::Config as CryptoConfig;
use std::{
cmp::max,
collections::HashMap,
ffi::OsStr,
process,
thread
};
use std::{cmp::max, collections::HashMap, ffi::OsStr, process, thread};
use structopt::{clap::Shell, StructOpt};
pub const DEFAULT_PEER_TIMEOUT: u16 = 300;
pub const DEFAULT_PORT: u16 = 3210;
#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct Config {
pub device_type: Type,
@ -52,7 +45,7 @@ pub struct Config {
pub user: Option<String>,
pub group: Option<String>,
pub hook: Option<String>,
pub hooks: HashMap<String, String>
pub hooks: HashMap<String, String>,
}
impl Default for Config {
@ -87,7 +80,7 @@ impl Default for Config {
user: None,
group: None,
hook: None,
hooks: HashMap::new()
hooks: HashMap::new(),
}
}
}
@ -295,7 +288,7 @@ impl Config {
if s.contains(':') {
let pos = s.find(':').unwrap();
let name = &s[..pos];
let hook = &s[pos+1..];
let hook = &s[pos + 1..];
self.hooks.insert(name.to_string(), hook.to_string());
} else {
self.hook = Some(s);
@ -311,13 +304,13 @@ impl Config {
store: self.beacon_store,
load: self.beacon_load,
interval: Some(self.beacon_interval),
password: self.beacon_password
password: self.beacon_password,
}),
device: Some(ConfigFileDevice {
name: Some(self.device_name),
path: self.device_path,
type_: Some(self.device_type),
fix_rp_filter: Some(self.fix_rp_filter)
fix_rp_filter: Some(self.fix_rp_filter),
}),
crypto: self.crypto,
group: self.group,
@ -333,13 +326,10 @@ impl Config {
pid_file: self.pid_file,
port_forwarding: Some(self.port_forwarding),
stats_file: self.stats_file,
statsd: Some(ConfigFileStatsd {
server: self.statsd_server,
prefix: self.statsd_prefix
}),
statsd: Some(ConfigFileStatsd { server: self.statsd_server, prefix: self.statsd_prefix }),
switch_timeout: Some(self.switch_timeout),
hook: self.hook,
hooks: self.hooks
hooks: self.hooks,
}
}
@ -351,7 +341,7 @@ impl Config {
}
pub fn call_hook(
&self, event: &'static str, envs: impl IntoIterator<Item = (&'static str, impl AsRef<OsStr>)>, detach: bool
&self, event: &'static str, envs: impl IntoIterator<Item = (&'static str, impl AsRef<OsStr>)>, detach: bool,
) {
let mut script = None;
if let Some(ref s) = self.hook {
@ -361,7 +351,7 @@ impl Config {
script = Some(s);
}
if script.is_none() {
return
return;
}
let script = script.unwrap();
let mut cmd = process::Command::new("sh");
@ -548,8 +538,8 @@ pub enum Command {
#[structopt(alias = "wsproxy")]
WsProxy {
/// Websocket listen address IP:PORT
#[structopt(long, short, default_value="3210")]
listen: String
#[structopt(long, short, default_value = "3210")]
listen: String,
},
/// Migrate an old config file
@ -563,8 +553,8 @@ pub enum Command {
/// Generate shell completions
Completion {
/// Shell to create completions for
#[structopt(long, default_value="bash")]
shell: Shell
#[structopt(long, default_value = "bash")]
shell: Shell,
},
/// Edit the config of a network
@ -572,7 +562,7 @@ pub enum Command {
Config {
/// Name of the network
#[structopt(short, long)]
name: Option<String>
name: Option<String>,
},
/// Install required utility files
@ -580,8 +570,8 @@ pub enum Command {
Install {
/// Remove installed files again
#[structopt(long)]
uninstall: bool
}
uninstall: bool,
},
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -637,7 +627,7 @@ pub struct ConfigFile {
pub user: Option<String>,
pub group: Option<String>,
pub hook: Option<String>,
pub hooks: HashMap<String, String>
pub hooks: HashMap<String, String>,
}
#[test]
@ -758,35 +748,38 @@ fn config_merge() {
prefix: Some("prefix".to_string()),
}),
hook: None,
hooks: HashMap::new()
});
assert_eq!(config, Config {
device_type: Type::Tun,
device_name: "vpncloud%d".to_string(),
device_path: None,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
listen: "3210".to_string(),
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600,
keepalive: Some(840),
switch_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: 7200,
beacon_password: Some("test123".to_string()),
mode: Mode::Normal,
port_forwarding: true,
claims: vec!["10.0.1.0/24".to_string()],
user: Some("nobody".to_string()),
group: Some("nogroup".to_string()),
pid_file: Some("/run/vpncloud.run".to_string()),
stats_file: Some("/var/log/vpncloud.stats".to_string()),
statsd_server: Some("example.com:1234".to_string()),
statsd_prefix: Some("prefix".to_string()),
..Default::default()
hooks: HashMap::new(),
});
assert_eq!(
config,
Config {
device_type: Type::Tun,
device_name: "vpncloud%d".to_string(),
device_path: None,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
listen: "3210".to_string(),
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600,
keepalive: Some(840),
switch_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: 7200,
beacon_password: Some("test123".to_string()),
mode: Mode::Normal,
port_forwarding: true,
claims: vec!["10.0.1.0/24".to_string()],
user: Some("nobody".to_string()),
group: Some("nogroup".to_string()),
pid_file: Some("/run/vpncloud.run".to_string()),
stats_file: Some("/var/log/vpncloud.stats".to_string()),
statsd_server: Some("example.com:1234".to_string()),
statsd_prefix: Some("prefix".to_string()),
..Default::default()
}
);
config.merge_args(Args {
type_: Some(Type::Tap),
device: Some("vpncloud0".to_string()),
@ -815,40 +808,43 @@ fn config_merge() {
group: Some("root".to_string()),
..Default::default()
});
assert_eq!(config, Config {
device_type: Type::Tap,
device_name: "vpncloud0".to_string(),
device_path: Some("/dev/null".to_string()),
fix_rp_filter: false,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
ifdown: Some("ifconfig $IFNAME down".to_string()),
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() },
listen: "[::]:3211".to_string(),
peers: vec![
"remote.machine.foo:3210".to_string(),
"remote.machine.bar:3210".to_string(),
"another:3210".to_string()
],
peer_timeout: 1801,
keepalive: Some(850),
switch_timeout: 301,
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
beacon_interval: 3600,
beacon_password: Some("test1234".to_string()),
mode: Mode::Switch,
port_forwarding: false,
claims: vec!["10.0.1.0/24".to_string()],
auto_claim: true,
user: Some("root".to_string()),
group: Some("root".to_string()),
pid_file: Some("/run/vpncloud-mynet.run".to_string()),
stats_file: Some("/var/log/vpncloud-mynet.stats".to_string()),
statsd_server: Some("example.com:2345".to_string()),
statsd_prefix: Some("prefix2".to_string()),
daemonize: true,
hook: None,
hooks: HashMap::new()
});
assert_eq!(
config,
Config {
device_type: Type::Tap,
device_name: "vpncloud0".to_string(),
device_path: Some("/dev/null".to_string()),
fix_rp_filter: false,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
ifdown: Some("ifconfig $IFNAME down".to_string()),
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::