mirror of https://github.com/dswd/vpncloud.git
Prepare for release
This commit is contained in:
parent
665b190257
commit
4fb92a36a6
|
@ -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
|
12
.whitesource
12
.whitesource
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"scanSettings": {
|
|
||||||
"baseBranches": []
|
|
||||||
},
|
|
||||||
"checkRunSettings": {
|
|
||||||
"vulnerableCheckRunConclusionLevel": "failure",
|
|
||||||
"displayMode": "diff"
|
|
||||||
},
|
|
||||||
"issueSettings": {
|
|
||||||
"minSeverityLevel": "LOW"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
This project follows [semantic versioning](http://semver.org).
|
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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[record]
|
[record]
|
||||||
command = /usr/bin/bash -l
|
command = bash -l
|
||||||
idle_time_limit = 2.5
|
idle_time_limit = 2.5
|
|
@ -1,6 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
docker build -t asciinema-recorder .
|
|
||||||
docker run -it --rm --network host -v $(pwd)/../../target/release/:/usr/local/bin/ -v $(pwd):/data asciinema-recorder asciinema "$@"
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd $(dirname $0)
|
||||||
|
|
||||||
|
docker build -t asciinema-recorder .
|
||||||
|
docker run -it --rm --network host \
|
||||||
|
-v $(pwd):/data \
|
||||||
|
-v /etc/hosts:/etc/hosts \
|
||||||
|
asciinema-recorder
|
|
@ -15,16 +15,15 @@ use std::{
|
||||||
process::{Command, Stdio},
|
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));
|
||||||
|
|
143
src/cloud.rs
143
src/cloud.rs
|
@ -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> {
|
||||||
|
|
182
src/config.rs
182
src/config.rs
|
@ -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()
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>());
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,5 @@ mod core;
|
||||||
mod init;
|
mod init;
|
||||||
mod rotate;
|
mod rotate;
|
||||||
|
|
||||||
pub use common::*;
|
|
||||||
pub use self::core::{EXTRA_LEN, TAG_LEN};
|
pub use self::core::{EXTRA_LEN, TAG_LEN};
|
||||||
|
pub use common::*;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
|
14
src/net.rs
14
src/net.rs
|
@ -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);
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
src/table.rs
26
src/table.rs
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
35
src/types.rs
35
src/types.rs
|
@ -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 {
|
||||||
|
|
||||||
|
|
117
src/wizard.rs
117
src/wizard.rs
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue