Prepare for release

This commit is contained in:
Dennis Schwerdel 2021-04-06 12:28:31 +02:00
parent 665b190257
commit 4fb92a36a6
36 changed files with 561 additions and 628 deletions

View File

@ -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 # 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 \ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends asciidoctor && apt-get -y install --no-install-recommends asciidoctor
RUN rm /etc/localtime && ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime 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

View File

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

View File

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

2
Cargo.lock generated
View File

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

View File

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

View File

@ -19,7 +19,7 @@ peers:
- REMOTE_HOST:PORT - 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 ### Project Status

View File

@ -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 vpncloud (2.1.0) stable; urgency=medium
* [added] Support for websocket proxy mode * [added] Support for websocket proxy mode
@ -9,7 +23,7 @@ vpncloud (2.1.0) stable; urgency=medium
* [fixed] Added missing peer address propagation * [fixed] Added missing peer address propagation
* [fixed] Fixed problem with peer addresses without port * [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 vpncloud (2.0.1) stable; urgency=medium

View File

@ -1,9 +1,13 @@
FROM ubuntu 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 /root/.asciinema
RUN mkdir /etc/vpncloud RUN mkdir /etc/vpncloud
WORKDIR /data WORKDIR /data
ADD config /root/.asciinema/config 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

View File

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

View File

@ -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 "$@"

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,23 @@
use super::{ use super::{
core::{test_speed, CryptoCore}, core::{test_speed, CryptoCore},
init::{self, InitResult, InitState, CLOSING}, init::{self, InitResult, InitState, CLOSING},
rotate::RotationState rotate::RotationState,
}; };
use crate::{ use crate::{
error::Error, error::Error,
types::NodeId, types::NodeId,
util::{from_base62, to_base62, MsgBuffer} util::{from_base62, to_base62, MsgBuffer},
}; };
use ring::{ use ring::{
aead::{self, Algorithm, LessSafeKey, UnboundKey}, aead::{self, Algorithm, LessSafeKey, UnboundKey},
agreement::{EphemeralPrivateKey, UnparsedPublicKey}, agreement::{EphemeralPrivateKey, UnparsedPublicKey},
pbkdf2, pbkdf2,
rand::{SecureRandom, SystemRandom}, rand::{SecureRandom, SystemRandom},
signature::{Ed25519KeyPair, KeyPair, ED25519_PUBLIC_KEY_LEN} signature::{Ed25519KeyPair, KeyPair, ED25519_PUBLIC_KEY_LEN},
}; };
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::{fmt::Debug, io::Read, num::NonZeroU32, sync::Arc, time::Duration}; use std::{fmt::Debug, io::Read, num::NonZeroU32, sync::Arc, time::Duration};
const SALT: &[u8; 32] = b"vpncloudVPNCLOUDvpncl0udVpnCloud"; const SALT: &[u8; 32] = b"vpncloudVPNCLOUDvpncl0udVpnCloud";
const INIT_MESSAGE_FIRST_BYTE: u8 = 0xff; const INIT_MESSAGE_FIRST_BYTE: u8 = 0xff;
const MESSAGE_TYPE_ROTATION: u8 = 0x10; const MESSAGE_TYPE_ROTATION: u8 = 0x10;
@ -28,7 +27,6 @@ pub type EcdhPublicKey = UnparsedPublicKey<SmallVec<[u8; 96]>>;
pub type EcdhPrivateKey = EphemeralPrivateKey; pub type EcdhPrivateKey = EphemeralPrivateKey;
pub type Key = SmallVec<[u8; 32]>; pub type Key = SmallVec<[u8; 32]>;
const DEFAULT_ALGORITHMS: [&str; 3] = ["AES128", "AES256", "CHACHA20"]; const DEFAULT_ALGORITHMS: [&str; 3] = ["AES128", "AES256", "CHACHA20"];
#[cfg(test)] #[cfg(test)]
@ -38,17 +36,15 @@ const SPEED_TEST_TIME: f32 = 0.1;
const ROTATE_INTERVAL: usize = 120; const ROTATE_INTERVAL: usize = 120;
pub trait Payload: Debug + PartialEq + Sized { pub trait Payload: Debug + PartialEq + Sized {
fn write_to(&self, buffer: &mut MsgBuffer); fn write_to(&self, buffer: &mut MsgBuffer);
fn read_from<R: Read>(r: R) -> Result<Self, Error>; fn read_from<R: Read>(r: R) -> Result<Self, Error>;
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Algorithms { pub struct Algorithms {
pub algorithm_speeds: SmallVec<[(&'static Algorithm, f32); 3]>, pub algorithm_speeds: SmallVec<[(&'static Algorithm, f32); 3]>,
pub allow_unencrypted: bool pub allow_unencrypted: bool,
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
@ -58,14 +54,14 @@ pub struct Config {
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: Option<String>, pub public_key: Option<String>,
pub trusted_keys: Vec<String>, pub trusted_keys: Vec<String>,
pub algorithms: Vec<String> pub algorithms: Vec<String>,
} }
pub struct Crypto { pub struct Crypto {
node_id: NodeId, node_id: NodeId,
key_pair: Arc<Ed25519KeyPair>, key_pair: Arc<Ed25519KeyPair>,
trusted_keys: Arc<[Ed25519PublicKey]>, trusted_keys: Arc<[Ed25519PublicKey]>,
algorithms: Algorithms algorithms: Algorithms,
} }
impl Crypto { impl Crypto {
@ -78,12 +74,12 @@ impl Crypto {
let algo = match &name.to_uppercase() as &str { let algo = match &name.to_uppercase() as &str {
"UNENCRYPTED" | "NONE" | "PLAIN" => { "UNENCRYPTED" | "NONE" | "PLAIN" => {
unencrypted = true; unencrypted = true;
continue continue;
} }
"AES128" | "AES128_GCM" | "AES_128" | "AES_128_GCM" => &aead::AES_128_GCM, "AES128" | "AES128_GCM" | "AES_128" | "AES_128_GCM" => &aead::AES_128_GCM,
"AES256" | "AES256_GCM" | "AES_256" | "AES_256_GCM" => &aead::AES_256_GCM, "AES256" | "AES256_GCM" | "AES_256" | "AES_256_GCM" => &aead::AES_256_GCM,
"CHACHA" | "CHACHA20" | "CHACHA20_POLY1305" => &aead::CHACHA20_POLY1305, "CHACHA" | "CHACHA20" | "CHACHA20_POLY1305" => &aead::CHACHA20_POLY1305,
_ => return Err(Error::InvalidConfig("Unknown crypto method")) _ => return Err(Error::InvalidConfig("Unknown crypto method")),
}; };
algos.push(algo) algos.push(algo)
} }
@ -100,7 +96,7 @@ impl Crypto {
} else if let Some(password) = &config.password { } else if let Some(password) = &config.password {
Self::keypair_from_password(password) Self::keypair_from_password(password)
} else { } else {
return Err(Error::InvalidConfig("Either private_key or password must be set")) return Err(Error::InvalidConfig("Either private_key or password must be set"));
}; };
let mut trusted_keys = vec![]; let mut trusted_keys = vec![];
for tn in &config.trusted_keys { for tn in &config.trusted_keys {
@ -134,7 +130,7 @@ impl Crypto {
node_id, node_id,
key_pair: Arc::new(key_pair), key_pair: Arc::new(key_pair),
trusted_keys: trusted_keys.into_boxed_slice().into(), trusted_keys: trusted_keys.into_boxed_slice().into(),
algorithms: algos algorithms: algos,
}) })
} }
@ -151,7 +147,7 @@ impl Crypto {
NonZeroU32::new(4096).unwrap(), NonZeroU32::new(4096).unwrap(),
SALT, SALT,
password.as_bytes(), password.as_bytes(),
&mut bytes &mut bytes,
); );
} }
} }
@ -185,7 +181,7 @@ impl Crypto {
fn parse_public_key(pubkey: &str) -> Result<Ed25519PublicKey, Error> { fn parse_public_key(pubkey: &str) -> Result<Ed25519PublicKey, Error> {
let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?; let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?;
if pubkey.len() != ED25519_PUBLIC_KEY_LEN { if pubkey.len() != ED25519_PUBLIC_KEY_LEN {
return Err(Error::InvalidConfig("Failed to parse public key")) return Err(Error::InvalidConfig("Failed to parse public key"));
} }
let mut result = [0; ED25519_PUBLIC_KEY_LEN]; let mut result = [0; ED25519_PUBLIC_KEY_LEN];
result.clone_from_slice(&pubkey); result.clone_from_slice(&pubkey);
@ -203,22 +199,20 @@ impl Crypto {
payload, payload,
self.key_pair.clone(), self.key_pair.clone(),
self.trusted_keys.clone(), self.trusted_keys.clone(),
self.algorithms.clone() self.algorithms.clone(),
) )
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum MessageResult<P: Payload> { pub enum MessageResult<P: Payload> {
Message(u8), Message(u8),
Initialized(P), Initialized(P),
InitializedWithReply(P), InitializedWithReply(P),
Reply, Reply,
None None,
} }
pub struct PeerCrypto<P: Payload> { pub struct PeerCrypto<P: Payload> {
#[allow(dead_code)] #[allow(dead_code)]
node_id: NodeId, node_id: NodeId,
@ -226,13 +220,13 @@ pub struct PeerCrypto<P: Payload> {
rotation: Option<RotationState>, rotation: Option<RotationState>,
unencrypted: bool, unencrypted: bool,
core: Option<CryptoCore>, core: Option<CryptoCore>,
rotate_counter: usize rotate_counter: usize,
} }
impl<P: Payload> PeerCrypto<P> { impl<P: Payload> PeerCrypto<P> {
pub fn new( pub fn new(
node_id: NodeId, init_payload: P, key_pair: Arc<Ed25519KeyPair>, trusted_keys: Arc<[Ed25519PublicKey]>, node_id: NodeId, init_payload: P, key_pair: Arc<Ed25519KeyPair>, trusted_keys: Arc<[Ed25519PublicKey]>,
algorithms: Algorithms algorithms: Algorithms,
) -> Self { ) -> Self {
Self { Self {
node_id, node_id,
@ -240,7 +234,7 @@ impl<P: Payload> PeerCrypto<P> {
rotation: None, rotation: None,
unencrypted: false, unencrypted: false,
core: None, core: None,
rotate_counter: 0 rotate_counter: 0,
} }
} }
@ -324,7 +318,7 @@ impl<P: Payload> PeerCrypto<P> {
} }
if !is_initiator { if !is_initiator {
if self.unencrypted { if self.unencrypted {
return Ok(MessageResult::Initialized(peer_payload)) return Ok(MessageResult::Initialized(peer_payload));
} }
assert!(!buffer.is_empty()); assert!(!buffer.is_empty());
buffer.prepend_byte(MESSAGE_TYPE_ROTATION); buffer.prepend_byte(MESSAGE_TYPE_ROTATION);
@ -337,7 +331,7 @@ impl<P: Payload> PeerCrypto<P> {
fn handle_rotate_message(&mut self, data: &[u8]) -> Result<(), Error> { fn handle_rotate_message(&mut self, data: &[u8]) -> Result<(), Error> {
if self.unencrypted { if self.unencrypted {
return Ok(()) return Ok(());
} }
if let Some(rot) = self.get_rotation()?.handle_message(data)? { if let Some(rot) = self.get_rotation()?.handle_message(data)? {
let core = self.get_core()?; let core = self.get_core()?;
@ -350,7 +344,7 @@ impl<P: Payload> PeerCrypto<P> {
fn encrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> { fn encrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
if self.unencrypted { if self.unencrypted {
return Ok(()) return Ok(());
} }
self.get_core()?.encrypt(buffer); self.get_core()?.encrypt(buffer);
Ok(()) Ok(())
@ -359,7 +353,7 @@ impl<P: Payload> PeerCrypto<P> {
fn decrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> { fn decrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
// HOT PATH // HOT PATH
if self.unencrypted { if self.unencrypted {
return Ok(()) return Ok(());
} }
self.get_core()?.decrypt(buffer) self.get_core()?.decrypt(buffer)
} }
@ -367,7 +361,7 @@ impl<P: Payload> PeerCrypto<P> {
pub fn handle_message(&mut self, buffer: &mut MsgBuffer) -> Result<MessageResult<P>, Error> { pub fn handle_message(&mut self, buffer: &mut MsgBuffer) -> Result<MessageResult<P>, Error> {
// HOT PATH // HOT PATH
if buffer.is_empty() { if buffer.is_empty() {
return Err(Error::InvalidCryptoState("No message in buffer")) return Err(Error::InvalidCryptoState("No message in buffer"));
} }
if is_init_message(buffer.buffer()) { if is_init_message(buffer.buffer()) {
// COLD PATH // COLD PATH
@ -411,7 +405,7 @@ impl<P: Payload> PeerCrypto<P> {
} }
if !out.is_empty() { if !out.is_empty() {
out.prepend_byte(INIT_MESSAGE_FIRST_BYTE); out.prepend_byte(INIT_MESSAGE_FIRST_BYTE);
return Ok(MessageResult::Reply) return Ok(MessageResult::Reply);
} }
if let Some(ref mut rotate) = self.rotation { if let Some(ref mut rotate) = self.rotation {
self.rotate_counter += 1; self.rotate_counter += 1;
@ -426,7 +420,7 @@ impl<P: Payload> PeerCrypto<P> {
if !out.is_empty() { if !out.is_empty() {
out.prepend_byte(MESSAGE_TYPE_ROTATION); out.prepend_byte(MESSAGE_TYPE_ROTATION);
self.encrypt_message(out)?; self.encrypt_message(out)?;
return Ok(MessageResult::Reply) return Ok(MessageResult::Reply);
} }
} }
} }
@ -439,7 +433,6 @@ pub fn is_init_message(msg: &[u8]) -> bool {
!msg.is_empty() && msg[0] == INIT_MESSAGE_FIRST_BYTE !msg.is_empty() && msg[0] == INIT_MESSAGE_FIRST_BYTE
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -499,7 +492,7 @@ mod tests {
let res = node2.handle_message(&mut msg).unwrap(); let res = node2.handle_message(&mut msg).unwrap();
assert_eq!(res, MessageResult::None); assert_eq!(res, MessageResult::None);
} }
other => assert_eq!(other, MessageResult::None) other => assert_eq!(other, MessageResult::None),
} }
match node2.every_second(&mut msg).unwrap() { match node2.every_second(&mut msg).unwrap() {
MessageResult::None => (), MessageResult::None => (),
@ -507,7 +500,7 @@ mod tests {
let res = node1.handle_message(&mut msg).unwrap(); let res = node1.handle_message(&mut msg).unwrap();
assert_eq!(res, MessageResult::None); assert_eq!(res, MessageResult::None);
} }
other => assert_eq!(other, MessageResult::None) other => assert_eq!(other, MessageResult::None),
} }
} }
} }

View File

@ -44,23 +44,21 @@
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use ring::{ use ring::{
aead::{self, LessSafeKey, UnboundKey}, aead::{self, LessSafeKey, UnboundKey},
rand::{SecureRandom, SystemRandom} rand::{SecureRandom, SystemRandom},
}; };
use std::{ use std::{
io::{Cursor, Read, Write}, io::{Cursor, Read, Write},
mem, mem,
time::{Duration, Instant} time::{Duration, Instant},
}; };
use crate::{error::Error, util::MsgBuffer}; use crate::{error::Error, util::MsgBuffer};
const NONCE_LEN: usize = 12; const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16; pub const TAG_LEN: usize = 16;
pub const EXTRA_LEN: usize = 8; pub const EXTRA_LEN: usize = 8;
fn random_data(size: usize) -> Vec<u8> { fn random_data(size: usize) -> Vec<u8> {
let rand = SystemRandom::new(); let rand = SystemRandom::new();
let mut data = vec![0; size]; let mut data = vec![0; size];
@ -96,7 +94,7 @@ impl Nonce {
num = num.wrapping_add(1); num = num.wrapping_add(1);
self.0[i] = num; self.0[i] = num;
if num > 0 { if num > 0 {
return return;
} }
} }
} }
@ -107,7 +105,7 @@ struct CryptoKey {
send_nonce: Nonce, send_nonce: Nonce,
min_nonce: Nonce, min_nonce: Nonce,
next_min_nonce: Nonce, next_min_nonce: Nonce,
seen_nonce: Nonce seen_nonce: Nonce,
} }
impl CryptoKey { impl CryptoKey {
@ -119,7 +117,7 @@ impl CryptoKey {
send_nonce, send_nonce,
min_nonce: Nonce::zero(), min_nonce: Nonce::zero(),
next_min_nonce: Nonce::zero(), next_min_nonce: Nonce::zero(),
seen_nonce: Nonce::zero() seen_nonce: Nonce::zero(),
} }
} }
@ -130,12 +128,11 @@ impl CryptoKey {
} }
} }
pub struct CryptoCore { pub struct CryptoCore {
rand: SystemRandom, rand: SystemRandom,
keys: [CryptoKey; 4], keys: [CryptoKey; 4],
current_key: usize, current_key: usize,
nonce_half: bool nonce_half: bool,
} }
impl CryptoCore { impl CryptoCore {
@ -150,11 +147,11 @@ impl CryptoCore {
CryptoKey::new(&rand, key, nonce_half), CryptoKey::new(&rand, key, nonce_half),
CryptoKey::new(&rand, dummy_key1, nonce_half), CryptoKey::new(&rand, dummy_key1, nonce_half),
CryptoKey::new(&rand, dummy_key2, nonce_half), CryptoKey::new(&rand, dummy_key2, nonce_half),
CryptoKey::new(&rand, dummy_key3, nonce_half) CryptoKey::new(&rand, dummy_key3, nonce_half),
], ],
current_key: 0, current_key: 0,
nonce_half, nonce_half,
rand rand,
} }
} }
@ -180,7 +177,7 @@ impl CryptoCore {
fn decrypt_with_key(key: &mut CryptoKey, nonce: Nonce, data_and_tag: &mut [u8]) -> Result<(), Error> { fn decrypt_with_key(key: &mut CryptoKey, nonce: Nonce, data_and_tag: &mut [u8]) -> Result<(), Error> {
if nonce < key.min_nonce { if nonce < key.min_nonce {
return Err(Error::Crypto("Old nonce rejected")) return Err(Error::Crypto("Old nonce rejected"));
} }
// decrypt // decrypt
let crypto_nonce = aead::Nonce::assume_unique_for_key(*nonce.as_bytes()); let crypto_nonce = aead::Nonce::assume_unique_for_key(*nonce.as_bytes());
@ -234,7 +231,6 @@ impl CryptoCore {
} }
} }
pub fn create_dummy_pair(algo: &'static aead::Algorithm) -> (CryptoCore, CryptoCore) { pub fn create_dummy_pair(algo: &'static aead::Algorithm) -> (CryptoCore, CryptoCore) {
let key_data = random_data(algo.key_len()); let key_data = random_data(algo.key_len());
let sender = CryptoCore::new(LessSafeKey::new(UnboundKey::new(algo, &key_data).unwrap()), true); let sender = CryptoCore::new(LessSafeKey::new(UnboundKey::new(algo, &key_data).unwrap()), true);
@ -260,7 +256,6 @@ pub fn test_speed(algo: &'static aead::Algorithm, max_time: &Duration) -> f64 {
data as f64 / duration / 1_000_000.0 data as f64 / duration / 1_000_000.0
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -303,7 +298,6 @@ mod tests {
test_encrypt_decrypt(&aead::CHACHA20_POLY1305) test_encrypt_decrypt(&aead::CHACHA20_POLY1305)
} }
fn test_tampering(algo: &'static aead::Algorithm) { fn test_tampering(algo: &'static aead::Algorithm) {
let (mut sender, mut receiver) = create_dummy_pair(algo); let (mut sender, mut receiver) = create_dummy_pair(algo);
let plain = random_data(1000); let plain = random_data(1000);
@ -434,7 +428,6 @@ mod tests {
test_key_rotation(&aead::CHACHA20_POLY1305); test_key_rotation(&aead::CHACHA20_POLY1305);
} }
#[test] #[test]
fn test_core_size() { fn test_core_size() {
assert_eq!(2384, mem::size_of::<CryptoCore>()); assert_eq!(2384, mem::size_of::<CryptoCore>());

View File

@ -54,10 +54,9 @@
// Once every second, both nodes check whether they have already finished the initialization. If not, they repeat their // Once every second, both nodes check whether they have already finished the initialization. If not, they repeat their
// last message. After 5 seconds, the initialization is aborted as failed. // last message. After 5 seconds, the initialization is aborted as failed.
use super::{ use super::{
core::{CryptoCore, EXTRA_LEN}, core::{CryptoCore, EXTRA_LEN},
Algorithms, EcdhPrivateKey, EcdhPublicKey, Ed25519PublicKey, Payload Algorithms, EcdhPrivateKey, EcdhPublicKey, Ed25519PublicKey, Payload,
}; };
use crate::{error::Error, types::NodeId, util::MsgBuffer}; use crate::{error::Error, types::NodeId, util::MsgBuffer};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
@ -66,17 +65,16 @@ use ring::{
agreement::{agree_ephemeral, X25519}, agreement::{agree_ephemeral, X25519},
digest, digest,
rand::{SecureRandom, SystemRandom}, rand::{SecureRandom, SystemRandom},
signature::{self, Ed25519KeyPair, KeyPair, ED25519, ED25519_PUBLIC_KEY_LEN} signature::{self, Ed25519KeyPair, KeyPair, ED25519, ED25519_PUBLIC_KEY_LEN},
}; };
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::{ use std::{
cmp, f32, cmp, f32,
fmt::Debug, fmt::Debug,
io::{self, Cursor, Read, Write}, io::{self, Cursor, Read, Write},
sync::Arc sync::Arc,
}; };
pub const STAGE_PING: u8 = 1; pub const STAGE_PING: u8 = 1;
pub const STAGE_PONG: u8 = 2; pub const STAGE_PONG: u8 = 2;
pub const STAGE_PENG: u8 = 3; pub const STAGE_PENG: u8 = 3;
@ -88,24 +86,23 @@ pub const MAX_FAILED_RETRIES: usize = 120;
pub const SALTED_NODE_ID_HASH_LEN: usize = 20; pub const SALTED_NODE_ID_HASH_LEN: usize = 20;
pub type SaltedNodeIdHash = [u8; SALTED_NODE_ID_HASH_LEN]; pub type SaltedNodeIdHash = [u8; SALTED_NODE_ID_HASH_LEN];
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum InitMsg { pub enum InitMsg {
Ping { Ping {
salted_node_id_hash: SaltedNodeIdHash, salted_node_id_hash: SaltedNodeIdHash,
ecdh_public_key: EcdhPublicKey, ecdh_public_key: EcdhPublicKey,
algorithms: Algorithms algorithms: Algorithms,
}, },
Pong { Pong {
salted_node_id_hash: SaltedNodeIdHash, salted_node_id_hash: SaltedNodeIdHash,
ecdh_public_key: EcdhPublicKey, ecdh_public_key: EcdhPublicKey,
algorithms: Algorithms, algorithms: Algorithms,
encrypted_payload: MsgBuffer encrypted_payload: MsgBuffer,
}, },
Peng { Peng {
salted_node_id_hash: SaltedNodeIdHash, salted_node_id_hash: SaltedNodeIdHash,
encrypted_payload: MsgBuffer encrypted_payload: MsgBuffer,
} },
} }
impl InitMsg { impl InitMsg {
@ -120,7 +117,7 @@ impl InitMsg {
match self { match self {
InitMsg::Ping { .. } => STAGE_PING, InitMsg::Ping { .. } => STAGE_PING,
InitMsg::Pong { .. } => STAGE_PONG, InitMsg::Pong { .. } => STAGE_PONG,
InitMsg::Peng { .. } => STAGE_PENG InitMsg::Peng { .. } => STAGE_PENG,
} }
} }
@ -128,7 +125,7 @@ impl InitMsg {
match self { match self {
InitMsg::Ping { salted_node_id_hash, .. } InitMsg::Ping { salted_node_id_hash, .. }
| InitMsg::Pong { salted_node_id_hash, .. } | InitMsg::Pong { salted_node_id_hash, .. }
| InitMsg::Peng { salted_node_id_hash, .. } => salted_node_id_hash | InitMsg::Peng { salted_node_id_hash, .. } => salted_node_id_hash,
} }
} }
@ -155,11 +152,11 @@ impl InitMsg {
if Self::calculate_hash(tk, &public_key_salt) == public_key_hash { if Self::calculate_hash(tk, &public_key_salt) == public_key_hash {
public_key_data.clone_from_slice(tk); public_key_data.clone_from_slice(tk);
found_key = true; found_key = true;
break break;
} }
} }
if !found_key { if !found_key {
return Err(Error::Crypto("untrusted peer")) return Err(Error::Crypto("untrusted peer"));
} }
let mut stage = None; let mut stage = None;
@ -171,19 +168,19 @@ impl InitMsg {
loop { loop {
let field = r.read_u8().map_err(|_| Error::Parse("Init message too short"))?; let field = r.read_u8().map_err(|_| Error::Parse("Init message too short"))?;
if field == Self::PART_END { if field == Self::PART_END {
break break;
} }
let field_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Parse("Init message too short"))? as usize; let field_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Parse("Init message too short"))? as usize;
match field { match field {
Self::PART_STAGE => { Self::PART_STAGE => {
if field_len != 1 { if field_len != 1 {
return Err(Error::CryptoInit("Invalid size for stage field")) return Err(Error::CryptoInit("Invalid size for stage field"));
} }
stage = Some(r.read_u8().map_err(|_| Error::Parse("Init message too short"))?) stage = Some(r.read_u8().map_err(|_| Error::Parse("Init message too short"))?)
} }
Self::PART_SALTED_NODE_ID_HASH => { Self::PART_SALTED_NODE_ID_HASH => {
if field_len != SALTED_NODE_ID_HASH_LEN { if field_len != SALTED_NODE_ID_HASH_LEN {
return Err(Error::CryptoInit("Invalid size for salted node id hash field")) return Err(Error::CryptoInit("Invalid size for salted node id hash field"));
} }
let mut id = [0; SALTED_NODE_ID_HASH_LEN]; let mut id = [0; SALTED_NODE_ID_HASH_LEN];
r.read_exact(&mut id).map_err(|_| Error::Parse("Init message too short"))?; r.read_exact(&mut id).map_err(|_| Error::Parse("Init message too short"))?;
@ -213,7 +210,7 @@ impl InitMsg {
1 => Some(&AES_128_GCM), 1 => Some(&AES_128_GCM),
2 => Some(&AES_256_GCM), 2 => Some(&AES_256_GCM),
3 => Some(&CHACHA20_POLY1305), 3 => Some(&CHACHA20_POLY1305),
_ => None _ => None,
}; };
let speed = let speed =
r.read_f32::<NetworkEndian>().map_err(|_| Error::Parse("Init message too short"))?; r.read_f32::<NetworkEndian>().map_err(|_| Error::Parse("Init message too short"))?;
@ -239,53 +236,53 @@ impl InitMsg {
let signed_data = &r.into_inner()[0..pos]; let signed_data = &r.into_inner()[0..pos];
let public_key = signature::UnparsedPublicKey::new(&ED25519, &public_key_data); let public_key = signature::UnparsedPublicKey::new(&ED25519, &public_key_data);
if public_key.verify(&signed_data, &signature).is_err() { if public_key.verify(&signed_data, &signature).is_err() {
return Err(Error::Crypto("invalid signature")) return Err(Error::Crypto("invalid signature"));
} }
let stage = match stage { let stage = match stage {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without stage")) None => return Err(Error::CryptoInit("Init message without stage")),
}; };
let salted_node_id_hash = match salted_node_id_hash { let salted_node_id_hash = match salted_node_id_hash {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without node id")) None => return Err(Error::CryptoInit("Init message without node id")),
}; };
let msg = match stage { let msg = match stage {
STAGE_PING => { STAGE_PING => {
let ecdh_public_key = match ecdh_public_key { let ecdh_public_key = match ecdh_public_key {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without ecdh public key")) None => return Err(Error::CryptoInit("Init message without ecdh public key")),
}; };
let algorithms = match algorithms { let algorithms = match algorithms {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without algorithms")) None => return Err(Error::CryptoInit("Init message without algorithms")),
}; };
Self::Ping { salted_node_id_hash, ecdh_public_key, algorithms } Self::Ping { salted_node_id_hash, ecdh_public_key, algorithms }
} }
STAGE_PONG => { STAGE_PONG => {
let ecdh_public_key = match ecdh_public_key { let ecdh_public_key = match ecdh_public_key {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without ecdh public key")) None => return Err(Error::CryptoInit("Init message without ecdh public key")),
}; };
let algorithms = match algorithms { let algorithms = match algorithms {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without algorithms")) None => return Err(Error::CryptoInit("Init message without algorithms")),
}; };
let encrypted_payload = match encrypted_payload { let encrypted_payload = match encrypted_payload {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without payload")) None => return Err(Error::CryptoInit("Init message without payload")),
}; };
Self::Pong { salted_node_id_hash, ecdh_public_key, algorithms, encrypted_payload } Self::Pong { salted_node_id_hash, ecdh_public_key, algorithms, encrypted_payload }
} }
STAGE_PENG => { STAGE_PENG => {
let encrypted_payload = match encrypted_payload { let encrypted_payload = match encrypted_payload {
Some(val) => val, Some(val) => val,
None => return Err(Error::CryptoInit("Init message without payload")) None => return Err(Error::CryptoInit("Init message without payload")),
}; };
Self::Peng { salted_node_id_hash, encrypted_payload } Self::Peng { salted_node_id_hash, encrypted_payload }
} }
_ => return Err(Error::CryptoInit("Invalid stage")) _ => return Err(Error::CryptoInit("Invalid stage")),
}; };
Ok((msg, public_key_data)) Ok((msg, public_key_data))
@ -324,7 +321,7 @@ impl InitMsg {
w.write_u16::<NetworkEndian>(key_bytes.len() as u16)?; w.write_u16::<NetworkEndian>(key_bytes.len() as u16)?;
w.write_all(&key_bytes)?; w.write_all(&key_bytes)?;
} }
_ => () _ => (),
} }
match &self { match &self {
@ -352,7 +349,7 @@ impl InitMsg {
w.write_f32::<NetworkEndian>(*speed)?; w.write_f32::<NetworkEndian>(*speed)?;
} }
} }
_ => () _ => (),
} }
match &self { match &self {
@ -361,7 +358,7 @@ impl InitMsg {
w.write_u16::<NetworkEndian>(encrypted_payload.len() as u16)?; w.write_u16::<NetworkEndian>(encrypted_payload.len() as u16)?;
w.write_all(encrypted_payload.message())?; w.write_all(encrypted_payload.message())?;
} }
_ => () _ => (),
} }
w.write_u8(Self::PART_END)?; w.write_u8(Self::PART_END)?;
@ -375,14 +372,12 @@ impl InitMsg {
} }
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum InitResult<P: Payload> { pub enum InitResult<P: Payload> {
Continue, Continue,
Success { peer_payload: P, is_initiator: bool } Success { peer_payload: P, is_initiator: bool },
} }
pub struct InitState<P: Payload> { pub struct InitState<P: Payload> {
node_id: NodeId, node_id: NodeId,
salted_node_id_hash: SaltedNodeIdHash, salted_node_id_hash: SaltedNodeIdHash,
@ -397,13 +392,13 @@ pub struct InitState<P: Payload> {
algorithms: Algorithms, algorithms: Algorithms,
#[allow(dead_code)] // Used in tests #[allow(dead_code)] // Used in tests
selected_algorithm: Option<&'static Algorithm>, selected_algorithm: Option<&'static Algorithm>,
failed_retries: usize failed_retries: usize,
} }
impl<P: Payload> InitState<P> { impl<P: Payload> InitState<P> {
pub fn new( pub fn new(
node_id: NodeId, payload: P, key_pair: Arc<Ed25519KeyPair>, trusted_keys: Arc<[Ed25519PublicKey]>, node_id: NodeId, payload: P, key_pair: Arc<Ed25519KeyPair>, trusted_keys: Arc<[Ed25519PublicKey]>,
algorithms: Algorithms algorithms: Algorithms,
) -> Self { ) -> Self {
let mut hash = [0; SALTED_NODE_ID_HASH_LEN]; let mut hash = [0; SALTED_NODE_ID_HASH_LEN];
let rng = SystemRandom::new(); let rng = SystemRandom::new();
@ -424,7 +419,7 @@ impl<P: Payload> InitState<P> {
selected_algorithm: None, selected_algorithm: None,
algorithms, algorithms,
failed_retries: 0, failed_retries: 0,
close_time: 60 close_time: 60,
} }
} }
@ -510,28 +505,22 @@ impl<P: Payload> InitState<P> {
let mut public_key = [0; ED25519_PUBLIC_KEY_LEN]; let mut public_key = [0; ED25519_PUBLIC_KEY_LEN];
public_key.clone_from_slice(self.key_pair.as_ref().public_key().as_ref()); public_key.clone_from_slice(self.key_pair.as_ref().public_key().as_ref());
let msg = match stage { let msg = match stage {
STAGE_PING => { STAGE_PING => InitMsg::Ping {
InitMsg::Ping { salted_node_id_hash: self.salted_node_id_hash,
salted_node_id_hash: self.salted_node_id_hash, ecdh_public_key: ecdh_public_key.unwrap(),
ecdh_public_key: ecdh_public_key.unwrap(), algorithms: self.algorithms.clone(),
algorithms: self.algorithms.clone() },
} STAGE_PONG => InitMsg::Pong {
} salted_node_id_hash: self.salted_node_id_hash,
STAGE_PONG => { ecdh_public_key: ecdh_public_key.unwrap(),
InitMsg::Pong { algorithms: self.algorithms.clone(),
salted_node_id_hash: self.salted_node_id_hash, encrypted_payload: self.encrypt_payload(),
ecdh_public_key: ecdh_public_key.unwrap(), },
algorithms: self.algorithms.clone(), STAGE_PENG => InitMsg::Peng {
encrypted_payload: self.encrypt_payload() salted_node_id_hash: self.salted_node_id_hash,
} encrypted_payload: self.encrypt_payload(),
} },
STAGE_PENG => { _ => unreachable!(),
InitMsg::Peng {
salted_node_id_hash: self.salted_node_id_hash,
encrypted_payload: self.encrypt_payload()
}
}
_ => unreachable!()
}; };
let mut bytes = out.buffer(); let mut bytes = out.buffer();
let len = msg.write_to(&mut bytes, &self.key_pair).expect("Buffer too small"); let len = msg.write_to(&mut bytes, &self.key_pair).expect("Buffer too small");
@ -550,7 +539,7 @@ impl<P: Payload> InitState<P> {
fn select_algorithm(&self, peer_algos: &Algorithms) -> Result<Option<(&'static Algorithm, f32)>, Error> { fn select_algorithm(&self, peer_algos: &Algorithms) -> Result<Option<(&'static Algorithm, f32)>, Error> {
if self.algorithms.allow_unencrypted && peer_algos.allow_unencrypted { if self.algorithms.allow_unencrypted && peer_algos.allow_unencrypted {
return Ok(None) return Ok(None);
} }
// For each supported algorithm, find the algorithm in the list of the peer (ignore algorithm if not found). // For each supported algorithm, find the algorithm in the list of the peer (ignore algorithm if not found).
// Take the minimal speed reported by either us or the peer. // Take the minimal speed reported by either us or the peer.
@ -584,7 +573,7 @@ impl<P: Payload> InitState<P> {
if self.salted_node_id_hash == salted_node_id_hash if self.salted_node_id_hash == salted_node_id_hash
|| self.check_salted_node_id_hash(&salted_node_id_hash, self.node_id) || self.check_salted_node_id_hash(&salted_node_id_hash, self.node_id)
{ {
return Err(Error::CryptoInitFatal("Connected to self")) return Err(Error::CryptoInitFatal("Connected to self"));
} }
if stage != self.next_stage { if stage != self.next_stage {
if self.next_stage == STAGE_PONG && stage == STAGE_PING { if self.next_stage == STAGE_PONG && stage == STAGE_PING {
@ -596,15 +585,15 @@ impl<P: Payload> InitState<P> {
self.last_message = None; self.last_message = None;
self.ecdh_private_key = None; self.ecdh_private_key = None;
} else { } else {
return Ok(InitResult::Continue) return Ok(InitResult::Continue);
} }
} else if self.next_stage == CLOSING { } else if self.next_stage == CLOSING {
return Ok(InitResult::Continue) return Ok(InitResult::Continue);
} else if self.last_message.is_some() { } else if self.last_message.is_some() {
self.repeat_last_message(out); self.repeat_last_message(out);
return Ok(InitResult::Continue) return Ok(InitResult::Continue);
} else { } else {
return Err(Error::CryptoInitFatal("Received invalid stage as first message")) return Err(Error::CryptoInitFatal("Received invalid stage as first message"));
} }
} }
self.failed_retries = 0; self.failed_retries = 0;
@ -666,7 +655,6 @@ impl<P: Payload> InitState<P> {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -698,7 +686,7 @@ mod tests {
rng.fill(&mut node2).unwrap(); rng.fill(&mut node2).unwrap();
let algorithms = Algorithms { let algorithms = Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: false allow_unencrypted: false,
}; };
let sender = InitState::new(node1, vec![1], key_pair.clone(), trusted_nodes.clone(), algorithms.clone()); let sender = InitState::new(node1, vec![1], key_pair.clone(), trusted_nodes.clone(), algorithms.clone());
let receiver = InitState::new(node2, vec![2], key_pair, trusted_nodes, algorithms); let receiver = InitState::new(node2, vec![2], key_pair, trusted_nodes, algorithms);
@ -718,12 +706,12 @@ mod tests {
assert_eq!(sender.stage(), WAITING_TO_CLOSE); assert_eq!(sender.stage(), WAITING_TO_CLOSE);
let result = match result { let result = match result {
InitResult::Success { .. } => receiver.handle_init(&mut out).unwrap(), InitResult::Success { .. } => receiver.handle_init(&mut out).unwrap(),
InitResult::Continue => unreachable!() InitResult::Continue => unreachable!(),
}; };
assert_eq!(receiver.stage(), CLOSING); assert_eq!(receiver.stage(), CLOSING);
match result { match result {
InitResult::Success { .. } => assert!(out.is_empty()), InitResult::Success { .. } => assert!(out.is_empty()),
InitResult::Continue => unreachable!() InitResult::Continue => unreachable!(),
} }
} }
@ -749,14 +737,14 @@ mod tests {
// lost peng, sender recovers // lost peng, sender recovers
out.clear(); out.clear();
} }
InitResult::Continue => unreachable!() InitResult::Continue => unreachable!(),
}; };
sender.every_second(&mut out).unwrap(); sender.every_second(&mut out).unwrap();
let result = receiver.handle_init(&mut out).unwrap(); let result = receiver.handle_init(&mut out).unwrap();
assert_eq!(receiver.stage(), CLOSING); assert_eq!(receiver.stage(), CLOSING);
match result { match result {
InitResult::Success { .. } => assert!(out.is_empty()), InitResult::Success { .. } => assert!(out.is_empty()),
InitResult::Continue => unreachable!() InitResult::Continue => unreachable!(),
} }
} }
@ -780,7 +768,7 @@ mod tests {
// lost peng, sender recovers // lost peng, sender recovers
out.clear(); out.clear();
} }
InitResult::Continue => unreachable!() InitResult::Continue => unreachable!(),
}; };
receiver.every_second(&mut out).unwrap(); receiver.every_second(&mut out).unwrap();
sender.handle_init(&mut out).unwrap(); sender.handle_init(&mut out).unwrap();
@ -788,7 +776,7 @@ mod tests {
assert_eq!(receiver.stage(), CLOSING); assert_eq!(receiver.stage(), CLOSING);
match result { match result {
InitResult::Success { .. } => assert!(out.is_empty()), InitResult::Success { .. } => assert!(out.is_empty()),
InitResult::Continue => unreachable!() InitResult::Continue => unreachable!(),
} }
} }
@ -837,7 +825,7 @@ mod tests {
} }
fn test_algorithm_negotiation( fn test_algorithm_negotiation(
algos1: Algorithms, algos2: Algorithms, success: bool, selected: Option<&'static Algorithm> algos1: Algorithms, algos2: Algorithms, success: bool, selected: Option<&'static Algorithm>,
) { ) {
let (mut sender, mut receiver) = create_pair(); let (mut sender, mut receiver) = create_pair();
sender.algorithms = algos1; sender.algorithms = algos1;
@ -847,7 +835,7 @@ mod tests {
let res = receiver.handle_init(&mut out); let res = receiver.handle_init(&mut out);
assert_eq!(res.is_ok(), success); assert_eq!(res.is_ok(), success);
if !success { if !success {
return return;
} }
sender.handle_init(&mut out).unwrap(); sender.handle_init(&mut out).unwrap();
receiver.handle_init(&mut out).unwrap(); receiver.handle_init(&mut out).unwrap();
@ -861,70 +849,70 @@ mod tests {
test_algorithm_negotiation( test_algorithm_negotiation(
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
true, true,
Some(&AES_128_GCM) Some(&AES_128_GCM),
); );
// Overlapping but different // Overlapping but different
test_algorithm_negotiation( test_algorithm_negotiation(
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
true, true,
Some(&AES_256_GCM) Some(&AES_256_GCM),
); );
// Select fastest pair // Select fastest pair
test_algorithm_negotiation( test_algorithm_negotiation(
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 40.0), (&AES_256_GCM, 50.0), (&CHACHA20_POLY1305, 60.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 40.0), (&AES_256_GCM, 50.0), (&CHACHA20_POLY1305, 60.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
true, true,
Some(&CHACHA20_POLY1305) Some(&CHACHA20_POLY1305),
); );
// Select unencrypted if supported by both // Select unencrypted if supported by both
test_algorithm_negotiation( test_algorithm_negotiation(
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: true allow_unencrypted: true,
}, },
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: true allow_unencrypted: true,
}, },
true, true,
None None,
); );
// Do not select unencrypted if only supported by one // Do not select unencrypted if only supported by one
test_algorithm_negotiation( test_algorithm_negotiation(
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: true allow_unencrypted: true,
}, },
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
true, true,
Some(&AES_128_GCM) Some(&AES_128_GCM),
); );
// Fail if no match // Fail if no match
@ -932,10 +920,10 @@ mod tests {
Algorithms { algorithm_speeds: smallvec![(&AES_128_GCM, 600.0)], allow_unencrypted: true }, Algorithms { algorithm_speeds: smallvec![(&AES_128_GCM, 600.0)], allow_unencrypted: true },
Algorithms { Algorithms {
algorithm_speeds: smallvec![(&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)], algorithm_speeds: smallvec![(&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
allow_unencrypted: false allow_unencrypted: false,
}, },
false, false,
Some(&AES_128_GCM) Some(&AES_128_GCM),
); );
} }
} }

View File

@ -7,5 +7,5 @@ mod core;
mod init; mod init;
mod rotate; mod rotate;
pub use self::core::{EXTRA_LEN, TAG_LEN};
pub use common::*; pub use common::*;
pub use self::core::{EXTRA_LEN, TAG_LEN};

View File

@ -34,20 +34,18 @@ use crate::{error::Error, util::MsgBuffer};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use ring::{ use ring::{
agreement::{agree_ephemeral, EphemeralPrivateKey, UnparsedPublicKey, X25519}, agreement::{agree_ephemeral, EphemeralPrivateKey, UnparsedPublicKey, X25519},
rand::SystemRandom rand::SystemRandom,
}; };
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::io::{self, Cursor, Read, Write}; use std::io::{self, Cursor, Read, Write};
type EcdhPublicKey = UnparsedPublicKey<SmallVec<[u8; 96]>>; type EcdhPublicKey = UnparsedPublicKey<SmallVec<[u8; 96]>>;
type EcdhPrivateKey = EphemeralPrivateKey; type EcdhPrivateKey = EphemeralPrivateKey;
pub struct RotationMessage { pub struct RotationMessage {
message_id: u64, message_id: u64,
propose: EcdhPublicKey, propose: EcdhPublicKey,
confirm: Option<EcdhPublicKey> confirm: Option<EcdhPublicKey>,
} }
impl RotationMessage { impl RotationMessage {
@ -91,13 +89,13 @@ pub struct RotationState {
pending: Option<(Key, EcdhPublicKey)>, // sent by remote, to be confirmed pending: Option<(Key, EcdhPublicKey)>, // sent by remote, to be confirmed
proposed: Option<EcdhPrivateKey>, // my own, proposed but not confirmed proposed: Option<EcdhPrivateKey>, // my own, proposed but not confirmed
message_id: u64, message_id: u64,
timeout: bool timeout: bool,
} }
pub struct RotatedKey { pub struct RotatedKey {
pub key: Key, pub key: Key,
pub id: u64, pub id: u64,
pub use_for_sending: bool pub use_for_sending: bool,
} }
impl RotationState { impl RotationState {
@ -155,7 +153,7 @@ impl RotationState {
pub fn process_message(&mut self, msg: RotationMessage) -> Option<RotatedKey> { pub fn process_message(&mut self, msg: RotationMessage) -> Option<RotatedKey> {
if msg.message_id <= self.message_id { if msg.message_id <= self.message_id {
return None return None;
} }
debug!("Received rotation message with id {}", msg.message_id); debug!("Received rotation message with id {}", msg.message_id);
self.timeout = false; self.timeout = false;
@ -167,7 +165,7 @@ impl RotationState {
if let Some(peer_key) = msg.confirm { if let Some(peer_key) = msg.confirm {
if let Some(private_key) = self.proposed.take() { if let Some(private_key) = self.proposed.take() {
let key = Self::derive_key(private_key, peer_key); let key = Self::derive_key(private_key, peer_key);
return Some(RotatedKey { key, id: msg.message_id, use_for_sending: true }) return Some(RotatedKey { key, id: msg.message_id, use_for_sending: true });
} }
} }
None None
@ -183,7 +181,7 @@ impl RotationState {
// Reconfirm last confirmed key // Reconfirm last confirmed key
Self::send( Self::send(
&RotationMessage { confirm: Some(confirmed_key.clone()), propose: proposed_key, message_id }, &RotationMessage { confirm: Some(confirmed_key.clone()), propose: proposed_key, message_id },
out out,
); );
} else { } else {
// First message has been lost // First message has been lost
@ -202,7 +200,7 @@ impl RotationState {
self.proposed = Some(private_key); self.proposed = Some(private_key);
self.confirmed = Some((confirm_key.clone(), message_id)); self.confirmed = Some((confirm_key.clone(), message_id));
Self::send(&RotationMessage { confirm: Some(confirm_key), propose: propose_key, message_id }, out); Self::send(&RotationMessage { confirm: Some(confirm_key), propose: propose_key, message_id }, out);
return Some(RotatedKey { key, id: message_id, use_for_sending: false }) return Some(RotatedKey { key, id: message_id, use_for_sending: false });
} else { } else {
// Nothing pending nor proposed, still waiting to receive message 1 // Nothing pending nor proposed, still waiting to receive message 1
// Do nothing, peer will retry // Do nothing, peer will retry
@ -221,7 +219,7 @@ mod tests {
impl MsgBuffer { impl MsgBuffer {
fn msg(&mut self) -> Option<RotationMessage> { fn msg(&mut self) -> Option<RotationMessage> {
if self.is_empty() { if self.is_empty() {
return None return None;
} }
let msg = RotationMessage::read_from(Cursor::new(self.message())).unwrap(); let msg = RotationMessage::read_from(Cursor::new(self.message())).unwrap();
self.set_length(0); self.set_length(0);

View File

@ -12,7 +12,7 @@ use std::{
net::{Ipv4Addr, UdpSocket}, net::{Ipv4Addr, UdpSocket},
os::unix::io::{AsRawFd, RawFd}, os::unix::io::{AsRawFd, RawFd},
str, str,
str::FromStr str::FromStr,
}; };
use crate::{crypto, error::Error, util::MsgBuffer}; use crate::{crypto, error::Error, util::MsgBuffer};
@ -24,13 +24,13 @@ union IfReqData {
flags: libc::c_short, flags: libc::c_short,
value: libc::c_int, value: libc::c_int,
addr: (libc::c_short, Ipv4Addr), addr: (libc::c_short, Ipv4Addr),
_dummy: [u8; 24] _dummy: [u8; 24],
} }
#[repr(C)] #[repr(C)]
struct IfReq { struct IfReq {
ifr_name: [u8; libc::IF_NAMESIZE], ifr_name: [u8; libc::IF_NAMESIZE],
data: IfReqData data: IfReqData,
} }
impl IfReq { impl IfReq {
@ -42,7 +42,6 @@ impl IfReq {
} }
} }
/// The type of a tun/tap device /// The type of a tun/tap device
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum Type { pub enum Type {
@ -51,14 +50,14 @@ pub enum Type {
Tun, Tun,
/// Tap interface: This interface transports Ethernet frames. /// Tap interface: This interface transports Ethernet frames.
#[serde(rename = "tap")] #[serde(rename = "tap")]
Tap Tap,
} }
impl fmt::Display for Type { impl fmt::Display for Type {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self { match *self {
Type::Tun => write!(formatter, "tun"), Type::Tun => write!(formatter, "tun"),
Type::Tap => write!(formatter, "tap") Type::Tap => write!(formatter, "tap"),
} }
} }
} }
@ -70,7 +69,7 @@ impl FromStr for Type {
Ok(match &text.to_lowercase() as &str { Ok(match &text.to_lowercase() as &str {
"tun" => Self::Tun, "tun" => Self::Tun,
"tap" => Self::Tap, "tap" => Self::Tap,
_ => return Err("Unknown device type") _ => return Err("Unknown device type"),
}) })
} }
} }
@ -109,15 +108,13 @@ pub trait Device: AsRawFd {
fn get_ip(&self) -> Result<Ipv4Addr, Error>; fn get_ip(&self) -> Result<Ipv4Addr, Error>;
} }
/// Represents a tun/tap device /// Represents a tun/tap device
pub struct TunTapDevice { pub struct TunTapDevice {
fd: File, fd: File,
ifname: String, ifname: String,
type_: Type type_: Type,
} }
impl TunTapDevice { impl TunTapDevice {
/// Creates a new tun/tap device /// Creates a new tun/tap device
/// ///
@ -142,7 +139,7 @@ impl TunTapDevice {
let fd = fs::OpenOptions::new().read(true).write(true).open(path)?; let fd = fs::OpenOptions::new().read(true).write(true).open(path)?;
let flags = match type_ { let flags = match type_ {
Type::Tun => libc::IFF_TUN | libc::IFF_NO_PI, Type::Tun => libc::IFF_TUN | libc::IFF_NO_PI,
Type::Tap => libc::IFF_TAP | libc::IFF_NO_PI Type::Tap => libc::IFF_TAP | libc::IFF_NO_PI,
}; };
let mut ifreq = IfReq::new(ifname); let mut ifreq = IfReq::new(ifname);
ifreq.data.flags = flags as libc::c_short; ifreq.data.flags = flags as libc::c_short;
@ -155,7 +152,7 @@ impl TunTapDevice {
ifname = ifname.trim_end_matches('\0').to_owned(); ifname = ifname.trim_end_matches('\0').to_owned();
Ok(Self { fd, ifname, type_ }) Ok(Self { fd, ifname, type_ })
} }
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
@ -163,7 +160,7 @@ impl TunTapDevice {
#[inline] #[inline]
pub fn default_path(type_: Type) -> &'static str { pub fn default_path(type_: Type) -> &'static str {
match type_ { match type_ {
Type::Tun | Type::Tap => "/dev/net/tun" Type::Tun | Type::Tap => "/dev/net/tun",
} }
} }
@ -211,7 +208,7 @@ impl TunTapDevice {
// IP version // IP version
4 => buffer.message_mut()[0..4].copy_from_slice(&[0x00, 0x00, 0x08, 0x00]), 4 => buffer.message_mut()[0..4].copy_from_slice(&[0x00, 0x00, 0x08, 0x00]),
6 => buffer.message_mut()[0..4].copy_from_slice(&[0x00, 0x00, 0x86, 0xdd]), 6 => buffer.message_mut()[0..4].copy_from_slice(&[0x00, 0x00, 0x86, 0xdd]),
_ => unreachable!() _ => unreachable!(),
} }
} }
} }
@ -283,7 +280,7 @@ impl Device for TunTapDevice {
self.correct_data_before_write(buffer); self.correct_data_before_write(buffer);
match self.fd.write_all(buffer.message()) { match self.fd.write_all(buffer.message()) {
Ok(_) => self.fd.flush().map_err(|e| Error::DeviceIo("Flush error", e)), Ok(_) => self.fd.flush().map_err(|e| Error::DeviceIo("Flush error", e)),
Err(e) => Err(Error::DeviceIo("Write error", e)) Err(e) => Err(Error::DeviceIo("Write error", e)),
} }
} }
@ -299,10 +296,9 @@ impl AsRawFd for TunTapDevice {
} }
} }
pub struct MockDevice { pub struct MockDevice {
inbound: VecDeque<Vec<u8>>, inbound: VecDeque<Vec<u8>>,
outbound: VecDeque<Vec<u8>> outbound: VecDeque<Vec<u8>>,
} }
impl MockDevice { impl MockDevice {
@ -366,7 +362,6 @@ impl AsRawFd for MockDevice {
} }
} }
#[allow(clippy::useless_conversion)] #[allow(clippy::useless_conversion)]
fn set_device_mtu(ifname: &str, mtu: usize) -> io::Result<()> { fn set_device_mtu(ifname: &str, mtu: usize) -> io::Result<()> {
let sock = UdpSocket::bind("0.0.0.0:0")?; let sock = UdpSocket::bind("0.0.0.0:0")?;
@ -375,7 +370,7 @@ fn set_device_mtu(ifname: &str, mtu: usize) -> io::Result<()> {
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFMTU.try_into().unwrap(), &mut ifreq) }; let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFMTU.try_into().unwrap(), &mut ifreq) };
match res { match res {
0 => Ok(()), 0 => Ok(()),
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
@ -386,7 +381,7 @@ fn get_device_mtu(ifname: &str) -> io::Result<usize> {
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFMTU.try_into().unwrap(), &mut ifreq) }; let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFMTU.try_into().unwrap(), &mut ifreq) };
match res { match res {
0 => Ok(unsafe { ifreq.data.value as usize }), 0 => Ok(unsafe { ifreq.data.value as usize }),
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
@ -399,12 +394,12 @@ fn get_device_addr(ifname: &str) -> io::Result<Ipv4Addr> {
0 => { 0 => {
let af = unsafe { ifreq.data.addr.0 }; let af = unsafe { ifreq.data.addr.0 };
if af as libc::c_int != libc::AF_INET { if af as libc::c_int != libc::AF_INET {
return Err(io::Error::new(io::ErrorKind::AddrNotAvailable, "Invalid address family".to_owned())) return Err(io::Error::new(io::ErrorKind::AddrNotAvailable, "Invalid address family".to_owned()));
} }
let ip = unsafe { ifreq.data.addr.1 }; let ip = unsafe { ifreq.data.addr.1 };
Ok(ip) Ok(ip)
} }
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
@ -416,7 +411,7 @@ fn set_device_addr(ifname: &str, addr: Ipv4Addr) -> io::Result<()> {
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFADDR.try_into().unwrap(), &mut ifreq) }; let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFADDR.try_into().unwrap(), &mut ifreq) };
match res { match res {
0 => Ok(()), 0 => Ok(()),
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
@ -430,12 +425,12 @@ fn get_device_netmask(ifname: &str) -> io::Result<Ipv4Addr> {
0 => { 0 => {
let af = unsafe { ifreq.data.addr.0 }; let af = unsafe { ifreq.data.addr.0 };
if af as libc::c_int != libc::AF_INET { if af as libc::c_int != libc::AF_INET {
return Err(io::Error::new(io::ErrorKind::AddrNotAvailable, "Invalid address family".to_owned())) return Err(io::Error::new(io::ErrorKind::AddrNotAvailable, "Invalid address family".to_owned()));
} }
let ip = unsafe { ifreq.data.addr.1 }; let ip = unsafe { ifreq.data.addr.1 };
Ok(ip) Ok(ip)
} }
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
@ -447,7 +442,7 @@ fn set_device_netmask(ifname: &str, addr: Ipv4Addr) -> io::Result<()> {
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFNETMASK.try_into().unwrap(), &mut ifreq) }; let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFNETMASK.try_into().unwrap(), &mut ifreq) };
match res { match res {
0 => Ok(()), 0 => Ok(()),
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
@ -456,7 +451,7 @@ fn set_device_enabled(ifname: &str, up: bool) -> io::Result<()> {
let sock = UdpSocket::bind("0.0.0.0:0")?; let sock = UdpSocket::bind("0.0.0.0:0")?;
let mut ifreq = IfReq::new(ifname); let mut ifreq = IfReq::new(ifname);
if unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFFLAGS.try_into().unwrap(), &mut ifreq) } != 0 { if unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFFLAGS.try_into().unwrap(), &mut ifreq) } != 0 {
return Err(IoError::last_os_error()) return Err(IoError::last_os_error());
} }
if up { if up {
unsafe { ifreq.data.value |= libc::IFF_UP | libc::IFF_RUNNING } unsafe { ifreq.data.value |= libc::IFF_UP | libc::IFF_RUNNING }
@ -466,11 +461,10 @@ fn set_device_enabled(ifname: &str, up: bool) -> io::Result<()> {
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFFLAGS.try_into().unwrap(), &mut ifreq) }; let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFFLAGS.try_into().unwrap(), &mut ifreq) };
match res { match res {
0 => Ok(()), 0 => Ok(()),
_ => Err(IoError::last_os_error()) _ => Err(IoError::last_os_error()),
} }
} }
fn get_default_device() -> io::Result<String> { fn get_default_device() -> io::Result<String> {
let fd = BufReader::new(File::open("/proc/net/route")?); let fd = BufReader::new(File::open("/proc/net/route")?);
let mut best = None; let mut best = None;
@ -479,7 +473,7 @@ fn get_default_device() -> io::Result<String> {
let parts = line.split('\t').collect::<Vec<_>>(); let parts = line.split('\t').collect::<Vec<_>>();
if parts[1] == "00000000" { if parts[1] == "00000000" {
best = Some(parts[0].to_string()); best = Some(parts[0].to_string());
break break;
} }
if parts[2] != "00000000" { if parts[2] != "00000000" {
best = Some(parts[0].to_string()) best = Some(parts[0].to_string())

View File

@ -6,7 +6,6 @@ use thiserror::Error;
use std::io; use std::io;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
/// Crypto init error, this is recoverable /// Crypto init error, this is recoverable
@ -52,5 +51,5 @@ pub enum Error {
Parse(&'static str), Parse(&'static str),
#[error("Name can not be resolved: {0}")] #[error("Name can not be resolved: {0}")]
NameUnresolvable(String) NameUnresolvable(String),
} }

View File

@ -4,7 +4,7 @@ use std::{
fs::{self, File}, fs::{self, File},
io::Write, io::Write,
os::unix::fs::PermissionsExt, os::unix::fs::PermissionsExt,
process::Command process::Command,
}; };
const MANPAGE: &[u8] = include_bytes!("../target/vpncloud.1.gz"); const MANPAGE: &[u8] = include_bytes!("../target/vpncloud.1.gz");

View File

@ -2,10 +2,13 @@
// Copyright (C) 2015-2021 Dennis Schwerdel // Copyright (C) 2015-2021 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)
#[macro_use] extern crate log; #[macro_use]
#[macro_use] extern crate serde; extern crate log;
#[macro_use]
extern crate serde;
#[cfg(test)] extern crate tempfile; #[cfg(test)]
extern crate tempfile;
#[macro_use] #[macro_use]
pub mod util; pub mod util;
@ -18,6 +21,8 @@ pub mod config;
pub mod crypto; pub mod crypto;
pub mod device; pub mod device;
pub mod error; pub mod error;
#[cfg(feature = "installer")]
pub mod installer;
pub mod messages; pub mod messages;
pub mod net; pub mod net;
pub mod oldconfig; pub mod oldconfig;
@ -27,9 +32,10 @@ pub mod port_forwarding;
pub mod table; pub mod table;
pub mod traffic; pub mod traffic;
pub mod types; pub mod types;
#[cfg(feature = "wizard")] pub mod wizard; #[cfg(feature = "wizard")]
#[cfg(feature = "websocket")] pub mod wsproxy; pub mod wizard;
#[cfg(feature = "installer")] pub mod installer; #[cfg(feature = "websocket")]
pub mod wsproxy;
use structopt::StructOpt; use structopt::StructOpt;
@ -42,7 +48,7 @@ use std::{
process, process,
str::FromStr, str::FromStr,
sync::Mutex, sync::Mutex,
thread thread,
}; };
use crate::{ use crate::{
@ -60,7 +66,7 @@ use crate::{
use crate::wsproxy::ProxyConnection; use crate::wsproxy::ProxyConnection;
struct DualLogger { struct DualLogger {
file: Option<Mutex<File>> file: Option<Mutex<File>>,
} }
impl DualLogger { impl DualLogger {
@ -116,18 +122,18 @@ fn run_script(script: &str, ifname: &str) {
error!("Script returned with error: {:?}", status.code()) error!("Script returned with error: {:?}", status.code())
} }
} }
Err(e) => error!("Failed to execute script {:?}: {}", script, e) Err(e) => error!("Failed to execute script {:?}: {}", script, e),
} }
} }
fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> { fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> {
let (ip_str, len_str) = match addr.find('/') { let (ip_str, len_str) = match addr.find('/') {
Some(pos) => (&addr[..pos], &addr[pos + 1..]), Some(pos) => (&addr[..pos], &addr[pos + 1..]),
None => (addr, "24") None => (addr, "24"),
}; };
let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?; let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?;
if prefix_len > 32 { if prefix_len > 32 {
return Err(format!("Invalid prefix length: {}", prefix_len)) return Err(format!("Invalid prefix length: {}", prefix_len));
} }
let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?; let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?;
let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap()); let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap());
@ -231,7 +237,7 @@ fn main() {
let args: Args = Args::from_args(); let args: Args = Args::from_args();
if args.version { if args.version {
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION")); println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
return return;
} }
let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}"); let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}");
log::set_boxed_logger(Box::new(logger)).unwrap(); log::set_boxed_logger(Box::new(logger)).unwrap();
@ -292,7 +298,7 @@ fn main() {
} }
} }
} }
return return;
} }
let mut config = Config::default(); let mut config = Config::default();
if let Some(ref file) = args.config { if let Some(ref file) = args.config {
@ -317,20 +323,20 @@ fn main() {
debug!("Config: {:?}", config); debug!("Config: {:?}", config);
if config.crypto.password.is_none() && config.crypto.private_key.is_none() { if config.crypto.password.is_none() && config.crypto.private_key.is_none() {
error!("Either password or private key must be set in config or given as parameter"); error!("Either password or private key must be set in config or given as parameter");
return return;
} }
#[cfg(feature = "websocket")] #[cfg(feature = "websocket")]
if config.listen.starts_with("ws://") { if config.listen.starts_with("ws://") {
let socket = try_fail!(ProxyConnection::listen(&config.listen), "Failed to open socket {}: {}", config.listen); let socket = try_fail!(ProxyConnection::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
match config.device_type { match config.device_type {
Type::Tap => run::<payload::Frame, _>(config, socket), Type::Tap => run::<payload::Frame, _>(config, socket),
Type::Tun => run::<payload::Packet, _>(config, socket) Type::Tun => run::<payload::Packet, _>(config, socket),
} }
return return;
} }
let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen); let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
match config.device_type { match config.device_type {
Type::Tap => run::<payload::Frame, _>(config, socket), Type::Tap => run::<payload::Frame, _>(config, socket),
Type::Tun => run::<payload::Packet, _>(config, socket) Type::Tun => run::<payload::Packet, _>(config, socket),
} }
} }

View File

@ -6,30 +6,27 @@ use crate::{
crypto::Payload, crypto::Payload,
error::Error, error::Error,
types::{NodeId, Range, RangeList, NODE_ID_BYTES}, types::{NodeId, Range, RangeList, NODE_ID_BYTES},
util::MsgBuffer util::MsgBuffer,
}; };
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::{ use std::{
io::{self, Cursor, Read, Seek, SeekFrom, Take, Write}, io::{self, Cursor, Read, Seek, SeekFrom, Take, Write},
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6} net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
}; };
pub const MESSAGE_TYPE_DATA: u8 = 0; pub const MESSAGE_TYPE_DATA: u8 = 0;
pub const MESSAGE_TYPE_NODE_INFO: u8 = 1; pub const MESSAGE_TYPE_NODE_INFO: u8 = 1;
pub const MESSAGE_TYPE_KEEPALIVE: u8 = 2; pub const MESSAGE_TYPE_KEEPALIVE: u8 = 2;
pub const MESSAGE_TYPE_CLOSE: u8 = 0xff; pub const MESSAGE_TYPE_CLOSE: u8 = 0xff;
pub type AddrList = SmallVec<[SocketAddr; 4]>; pub type AddrList = SmallVec<[SocketAddr; 4]>;
pub type PeerList = SmallVec<[PeerInfo; 16]>; pub type PeerList = SmallVec<[PeerInfo; 16]>;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct PeerInfo { pub struct PeerInfo {
pub node_id: Option<NodeId>, pub node_id: Option<NodeId>,
pub addrs: AddrList pub addrs: AddrList,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -38,7 +35,7 @@ pub struct NodeInfo {
pub peers: PeerList, pub peers: PeerList,
pub claims: RangeList, pub claims: RangeList,
pub peer_timeout: Option<u16>, pub peer_timeout: Option<u16>,
pub addrs: AddrList pub addrs: AddrList,
} }
impl NodeInfo { impl NodeInfo {
@ -53,7 +50,7 @@ impl NodeInfo {
let flags = r.read_u8()?; let flags = r.read_u8()?;
Self::read_addr_list_inner(r, flags) Self::read_addr_list_inner(r, flags)
} }
fn read_addr_list_inner<R: Read>(r: &mut Take<R>, flags: u8) -> Result<AddrList, io::Error> { fn read_addr_list_inner<R: Read>(r: &mut Take<R>, flags: u8) -> Result<AddrList, io::Error> {
let num_ipv4_addrs = (flags & 0x07) as usize; let num_ipv4_addrs = (flags & 0x07) as usize;
let num_ipv6_addrs = (flags & 0x38) as usize / 8; let num_ipv6_addrs = (flags & 0x38) as usize / 8;
@ -74,7 +71,7 @@ impl NodeInfo {
} }
Ok(addrs) Ok(addrs)
} }
fn decode_peer_list_part<R: Read>(r: &mut Take<R>) -> Result<PeerList, io::Error> { fn decode_peer_list_part<R: Read>(r: &mut Take<R>) -> Result<PeerList, io::Error> {
let mut peers = smallvec![]; let mut peers = smallvec![];
while r.limit() > 0 { while r.limit() > 0 {
@ -109,7 +106,7 @@ impl NodeInfo {
loop { loop {
let part = r.read_u8().map_err(|_| Error::Message("Truncated message"))?; let part = r.read_u8().map_err(|_| Error::Message("Truncated message"))?;
if part == Self::PART_END { if part == Self::PART_END {
break break;
} }
let part_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Message("Truncated message"))? as usize; let part_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Message("Truncated message"))? as usize;
let mut rp = r.take(part_len as u64); let mut rp = r.take(part_len as u64);
@ -139,7 +136,7 @@ impl NodeInfo {
} }
let node_id = match node_id { let node_id = match node_id {
Some(node_id) => node_id, Some(node_id) => node_id,
None => return Err(Error::Message("Payload without node_id")) None => return Err(Error::Message("Payload without node_id")),
}; };
Ok(Self { node_id, peers, claims, peer_timeout, addrs }) Ok(Self { node_id, peers, claims, peer_timeout, addrs })
} }
@ -155,7 +152,7 @@ impl NodeInfo {
for a in &p.addrs { for a in &p.addrs {
match a { match a {
SocketAddr::V4(addr) => addr_ipv4.push(*addr), SocketAddr::V4(addr) => addr_ipv4.push(*addr),
SocketAddr::V6(addr) => addr_ipv6.push(*addr) SocketAddr::V6(addr) => addr_ipv6.push(*addr),
} }
} }
while addr_ipv4.len() >= 8 { while addr_ipv4.len() >= 8 {
@ -190,7 +187,7 @@ impl NodeInfo {
for a in &self.addrs { for a in &self.addrs {
match a { match a {
SocketAddr::V4(addr) => addr_ipv4.push(*addr), SocketAddr::V4(addr) => addr_ipv4.push(*addr),
SocketAddr::V6(addr) => addr_ipv6.push(*addr) SocketAddr::V6(addr) => addr_ipv6.push(*addr),
} }
} }
while addr_ipv4.len() >= 8 { while addr_ipv4.len() >= 8 {
@ -213,7 +210,7 @@ impl NodeInfo {
} }
fn encode_part<F: FnOnce(&mut Cursor<&mut [u8]>) -> Result<(), io::Error>>( fn encode_part<F: FnOnce(&mut Cursor<&mut [u8]>) -> Result<(), io::Error>>(
cursor: &mut Cursor<&mut [u8]>, part: u8, f: F cursor: &mut Cursor<&mut [u8]>, part: u8, f: F,
) -> Result<(), io::Error> { ) -> Result<(), io::Error> {
cursor.write_u8(part)?; cursor.write_u8(part)?;
cursor.write_u16::<NetworkEndian>(0)?; cursor.write_u16::<NetworkEndian>(0)?;
@ -257,7 +254,6 @@ impl NodeInfo {
} }
} }
impl Payload for NodeInfo { impl Payload for NodeInfo {
fn write_to(&self, buffer: &mut MsgBuffer) { fn write_to(&self, buffer: &mut MsgBuffer) {
self.encode(buffer) self.encode(buffer)

View File

@ -5,9 +5,9 @@
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
io::{self, ErrorKind}, io::{self, ErrorKind},
net::{IpAddr, SocketAddr, UdpSocket, Ipv6Addr}, net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket},
os::unix::io::{AsRawFd, RawFd}, os::unix::io::{AsRawFd, RawFd},
sync::atomic::{AtomicBool, Ordering} sync::atomic::{AtomicBool, Ordering},
}; };
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource}; use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
@ -17,7 +17,7 @@ pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
// HOT PATH // HOT PATH
match addr { match addr {
SocketAddr::V4(addr4) => SocketAddr::new(IpAddr::V6(addr4.ip().to_ipv6_mapped()), addr4.port()), SocketAddr::V4(addr4) => SocketAddr::new(IpAddr::V6(addr4.ip().to_ipv6_mapped()), addr4.port()),
_ => addr _ => addr,
} }
} }
@ -84,7 +84,7 @@ pub struct MockSocket {
nat_peers: HashMap<SocketAddr, Time>, nat_peers: HashMap<SocketAddr, Time>,
address: SocketAddr, address: SocketAddr,
outbound: VecDeque<(SocketAddr, Vec<u8>)>, outbound: VecDeque<(SocketAddr, Vec<u8>)>,
inbound: VecDeque<(SocketAddr, Vec<u8>)> inbound: VecDeque<(SocketAddr, Vec<u8>)>,
} }
impl MockSocket { impl MockSocket {
@ -94,7 +94,7 @@ impl MockSocket {
nat_peers: HashMap::new(), nat_peers: HashMap::new(),
address, address,
outbound: VecDeque::with_capacity(10), outbound: VecDeque::with_capacity(10),
inbound: VecDeque::with_capacity(10) inbound: VecDeque::with_capacity(10),
} }
} }
@ -109,12 +109,12 @@ impl MockSocket {
pub fn put_inbound(&mut self, from: SocketAddr, data: Vec<u8>) -> bool { pub fn put_inbound(&mut self, from: SocketAddr, data: Vec<u8>) -> bool {
if !self.nat { if !self.nat {
self.inbound.push_back((from, data)); self.inbound.push_back((from, data));
return true return true;
} }
if let Some(timeout) = self.nat_peers.get(&from) { if let Some(timeout) = self.nat_peers.get(&from) {
if *timeout >= MockTimeSource::now() { if *timeout >= MockTimeSource::now() {
self.inbound.push_back((from, data)); self.inbound.push_back((from, data));
return true return true;
} }
} }
warn!("Sender {:?} is filtered out by NAT", from); warn!("Sender {:?} is filtered out by NAT", from);
@ -178,4 +178,4 @@ mod bench {
b.iter(|| sock.send_to(&data, &addr).unwrap()); b.iter(|| sock.send_to(&data, &addr).unwrap());
b.bytes = 1400; b.bytes = 1400;
} }
} }

View File

@ -13,7 +13,7 @@ pub enum OldCryptoMethod {
#[serde(rename = "aes256")] #[serde(rename = "aes256")]
AES256, AES256,
#[serde(rename = "aes128")] #[serde(rename = "aes128")]
AES128 AES128,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -57,7 +57,7 @@ pub struct OldConfigFile {
#[serde(alias = "statsd-prefix")] #[serde(alias = "statsd-prefix")]
pub statsd_prefix: Option<String>, pub statsd_prefix: Option<String>,
pub user: Option<String>, pub user: Option<String>,
pub group: Option<String> pub group: Option<String>,
} }
impl OldConfigFile { impl OldConfigFile {
@ -89,7 +89,7 @@ impl OldConfigFile {
interval: self.beacon_interval, interval: self.beacon_interval,
load: self.beacon_load, load: self.beacon_load,
store: self.beacon_store, store: self.beacon_store,
password: self.shared_key.clone() password: self.shared_key.clone(),
}), }),
claims: self.subnets, claims: self.subnets,
crypto: CryptoConfig { crypto: CryptoConfig {
@ -97,13 +97,13 @@ impl OldConfigFile {
password: Some(self.shared_key.unwrap_or_else(|| "none".to_string())), password: Some(self.shared_key.unwrap_or_else(|| "none".to_string())),
private_key: None, private_key: None,
public_key: None, public_key: None,
trusted_keys: vec![] trusted_keys: vec![],
}, },
device: Some(ConfigFileDevice { device: Some(ConfigFileDevice {
fix_rp_filter: None, fix_rp_filter: None,
name: self.device_name, name: self.device_name,
path: self.device_path, path: self.device_path,
type_: self.device_type type_: self.device_type,
}), }),
group: self.group, group: self.group,
ifdown: self.ifdown, ifdown: self.ifdown,
@ -121,7 +121,7 @@ impl OldConfigFile {
switch_timeout: self.dst_timeout, switch_timeout: self.dst_timeout,
user: self.user, user: self.user,
hook: None, hook: None,
hooks: HashMap::new() hooks: HashMap::new(),
} }
} }
} }

View File

@ -43,7 +43,7 @@ impl Protocol for Frame {
// treat vlan id 0x000 as untagged // treat vlan id 0x000 as untagged
src.copy_within(2..8, 0); src.copy_within(2..8, 0);
dst.copy_within(2..8, 0); dst.copy_within(2..8, 0);
return Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 })) return Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 }));
} }
Ok((Address { data: src, len: 8 }, Address { data: dst, len: 8 })) Ok((Address { data: src, len: 8 }, Address { data: dst, len: 8 }))
} else { } else {
@ -52,7 +52,6 @@ impl Protocol for Frame {
} }
} }
#[test] #[test]
fn decode_frame_without_vlan() { fn decode_frame_without_vlan() {
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]; let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8];
@ -93,13 +92,13 @@ impl Protocol for Packet {
fn parse(data: &[u8]) -> Result<(Address, Address), Error> { fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
// HOT PATH // HOT PATH
if data.is_empty() { if data.is_empty() {
return Err(Error::Parse("Empty header")) return Err(Error::Parse("Empty header"));
} }
let version = data[0] >> 4; let version = data[0] >> 4;
match version { match version {
4 => { 4 => {
if data.len() < 20 { if data.len() < 20 {
return Err(Error::Parse("Truncated IPv4 header")) return Err(Error::Parse("Truncated IPv4 header"));
} }
let src = Address::read_from_fixed(&data[12..], 4)?; let src = Address::read_from_fixed(&data[12..], 4)?;
let dst = Address::read_from_fixed(&data[16..], 4)?; let dst = Address::read_from_fixed(&data[16..], 4)?;
@ -107,18 +106,17 @@ impl Protocol for Packet {
} }
6 => { 6 => {
if data.len() < 40 { if data.len() < 40 {
return Err(Error::Parse("Truncated IPv6 header")) return Err(Error::Parse("Truncated IPv6 header"));
} }
let src = Address::read_from_fixed(&data[8..], 16)?; let src = Address::read_from_fixed(&data[8..], 16)?;
let dst = Address::read_from_fixed(&data[24..], 16)?; let dst = Address::read_from_fixed(&data[24..], 16)?;
Ok((src, dst)) Ok((src, dst))
} }
_ => Err(Error::Parse("Invalid IP protocol version")) _ => Err(Error::Parse("Invalid IP protocol version")),
} }
} }
} }
#[test] #[test]
fn decode_ipv4_packet() { fn decode_ipv4_packet() {
let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]; let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2];
@ -131,7 +129,7 @@ fn decode_ipv4_packet() {
fn decode_ipv6_packet() { fn decode_ipv6_packet() {
let data = [ let data = [
0x60, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, 0x60, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5,
4, 3, 2, 1 4, 3, 2, 1,
]; ];
let (src, dst) = Packet::parse(&data).unwrap(); let (src, dst) = Packet::parse(&data).unwrap();
assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6], len: 16 }); assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6], len: 16 });
@ -158,4 +156,4 @@ fn decode_invalid_packet() {
4, 3, 2 4, 3, 2
]) ])
.is_err()); .is_err());
} }

View File

@ -11,7 +11,7 @@ pub struct EpollWait {
event: libc::epoll_event, event: libc::epoll_event,
socket: RawFd, socket: RawFd,
device: RawFd, device: RawFd,
timeout: u32 timeout: u32,
} }
impl EpollWait { impl EpollWait {
@ -27,14 +27,14 @@ impl EpollWait {
let mut event = libc::epoll_event { u64: 0, events: 0 }; let mut event = libc::epoll_event { u64: 0, events: 0 };
let poll_fd = unsafe { libc::epoll_create(3) }; let poll_fd = unsafe { libc::epoll_create(3) };
if poll_fd == -1 { if poll_fd == -1 {
return Err(io::Error::last_os_error()) return Err(io::Error::last_os_error());
} }
for fd in &[socket, device] { for fd in &[socket, device] {
event.u64 = *fd as u64; event.u64 = *fd as u64;
event.events = flags; event.events = flags;
let res = unsafe { libc::epoll_ctl(poll_fd, libc::EPOLL_CTL_ADD, *fd, &mut event) }; let res = unsafe { libc::epoll_ctl(poll_fd, libc::EPOLL_CTL_ADD, *fd, &mut event) };
if res == -1 { if res == -1 {
return Err(io::Error::last_os_error()) return Err(io::Error::last_os_error());
} }
} }
Ok(Self { poll_fd, event, socket, device, timeout }) Ok(Self { poll_fd, event, socket, device, timeout })
@ -63,7 +63,7 @@ impl Iterator for EpollWait {
unreachable!() unreachable!()
} }
} }
_ => unreachable!() _ => unreachable!(),
}) })
} }
} }

View File

@ -8,12 +8,11 @@ mod epoll;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
pub use self::epoll::EpollWait as WaitImpl; pub use self::epoll::EpollWait as WaitImpl;
use std::io; use std::io;
pub enum WaitResult { pub enum WaitResult {
Timeout, Timeout,
Socket, Socket,
Device, Device,
Error(io::Error) Error(io::Error),
} }

View File

@ -19,7 +19,7 @@ mod internal {
pub internal_addr: SocketAddrV4, pub internal_addr: SocketAddrV4,
pub external_addr: SocketAddrV4, pub external_addr: SocketAddrV4,
gateway: Gateway, gateway: Gateway,
pub next_extension: Option<Time> pub next_extension: Option<Time>,
} }
impl PortForwarding { impl PortForwarding {
@ -32,11 +32,11 @@ mod internal {
if err.kind() == io::ErrorKind::WouldBlock { if err.kind() == io::ErrorKind::WouldBlock {
// Why this code? // Why this code?
info!("Port-forwarding: no router found"); info!("Port-forwarding: no router found");
return None return None;
} }
} }
error!("Port-forwarding: failed to find router: {}", err); error!("Port-forwarding: failed to find router: {}", err);
return None return None;
} }
}; };
debug!("Port-forwarding: found router at {}", gateway.addr); debug!("Port-forwarding: found router at {}", gateway.addr);
@ -46,7 +46,7 @@ mod internal {
Ok(ip) => ip, Ok(ip) => ip,
Err(err) => { Err(err) => {
error!("Port-forwarding: failed to obtain external IP: {}", err); error!("Port-forwarding: failed to obtain external IP: {}", err);
return None return None;
} }
}; };
if let Ok((port, timeout)) = Self::get_any_forwarding(&gateway, internal_addr, port) { if let Ok((port, timeout)) = Self::get_any_forwarding(&gateway, internal_addr, port) {
@ -64,19 +64,19 @@ mod internal {
fn get_any_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> { fn get_any_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> {
if let Ok(a) = Self::get_forwarding(gateway, addr, port) { if let Ok(a) = Self::get_forwarding(gateway, addr, port) {
return Ok(a) return Ok(a);
} }
if let Ok(a) = Self::get_forwarding(gateway, addr, 0) { if let Ok(a) = Self::get_forwarding(gateway, addr, 0) {
return Ok(a) return Ok(a);
} }
for i in 1..5 { for i in 1..5 {
if let Ok(a) = Self::get_forwarding(gateway, addr, port + i) { if let Ok(a) = Self::get_forwarding(gateway, addr, port + i) {
return Ok(a) return Ok(a);
} }
} }
for _ in 0..5 { for _ in 0..5 {
if let Ok(a) = Self::get_forwarding(gateway, addr, rand::random()) { if let Ok(a) = Self::get_forwarding(gateway, addr, rand::random()) {
return Ok(a) return Ok(a);
} }
} }
warn!("Failed to activate port forwarding"); warn!("Failed to activate port forwarding");
@ -125,20 +125,20 @@ mod internal {
pub fn check_extend(&mut self) { pub fn check_extend(&mut self) {
if let Some(deadline) = self.next_extension { if let Some(deadline) = self.next_extension {
if deadline > SystemTimeSource::now() { if deadline > SystemTimeSource::now() {
return return;
} }
} else { } else {
return return;
} }
match self.gateway.add_port( match self.gateway.add_port(
PortMappingProtocol::UDP, PortMappingProtocol::UDP,
self.external_addr.port(), self.external_addr.port(),
self.internal_addr, self.internal_addr,
LEASE_TIME, LEASE_TIME,
DESCRIPTION DESCRIPTION,
) { ) {
Ok(()) => debug!("Port-forwarding: extended port forwarding"), Ok(()) => debug!("Port-forwarding: extended port forwarding"),
Err(err) => debug!("Port-forwarding: failed to extend port forwarding: {}", err) Err(err) => debug!("Port-forwarding: failed to extend port forwarding: {}", err),
}; };
self.next_extension = Some(SystemTimeSource::now() + Time::from(LEASE_TIME) - 60); self.next_extension = Some(SystemTimeSource::now() + Time::from(LEASE_TIME) - 60);
} }
@ -146,7 +146,7 @@ mod internal {
fn deactivate(&self) { fn deactivate(&self) {
match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) { match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) {
Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"), Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"),
Err(err) => debug!("Port-forwarding: failed to deactivate port forwarding: {}", err) Err(err) => debug!("Port-forwarding: failed to deactivate port forwarding: {}", err),
} }
} }

View File

@ -4,27 +4,25 @@
use fnv::FnvHasher; use fnv::FnvHasher;
use std::{ use std::{
cmp::min, collections::HashMap, hash::BuildHasherDefault, io, io::Write, marker::PhantomData, net::SocketAddr cmp::min, collections::HashMap, hash::BuildHasherDefault, io, io::Write, marker::PhantomData, net::SocketAddr,
}; };
use crate::{ use crate::{
types::{Address, Range, RangeList}, types::{Address, Range, RangeList},
util::{addr_nice, Duration, Time, TimeSource} util::{addr_nice, Duration, Time, TimeSource},
}; };
type Hash = BuildHasherDefault<FnvHasher>; type Hash = BuildHasherDefault<FnvHasher>;
struct CacheValue { struct CacheValue {
peer: SocketAddr, peer: SocketAddr,
timeout: Time timeout: Time,
} }
struct ClaimEntry { struct ClaimEntry {
peer: SocketAddr, peer: SocketAddr,
claim: Range, claim: Range,
timeout: Time timeout: Time,
} }
pub struct ClaimTable<TS: TimeSource> { pub struct ClaimTable<TS: TimeSource> {
@ -32,7 +30,7 @@ pub struct ClaimTable<TS: TimeSource> {
cache_timeout: Duration, cache_timeout: Duration,
claims: Vec<ClaimEntry>, claims: Vec<ClaimEntry>,
claim_timeout: Duration, claim_timeout: Duration,
_dummy: PhantomData<TS> _dummy: PhantomData<TS>,
} }
impl<TS: TimeSource> ClaimTable<TS> { impl<TS: TimeSource> ClaimTable<TS> {
@ -57,7 +55,7 @@ impl<TS: TimeSource> ClaimTable<TS> {
entry.timeout = TS::now() + self.claim_timeout as Time; entry.timeout = TS::now() + self.claim_timeout as Time;
claims.swap_remove(pos); claims.swap_remove(pos);
if claims.is_empty() { if claims.is_empty() {
break break;
} }
} else { } else {
entry.timeout = 0 entry.timeout = 0
@ -92,7 +90,7 @@ impl<TS: TimeSource> ClaimTable<TS> {
pub fn lookup(&mut self, addr: Address) -> Option<SocketAddr> { pub fn lookup(&mut self, addr: Address) -> Option<SocketAddr> {
// HOT PATH // HOT PATH
if let Some(entry) = self.cache.get(&addr) { if let Some(entry) = self.cache.get(&addr) {
return Some(entry.peer) return Some(entry.peer);
} }
// COLD PATH // COLD PATH
let mut found = None; let mut found = None;
@ -104,11 +102,11 @@ impl<TS: TimeSource> ClaimTable<TS> {
} }
} }
if let Some(entry) = found { if let Some(entry) = found {
self.cache.insert(addr, CacheValue { self.cache.insert(
peer: entry.peer, addr,
timeout: min(TS::now() + self.cache_timeout as Time, entry.timeout) CacheValue { peer: entry.peer, timeout: min(TS::now() + self.cache_timeout as Time, entry.timeout) },
}); );
return Some(entry.peer) return Some(entry.peer);
} }
None None
} }
@ -155,4 +153,4 @@ impl<TS: TimeSource> ClaimTable<TS> {
} }
} }
// TODO: test // TODO: test

View File

@ -8,8 +8,8 @@ use std::{
net::SocketAddr, net::SocketAddr,
sync::{ sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Once Once,
} },
}; };
pub use crate::{ pub use crate::{
@ -19,10 +19,9 @@ pub use crate::{
net::MockSocket, net::MockSocket,
payload::{Frame, Packet, Protocol}, payload::{Frame, Packet, Protocol},
types::Range, types::Range,
util::{MockTimeSource, Time, TimeSource} util::{MockTimeSource, Time, TimeSource},
}; };
static INIT_LOGGER: Once = Once::new(); static INIT_LOGGER: Once = Once::new();
pub fn init_debug_logger() { pub fn init_debug_logger() {
@ -61,20 +60,18 @@ impl log::Log for DebugLogger {
} }
} }
type TestNode<P> = GenericCloud<MockDevice, P, MockSocket, MockTimeSource>; type TestNode<P> = GenericCloud<MockDevice, P, MockSocket, MockTimeSource>;
pub struct Simulator<P: Protocol> { pub struct Simulator<P: Protocol> {
next_port: u16, next_port: u16,
nodes: HashMap<SocketAddr, TestNode<P>>, nodes: HashMap<SocketAddr, TestNode<P>>,
messages: VecDeque<(SocketAddr, SocketAddr, Vec<u8>)> messages: VecDeque<(SocketAddr, SocketAddr, Vec<u8>)>,
} }
pub type TapSimulator = Simulator<Frame>; pub type TapSimulator = Simulator<Frame>;
#[allow(dead_code)] #[allow(dead_code)]
pub type TunSimulator = Simulator<Packet>; pub type TunSimulator = Simulator<Packet>;
impl<P: Protocol> Simulator<P> { impl<P: Protocol> Simulator<P> {
pub fn new() -> Self { pub fn new() -> Self {
init_debug_logger(); init_debug_logger();

View File

@ -6,16 +6,15 @@ use std::{
collections::HashMap, collections::HashMap,
io::{self, Write}, io::{self, Write},
net::SocketAddr, net::SocketAddr,
ops::AddAssign ops::AddAssign,
}; };
use super::{ use super::{
cloud::{Hash, STATS_INTERVAL}, cloud::{Hash, STATS_INTERVAL},
types::Address, types::Address,
util::{addr_nice, Bytes} util::{addr_nice, Bytes},
}; };
#[derive(Default)] #[derive(Default)]
pub struct TrafficEntry { pub struct TrafficEntry {
pub out_bytes_total: u64, pub out_bytes_total: u64,
@ -26,7 +25,7 @@ pub struct TrafficEntry {
pub in_packets_total: usize, pub in_packets_total: usize,
pub in_bytes: u64, pub in_bytes: u64,
pub in_packets: usize, pub in_packets: usize,
pub idle_periods: usize pub idle_periods: usize,
} }
impl AddAssign<&TrafficEntry> for TrafficEntry { impl AddAssign<&TrafficEntry> for TrafficEntry {
@ -72,12 +71,11 @@ impl TrafficEntry {
} }
} }
#[derive(Default)] #[derive(Default)]
pub struct TrafficStats { pub struct TrafficStats {
peers: HashMap<SocketAddr, TrafficEntry, Hash>, peers: HashMap<SocketAddr, TrafficEntry, Hash>,
payload: HashMap<(Address, Address), TrafficEntry, Hash>, payload: HashMap<(Address, Address), TrafficEntry, Hash>,
pub dropped: TrafficEntry pub dropped: TrafficEntry,
} }
impl TrafficStats { impl TrafficStats {

View File

@ -4,7 +4,7 @@
use crate::{ use crate::{
error::Error, error::Error,
util::{bytes_to_hex, Encoder} util::{bytes_to_hex, Encoder},
}; };
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -13,18 +13,17 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
io::{Read, Write}, io::{Read, Write},
net::{Ipv4Addr, Ipv6Addr}, net::{Ipv4Addr, Ipv6Addr},
str::FromStr str::FromStr,
}; };
pub const NODE_ID_BYTES: usize = 16; pub const NODE_ID_BYTES: usize = 16;
pub type NodeId = [u8; NODE_ID_BYTES]; pub type NodeId = [u8; NODE_ID_BYTES];
#[derive(Eq, Clone, Copy)] #[derive(Eq, Clone, Copy)]
pub struct Address { pub struct Address {
pub data: [u8; 16], pub data: [u8; 16],
pub len: u8 pub len: u8,
} }
impl Address { impl Address {
@ -37,7 +36,7 @@ impl Address {
#[inline] #[inline]
pub fn read_from_fixed<R: Read>(mut r: R, len: u8) -> Result<Address, Error> { pub fn read_from_fixed<R: Read>(mut r: R, len: u8) -> Result<Address, Error> {
if len > 16 { if len > 16 {
return Err(Error::Parse("Invalid address, too long")) return Err(Error::Parse("Invalid address, too long"));
} }
let mut data = [0; 16]; let mut data = [0; 16];
r.read_exact(&mut data[..len as usize]).map_err(|_| Error::Parse("Address too short"))?; r.read_exact(&mut data[..len as usize]).map_err(|_| Error::Parse("Address too short"))?;
@ -57,7 +56,6 @@ impl Address {
} }
} }
impl PartialEq for Address { impl PartialEq for Address {
#[inline] #[inline]
fn eq(&self, rhs: &Self) -> bool { fn eq(&self, rhs: &Self) -> bool {
@ -65,7 +63,6 @@ impl PartialEq for Address {
} }
} }
impl Hash for Address { impl Hash for Address {
#[inline] #[inline]
fn hash<H: Hasher>(&self, hasher: &mut H) { fn hash<H: Hasher>(&self, hasher: &mut H) {
@ -73,7 +70,6 @@ impl Hash for Address {
} }
} }
impl fmt::Display for Address { impl fmt::Display for Address {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let d = &self.data; let d = &self.data;
@ -108,7 +104,7 @@ impl FromStr for Address {
let ip = addr.octets(); let ip = addr.octets();
let mut res = [0; 16]; let mut res = [0; 16];
res[0..4].copy_from_slice(&ip); res[0..4].copy_from_slice(&ip);
return Ok(Address { data: res, len: 4 }) return Ok(Address { data: res, len: 4 });
} }
if let Ok(addr) = Ipv6Addr::from_str(text) { if let Ok(addr) = Ipv6Addr::from_str(text) {
let segments = addr.segments(); let segments = addr.segments();
@ -116,7 +112,7 @@ impl FromStr for Address {
for i in 0..8 { for i in 0..8 {
Encoder::write_u16(segments[i], &mut res[2 * i..]); Encoder::write_u16(segments[i], &mut res[2 * i..]);
} }
return Ok(Address { data: res, len: 16 }) return Ok(Address { data: res, len: 16 });
} }
let parts: SmallVec<[&str; 10]> = text.split(':').collect(); let parts: SmallVec<[&str; 10]> = text.split(':').collect();
if parts.len() == 6 { if parts.len() == 6 {
@ -124,17 +120,16 @@ impl FromStr for Address {
for i in 0..6 { for i in 0..6 {
bytes[i] = u8::from_str_radix(parts[i], 16).map_err(|_| Error::Parse("Failed to parse mac"))?; bytes[i] = u8::from_str_radix(parts[i], 16).map_err(|_| Error::Parse("Failed to parse mac"))?;
} }
return Ok(Address { data: bytes, len: 6 }) return Ok(Address { data: bytes, len: 6 });
} }
Err(Error::Parse("Failed to parse address")) Err(Error::Parse("Failed to parse address"))
} }
} }
#[derive(PartialEq, Eq, Hash, Clone, Copy)] #[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct Range { pub struct Range {
pub base: Address, pub base: Address,
pub prefix_len: u8 pub prefix_len: u8,
} }
pub type RangeList = SmallVec<[Range; 4]>; pub type RangeList = SmallVec<[Range; 4]>;
@ -142,14 +137,14 @@ pub type RangeList = SmallVec<[Range; 4]>;
impl Range { impl Range {
pub fn matches(&self, addr: Address) -> bool { pub fn matches(&self, addr: Address) -> bool {
if self.base.len != addr.len { if self.base.len != addr.len {
return false return false;
} }
let mut match_len = 0; let mut match_len = 0;
for i in 0..addr.len as usize { for i in 0..addr.len as usize {
let m = addr.data[i] ^ self.base.data[i]; let m = addr.data[i] ^ self.base.data[i];
match_len += m.leading_zeros() as u8; match_len += m.leading_zeros() as u8;
if m != 0 { if m != 0 {
break break;
} }
} }
match_len >= self.prefix_len match_len >= self.prefix_len
@ -175,7 +170,7 @@ impl FromStr for Range {
fn from_str(text: &str) -> Result<Self, Self::Err> { fn from_str(text: &str) -> Result<Self, Self::Err> {
let pos = match text.find('/') { let pos = match text.find('/') {
Some(pos) => pos, Some(pos) => pos,
None => return Err(Error::Parse("Invalid range format")) None => return Err(Error::Parse("Invalid range format")),
}; };
let prefix_len = u8::from_str(&text[pos + 1..]).map_err(|_| Error::Parse("Failed to parse prefix length"))?; let prefix_len = u8::from_str(&text[pos + 1..]).map_err(|_| Error::Parse("Failed to parse prefix length"))?;
let base = Address::from_str(&text[..pos])?; let base = Address::from_str(&text[..pos])?;
@ -195,7 +190,6 @@ impl fmt::Debug for Range {
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum Mode { pub enum Mode {
#[serde(rename = "normal")] #[serde(rename = "normal")]
@ -205,7 +199,7 @@ pub enum Mode {
#[serde(rename = "switch")] #[serde(rename = "switch")]
Switch, Switch,
#[serde(rename = "router")] #[serde(rename = "router")]
Router Router,
} }
impl fmt::Display for Mode { impl fmt::Display for Mode {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
@ -213,7 +207,7 @@ impl fmt::Display for Mode {
Mode::Normal => write!(formatter, "normal"), Mode::Normal => write!(formatter, "normal"),
Mode::Hub => write!(formatter, "hub"), Mode::Hub => write!(formatter, "hub"),
Mode::Switch => write!(formatter, "switch"), Mode::Switch => write!(formatter, "switch"),
Mode::Router => write!(formatter, "router") Mode::Router => write!(formatter, "router"),
} }
} }
} }
@ -226,12 +220,11 @@ impl FromStr for Mode {
"hub" => Self::Hub, "hub" => Self::Hub,
"switch" => Self::Switch, "switch" => Self::Switch,
"router" => Self::Router, "router" => Self::Router,
_ => return Err("Unknown mode") _ => return Err("Unknown mode"),
}) })
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -32,7 +32,7 @@ fn configure_connectivity(config: &mut Config, mode: usize, theme: &ColorfulThem
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Peer addresses (comma separated)") .with_prompt("Peer addresses (comma separated)")
.default(config.peers.join(",")) .default(config.peers.join(","))
.interact_text()? .interact_text()?,
); );
if mode >= MODE_ADVANCED { if mode >= MODE_ADVANCED {
config.port_forwarding = Confirm::with_theme(theme) config.port_forwarding = Confirm::with_theme(theme)
@ -54,12 +54,11 @@ fn configure_connectivity(config: &mut Config, mode: usize, theme: &ColorfulThem
Ok(()) Ok(())
} }
fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> { fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
if (config.crypto.password.is_some() || config.crypto.private_key.is_some()) if (config.crypto.password.is_some() || config.crypto.private_key.is_some())
&& !Confirm::with_theme(theme).with_prompt("Create new crypto config?").default(false).interact()? && !Confirm::with_theme(theme).with_prompt("Create new crypto config?").default(false).interact()?
{ {
return Ok(()) return Ok(());
} }
let mut use_password = true; let mut use_password = true;
if mode >= MODE_ADVANCED { if mode >= MODE_ADVANCED {
@ -75,7 +74,7 @@ fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
Password::with_theme(theme) Password::with_theme(theme)
.with_prompt("Password") .with_prompt("Password")
.with_confirmation("Confirm password", "Passwords do not match") .with_confirmation("Confirm password", "Passwords do not match")
.interact()? .interact()?,
); );
config.crypto.private_key = None; config.crypto.private_key = None;
config.crypto.public_key = None; config.crypto.public_key = None;
@ -113,13 +112,13 @@ fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
info!("Public key: {}", pub_key); info!("Public key: {}", pub_key);
(priv_key, pub_key) (priv_key, pub_key)
} }
_ => unreachable!() _ => unreachable!(),
}; };
config.crypto.trusted_keys = str_list( config.crypto.trusted_keys = str_list(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Trusted keys (public keys, comma separated)") .with_prompt("Trusted keys (public keys, comma separated)")
.default(pub_key.clone()) .default(pub_key.clone())
.interact_text()? .interact_text()?,
); );
config.crypto.private_key = Some(priv_key); config.crypto.private_key = Some(priv_key);
config.crypto.public_key = Some(pub_key); config.crypto.public_key = Some(pub_key);
@ -133,7 +132,7 @@ fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
("Unencrypted (dangerous)", unencrypted), ("Unencrypted (dangerous)", unencrypted),
("AES-128 in GCM mode", allowed_algos.contains(&&aead::AES_128_GCM)), ("AES-128 in GCM mode", allowed_algos.contains(&&aead::AES_128_GCM)),
("AES-256 in GCM mode", allowed_algos.contains(&&aead::AES_256_GCM)), ("AES-256 in GCM mode", allowed_algos.contains(&&aead::AES_256_GCM)),
("ChaCha20-Poly1305 (RFC 7539)", allowed_algos.contains(&&aead::CHACHA20_POLY1305)) ("ChaCha20-Poly1305 (RFC 7539)", allowed_algos.contains(&&aead::CHACHA20_POLY1305)),
]) ])
.interact()?; .interact()?;
config.crypto.algorithms = vec![]; config.crypto.algorithms = vec![];
@ -156,7 +155,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
{ {
0 => device::Type::Tun, 0 => device::Type::Tun,
1 => device::Type::Tap, 1 => device::Type::Tap,
_ => unreachable!() _ => unreachable!(),
} }
} }
if mode == MODE_EXPERT { if mode == MODE_EXPERT {
@ -166,7 +165,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Device path (empty for default)") .with_prompt("Device path (empty for default)")
.default(config.device_path.as_ref().cloned().unwrap_or_default()) .default(config.device_path.as_ref().cloned().unwrap_or_default())
.interact_text()? .interact_text()?,
); );
config.fix_rp_filter = Confirm::with_theme(theme) config.fix_rp_filter = Confirm::with_theme(theme)
.with_prompt("Automatically fix insecure rp_filter settings") .with_prompt("Automatically fix insecure rp_filter settings")
@ -179,7 +178,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
Mode::Normal => 0, Mode::Normal => 0,
Mode::Router => 1, Mode::Router => 1,
Mode::Switch => 2, Mode::Switch => 2,
Mode::Hub => 3 Mode::Hub => 3,
}) })
.interact()? .interact()?
{ {
@ -187,7 +186,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
1 => Mode::Router, 1 => Mode::Router,
2 => Mode::Switch, 2 => Mode::Switch,
3 => Mode::Hub, 3 => Mode::Hub,
_ => unreachable!() _ => unreachable!(),
}; };
if config.mode == Mode::Switch { if config.mode == Mode::Switch {
config.switch_timeout = Input::with_theme(theme) config.switch_timeout = Input::with_theme(theme)
@ -205,7 +204,7 @@ fn configure_addresses(config: &mut Config, mode: usize, theme: &ColorfulTheme)
.with_prompt("Virtual IP address (e.g. 10.0.0.1, leave empty for none)") .with_prompt("Virtual IP address (e.g. 10.0.0.1, leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.ip.as_ref().cloned().unwrap_or_default()) .default(config.ip.as_ref().cloned().unwrap_or_default())
.interact_text()? .interact_text()?,
); );
if config.device_type == device::Type::Tun { if config.device_type == device::Type::Tun {
if mode >= MODE_ADVANCED { if mode >= MODE_ADVANCED {
@ -220,7 +219,7 @@ fn configure_addresses(config: &mut Config, mode: usize, theme: &ColorfulTheme)
.with_prompt("Claim additional addresses (e.g. 10.0.0.0/24, comma separated, leave empty for none)") .with_prompt("Claim additional addresses (e.g. 10.0.0.0/24, comma separated, leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.claims.join(",")) .default(config.claims.join(","))
.interact_text()? .interact_text()?,
); );
} }
} else { } else {
@ -232,14 +231,14 @@ fn configure_addresses(config: &mut Config, mode: usize, theme: &ColorfulTheme)
.with_prompt("Interface setup command (leave empty for none)") .with_prompt("Interface setup command (leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.ifup.as_ref().cloned().unwrap_or_default()) .default(config.ifup.as_ref().cloned().unwrap_or_default())
.interact_text()? .interact_text()?,
); );
config.ifdown = str_opt( config.ifdown = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Interface tear down command (leave empty for none)") .with_prompt("Interface tear down command (leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.ifdown.as_ref().cloned().unwrap_or_default()) .default(config.ifdown.as_ref().cloned().unwrap_or_default())
.interact_text()? .interact_text()?,
); );
} }
Ok(()) Ok(())
@ -267,24 +266,20 @@ fn configure_beacon(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.interact()? .interact()?
{ {
0 => None, 0 => None,
1 => { 1 => Some(
Some( Input::with_theme(theme)
Input::with_theme(theme) .with_prompt("File path")
.with_prompt("File path") .default(config.beacon_store.clone().unwrap_or_default())
.default(config.beacon_store.clone().unwrap_or_default()) .interact_text()?,
.interact_text()? ),
) 2 => Some(format!(
} "|{}",
2 => { Input::<String>::with_theme(theme)
Some(format!( .with_prompt("Command")
"|{}", .default(config.beacon_store.clone().unwrap_or_default().trim_start_matches('|').to_string())
Input::<String>::with_theme(theme) .interact_text()?
.with_prompt("Command") )),
.default(config.beacon_store.clone().unwrap_or_default().trim_start_matches('|').to_string()) _ => unreachable!(),
.interact_text()?
))
}
_ => unreachable!()
}; };
config.beacon_load = match Select::with_theme(theme) config.beacon_load = match Select::with_theme(theme)
.with_prompt("How to load beacons") .with_prompt("How to load beacons")
@ -301,24 +296,20 @@ fn configure_beacon(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.interact()? .interact()?
{ {
0 => None, 0 => None,
1 => { 1 => Some(
Some( Input::with_theme(theme)
Input::with_theme(theme) .with_prompt("File path")
.with_prompt("File path") .default(config.beacon_load.clone().unwrap_or_default())
.default(config.beacon_load.clone().unwrap_or_default()) .interact_text()?,
.interact_text()? ),
) 2 => Some(format!(
} "|{}",
2 => { Input::<String>::with_theme(theme)
Some(format!( .with_prompt("Command")
"|{}", .default(config.beacon_load.clone().unwrap_or_default().trim_start_matches('|').to_string())
Input::<String>::with_theme(theme) .interact_text()?
.with_prompt("Command") )),
.default(config.beacon_load.clone().unwrap_or_default().trim_start_matches('|').to_string()) _ => unreachable!(),
.interact_text()?
))
}
_ => unreachable!()
}; };
config.beacon_interval = Input::with_theme(theme) config.beacon_interval = Input::with_theme(theme)
.with_prompt("Beacon interval (in seconds)") .with_prompt("Beacon interval (in seconds)")
@ -329,7 +320,7 @@ fn configure_beacon(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.with_prompt("Beacon password (leave empty for none)") .with_prompt("Beacon password (leave empty for none)")
.with_confirmation("Confirm password", "Passwords do not match") .with_confirmation("Confirm password", "Passwords do not match")
.allow_empty_password(true) .allow_empty_password(true)
.interact()? .interact()?,
); );
} }
Ok(()) Ok(())
@ -342,7 +333,7 @@ fn configure_stats(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
.with_prompt("Write stats to file (empty to disable)") .with_prompt("Write stats to file (empty to disable)")
.default(config.stats_file.clone().unwrap_or_default()) .default(config.stats_file.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
); );
} }
if mode == MODE_EXPERT { if mode == MODE_EXPERT {
@ -356,14 +347,14 @@ fn configure_stats(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
.with_prompt("Statsd server URL") .with_prompt("Statsd server URL")
.default(config.statsd_server.clone().unwrap_or_default()) .default(config.statsd_server.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
); );
config.statsd_prefix = str_opt( config.statsd_prefix = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Statsd prefix") .with_prompt("Statsd prefix")
.default(config.statsd_prefix.clone().unwrap_or_default()) .default(config.statsd_prefix.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
); );
} else { } else {
config.statsd_server = None; config.statsd_server = None;
@ -379,21 +370,21 @@ fn configure_process(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.with_prompt("Run as different user (empty to disable)") .with_prompt("Run as different user (empty to disable)")
.default(config.user.clone().unwrap_or_default()) .default(config.user.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
); );
config.group = str_opt( config.group = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Run as different group (empty to disable)") .with_prompt("Run as different group (empty to disable)")
.default(config.group.clone().unwrap_or_default()) .default(config.group.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
); );
config.pid_file = str_opt( config.pid_file = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Write process id to file (empty to disable)") .with_prompt("Write process id to file (empty to disable)")
.default(config.pid_file.clone().unwrap_or_default()) .default(config.pid_file.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
); );
} }
Ok(()) Ok(())
@ -411,7 +402,7 @@ fn configure_hooks(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
.with_prompt("Command to execute for all events (empty to disable)") .with_prompt("Command to execute for all events (empty to disable)")
.default(config.hook.clone().unwrap_or_default()) .default(config.hook.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
); );
let mut hooks: HashMap<String, String> = Default::default(); let mut hooks: HashMap<String, String> = Default::default();
for event in &[ for event in &[
@ -421,14 +412,14 @@ fn configure_hooks(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
"device_setup", "device_setup",
"device_configured", "device_configured",
"vpn_started", "vpn_started",
"vpn_shutdown" "vpn_shutdown",
] { ] {
if let Some(cmd) = str_opt( if let Some(cmd) = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt(format!("Command to execute for event '{}' (empty to disable)", event)) .with_prompt(format!("Command to execute for event '{}' (empty to disable)", event))
.default(config.hooks.get(*event).cloned().unwrap_or_default()) .default(config.hooks.get(*event).cloned().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()? .interact_text()?,
) { ) {
hooks.insert(event.to_string(), cmd); hooks.insert(event.to_string(), cmd);
} }
@ -470,7 +461,7 @@ pub fn configure(name: Option<String>) -> Result<(), io::Error> {
config.merge_file(config_file); config.merge_file(config_file);
} }
if file.parent().unwrap().metadata()?.permissions().readonly() { if file.parent().unwrap().metadata()?.permissions().readonly() {
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "Config file not writable")) return Err(io::Error::new(io::ErrorKind::PermissionDenied, "Config file not writable"));
} }
loop { loop {
@ -489,7 +480,7 @@ pub fn configure(name: Option<String>) -> Result<(), io::Error> {
configure_process(&mut config, mode, &theme)?; configure_process(&mut config, mode, &theme)?;
configure_hooks(&mut config, mode, &theme)?; configure_hooks(&mut config, mode, &theme)?;
if Confirm::with_theme(&theme).with_prompt("Finish configuration?").default(true).interact()? { if Confirm::with_theme(&theme).with_prompt("Finish configuration?").default(true).interact()? {
break break;
} }
} }

View File

@ -6,19 +6,18 @@ use super::{
net::{get_ip, mapped_addr, parse_listen, Socket}, net::{get_ip, mapped_addr, parse_listen, Socket},
poll::{WaitImpl, WaitResult}, poll::{WaitImpl, WaitResult},
port_forwarding::PortForwarding, port_forwarding::PortForwarding,
util::MsgBuffer util::MsgBuffer,
}; };
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use std::{ use std::{
io::{self, Cursor, Read, Write}, io::{self, Cursor, Read, Write},
net::{Ipv6Addr, SocketAddr, SocketAddrV6, TcpListener, TcpStream, UdpSocket}, net::{Ipv6Addr, SocketAddr, SocketAddrV6, TcpListener, TcpStream, UdpSocket},
os::unix::io::{AsRawFd, RawFd}, os::unix::io::{AsRawFd, RawFd},
thread::spawn thread::spawn,
}; };
use tungstenite::{client::AutoStream, connect, protocol::WebSocket, server::accept, Message}; use tungstenite::{client::AutoStream, connect, protocol::WebSocket, server::accept, Message};
use url::Url; use url::Url;
macro_rules! io_error { macro_rules! io_error {
($val:expr, $format:expr) => ( { ($val:expr, $format:expr) => ( {
$val.map_err(|err| io::Error::new(io::ErrorKind::Other, format!($format, err))) $val.map_err(|err| io::Error::new(io::ErrorKind::Other, format!($format, err)))
@ -35,7 +34,7 @@ fn write_addr<W: Write>(addr: SocketAddr, mut out: W) -> Result<(), io::Error> {
out.write_all(&addr.ip().octets())?; out.write_all(&addr.ip().octets())?;
out.write_u16::<NetworkEndian>(addr.port())?; out.write_u16::<NetworkEndian>(addr.port())?;
} }
_ => unreachable!() _ => unreachable!(),
} }
Ok(()) Ok(())
} }
@ -86,7 +85,7 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> {
WaitResult::Timeout => { WaitResult::Timeout => {
io_error!(websocket.write_message(Message::Ping(vec![])), "Failed to send ping: {}")?; io_error!(websocket.write_message(Message::Ping(vec![])), "Failed to send ping: {}")?;
} }
WaitResult::Error(err) => return Err(err) WaitResult::Error(err) => return Err(err),
} }
} }
Ok(()) Ok(())
@ -110,14 +109,14 @@ pub fn run_proxy(listen: &str) -> Result<(), io::Error> {
pub struct ProxyConnection { pub struct ProxyConnection {
addr: SocketAddr, addr: SocketAddr,
socket: WebSocket<AutoStream> socket: WebSocket<AutoStream>,
} }
impl ProxyConnection { impl ProxyConnection {
fn read_message(&mut self) -> Result<Vec<u8>, io::Error> { fn read_message(&mut self) -> Result<Vec<u8>, io::Error> {
loop { loop {
if let Message::Binary(data) = io_error!(self.socket.read_message(), "Failed to read from ws proxy: {}")? { if let Message::Binary(data) = io_error!(self.socket.read_message(), "Failed to read from ws proxy: {}")? {
return Ok(data) return Ok(data);
} }
} }
} }